Add project files.
This commit is contained in:
commit
4582955da2
18
.dockerignore
Normal file
18
.dockerignore
Normal file
@ -0,0 +1,18 @@
|
||||
**/.classpath
|
||||
**/.env
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.vs
|
||||
.vscode
|
||||
.DS_Store
|
||||
src/bin
|
||||
src/obj
|
||||
src/Properties
|
||||
src/sodoffmmo.csproj.user
|
||||
661
LICENSE
Normal file
661
LICENSE
Normal file
@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 <https://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
|
||||
<https://www.gnu.org/licenses/>.
|
||||
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
sodoffmmo:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Dockerfile
|
||||
ports:
|
||||
- "9933:9933"
|
||||
networks:
|
||||
- sodoff_network
|
||||
|
||||
networks:
|
||||
sodoff_network:
|
||||
name: sodoff_network
|
||||
# bellow network configuration should be put in at least one file
|
||||
# - but it may be in many or all
|
||||
# - without it it will work like `external: true`
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: "172.16.99.0/24"
|
||||
4
run.sh
Normal file
4
run.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
dotnet run --project src/sodoffmmo.csproj
|
||||
22
sodoff-mmo.sln
Normal file
22
sodoff-mmo.sln
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sodoffmmo", "src\sodoffmmo.csproj", "{049E36E9-29EC-4A15-96A5-99A049BD2F83}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{049E36E9-29EC-4A15-96A5-99A049BD2F83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{049E36E9-29EC-4A15-96A5-99A049BD2F83}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{049E36E9-29EC-4A15-96A5-99A049BD2F83}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{049E36E9-29EC-4A15-96A5-99A049BD2F83}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
10
src/Attributes/CommandHandlerAttribute.cs
Normal file
10
src/Attributes/CommandHandlerAttribute.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace sodoffmmo.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
class CommandHandlerAttribute : Attribute {
|
||||
public int ID { get; }
|
||||
|
||||
public CommandHandlerAttribute(int id) {
|
||||
ID = id;
|
||||
}
|
||||
}
|
||||
10
src/Attributes/ExtensionCommandHandlerAttribute.cs
Normal file
10
src/Attributes/ExtensionCommandHandlerAttribute.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace sodoffmmo.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
class ExtensionCommandHandlerAttribute : Attribute {
|
||||
public string Name { get; }
|
||||
|
||||
public ExtensionCommandHandlerAttribute(string name) {
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
14
src/Attributes/ManagementCommandAttribute.cs
Normal file
14
src/Attributes/ManagementCommandAttribute.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using sodoffmmo.Management;
|
||||
|
||||
namespace sodoffmmo.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
public class ManagementCommandAttribute : Attribute {
|
||||
public string Name { get; set; }
|
||||
public Role Role { get; set; }
|
||||
|
||||
public ManagementCommandAttribute(string name, Role role) {
|
||||
Name = name;
|
||||
Role = role;
|
||||
}
|
||||
}
|
||||
50
src/CommandHandlers/ChatMessageHandler.cs
Normal file
50
src/CommandHandlers/ChatMessageHandler.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
using sodoffmmo.Management;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("SCM")]
|
||||
class ChatMessageHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
string message = receivedObject.Get<NetworkObject>("p").Get<string>("chm");
|
||||
if (ManagementCommandProcessor.ProcessCommand(message, client))
|
||||
return Task.CompletedTask;
|
||||
if (client.TempMuted) {
|
||||
ClientMuted(client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!Configuration.ServerConfiguration.EnableChat && !client.Room.AllowChatOverride) {
|
||||
ChatDisabled(client);
|
||||
} else {
|
||||
Chat(client, message);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void ChatDisabled(Client client) {
|
||||
client.Send(Utils.BuildServerSideMessage("Unfortunately, chat has been disabled by server administrators", "Server"));
|
||||
}
|
||||
|
||||
public void ClientMuted(Client client) {
|
||||
client.Send(Utils.BuildServerSideMessage("You have been muted by the moderators", "Server"));
|
||||
}
|
||||
|
||||
public void Chat(Client client, string message) {
|
||||
if (Configuration.ServerConfiguration.Authentication >= AuthenticationMode.RequiredForChat && client.PlayerData.DiplayName == "placeholder") {
|
||||
client.Send(Utils.BuildServerSideMessage("You must be authenticated to use the chat", "Server"));
|
||||
return;
|
||||
}
|
||||
|
||||
client.Room.Send(Utils.BuildChatMessage(client.PlayerData.Uid, message, client.PlayerData.DiplayName), client);
|
||||
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject data = new();
|
||||
data.Add("arr", new string[] { "SCA", "-1", "1", message, "", "1" });
|
||||
cmd.Add("c", "SCA");
|
||||
cmd.Add("p", data);
|
||||
NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize();
|
||||
client.Send(packet);
|
||||
}
|
||||
}
|
||||
27
src/CommandHandlers/CounterHandler.cs
Normal file
27
src/CommandHandlers/CounterHandler.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("SCE")]
|
||||
class CounterEventHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) { // {"a":13,"c":1,"p":{"c":"SCE","p":{"NAME":"COUNT"},"r":-1}}
|
||||
if (client.Room is SpecialRoom room) {
|
||||
string name = receivedObject.Get<NetworkObject>("p").Get<string>("NAME");
|
||||
if (name == "COUNT" || name == "COUNT2" || name == "COUNT3") {
|
||||
int index = name switch {
|
||||
"COUNT" => 0,
|
||||
"COUNT2" => 1,
|
||||
"COUNT3" => 2
|
||||
};
|
||||
room.ambassadorGauges[index] = Math.Min(100, room.ambassadorGauges[index]+(1/Configuration.ServerConfiguration.AmbassadorGaugePlayers));
|
||||
room.Send(Utils.VlNetworkPacket(room.GetRoomVars(), client.Room.Id));
|
||||
} else {
|
||||
Console.WriteLine($"Invalid attempt to increment room var {name} in {room.Name}.");
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
18
src/CommandHandlers/DateTimeHandler.cs
Normal file
18
src/CommandHandlers/DateTimeHandler.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("DT")]
|
||||
class DateTimeHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject obj = new();
|
||||
obj.Add("arr", new string[] { "DT", DateTime.UtcNow.ToString("MM/dd/yyyy HH:mm:ss") });
|
||||
cmd.Add("c", "DT");
|
||||
cmd.Add("p", obj);
|
||||
client.Send(NetworkObject.WrapObject(1, 13, cmd).Serialize());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
29
src/CommandHandlers/EMDZombiesHandler.cs
Normal file
29
src/CommandHandlers/EMDZombiesHandler.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Globalization;
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
// TODO: This is currently stubbed. Supposed to do something.
|
||||
// Should probably be done by someone who actually played the game.
|
||||
[ExtensionCommandHandler("SU")]
|
||||
public class EMDZombiesUpdateHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("EN")]
|
||||
public class EMDZombiesEnterHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("EX")]
|
||||
public class EMDZombiesExitHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
20
src/CommandHandlers/ElapsedTimeSyncHandler.cs
Normal file
20
src/CommandHandlers/ElapsedTimeSyncHandler.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("RTM")]
|
||||
class ElapsedTimeSyncHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (client.Room != null) {
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject obj = new();
|
||||
obj.Add("arr", new string[] { "RTM", "-1", Runtime.CurrentRuntime.ToString() });
|
||||
cmd.Add("c", "RTM");
|
||||
cmd.Add("p", obj);
|
||||
client.Send(NetworkObject.WrapObject(1, 13, cmd).Serialize());
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
13
src/CommandHandlers/ExitHandler.cs
Normal file
13
src/CommandHandlers/ExitHandler.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[CommandHandler(26)]
|
||||
class ExitHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
client.SetRoom(null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
203
src/CommandHandlers/GauntletHandlers.cs
Normal file
203
src/CommandHandlers/GauntletHandlers.cs
Normal file
@ -0,0 +1,203 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
using System.Timers;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
|
||||
// Host Room For Any
|
||||
[ExtensionCommandHandler("gs.HRFA")]
|
||||
class GauntletCreateRoomHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
GauntletRoom.Join(client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Join Any Room
|
||||
[ExtensionCommandHandler("gs.JAR")]
|
||||
class GauntletJoinRoomHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
GauntletRoom.Join(client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Play Again
|
||||
[ExtensionCommandHandler("gs.PA")]
|
||||
class GauntletPlayAgainHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
GauntletRoom room = (client.Room as GauntletRoom)!;
|
||||
room.SetPlayerReady(client, false);
|
||||
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"LUNR",
|
||||
room.Id.ToString(),
|
||||
client.PlayerData.Uid
|
||||
}, "msg", room.Id);
|
||||
|
||||
room.Send(packet, client);
|
||||
room.SendPA(client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Lobby User Ready
|
||||
[ExtensionCommandHandler("gs.LUR")]
|
||||
class GauntletLobbyUserReadyHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
GauntletRoom room = (client.Room as GauntletRoom)!;
|
||||
room.SetPlayerReady(client);
|
||||
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"LUR",
|
||||
room.Id.ToString(),
|
||||
client.PlayerData.Uid
|
||||
}, "msg", room.Id);
|
||||
|
||||
room.Send(packet);
|
||||
|
||||
if (room.GetReadyCount() > 1) {
|
||||
packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"LCDD", // Lobby CountDown Done
|
||||
room.Id.ToString(),
|
||||
client.PlayerData.Uid
|
||||
}, "msg", room.Id);
|
||||
|
||||
room.Send(packet);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Lobby User Not Ready
|
||||
[ExtensionCommandHandler("gs.LUNR")]
|
||||
class GauntletLobbyUserNotReadyHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
GauntletRoom room = (client.Room as GauntletRoom)!;
|
||||
room.SetPlayerReady(client, false);
|
||||
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"LUNR",
|
||||
room.Id.ToString(),
|
||||
client.PlayerData.Uid
|
||||
}, "msg", room.Id);
|
||||
|
||||
room.Send(packet);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Game Level Load
|
||||
[ExtensionCommandHandler("gs.GLL")]
|
||||
class GauntletLevelLoadHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) { // {"a":13,"c":1,"p":{"c":"gs.GLL","p":{"0":"0","1":"0","2":"5","en":"GauntletGameExtension"},"r":365587}}
|
||||
GauntletRoom room = (client.Room as GauntletRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"GLL", // Game CountDown Start
|
||||
room.Id.ToString(),
|
||||
p.Get<string>("0"),
|
||||
p.Get<string>("1"),
|
||||
p.Get<string>("2") // TODO use size of p.fields - 1
|
||||
}, "msg", room.Id);
|
||||
room.Send(packet);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Game Level Loaded
|
||||
[ExtensionCommandHandler("gs.GLLD")]
|
||||
class GauntletLevelLoadedHandler : CommandHandler
|
||||
{
|
||||
private System.Timers.Timer? timer = null;
|
||||
private int counter;
|
||||
private GauntletRoom room;
|
||||
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
room = (client.Room as GauntletRoom)!;
|
||||
counter = 5;
|
||||
|
||||
// {"a":13,"c":1,"p":{"c":"msg","p":{"arr":["GCDS","365587","4"]},"r":365587}}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"GCDS", // Game CountDown Start
|
||||
room.Id.ToString(),
|
||||
(--counter).ToString()
|
||||
}, "msg", room.Id);
|
||||
room.Send(packet);
|
||||
|
||||
timer = new System.Timers.Timer(1500);
|
||||
timer.AutoReset = true;
|
||||
timer.Enabled = true;
|
||||
timer.Elapsed += OnTick;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnTick(Object? source, ElapsedEventArgs e) {
|
||||
NetworkPacket packet;
|
||||
if (--counter > 0) {
|
||||
// {"a":13,"c":1,"p":{"c":"msg","p":{"arr":["GCDS","365587","4"]},"r":365587}}
|
||||
packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"GCDU", // Game CountDown Update
|
||||
room.Id.ToString(),
|
||||
counter.ToString()
|
||||
}, "msg", room.Id);
|
||||
} else {
|
||||
// {"a":13,"c":1,"p":{"c":"msg","p":{"arr":["GS","365587"]},"r":365587}}
|
||||
packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"GS", // Game Start
|
||||
room.Id.ToString()
|
||||
}, "msg", room.Id);
|
||||
|
||||
timer!.Stop();
|
||||
timer!.Close();
|
||||
timer = null;
|
||||
}
|
||||
room.Send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
// Relay Game Data
|
||||
[ExtensionCommandHandler("gs.RGD")]
|
||||
class GauntletRelayGameDataHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) // {"a":13,"c":1,"p":{"c":"gs.RGD","p":{"0":"2700","1":"78","en":"GauntletGameExtension"},"r":4}}
|
||||
{
|
||||
GauntletRoom room = (client.Room as GauntletRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
// {"a":13,"c":1,"p":{"c":"msg","p":{"arr":["RGD","365587","150","75"]},"r":365587}}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"RGD", // Relay Game Data
|
||||
room.Id.ToString(),
|
||||
p.Get<string>("0"),
|
||||
p.Get<string>("1")
|
||||
}, "msg", room.Id);
|
||||
room.Send(packet, client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Game Complete
|
||||
[ExtensionCommandHandler("gs.GC")]
|
||||
class GauntletGameCompleteHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) // {"a":13,"c":1,"p":{"c":"gs.GC","p":{"0":"1550","1":"84","en":"GauntletGameExtension"},"r":4}}
|
||||
{
|
||||
GauntletRoom room = (client.Room as GauntletRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
room.ProcessResult(client, p.Get<string>("0"), p.Get<string>("1"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
16
src/CommandHandlers/GenericMessageHandler.cs
Normal file
16
src/CommandHandlers/GenericMessageHandler.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[CommandHandler(7)]
|
||||
class GenericMessageHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (!Configuration.ServerConfiguration.EnableCannedChat && receivedObject.Get<string>("m").StartsWith("C:"))
|
||||
return Task.CompletedTask;
|
||||
NetworkPacket packet = NetworkObject.WrapObject(0, 7, receivedObject).Serialize();
|
||||
client.Room.Send(packet);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
47
src/CommandHandlers/HandshakeHandler.cs
Normal file
47
src/CommandHandlers/HandshakeHandler.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[CommandHandler(0)]
|
||||
class HandshakeHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
string? token = receivedObject.Get<string>("rt");
|
||||
if (token != null) {
|
||||
client.Send(NetworkObject.WrapObject(0, 1006, new NetworkObject()).Serialize());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
string? api = receivedObject.Get<string>("api");
|
||||
if (api != null && api[0] == '0') {
|
||||
client.OldApi = true;
|
||||
}
|
||||
|
||||
NetworkObject obj = new();
|
||||
|
||||
obj.Add("tk", RandomString(32));
|
||||
obj.Add("ct", 1024);
|
||||
obj.Add("ms", 1000000);
|
||||
|
||||
client.Send(NetworkObject.WrapObject(0, 0, obj).Serialize());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private string RandomString(int length) {
|
||||
Random random = new Random();
|
||||
const string pool = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var builder = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
var c = pool[random.Next(0, pool.Length)];
|
||||
builder.Append(c);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
17
src/CommandHandlers/JoinLimboHandler.cs
Normal file
17
src/CommandHandlers/JoinLimboHandler.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers
|
||||
{
|
||||
[ExtensionCommandHandler("JL")]
|
||||
public class JoinLimboHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
client.SetRoom(Room.GetOrAdd("LIMBO"));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/CommandHandlers/JoinPrivateRoomHandler.cs
Normal file
18
src/CommandHandlers/JoinPrivateRoomHandler.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("JO")]
|
||||
class JoinPrivateRoomHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
var p = receivedObject.Get<NetworkObject>("p");
|
||||
string roomName = p.Get<string>("rn") + "_" + p.Get<string>("0");
|
||||
Room room = Room.GetOrAdd(roomName, autoRemove: true);
|
||||
client.SetRoom(room);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
20
src/CommandHandlers/JoinRoomHandler.cs
Normal file
20
src/CommandHandlers/JoinRoomHandler.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("JA")]
|
||||
class JoinRoomHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
string roomName = receivedObject.Get<NetworkObject>("p").Get<string>("rn");
|
||||
if (roomName is null) {
|
||||
roomName = client.PlayerData.ZoneName;
|
||||
}
|
||||
Room room = Room.GetOrAdd(roomName);
|
||||
client.SetRoom(room);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
32
src/CommandHandlers/JoinUserHandler.cs
Normal file
32
src/CommandHandlers/JoinUserHandler.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers
|
||||
{
|
||||
[ExtensionCommandHandler("JU")]
|
||||
public class JoinUserHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
string mpId = receivedObject.Get<NetworkObject>("p").Get<string>("0");
|
||||
|
||||
if (mpId != null)
|
||||
{
|
||||
Room? room = Room.AllRooms().FirstOrDefault(e => e.Id == Int32.Parse(mpId));
|
||||
|
||||
if (room != null)
|
||||
{
|
||||
client.SetRoom(room);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/CommandHandlers/LoginHandler.cs
Normal file
96
src/CommandHandlers/LoginHandler.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
using sodoffmmo.Management;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[CommandHandler(1)]
|
||||
class LoginHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
client.PlayerData.UNToken = receivedObject.Get<string>("un");
|
||||
client.PlayerData.ZoneName = receivedObject.Get<string>("zn");
|
||||
|
||||
if (!ValidToken(client)) {
|
||||
NetworkObject obj = new();
|
||||
obj.Add("dr", (byte)1);
|
||||
client.Send(NetworkObject.WrapObject(0, 1005, obj).Serialize());
|
||||
client.ScheduleDisconnect();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
NetworkArray rl = new();
|
||||
|
||||
NetworkArray r1 = new();
|
||||
r1.Add(0);
|
||||
r1.Add("MP_SYS");
|
||||
r1.Add("default");
|
||||
r1.Add(true);
|
||||
r1.Add(false);
|
||||
r1.Add(false);
|
||||
r1.Add((short)0);
|
||||
r1.Add((short)10);
|
||||
r1.Add(new NetworkArray());
|
||||
r1.Add((short)0);
|
||||
r1.Add((short)0);
|
||||
rl.Add(r1);
|
||||
|
||||
NetworkArray r2 = new();
|
||||
r2.Add(1);
|
||||
r2.Add("ADMIN");
|
||||
r2.Add("default");
|
||||
r2.Add(false);
|
||||
r2.Add(false);
|
||||
r2.Add(true);
|
||||
r2.Add((short)0);
|
||||
r2.Add((short)1);
|
||||
r2.Add(WorldEvent.Get().EventInfoArray(true));
|
||||
rl.Add(r2);
|
||||
|
||||
NetworkObject content = new();
|
||||
content.Add("rl", rl);
|
||||
content.Add("zn", client.PlayerData.ZoneName);
|
||||
content.Add("rs", (short)5);
|
||||
content.Add("un", client.PlayerData.UNToken);
|
||||
content.Add("id", client.ClientID);
|
||||
content.Add("pi", (short)1);
|
||||
|
||||
client.Send(NetworkObject.WrapObject(0, 1, content).Serialize());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private bool ValidToken(Client client) {
|
||||
if (Configuration.ServerConfiguration.Authentication == AuthenticationMode.Disabled ||
|
||||
(client.PlayerData.UNToken == Configuration.ServerConfiguration.BypassToken && !string.IsNullOrEmpty(Configuration.ServerConfiguration.BypassToken)))
|
||||
return true;
|
||||
|
||||
try {
|
||||
HttpClient httpClient = new();
|
||||
var content = new FormUrlEncodedContent(
|
||||
new Dictionary<string, string> {
|
||||
{ "token", client.PlayerData.UNToken },
|
||||
});
|
||||
|
||||
httpClient.Timeout = new TimeSpan(0, 0, 3);
|
||||
var response = httpClient.PostAsync($"{Configuration.ServerConfiguration.ApiUrl}/Authentication/MMOAuthentication", content).Result;
|
||||
string? responseString = response.Content.ReadAsStringAsync().Result;
|
||||
|
||||
if (response.StatusCode != System.Net.HttpStatusCode.OK)
|
||||
throw new Exception($"Response status code {response.StatusCode}");
|
||||
if (responseString == null)
|
||||
throw new Exception("Response string null");
|
||||
|
||||
AuthenticationInfo info = Utils.DeserializeXml<AuthenticationInfo>(responseString);
|
||||
if (info.Authenticated) {
|
||||
client.PlayerData.DiplayName = info.DisplayName;
|
||||
client.PlayerData.Role = info.Role;
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"Authentication exception IID: {client.ClientID} - {ex}");
|
||||
}
|
||||
return Configuration.ServerConfiguration.Authentication != AuthenticationMode.Required; // return true on auth err if not Required mode
|
||||
}
|
||||
}
|
||||
22
src/CommandHandlers/LogoutHandler.cs
Normal file
22
src/CommandHandlers/LogoutHandler.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[CommandHandler(2)]
|
||||
class LogoutHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
client.SetRoom(null);
|
||||
client.PlayerData.UNToken = null;
|
||||
client.PlayerData.ZoneName = null;
|
||||
|
||||
NetworkObject content = new();
|
||||
content.Add("zn", "");
|
||||
|
||||
client.Send(NetworkObject.WrapObject(0, 2, content).Serialize());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
22
src/CommandHandlers/PingHandler.cs
Normal file
22
src/CommandHandlers/PingHandler.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("PNG")]
|
||||
class PingHandler : CommandHandler {
|
||||
public bool RunInBackground { get; } = true;
|
||||
|
||||
public override async Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (Configuration.ServerConfiguration.PingDelay > 0) {
|
||||
await Task.Delay(Configuration.ServerConfiguration.PingDelay);
|
||||
}
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject obj = new();
|
||||
obj.Add("arr", new string[] { "PNG", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() });
|
||||
cmd.Add("c", "PNG");
|
||||
cmd.Add("p", obj);
|
||||
client.Send(NetworkObject.WrapObject(1, 13, cmd).Serialize());
|
||||
}
|
||||
}
|
||||
35
src/CommandHandlers/PublicMessageHandlers.cs
Normal file
35
src/CommandHandlers/PublicMessageHandlers.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("PM")]
|
||||
class RacingPMHandler : CommandHandler
|
||||
{
|
||||
// rec: {"a":13,"c":1,"p":{"c":"PM","p":{"M":"DT:c4647597-a72a-4f34-973c-5a10218d9a64:1000","en":"we"},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
// send: {"a":13,"c":1,"p":{"c":"PM","p":{"arr":[{"M":["DT:f05fc387-7358-4bff-be04-7c316f0a8de8:1000"],"MID":3529441}]}}}
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject p = new();
|
||||
NetworkArray arr = new();
|
||||
NetworkObject data = new();
|
||||
string M = receivedObject.Get<NetworkObject>("p").Get<string>("M");
|
||||
if (M.StartsWith("WF:") || M.StartsWith("WFWD:")) {
|
||||
// When firing weapon in EMD, recieving clients expect userid, but the sending client sends its token instead.
|
||||
string token = M.Split(':')[1];
|
||||
M = M.Replace(token, client.PlayerData.Uid);
|
||||
}
|
||||
data.Add("M", new string[] {M});
|
||||
data.Add("MID", client.ClientID);
|
||||
arr.Add(data);
|
||||
p.Add("arr", arr);
|
||||
cmd.Add("c", "PM");
|
||||
cmd.Add("p", p);
|
||||
NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize();
|
||||
|
||||
if (client.Room != null) // Throws an exception in Eat my Dust when the player fires their weapon before fully in the room.
|
||||
client.Room.Send(packet);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
83
src/CommandHandlers/RacingHandlers.cs
Normal file
83
src/CommandHandlers/RacingHandlers.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
using System.Timers;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
// Set Player Ready
|
||||
[ExtensionCommandHandler("dr.PR")]
|
||||
class RacingPlayerReadyHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) { // {"a":13,"c":1,"p":{"c":"dr.PR","p":{"IMR":"True","en":""},"r":-1}}
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
RacingPlayerState ready = p.Get<string>("IMR") == "True" ? RacingPlayerState.Ready : RacingPlayerState.NotReady;
|
||||
|
||||
// client send also {"a":7,"c":0,"p":{"m":"IMR:ae70ef16-c52c-43e4-9305-d6ea3e378a0d:True","r":412456,"t":0,"u":3529456}}
|
||||
// so server do not need generate this packet
|
||||
|
||||
if (client.Room.Group == "RacingDragon") {
|
||||
RacingRoom room = (client.Room as RacingRoom)!;
|
||||
room.SetPlayerState(client, ready);
|
||||
Console.WriteLine($"IMR Lobby: {client.ClientID} {ready}");
|
||||
room.TryLoad();
|
||||
} else {
|
||||
RacingLobby.Lobby.SetPlayerState(client, ready);
|
||||
Console.WriteLine($"IMR: {client.ClientID} {ready}");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// Player Status Request
|
||||
[ExtensionCommandHandler("dr.PS")]
|
||||
class RacingPlayerStatusHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
client.Send(RacingLobby.Lobby.GetPS());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// User Ready ACK
|
||||
[ExtensionCommandHandler("dr.UACK")]
|
||||
class RacingUACKHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
RacingRoom room = (client.Room as RacingRoom)!;
|
||||
room.SetPlayerState(client, RacingPlayerState.RaceReady1);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
// All Ready ACK
|
||||
[ExtensionCommandHandler("dr.ARACK")]
|
||||
class RacingARACKHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
RacingRoom room = (client.Room as RacingRoom)!;
|
||||
room.SetPlayerState(client, RacingPlayerState.RaceReady2);
|
||||
|
||||
if (room.GetPlayersCount(RacingPlayerState.RaceReady2) == room.ClientsCount) {
|
||||
NetworkPacket packet = room.GetSTAPacket();
|
||||
room.Send(packet);
|
||||
Console.WriteLine($"STA");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("dr.AR")]
|
||||
class RacingARHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) { // {"a":13,"c":1,"p":{"c":"dr.AR","p":{"CT":"112.1268","FD":"3008.283","LC":"3","UN":"scourgexxwulf","en":""},"r":412467}}
|
||||
RacingRoom room = (client.Room as RacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
room.SetResults(client, p.Get<string>("UN"), p.Get<string>("CT"), p.Get<string>("LC"));
|
||||
room.SendResults();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
344
src/CommandHandlers/SWRacingHandlers.cs
Normal file
344
src/CommandHandlers/SWRacingHandlers.cs
Normal file
@ -0,0 +1,344 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers
|
||||
{
|
||||
[ExtensionCommandHandler("sl.AV")]
|
||||
class SWCreateRoomHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = new();
|
||||
|
||||
client.SetRoom(room);
|
||||
|
||||
room.AddPlayer(client);
|
||||
room.SetIsPrebuiltTrack(client, true);
|
||||
room.SetTrack(client, -9999);
|
||||
|
||||
room.Send(room.GetAMPacket());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("sl.AU")]
|
||||
class SWHostWithBuddysHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
// more than likely its the same as AV except flagged as a BuddiesOnly room lol
|
||||
|
||||
SWRacingRoom room = new()
|
||||
{
|
||||
BuddiesOnly = true
|
||||
};
|
||||
|
||||
client.SetRoom(room);
|
||||
|
||||
room.AddPlayer(client);
|
||||
room.SetIsPrebuiltTrack(client, true);
|
||||
room.SetTrack(client, -9999);
|
||||
|
||||
room.Send(room.GetAMPacket());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("sl.AT")]
|
||||
class SWJoinBuddyHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string[] req = p.Get<string>("AT").Split("|");
|
||||
|
||||
Client? host = Server.AllClients?.FirstOrDefault(e => e.PlayerData.Uid == req[0]);
|
||||
if(host?.Room is not SWRacingRoom room)
|
||||
{
|
||||
client.Send(Utils.ArrNetworkPacket(["ZV", "1"]));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
client.SetRoom(room);
|
||||
room.AddPlayer(client);
|
||||
room.Send(room.GetAMPacket());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("sl.AQ")]
|
||||
class SWJoinRoomHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
foreach (var room in Room.AllRooms())
|
||||
{
|
||||
if (room is SWRacingRoom swRoom)
|
||||
{
|
||||
if (swRoom!.BuddiesOnly) continue;
|
||||
if (swRoom!.RaceOngoing) continue;
|
||||
else if (swRoom!.ClientsCount >= 4) continue;
|
||||
else
|
||||
{
|
||||
client.SetRoom(room);
|
||||
|
||||
swRoom.AddPlayer(client);
|
||||
room.Send(swRoom.GetAMPacket());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no rooms are available, treat it like AV and just make a room
|
||||
|
||||
SWRacingRoom racingRoom = new();
|
||||
client.SetRoom(racingRoom);
|
||||
|
||||
racingRoom.AddPlayer(client);
|
||||
racingRoom.SetIsPrebuiltTrack(client, true);
|
||||
racingRoom.SetTrack(client, -9999);
|
||||
|
||||
racingRoom.Send(racingRoom.GetAMPacket());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.AN")]
|
||||
class SWReadyHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
room.SetReady(client, true);
|
||||
|
||||
room.Send(Utils.ArrNetworkPacket([$"AN|{room.GetIndex(client)}"]));
|
||||
|
||||
room.TryStartLobbyCountdown();
|
||||
if (room.CountdownStarted)
|
||||
room.Send(Utils.ArrNetworkPacket(["AD"], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.AE")]
|
||||
class SWTransformRelayHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string[] fields = p.Get<string>("ZC").Split('|');
|
||||
|
||||
room.SetPlayerPosition(client, float.Parse(fields[1]), float.Parse(fields[2]), float.Parse(fields[3]));
|
||||
|
||||
List<string> outArr = [ room.GetIndex(client).ToString() ];
|
||||
outArr.AddRange(fields[1..]);
|
||||
outArr.Add(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
|
||||
|
||||
outArr.Insert(0, "AE");
|
||||
string joined = string.Join('|', outArr);
|
||||
|
||||
room.Send(Utils.ArrNetworkPacket([joined], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.AG")]
|
||||
class SWProjectileTransformRelayHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string[] fields = p.Get<string>("AG").Split("|");
|
||||
|
||||
List<string> outArr = [room.GetIndex(client).ToString()];
|
||||
outArr.AddRange(fields[1..]);
|
||||
outArr.Add(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
|
||||
|
||||
outArr.Insert(0, "AG");
|
||||
string joined = string.Join('|', outArr);
|
||||
|
||||
room.Send(Utils.ArrNetworkPacket([joined], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.AF")]
|
||||
class SWProjectileDestroyedHandler : CommandHandler
|
||||
{
|
||||
const float hitRadius = 5f; // calibrate me if to OP
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string[] fields = p.Get<string>("AF").Split("|");
|
||||
|
||||
Vector3 blast = new(float.Parse(fields[1]), float.Parse(fields[2]), float.Parse(fields[3]));
|
||||
|
||||
List<string> outFields = ["AF", room.GetIndex(client).ToString(), fields[0], fields[1], fields[2], fields[3]];
|
||||
outFields.AddRange(room.GetPlayersWithinRadius(blast, hitRadius, client).Select(idx => idx.ToString()));
|
||||
|
||||
room.Send(Utils.ArrNetworkPacket([string.Join("|", outFields)], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.AC")]
|
||||
class SWSceneLoadedHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
if (room.SetSceneLoaded(client))
|
||||
room.Send(Utils.ArrNetworkPacket(["AD"], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.ZI")]
|
||||
class SWNotReadyHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
room.SetReady(client, false);
|
||||
|
||||
room.Send(Utils.ArrNetworkPacket([$"ZI|{room.GetIndex(client)}"]));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.ZG")]
|
||||
class SWUpdateBoatHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string packed = p.Get<string>("ZG");
|
||||
string[] unpacked = packed.Split('|');
|
||||
|
||||
room.SetBoat(client, unpacked[1], int.Parse(unpacked[0]));
|
||||
|
||||
string joined = string.Join("|", "ZG", room.GetIndex(client).ToString(), unpacked[0], unpacked[1]);
|
||||
room.Send(Utils.ArrNetworkPacket([joined], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.ZL")]
|
||||
class SWUpdateTrackHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string packed = p.Get<string>("ZL");
|
||||
string[] unpacked = packed.Split('|');
|
||||
|
||||
room.SetTrack(client, int.Parse(unpacked[1]));
|
||||
room.SetIsPrebuiltTrack(client, unpacked[0] == "ZP");
|
||||
room.SetTrackStatus(client, unpacked[2]);
|
||||
|
||||
string joined = string.Join("|", "ZL", room.GetIndex(client).ToString(), unpacked[0], unpacked[1], unpacked[2]);
|
||||
room.Send(Utils.ArrNetworkPacket([joined], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.ZU")]
|
||||
class SWUpdateUsernameHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string packed = p.Get<string>("ZU");
|
||||
|
||||
var uid = packed.Split("|")[0];
|
||||
room.SetToken(client, uid);
|
||||
|
||||
string joined = string.Join("|", "ZU", room.GetIndex(client).ToString(), uid);
|
||||
room.Send(Utils.ArrNetworkPacket([joined], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.ZB")]
|
||||
class SWCountdownDoneHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
if (room.SetCountdownFinished(client))
|
||||
room.Send(Utils.ArrNetworkPacket(["ZF"], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.ZD")]
|
||||
class SWLapPlaceUpdateHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
string[] unpacked = p.Get<string>("ZD").Split('|');
|
||||
|
||||
room.SetPlayerLap(client, int.Parse(unpacked[0]));
|
||||
|
||||
if (unpacked.Length == 2)
|
||||
room.SetFinishedTime(client, float.Parse(unpacked[1]));
|
||||
|
||||
string joined = string.Join("|", "ZD", room.GetIndex(client).ToString(), unpacked[0]);
|
||||
|
||||
if (unpacked.Length == 2)
|
||||
joined += "|" + unpacked[1];
|
||||
|
||||
room.Send(Utils.ArrNetworkPacket([joined], "", room.Id));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("swl.ZW")]
|
||||
class SWPlayAgainHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
SWRacingRoom room = (client.Room as SWRacingRoom)!;
|
||||
|
||||
room.RaceOngoing = false;
|
||||
room.CountdownStarted = false;
|
||||
|
||||
// probably need to do something to reset player states also but we'll worry about that later, send it
|
||||
room.Send(Utils.ArrNetworkPacket(["ZW"]));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/CommandHandlers/SendMessageBoardHandler.cs
Normal file
37
src/CommandHandlers/SendMessageBoardHandler.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("SMB")]
|
||||
class SendMessageBoardHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
NetworkObject args = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
string toUserId = args.Get<string>("tgt");
|
||||
string content = args.Get<string>("cnt");
|
||||
string level = args.Get<string>("lvl");
|
||||
|
||||
if (toUserId == string.Empty) toUserId = client.PlayerData.Uid; // send to self
|
||||
|
||||
ApiWebService apiWebService = new();
|
||||
|
||||
// first check for any kind of ban
|
||||
var banType = apiWebService.CheckForUserBan(client);
|
||||
|
||||
if (banType != null && banType >= UserBanType.TemporaryOpenChatBan) { client.Send(Utils.ArrNetworkPacket(new string[] { "SMF" }, "SMF")); return Task.CompletedTask; }
|
||||
|
||||
// send message
|
||||
var result = apiWebService.SendMessageBoard(client, toUserId, content, level, "0");
|
||||
|
||||
if (result)
|
||||
client.Send(Utils.ArrNetworkPacket(new string[] { "SMA", "-1", "SUCCESS", "1", DateTime.UtcNow.ToString() }, "SMA"));
|
||||
else client.Send(Utils.ArrNetworkPacket(new string[] { "SMF" }, "SMF"));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
38
src/CommandHandlers/SendMessageReplyHandler.cs
Normal file
38
src/CommandHandlers/SendMessageReplyHandler.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("SMR")]
|
||||
class SendMessageReplyHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
NetworkObject args = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
string toUserId = args.Get<string>("tgt");
|
||||
string content = args.Get<string>("cnt");
|
||||
string level = args.Get<string>("lvl");
|
||||
string msgId = args.Get<string>("rtm");
|
||||
|
||||
if (toUserId == string.Empty) toUserId = client.PlayerData.Uid; // send to self
|
||||
|
||||
ApiWebService apiWebService = new();
|
||||
|
||||
// first check for any kind of ban
|
||||
var banType = apiWebService.CheckForUserBan(client);
|
||||
|
||||
if (banType != null && banType >= UserBanType.TemporaryOpenChatBan) { client.Send(Utils.ArrNetworkPacket(new string[] { "SMF" }, "SMF")); return Task.CompletedTask; }
|
||||
|
||||
// send message
|
||||
var result = apiWebService.SendMessageBoard(client, toUserId, content, level, msgId);
|
||||
|
||||
if (result)
|
||||
client.Send(Utils.ArrNetworkPacket(new string[] { "SMA", "-1", "SUCCESS", "1", DateTime.UtcNow.ToString() }, "SMA"));
|
||||
else client.Send(Utils.ArrNetworkPacket(new string[] { "SMF" }, "SMF"));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
32
src/CommandHandlers/SendUserEventHandler.cs
Normal file
32
src/CommandHandlers/SendUserEventHandler.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers
|
||||
{
|
||||
[ExtensionCommandHandler("SUE")]
|
||||
public class SendUserEventHandler : CommandHandler
|
||||
{
|
||||
public override Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
string userId = p.Get<string>("UID");
|
||||
string cmd = p.Get<string>("CMD");
|
||||
string[] arr = p.Get<string[]>("ARR");
|
||||
|
||||
// find client in all clients list
|
||||
Client? client1 = Server.AllClients?.FirstOrDefault(e => e.PlayerData.Uid == userId);
|
||||
|
||||
// send command
|
||||
if (client1 != null) client1.Send(Utils.ArrNetworkPacket(arr, cmd));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/CommandHandlers/SetPositionVariablesHandler.cs
Normal file
77
src/CommandHandlers/SetPositionVariablesHandler.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("SPV")]
|
||||
class SetPositionVariablesHandler : CommandHandler {
|
||||
Client client;
|
||||
NetworkObject spvData;
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (client.Room == null) {
|
||||
Console.WriteLine($"SPV Missing Room IID: {client.ClientID}");
|
||||
client.Send(NetworkObject.WrapObject(0, 1006, new NetworkObject()).Serialize());
|
||||
client.ScheduleDisconnect();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
this.client = client;
|
||||
spvData = receivedObject.Get<NetworkObject>("p");
|
||||
UpdatePositionVariables();
|
||||
SendSPVCommand();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void UpdatePositionVariables() {
|
||||
float[] pos = spvData.Get<float[]>("U");
|
||||
client.PlayerData.R = spvData.Get<float>("R");
|
||||
client.PlayerData.P1 = pos[0];
|
||||
client.PlayerData.P2 = pos[1];
|
||||
client.PlayerData.P3 = pos[2];
|
||||
client.PlayerData.R1 = pos[3];
|
||||
client.PlayerData.R2 = pos[4];
|
||||
client.PlayerData.R3 = pos[5];
|
||||
client.PlayerData.Mx = spvData.Get<float>("MX");
|
||||
client.PlayerData.F = spvData.Get<int>("F");
|
||||
client.PlayerData.Mbf = spvData.Get<int>("MBF");
|
||||
}
|
||||
|
||||
private void SendSPVCommand() {
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject obj = new();
|
||||
NetworkArray container = new();
|
||||
NetworkObject vars = new();
|
||||
vars.Add("R", client.PlayerData.R);
|
||||
vars.Add("U", new float[] { client.PlayerData.P1, client.PlayerData.P2, client.PlayerData.P3, client.PlayerData.R1, client.PlayerData.R2, client.PlayerData.R3 });
|
||||
vars.Add("MX", client.PlayerData.Mx);
|
||||
vars.Add("F", client.PlayerData.F);
|
||||
vars.Add("MBF", client.PlayerData.Mbf);
|
||||
|
||||
// user event
|
||||
string? ue = spvData.Get<string>("UE");
|
||||
if (ue != null) {
|
||||
long time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
if ((time - client.PlayerData.last_ue_time) > 499 || Configuration.ServerConfiguration.AllowChaos) {
|
||||
vars.Add("UE", ue);
|
||||
client.PlayerData.last_ue_time = time;
|
||||
}
|
||||
}
|
||||
// pitch
|
||||
float? cup = spvData.Get<float?>("CUP");
|
||||
if (cup != null)
|
||||
vars.Add("CUP", (float)cup);
|
||||
|
||||
vars.Add("NT", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
|
||||
vars.Add("t", (int)(Runtime.CurrentRuntime / 1000));
|
||||
vars.Add("MID", client.ClientID);
|
||||
|
||||
container.Add(vars);
|
||||
obj.Add("arr", container);
|
||||
cmd.Add("c", "SPV");
|
||||
cmd.Add("p", obj);
|
||||
|
||||
NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize();
|
||||
client.Room?.Send(packet, client);
|
||||
}
|
||||
}
|
||||
94
src/CommandHandlers/SetUserVariablesHandler.cs
Normal file
94
src/CommandHandlers/SetUserVariablesHandler.cs
Normal file
@ -0,0 +1,94 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("SUV")]
|
||||
class SetUserVariablesHandler : CommandHandler {
|
||||
NetworkObject suvData;
|
||||
Client client;
|
||||
string? uid;
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (client.Room == null) {
|
||||
Console.WriteLine($"SUV Missing Room IID: {client.ClientID}");
|
||||
client.Send(NetworkObject.WrapObject(0, 1006, new NetworkObject()).Serialize());
|
||||
client.ScheduleDisconnect();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
this.client = client;
|
||||
suvData = receivedObject.Get<NetworkObject>("p");
|
||||
uid = suvData.Get<string>("UID");
|
||||
|
||||
// TODO
|
||||
if (uid != null && (client.PlayerData.Uid != uid || !client.PlayerData.IsValid)) {
|
||||
Console.WriteLine($"SUV {client.Room.Name} ({client.Room.ClientsCount}) IID: {client.ClientID} UID: {uid}");
|
||||
client.PlayerData.Uid = uid;
|
||||
client.PlayerData.InitFromNetworkData(suvData);
|
||||
UpdatePlayersInRoom();
|
||||
SendSUVToPlayerInRoom();
|
||||
if (client.Room is SpecialRoom room) room.SendAllAlerts(client);
|
||||
} else {
|
||||
UpdateVars();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void UpdateVars() {
|
||||
bool updated = false;
|
||||
NetworkArray vl = new();
|
||||
NetworkObject data = new();
|
||||
|
||||
foreach (string varName in PlayerData.SupportedVariables) {
|
||||
string? value = suvData.Get<string>(varName);
|
||||
if (value != null) {
|
||||
value = client.PlayerData.SetVariable(varName, value);
|
||||
updated = true;
|
||||
data.Add(varName, value);
|
||||
vl.Add(NetworkArray.Param(varName, value));
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
client.SendSUV(vl, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayersInRoom() {
|
||||
NetworkObject data = new();
|
||||
NetworkObject acknowledgement = new();
|
||||
data.Add("r", client.Room.Id);
|
||||
|
||||
NetworkArray user = client.PlayerData.GetNetworkData(client.ClientID, out NetworkArray playerData);
|
||||
data.Add("u", user);
|
||||
|
||||
acknowledgement.Add("u", client.ClientID);
|
||||
acknowledgement.Add("vl", playerData);
|
||||
NetworkPacket ackPacket = NetworkObject.WrapObject(0, 12, acknowledgement).Serialize();
|
||||
NetworkObject obj = ackPacket.GetObject();
|
||||
ackPacket.Compress();
|
||||
client.Send(ackPacket);
|
||||
|
||||
NetworkPacket packet = NetworkObject.WrapObject(0, 1000, data).Serialize();
|
||||
packet.Compress();
|
||||
|
||||
client.Room.Send(packet, client);
|
||||
}
|
||||
|
||||
private void SendSUVToPlayerInRoom() {
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject obj = new();
|
||||
|
||||
cmd.Add("c", "SUV");
|
||||
if (client.OldApi) {
|
||||
obj.Add("MID", client.ClientID.ToString());
|
||||
} else {
|
||||
obj.Add("MID", client.ClientID);
|
||||
}
|
||||
cmd.Add("p", obj);
|
||||
|
||||
NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize();
|
||||
client.Room.Send(packet, client);
|
||||
}
|
||||
}
|
||||
22
src/CommandHandlers/UDPSNPCommandHandler.cs
Normal file
22
src/CommandHandlers/UDPSNPCommandHandler.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers
|
||||
{
|
||||
[ExtensionCommandHandler("SNP")]
|
||||
class UDPSNPCommandHandler : CommandHandler
|
||||
{
|
||||
public override async Task Handle(Client client, NetworkObject receivedObject)
|
||||
{
|
||||
// this is only sent over UDP, so we may need to send this over UDP aswell
|
||||
|
||||
var res = Utils.ArrNetworkPacket(["SNP", "1"], "");
|
||||
client.Send(res, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
134
src/CommandHandlers/WorldEventHandlers.cs
Normal file
134
src/CommandHandlers/WorldEventHandlers.cs
Normal file
@ -0,0 +1,134 @@
|
||||
using System.Globalization;
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.CommandHandlers;
|
||||
|
||||
[ExtensionCommandHandler("wex.WES")] // event status request
|
||||
class WorldEventStatusHandler : CommandHandler {
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
client.Send(Utils.ArrNetworkPacket( new string[] {
|
||||
"WESR",
|
||||
"WE_" + Configuration.ServerConfiguration.EventName + "|" + WorldEvent.Get().EventInfo(),
|
||||
"EvEnd|" + WorldEvent.Get().GetLastResults()
|
||||
}));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("wex.OV")]
|
||||
class WorldEventHealthHandler : CommandHandler {
|
||||
// rec: {"a":13,"c":1,"p":{"c":"wex.OV","p":{"en":"","event":"ScoutAttack","eventUID":"ZydLUmCC","oh":"0.003444444","uid":"ZydLUmCC1"},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
// NOTE: this should be process on event in any state - we use it to make event active
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
float healthUpdateVal = float.Parse(
|
||||
p.Get<string>("oh"),
|
||||
System.Globalization.CultureInfo.InvariantCulture
|
||||
);
|
||||
string targetUid = p.Get<string>("uid");
|
||||
|
||||
float health = WorldEvent.Get().UpdateHealth(targetUid, healthUpdateVal);
|
||||
|
||||
if (health >= 0.0f) {
|
||||
// send: {"a":11,"c":0,"p":{"r":367256,"vl":[["WEH_ZydLUmCC1",4,"0.33133352,Thu Jun 22 02:02:43 UTC 2023",false,false]]}}
|
||||
NetworkPacket packet = Utils.VlNetworkPacket(
|
||||
"WEH_" + targetUid,
|
||||
health.ToString("0.0#####", CultureInfo.GetCultureInfo("en-US")) + "," + DateTime.UtcNow.ToString("ddd MMM dd HH:mm:ss UTC yyyy", CultureInfo.GetCultureInfo("en-US")),
|
||||
WorldEvent.Get().GetRoom().Id
|
||||
);
|
||||
WorldEvent.Get().GetRoom().Send(packet);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("wex.OVF")] // flare info from ship AI -> resend as WEF_
|
||||
class WorldEventFlareHandler : CommandHandler {
|
||||
// rec: {"a":13,"c":1,"p":{"c":"wex.OVF","p":{"en":"","fuid":"WpnpDyJ51,14,0","oh":"0","ts":"6/29/2023 3:03:18 AM"},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (!WorldEvent.Get().IsActive())
|
||||
return Task.CompletedTask;
|
||||
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
// send: {"a":11,"c":0,"p":{"r":403777,"vl":[["WEF_WpnpDyJ51,14,0",4,"0,6/29/2023 3:03:18 AM",false,false]]}}
|
||||
NetworkPacket packet = Utils.VlNetworkPacket(
|
||||
"WEF_" + p.Get<string>("fuid"),
|
||||
p.Get<string>("oh") + "," + p.Get<string>("ts"),
|
||||
WorldEvent.Get().GetRoom().Id
|
||||
);
|
||||
WorldEvent.Get().GetRoom().Send(packet);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("wex.ST")] // missile info from ship AI -> resend as WA
|
||||
class WorldEventMissileHandler : CommandHandler {
|
||||
// rec: {"a":13,"c":1,"p":{"c":"wex.ST","p":{"en":"","objID":"-4X_gWAo1","tID":"f5b6254a-df78-4e24-aa9d-7e14539fb858","uID":"1f8eeb6b-753f-4e7f-af13-42cdd69d14e7","wID":"5"},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (!WorldEvent.Get().IsActive())
|
||||
return Task.CompletedTask;
|
||||
|
||||
NetworkObject p = receivedObject.Get<NetworkObject>("p");
|
||||
|
||||
// send: {"a":13,"c":1,"p":{"c":"","p":{"arr":["WA","1f8eeb6b-753f-4e7f-af13-42cdd69d14e7","5","f5b6254a-df78-4e24-aa9d-7e14539fb858","-4X_gWAo1"]}}}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"WA",
|
||||
p.Get<string>("uID"),
|
||||
p.Get<string>("wID"),
|
||||
p.Get<string>("tID"),
|
||||
p.Get<string>("objID")
|
||||
});
|
||||
WorldEvent.Get().GetRoom().Send(packet);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("wex.PS")]
|
||||
class WorldEventScoreHandler : CommandHandler {
|
||||
// rec: {"a":13,"c":1,"p":{"c":"wex.PS","p":{"ScoreData":"Datashyo/10","en":"","id":"ScoutAttack"},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
if (!WorldEvent.Get().IsActive())
|
||||
return Task.CompletedTask;
|
||||
|
||||
string scoreData = receivedObject.Get<NetworkObject>("p").Get<string>("ScoreData");
|
||||
string[] keyValPair = scoreData.Split('/');
|
||||
WorldEvent.Get().UpdateScore(keyValPair[0], keyValPair[1]);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("wex.AIACK")] // AI ack
|
||||
class WorldEventAIACKHandler : CommandHandler {
|
||||
// rec: {"a":13,"c":1,"p":{"c":"wex.AIACK","p":{"en":"","id":"f322dd98-e9fb-4b2d-a5e0-1c98680517b5","uid":"SoDOff1"},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
WorldEvent.Get().UpdateAI(client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
[ExtensionCommandHandler("wex.AIP")] // AI ping
|
||||
class WorldEventAIPingHandler : CommandHandler {
|
||||
// rec: {"a":13,"c":1,"p":{"c":"wex.AIP","p":{"en":""},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
WorldEvent.Get().UpdateAI(client);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
[ExtensionCommandHandler("wex.ETS")] // time span
|
||||
class WorldEventTimeSpanHandler : CommandHandler {
|
||||
// rec: {"a":13,"c":1,"p":{"c":"wex.ETS","p":{"en":"","timeSpan":"300"},"r":-1}}
|
||||
public override Task Handle(Client client, NetworkObject receivedObject) {
|
||||
float timeSpan = float.Parse(
|
||||
receivedObject.Get<NetworkObject>("p").Get<string>("timeSpan"),
|
||||
System.Globalization.CultureInfo.InvariantCulture
|
||||
);
|
||||
WorldEvent.Get().SetTimeSpan(client, timeSpan);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
126
src/Core/ApiWebService.cs
Normal file
126
src/Core/ApiWebService.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Net.Http.Json;
|
||||
using sodoffmmo.CommandHandlers;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
|
||||
public class ApiWebService
|
||||
{
|
||||
public UserBanType? CheckForUserBan(Client client)
|
||||
{
|
||||
HttpClient httpClient = new();
|
||||
var content = new FormUrlEncodedContent(
|
||||
new Dictionary<string, string> {
|
||||
{ "token", client.PlayerData.UNToken }
|
||||
}
|
||||
);
|
||||
httpClient.Timeout = new TimeSpan(0, 0, 3);
|
||||
|
||||
try
|
||||
{
|
||||
var response = httpClient.PostAsync($"{Configuration.ServerConfiguration.ApiUrl}/Moderation/CheckForVikingBan", content).Result;
|
||||
Log("Moderation/CheckForVikingBan");
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.OK && response.Content != null) return response.Content.ReadFromJsonAsync<UserBanType>().Result;
|
||||
else return null;
|
||||
} catch (Exception e) { LogError(e.Message); return null; }
|
||||
}
|
||||
|
||||
public string? BanUser(Client client, string userId, string banType, string days)
|
||||
{
|
||||
HttpClient httpClient = new();
|
||||
var content = new FormUrlEncodedContent(
|
||||
new Dictionary<string, string> {
|
||||
{ "token", client.PlayerData.UNToken },
|
||||
{ "userId", userId },
|
||||
{ "banType", banType },
|
||||
{ "days", days }
|
||||
}
|
||||
);
|
||||
httpClient.Timeout = new TimeSpan(0, 0, 3);
|
||||
|
||||
try
|
||||
{
|
||||
var response = httpClient.PostAsync($"{Configuration.ServerConfiguration.ApiUrl}/Moderation/AddBanToVikingByGuid", content).Result;
|
||||
Log("Moderation/AddBanToVikingByGuid");
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.OK && response.Content != null) return response.Content.ReadAsStringAsync().Result;
|
||||
else return null;
|
||||
} catch (Exception e) { LogError(e.Message); return null; }
|
||||
}
|
||||
|
||||
public bool SendMessageBoard(Client client, string userId, string data, string level, string replyMessageId)
|
||||
{
|
||||
HttpClient httpClient = new();
|
||||
var content = new FormUrlEncodedContent
|
||||
(
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "token", client.PlayerData.UNToken },
|
||||
{ "userId", userId },
|
||||
{ "data", data },
|
||||
{ "messageLevel", level },
|
||||
{ "replyMessageId", replyMessageId }
|
||||
}
|
||||
);
|
||||
httpClient.Timeout = new TimeSpan(0, 0, 3);
|
||||
|
||||
try
|
||||
{
|
||||
var response = httpClient.PostAsync($"{Configuration.ServerConfiguration.ApiUrl}/Messaging/PostTextMessage", content).Result;
|
||||
Log("Messaging/PostTextMessage");
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.OK && response.Content != null) return response.Content.ReadFromJsonAsync<bool>().Result;
|
||||
else return false;
|
||||
} catch (Exception e) { LogError(e.Message); return false; }
|
||||
}
|
||||
|
||||
public bool SetOnline(Client client, bool online)
|
||||
{
|
||||
HttpClient httpClient = new();
|
||||
var content = new FormUrlEncodedContent
|
||||
(
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "token", client.PlayerData.UNToken },
|
||||
{ "online", online.ToString() }
|
||||
}
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
var response = httpClient.PostAsync($"{Configuration.ServerConfiguration.ApiUrl}/Precense/SetVikingOnline", content).Result;
|
||||
Log("Precense/SetVikingOnline");
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.OK && response.Content != null) return response.Content.ReadFromJsonAsync<bool>().Result;
|
||||
else return false;
|
||||
} catch (Exception e) { LogError(e.Message); return false; }
|
||||
}
|
||||
|
||||
public bool SetRoom(Client client, int roomId, string zoneName)
|
||||
{
|
||||
HttpClient httpClient = new();
|
||||
var content = new FormUrlEncodedContent
|
||||
(
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{ "token", client.PlayerData.UNToken },
|
||||
{ "roomId", roomId.ToString() },
|
||||
{ "zoneName", zoneName }
|
||||
}
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
var response = httpClient.PostAsync($"{Configuration.ServerConfiguration.ApiUrl}/Precense/SetVikingRoom", content).Result;
|
||||
Log("Precense/SetVikingRoom");
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.OK && response.Content != null) return response.Content.ReadFromJsonAsync<bool>().Result;
|
||||
else return false;
|
||||
}
|
||||
catch (Exception e) { LogError(e.Message); return false; }
|
||||
}
|
||||
|
||||
private void Log(string endpoint) => Console.WriteLine($"Sent API Request To {Configuration.ServerConfiguration.ApiUrl}/{endpoint}");
|
||||
|
||||
private void LogError(string message) => Console.WriteLine($"An Error Has Occured When Sending An API Request - {message}");
|
||||
}
|
||||
164
src/Core/Client.cs
Normal file
164
src/Core/Client.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using sodoffmmo.Data;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
public class Client {
|
||||
static int id;
|
||||
static object lck = new();
|
||||
|
||||
public int ClientID { get; private set; }
|
||||
public IPEndPoint? UDPEndPoint { get; set; }
|
||||
public PlayerData PlayerData { get; set; } = new();
|
||||
public Room? Room { get; private set; }
|
||||
public bool OldApi { get; set; } = false;
|
||||
public bool TempMuted { get; set; } = false;
|
||||
|
||||
private readonly Socket socket;
|
||||
SocketBuffer socketBuffer = new();
|
||||
private volatile bool scheduledDisconnect = false;
|
||||
private readonly object clientLock = new();
|
||||
|
||||
public Client(Socket clientSocket) {
|
||||
socket = clientSocket;
|
||||
lock (lck) {
|
||||
ClientID = ++id;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Receive() {
|
||||
byte[] buffer = new byte[2048];
|
||||
int len = await socket.ReceiveAsync(buffer, SocketFlags.None);
|
||||
if (len == 0)
|
||||
throw new SocketException();
|
||||
socketBuffer.Write(buffer, len);
|
||||
}
|
||||
|
||||
public bool TryGetNextPacket(out NetworkPacket packet) {
|
||||
return socketBuffer.ReadPacket(out packet);
|
||||
}
|
||||
|
||||
public void Send(NetworkPacket packet, bool useUDP = false) {
|
||||
try {
|
||||
if (useUDP && UDPEndPoint != null)
|
||||
{
|
||||
Server.SharedUDPClient.Send(packet.SendData, packet.SendData.Length, UDPEndPoint);
|
||||
return;
|
||||
}
|
||||
socket.Send(packet.SendData);
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine(ex.ToString());
|
||||
ScheduleDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRoom(Room? room) {
|
||||
// api web service for setting precense
|
||||
ApiWebService apiWebService = new();
|
||||
|
||||
lock (clientLock) {
|
||||
// set variable player data as not valid, but do not reset all player data
|
||||
PlayerData.IsValid = false;
|
||||
|
||||
if (Room != null) {
|
||||
Console.WriteLine($"Leave room: {Room.Name} (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}");
|
||||
Room.RemoveClient(this);
|
||||
|
||||
NetworkObject data = new();
|
||||
data.Add("r", Room.Id);
|
||||
data.Add("u", ClientID);
|
||||
Room.Send(NetworkObject.WrapObject(0, 1004, data).Serialize());
|
||||
|
||||
apiWebService.SetOnline(this, false);
|
||||
apiWebService.SetRoom(this, 0, string.Empty);
|
||||
}
|
||||
|
||||
// set new room (null when SetRoom is used as LeaveRoom)
|
||||
Room = room;
|
||||
|
||||
if (Room != null) {
|
||||
Console.WriteLine($"Join room: {Room.Name} RoomID (id={Room.Id}, size={Room.ClientsCount}) IID: {ClientID}");
|
||||
Room.AddClient(this);
|
||||
|
||||
Send(Room.SubscribeRoom());
|
||||
if (Room.Name != "LIMBO") UpdatePlayerUserVariables(); // do not update user vars if room is limbo
|
||||
|
||||
apiWebService.SetOnline(this, true);
|
||||
|
||||
if (Room.Name != "LIMBO")
|
||||
{
|
||||
UpdatePlayerUserVariables();
|
||||
apiWebService.SetRoom(this, Room.Id, Room.Name);
|
||||
} // do not update user vars or set room if limbo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePlayerUserVariables() {
|
||||
foreach (Client c in Room.Clients) {
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject obj = new();
|
||||
cmd.Add("c", "SUV");
|
||||
if (OldApi) {
|
||||
obj.Add("MID", c.ClientID.ToString());
|
||||
} else {
|
||||
obj.Add("MID", c.ClientID);
|
||||
}
|
||||
cmd.Add("p", obj);
|
||||
Send(NetworkObject.WrapObject(1, 13, cmd).Serialize());
|
||||
}
|
||||
}
|
||||
|
||||
public void SendSUV(NetworkArray vl, NetworkObject data) {
|
||||
NetworkObject data2 = new();
|
||||
data2.Add("u", ClientID);
|
||||
data2.Add("vl", vl);
|
||||
NetworkPacket packet = NetworkObject.WrapObject(0, 12, data2).Serialize();
|
||||
Room.Send(packet);
|
||||
|
||||
NetworkObject cmd = new();
|
||||
cmd.Add("c", "SUV");
|
||||
NetworkArray arr = new();
|
||||
if (OldApi) {
|
||||
data.Add("MID", ClientID.ToString());
|
||||
} else {
|
||||
data.Add("MID", ClientID);
|
||||
}
|
||||
data.Add("RID", Room.Id.ToString());
|
||||
arr.Add(data);
|
||||
NetworkObject container = new();
|
||||
container.Add("arr", arr);
|
||||
cmd.Add("p", container);
|
||||
packet = NetworkObject.WrapObject(1, 13, cmd).Serialize();
|
||||
Room.Send(packet, this);
|
||||
}
|
||||
|
||||
public void Disconnect() {
|
||||
try {
|
||||
socket.Shutdown(SocketShutdown.Both);
|
||||
} finally {
|
||||
socket.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void ScheduleDisconnect() {
|
||||
ApiWebService apiWebService = new();
|
||||
if (Room != null)
|
||||
{
|
||||
// quiet remove from room (to avoid issues in Room.Send)
|
||||
// - do not change Room value here
|
||||
// - full remove will be will take place Server.HandleClient (before real disconnected)
|
||||
Room.RemoveClient(this);
|
||||
apiWebService.SetOnline(this, false);
|
||||
}
|
||||
scheduledDisconnect = true;
|
||||
}
|
||||
|
||||
public bool Connected {
|
||||
get {
|
||||
return socket.Connected && !scheduledDisconnect;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Core/CommandHandler.cs
Normal file
8
src/Core/CommandHandler.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
public abstract class CommandHandler {
|
||||
public bool RunInBackground { get; }
|
||||
|
||||
public abstract Task Handle(Client client, NetworkObject receivedObject);
|
||||
}
|
||||
55
src/Core/Configuration.cs
Normal file
55
src/Core/Configuration.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
internal static class Configuration {
|
||||
|
||||
public static ServerConfiguration ServerConfiguration { get; private set; } = new ServerConfiguration();
|
||||
|
||||
public static void Initialize() {
|
||||
IConfigurationRoot config = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json", true)
|
||||
.Build();
|
||||
|
||||
ServerConfiguration? serverConfiguration = config.GetSection("MMOServer").Get<ServerConfiguration>();
|
||||
if (serverConfiguration is null)
|
||||
return;
|
||||
|
||||
ServerConfiguration = serverConfiguration;
|
||||
if (string.IsNullOrEmpty(ServerConfiguration.ApiUrl)) {
|
||||
ServerConfiguration.Authentication = AuthenticationMode.Disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ServerConfiguration {
|
||||
public string ListenIP { get; set; } = string.Empty;
|
||||
public int Port { get; set; } = 9933;
|
||||
public string EventName { get; set; } = "ScoutAttack";
|
||||
public int EventTimer { get; set; } = 30;
|
||||
public int FirstEventTimer { get; set; } = 10;
|
||||
public Dictionary<string, string[][]> RoomAlerts { get; set; } = new();
|
||||
public string[] AmbassadorRooms { get; set; } = Array.Empty<string>();
|
||||
public int AmbassadorGaugeStart { get; set; } = 75;
|
||||
public float AmbassadorGaugeDecayRate { get; set; } = 60;
|
||||
public bool AmbassadorGaugeDecayOnlyWhenInRoom { get; set; } = true;
|
||||
public float AmbassadorGaugePlayers { get; set; } = 0.5f;
|
||||
public int RacingMaxPlayers { get; set; } = 6;
|
||||
public int RacingMinPlayers { get; set; } = 2;
|
||||
public int RacingMainLobbyTimer { get; set; } = 15;
|
||||
public int PingDelay { get; set; } = 17;
|
||||
public bool EnableChat { get; set; } = true;
|
||||
public bool EnableCannedChat { get; set; } = true;
|
||||
public bool AllowChaos { get; set; } = false;
|
||||
public AuthenticationMode Authentication { get; set; } = AuthenticationMode.Disabled;
|
||||
public string ApiUrl { get; set; } = "";
|
||||
public string BypassToken { get; set; } = "";
|
||||
}
|
||||
|
||||
public enum AuthenticationMode {
|
||||
Disabled, Optional, RequiredForChat, Required
|
||||
}
|
||||
130
src/Core/GauntletRoom.cs
Normal file
130
src/Core/GauntletRoom.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
public class GauntletRoom : Room {
|
||||
static object NextRoomLock = new object();
|
||||
static GauntletRoom? NextRoom = null;
|
||||
|
||||
public static GauntletRoom Get() {
|
||||
lock(NextRoomLock) {
|
||||
if (NextRoom != null && NextRoom.ClientsCount == 1) {
|
||||
var ret = NextRoom!;
|
||||
NextRoom = null;
|
||||
return ret;
|
||||
} else {
|
||||
NextRoom = new GauntletRoom();
|
||||
return NextRoom!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GauntletRoom() : base (null, "GauntletDO", true) {
|
||||
base.RoomVariables.Add(NetworkArray.VlElement("IS_RACE_ROOM", true));
|
||||
}
|
||||
|
||||
class Status {
|
||||
public string uid;
|
||||
public bool isReady = false;
|
||||
public string resultA = "";
|
||||
public string resultB = "";
|
||||
|
||||
public Status(string uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<Client, Status> players = new();
|
||||
|
||||
public void AddPlayer(Client client) {
|
||||
players[client] = new Status(client.PlayerData.Uid);
|
||||
}
|
||||
|
||||
public void SetPlayerReady(Client client, bool status = true) {
|
||||
players[client].isReady = status;
|
||||
}
|
||||
|
||||
public int GetReadyCount() {
|
||||
int count = 0;
|
||||
foreach(var player in players) {
|
||||
if (player.Value.isReady) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void SendUJR() {
|
||||
// {"a":13,"c":1,"p":{"c":"msg","p":{"arr":["UJR","287997","2","f66cc516-7ea3-40a5-9021-01ff8f290123","false","2","03a3ad99-87a5-4af4-8966-0b2733a05e0f","false","1"]},"r":287997}}
|
||||
List<string> info = new();
|
||||
info.Add("UJR"); // User Joined Room
|
||||
info.Add(base.Id.ToString());
|
||||
info.Add("2");
|
||||
foreach(var player in players) {
|
||||
info.Add(player.Value.uid);
|
||||
info.Add(player.Value.isReady.ToString());
|
||||
info.Add("1"); // TODO this should be player gender
|
||||
}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(info.ToArray(), "msg", base.Id);
|
||||
|
||||
Send(packet);
|
||||
}
|
||||
|
||||
public void SendPA(Client client) {
|
||||
// {"a":13,"c":1,"p":{"c":"msg","p":{"arr":["UJR","287997","2","f66cc516-7ea3-40a5-9021-01ff8f290123","false","2","03a3ad99-87a5-4af4-8966-0b2733a05e0f","false","1"]},"r":287997}}
|
||||
List<string> info = new();
|
||||
info.Add("PA"); // Play Again
|
||||
info.Add(base.Id.ToString());
|
||||
info.Add("1");
|
||||
foreach(var player in players) {
|
||||
info.Add(player.Value.uid);
|
||||
info.Add(player.Value.isReady.ToString());
|
||||
info.Add("1"); // TODO this should be player gender
|
||||
}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(info.ToArray(), "msg", base.Id);
|
||||
|
||||
client.Send(packet);
|
||||
}
|
||||
|
||||
public bool ProcessResult(Client client, string resultA, string resultB) {
|
||||
lock (base.roomLock) {
|
||||
players[client].resultA = resultA;
|
||||
players[client].resultB = resultB;
|
||||
|
||||
int count = 0;
|
||||
foreach(var player in players) {
|
||||
if (player.Value.resultB != "") ++count;
|
||||
}
|
||||
if (count != 2)
|
||||
return false;
|
||||
|
||||
// {"a":13,"c":1,"p":{"c":"msg","p":{"arr":["GC","365587","03a3ad99-87a5-4af4-8966-0b2733a05e0f","10850","79","1","bff0c312-8763-497d-aa0c-a5dfc7d8b861","21050","73","1"]},"r":365587}}
|
||||
List<string> info = new();
|
||||
info.Add("GC");
|
||||
info.Add(base.Id.ToString());
|
||||
foreach(var player in players) {
|
||||
if (player.Value.resultB == "")
|
||||
continue;
|
||||
info.Add(player.Value.uid);
|
||||
info.Add(player.Value.resultA);
|
||||
info.Add(player.Value.resultB);
|
||||
info.Add("1");
|
||||
}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(info.ToArray(), "msg", base.Id);
|
||||
|
||||
Send(packet);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static object joinLock = new object();
|
||||
|
||||
static public void Join(Client client, GauntletRoom? room = null) {
|
||||
lock(joinLock) {
|
||||
if (room is null)
|
||||
room = GauntletRoom.Get();
|
||||
|
||||
client.SetRoom(room);
|
||||
room.AddPlayer(client); // client will be not removed from GauntletRoom.players ... after remove all client from room whole GauntletRoom.players will be removed
|
||||
room.SendUJR();
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/Core/ModuleManager.cs
Normal file
50
src/Core/ModuleManager.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System.Reflection;
|
||||
using sodoffmmo.Attributes;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
|
||||
class ModuleManager {
|
||||
Dictionary<int, Type> handlers = new();
|
||||
Dictionary<string, Type> extHandlers = new();
|
||||
|
||||
public void RegisterModules() {
|
||||
RegisterHandlers();
|
||||
RegisterExtensionHandlers();
|
||||
}
|
||||
|
||||
public CommandHandler GetCommandHandler(int id) {
|
||||
if (handlers.TryGetValue(id, out Type? handler))
|
||||
return (CommandHandler)Activator.CreateInstance(handler)!;
|
||||
throw new Exception($"Command handler with ID {id} not found!");
|
||||
}
|
||||
|
||||
public CommandHandler GetCommandHandler(string name) {
|
||||
if (extHandlers.TryGetValue(name, out Type? handler))
|
||||
return (CommandHandler)Activator.CreateInstance(handler)!;
|
||||
throw new Exception($"Command handler with name \"{name}\" not found!");
|
||||
}
|
||||
|
||||
private void RegisterHandlers() {
|
||||
handlers.Clear();
|
||||
var handlerTypes = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(type => typeof(CommandHandler).IsAssignableFrom(type))
|
||||
.Where(type => type.GetCustomAttribute<CommandHandlerAttribute>() != null);
|
||||
|
||||
foreach (var handlerType in handlerTypes) {
|
||||
CommandHandlerAttribute attrib = (handlerType.GetCustomAttribute(typeof(CommandHandlerAttribute)) as CommandHandlerAttribute)!;
|
||||
handlers[attrib.ID] = handlerType;
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterExtensionHandlers() {
|
||||
extHandlers.Clear();
|
||||
var extHandlerTypes = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(type => typeof(CommandHandler).IsAssignableFrom(type))
|
||||
.Where(type => type.GetCustomAttribute<ExtensionCommandHandlerAttribute>() != null);
|
||||
|
||||
foreach (var extHandlerType in extHandlerTypes) {
|
||||
ExtensionCommandHandlerAttribute attrib = (extHandlerType.GetCustomAttribute(typeof(ExtensionCommandHandlerAttribute)) as ExtensionCommandHandlerAttribute)!;
|
||||
extHandlers[attrib.Name] = extHandlerType;
|
||||
}
|
||||
}
|
||||
}
|
||||
328
src/Core/Racing.cs
Normal file
328
src/Core/Racing.cs
Normal file
@ -0,0 +1,328 @@
|
||||
using System.Globalization;
|
||||
using sodoffmmo.Data;
|
||||
using System.Timers;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
|
||||
public enum RacingPlayerState {
|
||||
NotReady,
|
||||
Ready,
|
||||
InRacingRoom,
|
||||
RaceReady1,
|
||||
RaceReady2
|
||||
}
|
||||
|
||||
public class RacingRoom : Room {
|
||||
static Random random = new Random();
|
||||
|
||||
public static RacingRoom Get() {
|
||||
return new RacingRoom();
|
||||
}
|
||||
|
||||
public RacingRoom() : base (null, "RacingDragon", true) {
|
||||
TID = random.Next(105);
|
||||
|
||||
players = new();
|
||||
results = new();
|
||||
timer = null;
|
||||
|
||||
base.RoomVariables = new();
|
||||
base.RoomVariables.Add(NetworkArray.VlElement("IS_RACE_ROOM", "SINGLERACE#1#1#SINGLERACE#0#2"));
|
||||
base.RoomVariables.Add(NetworkArray.VlElement("TID", TID));
|
||||
}
|
||||
|
||||
public int TID;
|
||||
|
||||
// players ready status
|
||||
|
||||
Dictionary<Client, RacingPlayerState> players;
|
||||
|
||||
public void SetPlayerState(Client client, RacingPlayerState state) {
|
||||
players[client] = state;
|
||||
}
|
||||
|
||||
public bool IsPlayerState(Client client, RacingPlayerState state) {
|
||||
if (players.TryGetValue(client, out var info)) {
|
||||
return info == state;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int GetPlayersCount(RacingPlayerState state) {
|
||||
int count = 0;
|
||||
foreach(var player in players) {
|
||||
if (player.Value == state) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// results
|
||||
|
||||
class Result {
|
||||
public string userName;
|
||||
public string time;
|
||||
public string laps;
|
||||
|
||||
public Result(string userName, string time, string laps) {
|
||||
this.userName = userName;
|
||||
this.time = time;
|
||||
this.laps = laps;
|
||||
}
|
||||
}
|
||||
|
||||
SortedDictionary<float, Result> results;
|
||||
|
||||
public void SetResults(Client client, string userName, string time, string laps) {
|
||||
float timef = float.Parse(time, System.Globalization.CultureInfo.InvariantCulture);
|
||||
while (results.ContainsKey(timef))
|
||||
timef += 0.000001f;
|
||||
|
||||
results.Add(timef, new Result(userName, time, laps));
|
||||
}
|
||||
|
||||
public void SendResults() {
|
||||
if (ClientsCount == results.Count) {
|
||||
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","GR","Zavertin","91.81613","3","scourgexxwulf","111.81613","3"]},"r":412467}}
|
||||
List<string> info = new();
|
||||
info.Add("RA");
|
||||
info.Add("");
|
||||
info.Add("GR");
|
||||
foreach(var result in results) {
|
||||
info.Add(result.Value.userName);
|
||||
info.Add(result.Value.time);
|
||||
info.Add(result.Value.laps);
|
||||
}
|
||||
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(info.ToArray(), "", Id);
|
||||
Send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
// start and countdown
|
||||
|
||||
System.Timers.Timer? timer = null;
|
||||
int counter;
|
||||
|
||||
private void SetTimer(double timeout, System.Timers.ElapsedEventHandler callback, bool AutoReset = false) {
|
||||
if (timer != null) {
|
||||
timer.Stop();
|
||||
timer.Close();
|
||||
}
|
||||
|
||||
timer = new System.Timers.Timer(timeout * 1000);
|
||||
timer.AutoReset = AutoReset;
|
||||
timer.Enabled = true;
|
||||
timer.Elapsed += callback;
|
||||
}
|
||||
|
||||
public void Init() {
|
||||
counter = 20;
|
||||
SetTimer(0.2, SendJoin);
|
||||
}
|
||||
|
||||
private void SendJoin(Object? source, ElapsedEventArgs e) {
|
||||
foreach(var player in players) {
|
||||
player.Key.SetRoom(this);
|
||||
}
|
||||
SetTimer(1, CountDown, true);
|
||||
}
|
||||
|
||||
private void CountDown(Object? source, ElapsedEventArgs e) {
|
||||
if (!TryLoad()) {
|
||||
if (counter == 0) {
|
||||
Load();
|
||||
} else {
|
||||
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","LT","18"]},"r":412467}}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"RA",
|
||||
"",
|
||||
"LT",
|
||||
(--counter).ToString()
|
||||
}, "", Id);
|
||||
Send(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryLoad() {
|
||||
if (GetPlayersCount(RacingPlayerState.Ready) == ClientsCount) {
|
||||
Load();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Load() {
|
||||
timer!.Stop();
|
||||
timer!.Close();
|
||||
|
||||
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","ST"]},"r":412467}}
|
||||
NetworkPacket packet = Utils.ArrNetworkPacket(new string[] {
|
||||
"RA",
|
||||
"",
|
||||
"ST"
|
||||
}, "", Id);
|
||||
Send(packet);
|
||||
}
|
||||
|
||||
// TODO StratTimer → kick out to main lobby players without RacingPlayerState.RaceReady1 after timeout, next kick out players without RacingPlayerState.RaceReady2 after timeout2
|
||||
|
||||
// TODO EndTimer → {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","ET","1"]},"r":412467}}
|
||||
|
||||
// utils
|
||||
|
||||
public NetworkPacket GetTIDPacket() {
|
||||
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","TID","","10","0","0","0","0"]}}}
|
||||
return Utils.ArrNetworkPacket(new string[] {
|
||||
"RA",
|
||||
"",
|
||||
"TID",
|
||||
"", // game mode (unused)
|
||||
TID.ToString(), // track id
|
||||
"0", // theme id
|
||||
"0","0","0" // unused (?)
|
||||
});
|
||||
}
|
||||
|
||||
public NetworkPacket GetSTAPacket() {
|
||||
string staList = "";
|
||||
int i = 0;
|
||||
foreach(var player in players) {
|
||||
if (i > 0)
|
||||
staList += ":";
|
||||
staList += player.Key.PlayerData.Uid + ":" + (++i).ToString();
|
||||
}
|
||||
|
||||
// {"a":13,"c":1,"p":{"c":"","p":{"arr":["RA","","STA","c4647597-a72a-4f34-973c-5a10218d9a64:1:f05fc387-7358-4bff-be04-7c316f0a8de8:2:ae70ef16-c52c-43e4-9305-d6ea3e378a0d:3","1688128174649"]},"r":412467}}
|
||||
return Utils.ArrNetworkPacket(new string[] {
|
||||
"RA",
|
||||
"",
|
||||
"STA",
|
||||
staList
|
||||
}, "", Id);
|
||||
}
|
||||
}
|
||||
|
||||
public class RacingLobby {
|
||||
static object lobbyLock = new object();
|
||||
public readonly static RacingLobby Lobby = new RacingLobby();
|
||||
|
||||
private RacingLobby() {
|
||||
timer = new System.Timers.Timer(1000);
|
||||
timer.AutoReset = true;
|
||||
timer.Enabled = true;
|
||||
timer.Elapsed += CheckRacingRoomCountdown;
|
||||
}
|
||||
|
||||
System.Timers.Timer timer;
|
||||
int counter;
|
||||
|
||||
private void CheckRacingRoomCountdown(Object? source, System.Timers.ElapsedEventArgs e) {
|
||||
lock (lobbyLock) {
|
||||
int readyPlayersCount = GetPlayersCount(RacingPlayerState.Ready);
|
||||
if (readyPlayersCount >= Configuration.ServerConfiguration.RacingMaxPlayers) {
|
||||
SendToRacingRoom();
|
||||
counter = -1;
|
||||
} else if (readyPlayersCount >= Configuration.ServerConfiguration.RacingMinPlayers) {
|
||||
if (counter < 0) {
|
||||
counter = Configuration.ServerConfiguration.RacingMainLobbyTimer;
|
||||
}
|
||||
if (--counter == 0) {
|
||||
SendToRacingRoom();
|
||||
counter = -1;
|
||||
}
|
||||
} else {
|
||||
counter = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Status {
|
||||
public string uid;
|
||||
public RacingPlayerState state = RacingPlayerState.NotReady;
|
||||
public Status (string uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<Client, Status> lobbyPlayers = new();
|
||||
|
||||
public void SetPlayerState(Client client, RacingPlayerState state) {
|
||||
lock (lobbyLock) {
|
||||
if (!lobbyPlayers.ContainsKey(client)) {
|
||||
lobbyPlayers[client] = new Status(client.PlayerData.Uid);
|
||||
}
|
||||
lobbyPlayers[client].state = state;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPlayerState(Client client, RacingPlayerState state) {
|
||||
lock (lobbyLock) {
|
||||
if (lobbyPlayers.TryGetValue(client, out var info)) {
|
||||
return info.state == state;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetPlayersCount(RacingPlayerState state) {
|
||||
lock (lobbyLock) {
|
||||
int count = 0;
|
||||
foreach(var player in lobbyPlayers) {
|
||||
if (player.Value.state == state) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private bool SendToRacingRoom() {
|
||||
// lock (lobbyLock) {
|
||||
if (GetPlayersCount(RacingPlayerState.Ready) >= Configuration.ServerConfiguration.RacingMinPlayers) {
|
||||
int i = 0;
|
||||
RacingRoom room = RacingRoom.Get();
|
||||
foreach (var player in lobbyPlayers) {
|
||||
if (player.Value.state == RacingPlayerState.Ready) {
|
||||
if (++i > Configuration.ServerConfiguration.RacingMaxPlayers)
|
||||
break;
|
||||
// set client state in Lobby
|
||||
player.Value.state = RacingPlayerState.InRacingRoom;
|
||||
// send TID info to client
|
||||
player.Key.Send(room.GetTIDPacket());
|
||||
// set client state in racing room
|
||||
room.SetPlayerState(player.Key, RacingPlayerState.NotReady);
|
||||
}
|
||||
}
|
||||
// join clients to racing room and start countdown
|
||||
// after change room, client will be removed from lobbyPlayers (in GetPS)
|
||||
room.Init();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// }
|
||||
}
|
||||
|
||||
public NetworkPacket GetPS() {
|
||||
List<Client> toRemove = new();
|
||||
Room room = Room.Get("DragonRacingDO");
|
||||
|
||||
// {"a":13,"c":1,"p":{"c":"PS","p":{"arr":["RA","","PS","e6147216-8100-4552-864d-be8f1347e201","8cb5842d-735a-4259-80af-e2e204b9c2bd"]}}}
|
||||
List<string> info = new();
|
||||
info.Add("RA");
|
||||
info.Add("");
|
||||
info.Add("PS");
|
||||
foreach(var player in lobbyPlayers) {
|
||||
if (player.Key.Room != room) {
|
||||
toRemove.Add(player.Key);
|
||||
} else if (player.Value.state != RacingPlayerState.InRacingRoom) {
|
||||
info.Add(player.Value.uid);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var player in toRemove) {
|
||||
lobbyPlayers.Remove(player);
|
||||
}
|
||||
|
||||
return Utils.ArrNetworkPacket(info.ToArray(), "PS");
|
||||
}
|
||||
}
|
||||
160
src/Core/Room.cs
Normal file
160
src/Core/Room.cs
Normal file
@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Net.Sockets;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
public class Room {
|
||||
public static int MaxId { get; private set; } = 2;
|
||||
static object RoomsListLock = new object();
|
||||
protected static Dictionary<string, Room> rooms = new();
|
||||
|
||||
List<Client> clients = new();
|
||||
protected object roomLock = new object();
|
||||
|
||||
public int Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Group { get; private set; }
|
||||
public bool AutoRemove { get; private set; }
|
||||
public bool IsRemoved { get; private set; } = false;
|
||||
|
||||
public bool AllowChatOverride { get; set; } = false;
|
||||
public NetworkArray RoomVariables = new();
|
||||
|
||||
public Room(string? name, string? group = null, bool autoRemove = false) {
|
||||
Id = ++MaxId;
|
||||
if (name is null) {
|
||||
Name = group! + "_" + MaxId;
|
||||
} else {
|
||||
Name = name;
|
||||
}
|
||||
if (group is null) {
|
||||
Group = name!;
|
||||
} else {
|
||||
Group = group;
|
||||
}
|
||||
AutoRemove = autoRemove;
|
||||
rooms.Add(Name, this);
|
||||
}
|
||||
|
||||
public int ClientsCount => clients.Count;
|
||||
|
||||
public IEnumerable<Client> Clients {
|
||||
get {
|
||||
List<Client> list;
|
||||
lock (roomLock) {
|
||||
list = new List<Client>(clients);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddClient(Client client) {
|
||||
lock (roomLock) {
|
||||
if (IsRemoved)
|
||||
throw new Exception("Call AddClient on removed room");
|
||||
client.Send(RespondJoinRoom());
|
||||
// NOTE: send RespondJoinRoom() and add client to clients as atomic operation
|
||||
// to make sure to client get full list of players in room
|
||||
clients.Add(client);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveClient(Client client) {
|
||||
lock (roomLock) {
|
||||
clients.Remove(client);
|
||||
if (AutoRemove && ClientsCount == 0) {
|
||||
IsRemoved = true;
|
||||
rooms.Remove(Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(NetworkPacket packet, Client? skip = null, bool useUDP = false) {
|
||||
foreach (var roomClient in Clients) {
|
||||
if (roomClient != skip) {
|
||||
roomClient.Send(packet, useUDP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Exists(string name) => rooms.ContainsKey(name);
|
||||
|
||||
public static Room Get(string name) => rooms[name];
|
||||
|
||||
public static Room GetOrAdd(string name, bool autoRemove = false) {
|
||||
lock(RoomsListLock) {
|
||||
if (!Room.Exists(name))
|
||||
return new Room(name, autoRemove: autoRemove);
|
||||
return rooms[name];
|
||||
}
|
||||
}
|
||||
|
||||
public static Room[] AllRooms() {
|
||||
return rooms.Values.ToArray();
|
||||
}
|
||||
|
||||
public static void DisableAllChatOverrides() {
|
||||
lock (RoomsListLock) {
|
||||
foreach (var room in rooms) {
|
||||
room.Value.AllowChatOverride = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkPacket RespondJoinRoom() {
|
||||
NetworkObject obj = new();
|
||||
NetworkArray roomInfo = new();
|
||||
roomInfo.Add(Id);
|
||||
roomInfo.Add(Name); // Room Name
|
||||
roomInfo.Add(Group); // Group Name
|
||||
roomInfo.Add(true); // is game
|
||||
roomInfo.Add(false); // is hidden
|
||||
roomInfo.Add(false); // is password protected
|
||||
roomInfo.Add((short)clients.Count); // player count
|
||||
roomInfo.Add((short)4096); // max player count
|
||||
roomInfo.Add(GetRoomVars()); // variables (plus added data)
|
||||
roomInfo.Add((short)0); // spectator count
|
||||
roomInfo.Add((short)0); // max spectator count
|
||||
|
||||
NetworkArray userList = new();
|
||||
foreach (Client player in clients) {
|
||||
if (player.PlayerData.Uid != "")
|
||||
userList.Add(player.PlayerData.GetNetworkData(player.ClientID, out _));
|
||||
}
|
||||
|
||||
obj.Add("r", roomInfo);
|
||||
obj.Add("ul", userList);
|
||||
|
||||
NetworkPacket packet = NetworkObject.WrapObject(0, 4, obj).Serialize();
|
||||
packet.Compress();
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
public NetworkPacket SubscribeRoom() {
|
||||
NetworkObject obj = new();
|
||||
NetworkArray list = new();
|
||||
|
||||
NetworkArray r1 = new();
|
||||
r1.Add(Id);
|
||||
r1.Add(Name); // Room Name
|
||||
r1.Add(Group); // Group Name
|
||||
r1.Add(true);
|
||||
r1.Add(false);
|
||||
r1.Add(false);
|
||||
r1.Add((short)clients.Count); // player count
|
||||
r1.Add((short)4096); // max player count
|
||||
r1.Add(GetRoomVars());
|
||||
r1.Add((short)0);
|
||||
r1.Add((short)0);
|
||||
|
||||
list.Add(r1);
|
||||
|
||||
obj.Add("rl", list);
|
||||
obj.Add("g", Group);
|
||||
return NetworkObject.WrapObject(0, 15, obj).Serialize();
|
||||
}
|
||||
|
||||
internal virtual NetworkArray GetRoomVars() { return RoomVariables; }
|
||||
}
|
||||
20
src/Core/Runtime.cs
Normal file
20
src/Core/Runtime.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
public static class Runtime {
|
||||
static Runtime() {
|
||||
var currentProcess = Process.GetCurrentProcess();
|
||||
lastSystemTime = (long)(DateTime.Now - currentProcess.StartTime).TotalMilliseconds;
|
||||
currentProcess.Dispose();
|
||||
stopwatch = new Stopwatch(); stopwatch.Start();
|
||||
}
|
||||
|
||||
private static long lastSystemTime;
|
||||
private static Stopwatch stopwatch;
|
||||
|
||||
public static long CurrentRuntime {
|
||||
get {
|
||||
return stopwatch.ElapsedMilliseconds + lastSystemTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
261
src/Core/SWRacing.cs
Normal file
261
src/Core/SWRacing.cs
Normal file
@ -0,0 +1,261 @@
|
||||
using sodoffmmo.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
namespace sodoffmmo.Core
|
||||
{
|
||||
public class SWRacingRoom : Room
|
||||
{
|
||||
|
||||
class SWPlayerState
|
||||
{
|
||||
public int Index;
|
||||
public Vector3 PosTransform = new();
|
||||
public string Token = "";
|
||||
public bool IsHost;
|
||||
public bool IsReady;
|
||||
public bool SceneLoaded;
|
||||
public bool CountdownFinished;
|
||||
public int ColorIndex;
|
||||
public string VehicleResName = "";
|
||||
public int CurrentLap;
|
||||
public float FinishedTime = -1f;
|
||||
public bool IsTrackPrebuilt;
|
||||
public int TrackIdx;
|
||||
public string TrackStatus = "ZR";
|
||||
}
|
||||
|
||||
public SWRacingRoom() : base(null, "ShipWreckMPRoom", true)
|
||||
{
|
||||
}
|
||||
|
||||
public bool CountdownStarted { get; set; } = false;
|
||||
public bool RaceOngoing { get; set; } = false;
|
||||
public bool BuddiesOnly { get; set; } = false;
|
||||
|
||||
Dictionary<Client, SWPlayerState> Players = new();
|
||||
Client? host = null;
|
||||
int nextIndex = 1;
|
||||
|
||||
public void AddPlayer(Client client)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = new SWPlayerState { Index = nextIndex++ };
|
||||
if(host is null)
|
||||
{
|
||||
host = client;
|
||||
state.IsHost = true;
|
||||
}
|
||||
Players[client] = state;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetIndex(Client client)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
return state.Index;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetReadyCount()
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
int readyCount = 0;
|
||||
foreach(var (client, s) in Players)
|
||||
{
|
||||
if (s.IsReady)
|
||||
readyCount++;
|
||||
}
|
||||
return readyCount;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetPlayersWithinRadius(Vector3 blast, float hitRadius, Client? excluding)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
float targetRadiusSquared = hitRadius * hitRadius;
|
||||
List<int> targetedPlayers = [];
|
||||
|
||||
foreach(var player in Players)
|
||||
{
|
||||
if (player.Key.ClientID == excluding?.ClientID) continue;
|
||||
if (Vector3.DistanceSquared(player.Value.PosTransform, blast) < targetRadiusSquared)
|
||||
targetedPlayers.Add(player.Value.Index);
|
||||
}
|
||||
|
||||
return targetedPlayers;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPlayerPosition(Client client, float X, float Y, float Z)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.PosTransform.X = X;
|
||||
state.PosTransform.Y = Y;
|
||||
state.PosTransform.Z = Z;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetReady(Client client, bool ready)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.IsReady = ready;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTrack(Client client, int trackId)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.TrackIdx = trackId;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTrackStatus(Client client, string statusCode)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.TrackStatus = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetIsPrebuiltTrack(Client client, bool isPrebuiltTrack)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.IsTrackPrebuilt = isPrebuiltTrack;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBoat(Client client, string boatRes, int colorIndex = 0)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.VehicleResName = boatRes;
|
||||
state.ColorIndex = colorIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetToken(Client client, string token)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.Token = token;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetSceneLoaded(Client client)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
Players[client].SceneLoaded = true;
|
||||
foreach (var (_, s) in Players)
|
||||
if (!s.SceneLoaded) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetCountdownFinished(Client client)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
Players[client].CountdownFinished = true;
|
||||
foreach (var (_, s) in Players)
|
||||
if (!s.CountdownFinished) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPlayerLap(Client client, int lap)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.CurrentLap = lap;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFinishedTime(Client client, float time)
|
||||
{
|
||||
lock (roomLock)
|
||||
{
|
||||
var state = Players[client];
|
||||
state.FinishedTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkPacket GetAMPacket()
|
||||
{
|
||||
List<string> info = [ "AM" ];
|
||||
foreach(var (client, s) in Players)
|
||||
{
|
||||
info.Add(s.IsHost ? "ZJ" : "");
|
||||
info.Add(client.PlayerData.UNToken);
|
||||
info.Add(s.Index.ToString());
|
||||
info.Add(s.ColorIndex.ToString());
|
||||
info.Add(s.VehicleResName);
|
||||
info.Add(s.IsReady ? "AN" : "");
|
||||
info.Add(s.IsTrackPrebuilt ? "ZP" : "ZQ");
|
||||
info.Add(s.TrackIdx.ToString());
|
||||
info.Add(s.TrackStatus);
|
||||
}
|
||||
|
||||
string joined = string.Join("|", info);
|
||||
|
||||
return Utils.ArrNetworkPacket(["AM", joined], "", Id);
|
||||
}
|
||||
|
||||
System.Timers.Timer? countdownTimer;
|
||||
int lobbyCounter;
|
||||
|
||||
public void TryStartLobbyCountdown()
|
||||
{
|
||||
Console.WriteLine($"Attempted To Start SWL Lobby Countdown On SWL {Id}. Player Count - {Players.Count}");
|
||||
|
||||
lock (roomLock)
|
||||
{
|
||||
if (Players.Count <= 1) return; // this is multiplayers, don't just start a singleplayer time trial lmao
|
||||
if (CountdownStarted || GetReadyCount() < Players.Count) return;
|
||||
|
||||
string joined = string.Join("|", "AB", Players.Count.ToString(), "3");
|
||||
Send(Utils.ArrNetworkPacket([joined], "", Id));
|
||||
|
||||
CountdownStarted = true;
|
||||
Console.WriteLine($"Countdown Started On SWL {Id}");
|
||||
}
|
||||
|
||||
lobbyCounter = 3;
|
||||
countdownTimer = new(1000) { AutoReset = true };
|
||||
countdownTimer.Elapsed += TickLobbyCountdown;
|
||||
countdownTimer.Enabled = true;
|
||||
}
|
||||
|
||||
private void TickLobbyCountdown(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"Lobby Countdown Tick TS - {DateTime.Now}");
|
||||
if(--lobbyCounter <= 0)
|
||||
{
|
||||
countdownTimer!.Stop();
|
||||
string joined = string.Join("|", "ZH", lobbyCounter.ToString());
|
||||
Send(Utils.ArrNetworkPacket([joined], "", Id));
|
||||
RaceOngoing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
191
src/Core/SpecialRoom.cs
Normal file
191
src/Core/SpecialRoom.cs
Normal file
@ -0,0 +1,191 @@
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
|
||||
public class SpecialRoom : Room {
|
||||
public double[] ambassadorGauges = new double[3]; // There is always a maximum of 3.
|
||||
System.Timers.Timer? ambassadorTimer;
|
||||
|
||||
public static void CreateRooms() {
|
||||
foreach (var room in Configuration.ServerConfiguration.RoomAlerts) {
|
||||
foreach (var alert in room.Value) {
|
||||
AlertInfo alertInfo = new AlertInfo(
|
||||
alert[0], // type
|
||||
float.Parse(alert[1], System.Globalization.CultureInfo.InvariantCulture.NumberFormat), // duration
|
||||
Int32.Parse(alert[2]), Int32.Parse(alert[3]), // start min - max for random start time
|
||||
Int32.Parse(alert[4]), Int32.Parse(alert[5]) // extra parameters for specific alarm types
|
||||
);
|
||||
Console.WriteLine($"Setup alert {alertInfo} for {room.Key}");
|
||||
(rooms.GetValueOrDefault(room.Key) as SpecialRoom ?? new SpecialRoom(room.Key)).AddAlert(alertInfo);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var room in Configuration.ServerConfiguration.AmbassadorRooms) {
|
||||
Console.WriteLine($"Setup Ambassador for {room}");
|
||||
(rooms.GetValueOrDefault(room) as SpecialRoom ?? new SpecialRoom(room)).InitAmbassador();
|
||||
}
|
||||
}
|
||||
|
||||
public SpecialRoom(string name) : base(name) {}
|
||||
|
||||
public void InitAmbassador() {
|
||||
for (int i=0;i<3;i++) ambassadorGauges[i] = Configuration.ServerConfiguration.AmbassadorGaugeStart;
|
||||
ambassadorTimer = new(Configuration.ServerConfiguration.AmbassadorGaugeDecayRate * 1000) {
|
||||
AutoReset = true,
|
||||
Enabled = true
|
||||
};
|
||||
ambassadorTimer.Elapsed += (sender, e) => {
|
||||
if (!Configuration.ServerConfiguration.AmbassadorGaugeDecayOnlyWhenInRoom || ClientsCount > 0) {
|
||||
for (int i=0;i<3;i++) ambassadorGauges[i] = Math.Max(0, ambassadorGauges[i]-1);
|
||||
Send(Utils.VlNetworkPacket(GetRoomVars(), Id));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal override NetworkArray GetRoomVars() {
|
||||
NetworkArray vars = new();
|
||||
vars.Add(NetworkArray.VlElement("COUNT", (int)Math.Round(ambassadorGauges[0]), isPersistent: true));
|
||||
vars.Add(NetworkArray.VlElement("COUNT2", (int)Math.Round(ambassadorGauges[1]), isPersistent: true));
|
||||
vars.Add(NetworkArray.VlElement("COUNT3", (int)Math.Round(ambassadorGauges[2]), isPersistent: true));
|
||||
for (int i=0;i<RoomVariables.Length;i++) vars.Add(RoomVariables[i]);
|
||||
return vars;
|
||||
}
|
||||
|
||||
private int alertId = -1;
|
||||
private Random random = new();
|
||||
|
||||
List<AlertInfo> alerts = new();
|
||||
|
||||
public void AddAlert(AlertInfo alert) {
|
||||
alerts.Add(alert);
|
||||
ResetAlertTimer(alert);
|
||||
}
|
||||
|
||||
public void SendAllAlerts(Client client) {
|
||||
return; // Disables joining ongoing alerts (since it doesn't work properly).
|
||||
|
||||
foreach (AlertInfo alert in alerts) {
|
||||
if (alert.IsRunning()) StartAlert(alert, client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void StartAlert(AlertInfo alert, Client? specificClient = null) {
|
||||
NetworkArray NewRoomVariables = new();
|
||||
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_START, alertId++, isPersistent: true));
|
||||
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_TYPE, alert.type, isPersistent: true));
|
||||
double duration = (alert.endTime - DateTime.Now).TotalSeconds;
|
||||
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_LENGTH, alert.type == "1" ? alert.redAlertDuration : duration, isPersistent: true));
|
||||
if (alert.type == "1") {
|
||||
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_TIMEOUT, duration, isPersistent: true));
|
||||
} else if (alert.type == "3") {
|
||||
alert.songId = random.Next(0, alert.songs);
|
||||
NewRoomVariables.Add(NetworkArray.VlElement(REDALERT_SONG, (double)alert.songId, isPersistent: true));
|
||||
}
|
||||
NetworkPacket packet = Utils.VlNetworkPacket(NewRoomVariables, Id);
|
||||
if (specificClient is null) {
|
||||
RoomVariables = NewRoomVariables;
|
||||
Send(packet);
|
||||
RoomVariables = new();
|
||||
Console.WriteLine("Started event " +alert + " in room " + Name);
|
||||
} else {
|
||||
specificClient.Send(packet);
|
||||
Console.WriteLine("Added " + specificClient.PlayerData.DiplayName + " to event " + alert + " with " + duration + " seconds remaining");
|
||||
}
|
||||
}
|
||||
|
||||
void ResetAlertTimer(AlertInfo alert) {
|
||||
System.Timers.Timer? timer = alert.timer;
|
||||
if (timer != null) {
|
||||
timer.Stop();
|
||||
timer.Close();
|
||||
}
|
||||
DateTime startTime = DateTime.Now.AddMilliseconds(random.Next(alert.minTime * 1000, alert.maxTime * 1000));
|
||||
DateTime endTime = startTime.AddSeconds(alert.duration);
|
||||
for (int i = 0; i < alerts.IndexOf(alert); i++) {
|
||||
// Prevent overlap between two events.
|
||||
if (alerts[i].Overlaps(endTime)) {
|
||||
startTime = alerts[i].endTime.AddSeconds(5);
|
||||
endTime = startTime.AddSeconds(alert.duration);
|
||||
}
|
||||
}
|
||||
timer = new System.Timers.Timer((startTime - DateTime.Now).TotalMilliseconds);
|
||||
timer.AutoReset = false;
|
||||
timer.Enabled = true;
|
||||
timer.Elapsed += (sender, e) => StartAlert(alert);
|
||||
timer.Elapsed += (sender, e) => ResetAlertTimer(alert);
|
||||
alert.timer = timer;
|
||||
Console.WriteLine("Event " + alert + " in " + Name + " scheduled for " + startTime.ToString("MM/dd/yyyy HH:mm:ss tt") + " (in " + (startTime - DateTime.Now).TotalSeconds + " seconds)");
|
||||
alert.startTime = startTime;
|
||||
alert.endTime = endTime;
|
||||
}
|
||||
|
||||
private const string REDALERT_START = "RA_S";
|
||||
private const string REDALERT_TYPE = "RA_A";
|
||||
private const string REDALERT_LENGTH = "RA_L";
|
||||
private const string REDALERT_TIMEOUT = "RA_T";
|
||||
private const string REDALERT_SONG = "RA_SO";
|
||||
|
||||
public class AlertInfo {
|
||||
public readonly string type;
|
||||
public readonly double duration;
|
||||
public readonly int minTime;
|
||||
public readonly int maxTime;
|
||||
public readonly int redAlertDuration;
|
||||
public readonly int songs;
|
||||
public int songId;
|
||||
|
||||
public DateTime startTime {
|
||||
get {
|
||||
return newStartTime;
|
||||
}
|
||||
set {
|
||||
oldStartTime = newStartTime;
|
||||
newStartTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime endTime {
|
||||
get {
|
||||
return newEndTime;
|
||||
}
|
||||
set {
|
||||
oldEndTime = newEndTime;
|
||||
newEndTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime newStartTime;
|
||||
private DateTime newEndTime;
|
||||
private DateTime oldStartTime;
|
||||
private DateTime oldEndTime;
|
||||
|
||||
public System.Timers.Timer? timer = null;
|
||||
|
||||
public AlertInfo(string type, double duration = 20.0, int minTime = 30, int maxTime = 240, int redAlertDuration = 60, int songs = 16) {
|
||||
this.type = type;
|
||||
this.duration = duration;
|
||||
this.minTime = minTime;
|
||||
this.maxTime = maxTime;
|
||||
this.redAlertDuration = redAlertDuration;
|
||||
this.songs = songs;
|
||||
}
|
||||
|
||||
public bool Overlaps(DateTime time) {
|
||||
return (time >= oldStartTime && time <= oldEndTime);
|
||||
}
|
||||
|
||||
public bool IsRunning() {
|
||||
return Overlaps(DateTime.Now);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return type switch {
|
||||
"1" => "RedAlert",
|
||||
"2" => "DiscoAlert",
|
||||
"3" => "DanceOff",
|
||||
_ => type
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/Core/UserBanType.cs
Normal file
10
src/Core/UserBanType.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace sodoffmmo.Core;
|
||||
|
||||
public enum UserBanType
|
||||
{
|
||||
NotBanned = 0,
|
||||
IndefiniteOpenChatBan = 1,
|
||||
TemporaryOpenChatBan = 2,
|
||||
IndefiniteAccountBan = 3,
|
||||
TemporaryAccountBan = 4
|
||||
}
|
||||
56
src/Core/Utils.cs
Normal file
56
src/Core/Utils.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using sodoffmmo.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
internal static class Utils {
|
||||
public static NetworkPacket VlNetworkPacket(NetworkArray vl, int roomID) {
|
||||
NetworkObject obj = new();
|
||||
obj.Add("r", roomID);
|
||||
obj.Add("vl", vl);
|
||||
return NetworkObject.WrapObject(0, 11, obj).Serialize();
|
||||
}
|
||||
|
||||
public static NetworkPacket VlNetworkPacket(string a, string b, int roomID) {
|
||||
NetworkArray vl = new();
|
||||
vl.Add(NetworkArray.VlElement(a, b));
|
||||
return VlNetworkPacket(vl, roomID);
|
||||
}
|
||||
|
||||
public static NetworkPacket ArrNetworkPacket(string[] data, string c = "", int? roomID = null) {
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject obj = new();
|
||||
obj.Add("arr", data);
|
||||
cmd.Add("c", c);
|
||||
cmd.Add("p", obj);
|
||||
NetworkObject ret = NetworkObject.WrapObject(1, 13, cmd);
|
||||
if (roomID != null)
|
||||
ret.Add("r", (int)roomID);
|
||||
return ret.Serialize();
|
||||
}
|
||||
|
||||
public static T DeserializeXml<T>(string xmlString) {
|
||||
var serializer = new XmlSerializer(typeof(T));
|
||||
using (var reader = new StringReader(xmlString))
|
||||
return (T)serializer.Deserialize(reader);
|
||||
}
|
||||
|
||||
public static NetworkPacket BuildChatMessage(string uid, string message, string displayName) {
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject data = new();
|
||||
data.Add("arr", new string[] { "CMR", "-1", uid, "1", message, "", "1", displayName });
|
||||
cmd.Add("c", "CMR");
|
||||
cmd.Add("p", data);
|
||||
|
||||
return NetworkObject.WrapObject(1, 13, cmd).Serialize();
|
||||
}
|
||||
|
||||
public static NetworkPacket BuildServerSideMessage(string message, string displayName) {
|
||||
return BuildChatMessage("-1", message, displayName);
|
||||
}
|
||||
|
||||
}
|
||||
285
src/Core/WorldEvent.cs
Normal file
285
src/Core/WorldEvent.cs
Normal file
@ -0,0 +1,285 @@
|
||||
using System.Globalization;
|
||||
using sodoffmmo.Data;
|
||||
using System.Timers;
|
||||
|
||||
namespace sodoffmmo.Core;
|
||||
class WorldEvent {
|
||||
enum State {
|
||||
Active,
|
||||
End,
|
||||
NotActive
|
||||
}
|
||||
private static WorldEvent? _instance = null;
|
||||
private static object EventLock = new object();
|
||||
private Random random = new Random();
|
||||
private System.Timers.Timer? timer = null;
|
||||
|
||||
public static WorldEvent Get() {
|
||||
lock(EventLock) {
|
||||
if (_instance == null) {
|
||||
_instance = new WorldEvent();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private WorldEvent() {
|
||||
startTime = DateTime.UtcNow.AddMinutes(-60);
|
||||
startTimeString = startTime.ToString("MM/dd/yyyy HH:mm:ss");
|
||||
room = Room.GetOrAdd("HubTrainingDO");
|
||||
uid = "sodoff";
|
||||
state = State.End;
|
||||
ScheduleEvent(Configuration.ServerConfiguration.FirstEventTimer); // WE_ != WEN_
|
||||
}
|
||||
|
||||
// controlled (init/reset) by Reset()
|
||||
private Room room;
|
||||
private string uid;
|
||||
private Client? operatorAI;
|
||||
private State state;
|
||||
|
||||
private DateTime startTime;
|
||||
private DateTime endTime;
|
||||
private DateTime nextStartTime;
|
||||
private DateTime AITime;
|
||||
private bool endTimeIsSet;
|
||||
private string startTimeString;
|
||||
private string nextStartTimeString;
|
||||
|
||||
// controlled (init/reset) by InitEvent()
|
||||
private Dictionary<string, float> health = new();
|
||||
private Dictionary<string, string> players = new();
|
||||
private string lastResults = "";
|
||||
|
||||
// reset event - set new id, start time, end time, etc
|
||||
private void Reset(DateTime newStartTime) {
|
||||
lock (EventLock) {
|
||||
uid = Path.GetRandomFileName().Substring(0, 8); // this is used as RandomSeed for random select ship variant
|
||||
operatorAI = null;
|
||||
state = State.NotActive;
|
||||
|
||||
startTime = newStartTime;
|
||||
startTimeString = startTime.ToString("MM/dd/yyyy HH:mm:ss");
|
||||
AITime = startTime.AddMinutes(-1);
|
||||
UpdateEndTime(600 + 90);
|
||||
endTimeIsSet = false;
|
||||
|
||||
nextStartTime = startTime;
|
||||
nextStartTimeString = startTimeString;
|
||||
|
||||
Console.WriteLine($"Event {uid} start time: {startTimeString}");
|
||||
}
|
||||
}
|
||||
|
||||
// set / update event end time in results of Reset() or SetTimeSpan()
|
||||
private void UpdateEndTime(double timeout) {
|
||||
endTime = startTime.AddSeconds(timeout);
|
||||
Console.WriteLine($"Event {uid} end time: {endTime}");
|
||||
SetTimer((endTime - DateTime.UtcNow).TotalSeconds, PreEndEvent);
|
||||
}
|
||||
|
||||
// schedule next event and set timer to call PreInit
|
||||
private void ScheduleEvent(float minutes = 0) {
|
||||
if (minutes > 2)
|
||||
nextStartTime = DateTime.UtcNow.AddMinutes(minutes);
|
||||
else
|
||||
nextStartTime = startTime.AddMinutes(Configuration.ServerConfiguration.EventTimer);
|
||||
nextStartTimeString = nextStartTime.ToString("MM/dd/yyyy HH:mm:ss");
|
||||
|
||||
double timeout = (nextStartTime - DateTime.UtcNow).TotalSeconds - 120;
|
||||
if (timeout > 0)
|
||||
SetTimer((nextStartTime - DateTime.UtcNow).TotalSeconds - 120, PreInit);
|
||||
else
|
||||
Console.WriteLine($"Events disabled");
|
||||
}
|
||||
|
||||
// reset event and set timer to call PreEndEvent, send new WE_ info
|
||||
private void PreInit(Object? source, ElapsedEventArgs e) {
|
||||
Reset(nextStartTime); // WE_ == WEN_
|
||||
AnnounceEvent();
|
||||
}
|
||||
|
||||
// check init state and init event (set AI, reset health, score) if need in response to client (shot, etc) request
|
||||
private void InitEvent() {
|
||||
lock (EventLock) {
|
||||
if (AITime < DateTime.UtcNow && state != State.End) {
|
||||
var clients = room.Clients.ToList();
|
||||
operatorAI = clients[random.Next(0, clients.Count)];
|
||||
AITime = DateTime.UtcNow.AddSeconds(3.5);
|
||||
|
||||
if (state == State.NotActive) {
|
||||
// clear here because after Reset() we can get late packages about previous events
|
||||
health = new();
|
||||
players = new();
|
||||
lastResults = "";
|
||||
state = State.Active;
|
||||
}
|
||||
|
||||
operatorAI.Send(Utils.VlNetworkPacket("WE__AI", operatorAI.PlayerData.Uid, room.Id));
|
||||
Console.WriteLine($"Event {uid} AI operator: {operatorAI.PlayerData.Uid}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PreEndEvent(Object? source, ElapsedEventArgs e) {
|
||||
Console.WriteLine($"Event {uid} force end from timer");
|
||||
EndEvent(true);
|
||||
}
|
||||
|
||||
private bool EndEvent(bool force = false) {
|
||||
bool results = false;
|
||||
string targets = "";
|
||||
if (health.Count > 0) {
|
||||
results = true;
|
||||
foreach (var x in health) {
|
||||
results = results && (x.Value == 0.0f);
|
||||
targets += x.Key + ":" + x.Value.ToString("0.0#####", CultureInfo.GetCultureInfo("en-US")) + ",";
|
||||
}
|
||||
}
|
||||
if (results || force) {
|
||||
lock (EventLock) {
|
||||
if (state == State.End || (state == State.NotActive && !force))
|
||||
return true;
|
||||
state = State.End;
|
||||
}
|
||||
|
||||
string scores = "";
|
||||
foreach (var x in players) {
|
||||
scores += x.Key + "/" + x.Value + ",";
|
||||
}
|
||||
lastResults = $"{uid};{results};{scores};{targets}";
|
||||
|
||||
Console.WriteLine($"Event {uid} end: {results} {targets} {scores}");
|
||||
|
||||
SetTimer(2, PostEndEvent1); // looks like client don't like get _End before WEH_ with 0.0 ... so wait to send _End
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// send reward info
|
||||
private void PostEndEvent1(Object? source, ElapsedEventArgs e) {
|
||||
NetworkArray arr = new();
|
||||
|
||||
arr.Add(NetworkArray.VlElement("WE_" + Configuration.ServerConfiguration.EventName + "_End", lastResults));
|
||||
arr.Add(NetworkArray.VlElement("WE_" + Configuration.ServerConfiguration.EventName, NetworkArray.NULL));
|
||||
arr.Add(NetworkArray.VlElement("WE__AI", NetworkArray.NULL));
|
||||
foreach (var t in health) {
|
||||
arr.Add(NetworkArray.VlElement("WEH_" + t.Key, NetworkArray.NULL));
|
||||
arr.Add(NetworkArray.VlElement("WEF_" + t.Key, NetworkArray.NULL));
|
||||
}
|
||||
|
||||
room.Send(Utils.VlNetworkPacket(arr, room.Id));
|
||||
|
||||
Console.WriteLine($"Event {uid} sent _End");
|
||||
|
||||
SetTimer(60, PostEndEvent2);
|
||||
}
|
||||
|
||||
// schedule next event, set timer to call PreInit() and send new WEN_ info
|
||||
private void PostEndEvent2(Object? source, ElapsedEventArgs e) {
|
||||
ScheduleEvent(); // WE_ != WEN_
|
||||
AnnounceEvent(false, true); // send only WEN_ (WE_ should stay unchanged ... as WE_..._End)
|
||||
}
|
||||
|
||||
// set server side timer for word event state changes
|
||||
private void SetTimer(double timeout, System.Timers.ElapsedEventHandler callback) {
|
||||
if (timer != null) {
|
||||
timer.Stop();
|
||||
timer.Close();
|
||||
}
|
||||
|
||||
timer = new System.Timers.Timer(timeout * 1000);
|
||||
timer.AutoReset = false;
|
||||
timer.Enabled = true;
|
||||
timer.Elapsed += callback;
|
||||
|
||||
Console.WriteLine($"Event timer {callback.Method.Name} set to {timeout} s");
|
||||
}
|
||||
|
||||
// send event info
|
||||
private void AnnounceEvent(bool WE = true, bool WEN = true) {
|
||||
Console.WriteLine($"Event {uid} send event notification (WE_ = {(WE ? startTimeString : WE)} WEN_ = {(WEN ? nextStartTimeString : WEN)}, room = {room.Id}) to all clients");
|
||||
NetworkPacket packet = Utils.VlNetworkPacket(EventInfoArray(WE, WEN), room.Id);
|
||||
foreach (var r in Room.AllRooms()) {
|
||||
r.Send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public string EventInfo() {
|
||||
return startTimeString + "," + uid + ", false, HubTrainingDO";
|
||||
}
|
||||
|
||||
public NetworkArray EventInfoArray(bool WE = true, bool WEN = true) {
|
||||
NetworkArray vl = new();
|
||||
if (WE) {
|
||||
vl.Add(NetworkArray.VlElement("WE_" + Configuration.ServerConfiguration.EventName, EventInfo(), isPersistent:true));
|
||||
}
|
||||
if (WEN) {
|
||||
vl.Add(NetworkArray.VlElement("WEN_" + Configuration.ServerConfiguration.EventName, nextStartTimeString, isPersistent:true));
|
||||
}
|
||||
return vl;
|
||||
}
|
||||
|
||||
public string GetUid() => uid;
|
||||
|
||||
public Room GetRoom() => room;
|
||||
|
||||
public float UpdateHealth(string targetUid, float updateVal) {
|
||||
InitEvent();
|
||||
|
||||
lock (EventLock) {
|
||||
if (state != State.Active) {
|
||||
Console.WriteLine($"Event {uid} reject UpdateHealth for {targetUid} with event state {state}");
|
||||
return -1.0f; // do not send WEH_ when event is not active
|
||||
}
|
||||
}
|
||||
|
||||
if (!health.ContainsKey(targetUid))
|
||||
health.Add(targetUid, 1.0f);
|
||||
health[targetUid] -= updateVal;
|
||||
|
||||
if (health[targetUid] < 0.0001f) {
|
||||
health[targetUid] = 0.0f;
|
||||
EndEvent();
|
||||
}
|
||||
|
||||
if (endTime < DateTime.UtcNow) {
|
||||
Console.WriteLine($"Event {uid} force end from UpdateHealth");
|
||||
EndEvent(true);
|
||||
}
|
||||
|
||||
return health[targetUid];
|
||||
}
|
||||
|
||||
public void SetTimeSpan(Client client, float seconds) {
|
||||
if (state != State.Active) {
|
||||
return;
|
||||
}
|
||||
if (client == operatorAI || !endTimeIsSet) {
|
||||
Console.WriteLine($"Event {uid} set TimeSpan: {seconds} from operator: {client == operatorAI}");
|
||||
UpdateEndTime(seconds);
|
||||
endTimeIsSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateScore(string client, string value) {
|
||||
if (!players.ContainsKey(client)) {
|
||||
players.Add(client, value);
|
||||
} else {
|
||||
players[client] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateAI(Client client) {
|
||||
if (client == operatorAI)
|
||||
AITime = DateTime.UtcNow.AddSeconds(7);
|
||||
}
|
||||
|
||||
public float GetHealth(string targetUid) => health[targetUid];
|
||||
|
||||
public bool IsActive() => (state == State.Active);
|
||||
|
||||
public string GetLastResults() => lastResults;
|
||||
}
|
||||
55
src/Data/DataDecoder.cs
Normal file
55
src/Data/DataDecoder.cs
Normal file
@ -0,0 +1,55 @@
|
||||
namespace sodoffmmo.Data;
|
||||
|
||||
internal static class DataDecoder {
|
||||
internal static DataWrapper DecodeNull(NetworkData data) => new DataWrapper(NetworkDataType.Null, null!);
|
||||
|
||||
internal static DataWrapper DecodeBool(NetworkData data) => new DataWrapper(NetworkDataType.Bool, data.ReadBool());
|
||||
|
||||
internal static DataWrapper DecodeByte(NetworkData data) => new DataWrapper(NetworkDataType.Byte, data.ReadByte());
|
||||
|
||||
internal static DataWrapper DecodeShort(NetworkData data) => new DataWrapper(NetworkDataType.Short, data.ReadShort());
|
||||
|
||||
internal static DataWrapper DecodeInt(NetworkData data) => new DataWrapper(NetworkDataType.Int, data.ReadInt());
|
||||
|
||||
internal static DataWrapper DecodeLong(NetworkData data) => new DataWrapper(NetworkDataType.Long, data.ReadLong());
|
||||
|
||||
internal static DataWrapper DecodeFloat(NetworkData data) => new DataWrapper(NetworkDataType.Float, data.ReadFloat());
|
||||
|
||||
internal static DataWrapper DecodeDouble(NetworkData data) => new DataWrapper(NetworkDataType.Double, data.ReadDouble());
|
||||
|
||||
internal static DataWrapper DecodeString(NetworkData data) => new DataWrapper(NetworkDataType.String, data.ReadString());
|
||||
|
||||
internal static DataWrapper DecodeByteArray(NetworkData data) {
|
||||
int count = data.ReadInt();
|
||||
return new DataWrapper(NetworkDataType.ByteArray, data.ReadChunk(count));
|
||||
}
|
||||
|
||||
internal static DataWrapper DecodeBoolArray(NetworkData data)
|
||||
=> new DataWrapper(NetworkDataType.BoolArray, DecodeTypedArray(data, d => d.ReadBool()));
|
||||
|
||||
internal static DataWrapper DecodeShortArray(NetworkData data)
|
||||
=> new DataWrapper(NetworkDataType.ShortArray, DecodeTypedArray(data, d => d.ReadShort()));
|
||||
|
||||
internal static DataWrapper DecodeIntArray(NetworkData data)
|
||||
=> new DataWrapper(NetworkDataType.IntArray, DecodeTypedArray(data, d => d.ReadInt()));
|
||||
|
||||
internal static DataWrapper DecodeLongArray(NetworkData data)
|
||||
=> new DataWrapper(NetworkDataType.LongArray, DecodeTypedArray(data, d => d.ReadLong()));
|
||||
|
||||
internal static DataWrapper DecodeFloatArray(NetworkData data)
|
||||
=> new DataWrapper(NetworkDataType.FloatArray, DecodeTypedArray(data, d => d.ReadFloat()));
|
||||
|
||||
internal static DataWrapper DecodeDoubleArray(NetworkData data)
|
||||
=> new DataWrapper(NetworkDataType.DoubleArray, DecodeTypedArray(data, d => d.ReadDouble()));
|
||||
|
||||
internal static DataWrapper DecodeStringArray(NetworkData data)
|
||||
=> new DataWrapper(NetworkDataType.IntArray, DecodeTypedArray(data, d => d.ReadString()));
|
||||
|
||||
private static T[] DecodeTypedArray<T>(NetworkData data, Func<NetworkData, T> readFunction) {
|
||||
short length = data.ReadShort();
|
||||
T[] arr = new T[length];
|
||||
for (short i = 0; i < length; i++)
|
||||
arr[i] = readFunction(data);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
132
src/Data/DataEncoder.cs
Normal file
132
src/Data/DataEncoder.cs
Normal file
@ -0,0 +1,132 @@
|
||||
namespace sodoffmmo.Data;
|
||||
internal static class DataEncoder {
|
||||
internal static byte[] EncodeNull() => new byte[] { 0 };
|
||||
|
||||
internal static byte[] EncodeByte(byte value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.Byte);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeBool(bool value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.Bool);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeShort(short value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.Short);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeInt(int value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.Int);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeLong(long value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.Long);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeFloat(float value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.Float);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeDouble(double value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.Double);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeString(string value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.String);
|
||||
data.WriteValue(value);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeBoolArray(bool[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.BoolArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeByteArray(byte[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.ByteArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeShortArray(short[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.ShortArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeIntArray(int[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.IntArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeLongArray(long[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.LongArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeFloatArray(float[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.FloatArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeDoubleArray(double[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.DoubleArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeStringArray(string[] value) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)NetworkDataType.StringArray);
|
||||
data.WriteValue(Convert.ToInt16(value.Length));
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
data.WriteValue(value[i]);
|
||||
return data.Data;
|
||||
}
|
||||
}
|
||||
11
src/Data/DataWrapper.cs
Normal file
11
src/Data/DataWrapper.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace sodoffmmo.Data;
|
||||
public class DataWrapper {
|
||||
|
||||
public int Type { get; private set; }
|
||||
public object Data { get; private set; }
|
||||
|
||||
public DataWrapper(NetworkDataType type, object data) {
|
||||
this.Type = (int)type;
|
||||
this.Data = data;
|
||||
}
|
||||
}
|
||||
144
src/Data/NetworkArray.cs
Normal file
144
src/Data/NetworkArray.cs
Normal file
@ -0,0 +1,144 @@
|
||||
namespace sodoffmmo.Data;
|
||||
public class NetworkArray {
|
||||
List<DataWrapper> arrData = new();
|
||||
|
||||
public DataWrapper this[int i] {
|
||||
get { return arrData[i]; }
|
||||
}
|
||||
|
||||
public int Length {
|
||||
get { return arrData.Count; }
|
||||
}
|
||||
|
||||
public void Add(bool value) => AddObject(NetworkDataType.Bool, value);
|
||||
|
||||
public void Add(byte value) => AddObject(NetworkDataType.Byte, value);
|
||||
|
||||
public void Add(short value) => AddObject(NetworkDataType.Short, value);
|
||||
|
||||
public void Add(int value) => AddObject(NetworkDataType.Int, value);
|
||||
|
||||
public void Add(long value) => AddObject(NetworkDataType.Long, value);
|
||||
|
||||
public void Add(float value) => AddObject(NetworkDataType.Float, value);
|
||||
|
||||
public void Add(double value) => AddObject(NetworkDataType.Double, value);
|
||||
|
||||
public void Add(string value) => AddObject(NetworkDataType.String, value);
|
||||
|
||||
public void Add(bool[] value) => AddObject(NetworkDataType.BoolArray, value);
|
||||
|
||||
public void Add(NetworkData value) => AddObject(NetworkDataType.ByteArray, value.Data);
|
||||
|
||||
public void Add(short[] value) => AddObject(NetworkDataType.ShortArray, value);
|
||||
|
||||
public void Add(int[] value) => AddObject(NetworkDataType.IntArray, value);
|
||||
|
||||
public void Add(long[] value) => AddObject(NetworkDataType.LongArray, value);
|
||||
|
||||
public void Add(float[] value) => AddObject(NetworkDataType.FloatArray, value);
|
||||
|
||||
public void Add(double[] value) => AddObject(NetworkDataType.DoubleArray, value);
|
||||
|
||||
public void Add(string[] value) => AddObject(NetworkDataType.StringArray, value);
|
||||
|
||||
public void Add(NetworkArray value) => AddObject(NetworkDataType.NetworkArray, value);
|
||||
|
||||
public void Add(NetworkObject value) => AddObject(NetworkDataType.NetworkObject, value);
|
||||
|
||||
public void Add(DataWrapper dataWrapper) => arrData.Add(dataWrapper);
|
||||
|
||||
private void AddObject(NetworkDataType dataType, object obj) => Add(new DataWrapper(dataType, obj));
|
||||
|
||||
public T GetValue<T>(int index) {
|
||||
if (index >= arrData.Count)
|
||||
throw new IndexOutOfRangeException();
|
||||
return (T)arrData[index].Data;
|
||||
}
|
||||
|
||||
public bool GetBool(int index) => GetValue<bool>(index);
|
||||
|
||||
public byte GetByte(int index) => GetValue<byte>(index);
|
||||
|
||||
public short GetShort(int index) => GetValue<short>(index);
|
||||
|
||||
public int GetInt(int index) => GetValue<int>(index);
|
||||
|
||||
public long GetLong(int index) => GetValue<long>(index);
|
||||
|
||||
public float GetFloat(int index) => GetValue<float>(index);
|
||||
|
||||
public double GetDouble(int index) => GetValue<double>(index);
|
||||
|
||||
public string GetUtfString(int index) => GetValue<string>(index);
|
||||
|
||||
public bool[] GetBoolArray(int index) => GetValue<bool[]>(index);
|
||||
|
||||
public NetworkData GetNetworkData(int index) => GetValue<NetworkData>(index);
|
||||
|
||||
public short[] GetShortArray(int index) => GetValue<short[]>(index);
|
||||
|
||||
public int[] GetIntArray(int index) => GetValue<int[]>(index);
|
||||
|
||||
public long[] GetLongArray(int index) => GetValue<long[]>(index);
|
||||
|
||||
public float[] GetFloatArray(int index) => GetValue<float[]>(index);
|
||||
|
||||
public double[] GetDoubleArray(int index) => GetValue<double[]>(index);
|
||||
|
||||
public string[] GetStringArray(int index) => GetValue<string[]>(index);
|
||||
|
||||
public NetworkArray GetNetworkArray(int index) => GetValue<NetworkArray>(index);
|
||||
|
||||
public NetworkObject GetNetworkObject(int index) => GetValue<NetworkObject>(index);
|
||||
|
||||
public bool Contains(object obj) {
|
||||
if (obj is NetworkObject || obj is NetworkArray)
|
||||
throw new Exception("Unsupported object type");
|
||||
for (int i = 0; i < arrData.Count; i++) {
|
||||
if (object.Equals(arrData[i], obj))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public class NullClass {};
|
||||
public static NullClass NULL = new ();
|
||||
|
||||
public void AddWithType<T>(T value) {
|
||||
if (typeof(T) == typeof(NullClass))
|
||||
AddWithTypeObject(NetworkDataType.Null, 0, null);
|
||||
else if (typeof(T) == typeof(bool))
|
||||
AddWithTypeObject(NetworkDataType.Bool, 1, value);
|
||||
else if (typeof(T) == typeof(int))
|
||||
AddWithTypeObject(NetworkDataType.Int, 2, value);
|
||||
else if (typeof(T) == typeof(double))
|
||||
AddWithTypeObject(NetworkDataType.Double, 3, value);
|
||||
else if (typeof(T) == typeof(float))
|
||||
AddWithTypeObject(NetworkDataType.Float, 3, value);
|
||||
else if (typeof(T) == typeof(string))
|
||||
AddWithTypeObject(NetworkDataType.String, 4, value);
|
||||
else
|
||||
throw new Exception("Unsupported type");
|
||||
}
|
||||
|
||||
private void AddWithTypeObject(NetworkDataType dataType, byte typeId, object? obj) {
|
||||
Add(new DataWrapper(NetworkDataType.Byte, typeId));
|
||||
Add(new DataWrapper(dataType, obj));
|
||||
}
|
||||
|
||||
public static NetworkArray Param<T>(string name, T value) {
|
||||
NetworkArray arr = new();
|
||||
arr.Add(name);
|
||||
arr.AddWithType(value);
|
||||
return arr;
|
||||
}
|
||||
public static NetworkArray VlElement<T>(string name, T value, bool isPrivate = false, bool isPersistent = false) {
|
||||
NetworkArray arr = new();
|
||||
arr.Add(name);
|
||||
arr.AddWithType(value);
|
||||
arr.Add(isPrivate);
|
||||
arr.Add(isPersistent);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
131
src/Data/NetworkData.cs
Normal file
131
src/Data/NetworkData.cs
Normal file
@ -0,0 +1,131 @@
|
||||
using System.Text;
|
||||
|
||||
namespace sodoffmmo.Data;
|
||||
public class NetworkData {
|
||||
byte[] data;
|
||||
int offset = 0;
|
||||
|
||||
public byte[] Data {
|
||||
get { return data; }
|
||||
}
|
||||
|
||||
public NetworkData() {
|
||||
data = new byte[0];
|
||||
}
|
||||
|
||||
public NetworkData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int RemainingLength {
|
||||
get {
|
||||
return data.Length - offset;
|
||||
}
|
||||
}
|
||||
|
||||
public void Seek(int offset) {
|
||||
int newOffset = this.offset + offset;
|
||||
if (newOffset >= 0 && newOffset < data.Length)
|
||||
this.offset = newOffset;
|
||||
}
|
||||
|
||||
public byte[] ReverseOrder(byte[] data) {
|
||||
if (!BitConverter.IsLittleEndian) return data;
|
||||
Array.Reverse(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public byte ReadByte() {
|
||||
return data[offset++];
|
||||
}
|
||||
public byte[] ReadChunk(int count) {
|
||||
byte[] chunk = new byte[count];
|
||||
Buffer.BlockCopy(data, offset, chunk, 0, count);
|
||||
offset += count;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public short ReadShort() {
|
||||
byte[] arr = ReverseOrder(ReadChunk(2));
|
||||
return BitConverter.ToInt16(arr);
|
||||
}
|
||||
|
||||
public ushort ReadUShort() {
|
||||
byte[] arr = ReverseOrder(ReadChunk(2));
|
||||
return BitConverter.ToUInt16(arr);
|
||||
}
|
||||
|
||||
public bool ReadBool() => data[offset++] == 1;
|
||||
|
||||
public int ReadInt() {
|
||||
byte[] arr = ReverseOrder(ReadChunk(4));
|
||||
return BitConverter.ToInt32(arr);
|
||||
}
|
||||
|
||||
public long ReadLong() {
|
||||
byte[] arr = ReverseOrder(ReadChunk(8));
|
||||
return BitConverter.ToInt64(arr);
|
||||
}
|
||||
|
||||
public float ReadFloat() {
|
||||
byte[] arr = ReverseOrder(ReadChunk(4));
|
||||
return BitConverter.ToSingle(arr);
|
||||
}
|
||||
|
||||
public double ReadDouble() {
|
||||
byte[] arr = ReverseOrder(ReadChunk(8));
|
||||
return BitConverter.ToDouble(arr);
|
||||
}
|
||||
|
||||
public string ReadString() {
|
||||
ushort count = ReadUShort();
|
||||
string str = Encoding.UTF8.GetString(data, offset, count);
|
||||
offset += count;
|
||||
return str;
|
||||
}
|
||||
|
||||
public void WriteChunk(byte[] chunk, int offset, int count) {
|
||||
byte[] newData = new byte[data.Length + count];
|
||||
Buffer.BlockCopy(data, 0, newData, 0, data.Length);
|
||||
Buffer.BlockCopy(chunk, offset, newData, data.Length, count);
|
||||
data = newData;
|
||||
}
|
||||
|
||||
public void WriteChunk(byte[] chunk) => WriteChunk(chunk, 0, chunk.Length);
|
||||
|
||||
public void WriteValue(byte b) => WriteChunk(new byte[] { b });
|
||||
|
||||
public void WriteValue(bool b) => WriteChunk(new byte[] { (byte)((!b) ? 0 : 1) });
|
||||
|
||||
public void WriteValue(int i) => WriteChunk(ReverseOrder(BitConverter.GetBytes(i)));
|
||||
|
||||
public void WriteValue(short s) => WriteChunk(ReverseOrder(BitConverter.GetBytes(s)));
|
||||
|
||||
public void WriteValue(ushort us) => WriteChunk(ReverseOrder(BitConverter.GetBytes(us)));
|
||||
|
||||
public void WriteValue(long l) => WriteChunk(ReverseOrder(BitConverter.GetBytes(l)));
|
||||
|
||||
public void WriteValue(float f) => WriteChunk(ReverseOrder(BitConverter.GetBytes(f)));
|
||||
|
||||
public void WriteValue(double d) => WriteChunk(ReverseOrder(BitConverter.GetBytes(d)));
|
||||
|
||||
public void WriteValue(string str) {
|
||||
WriteValue(GetUTFStringLength(str));
|
||||
WriteChunk(Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
|
||||
private ushort GetUTFStringLength(string str) {
|
||||
ushort length = 0;
|
||||
foreach (int c in str) {
|
||||
if (c > 0 && c < 128)
|
||||
++length;
|
||||
else if (c > 2047)
|
||||
length += 3;
|
||||
else
|
||||
length += 2;
|
||||
}
|
||||
if (length > 32768)
|
||||
throw new Exception("String is too long");
|
||||
return length;
|
||||
}
|
||||
}
|
||||
23
src/Data/NetworkDataType.cs
Normal file
23
src/Data/NetworkDataType.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace sodoffmmo.Data;
|
||||
|
||||
public enum NetworkDataType {
|
||||
Null,
|
||||
Bool,
|
||||
Byte,
|
||||
Short,
|
||||
Int,
|
||||
Long,
|
||||
Float,
|
||||
Double,
|
||||
String,
|
||||
BoolArray,
|
||||
ByteArray,
|
||||
ShortArray,
|
||||
IntArray,
|
||||
LongArray,
|
||||
FloatArray,
|
||||
DoubleArray,
|
||||
StringArray,
|
||||
NetworkArray,
|
||||
NetworkObject,
|
||||
}
|
||||
200
src/Data/NetworkObject.cs
Normal file
200
src/Data/NetworkObject.cs
Normal file
@ -0,0 +1,200 @@
|
||||
namespace sodoffmmo.Data;
|
||||
public class NetworkObject {
|
||||
Dictionary<string, DataWrapper> fields = new();
|
||||
|
||||
public NetworkObject() {}
|
||||
|
||||
public NetworkObject(NetworkData data) {
|
||||
Deserialize(data);
|
||||
}
|
||||
|
||||
public NetworkObject(byte[] data) : this(new NetworkData(data)) { }
|
||||
|
||||
public void Add(string label, DataWrapper wrapper) => fields[label] = wrapper;
|
||||
|
||||
public void Add(string label, byte value) => fields[label] = new DataWrapper(NetworkDataType.Byte, value);
|
||||
|
||||
public void Add(string label, bool value) => fields[label] = new DataWrapper(NetworkDataType.Bool, value);
|
||||
|
||||
public void Add(string label, short value) => fields[label] = new DataWrapper(NetworkDataType.Short, value);
|
||||
|
||||
public void Add(string label, int value) => fields[label] = new DataWrapper(NetworkDataType.Int, value);
|
||||
|
||||
public void Add(string label, long value) => fields[label] = new DataWrapper(NetworkDataType.Long, value);
|
||||
|
||||
public void Add(string label, float value) => fields[label] = new DataWrapper(NetworkDataType.Float, value);
|
||||
|
||||
public void Add(string label, double value) => fields[label] = new DataWrapper(NetworkDataType.Double, value);
|
||||
|
||||
public void Add(string label, string value) => fields[label] = new DataWrapper(NetworkDataType.String, value);
|
||||
|
||||
public void Add(string label, byte[] value) => fields[label] = new DataWrapper(NetworkDataType.ByteArray, value);
|
||||
|
||||
public void Add(string label, bool[] value) => fields[label] = new DataWrapper(NetworkDataType.BoolArray, value);
|
||||
|
||||
public void Add(string label, short[] value) => fields[label] = new DataWrapper(NetworkDataType.ShortArray, value);
|
||||
|
||||
public void Add(string label, int[] value) => fields[label] = new DataWrapper(NetworkDataType.IntArray, value);
|
||||
|
||||
public void Add(string label, long[] value) => fields[label] = new DataWrapper(NetworkDataType.LongArray, value);
|
||||
|
||||
public void Add(string label, float[] value) => fields[label] = new DataWrapper(NetworkDataType.FloatArray, value);
|
||||
|
||||
public void Add(string label, double[] value) => fields[label] = new DataWrapper(NetworkDataType.DoubleArray, value);
|
||||
|
||||
public void Add(string label, string[] value) => fields[label] = new DataWrapper(NetworkDataType.StringArray, value);
|
||||
|
||||
public void Add(string label, NetworkArray value) => fields[label] = new DataWrapper(NetworkDataType.NetworkArray, value);
|
||||
|
||||
public void Add(string label, NetworkObject value) => fields[label] = new DataWrapper(NetworkDataType.NetworkObject, value);
|
||||
|
||||
public T Get<T>(string key) {
|
||||
if (!fields.ContainsKey(key))
|
||||
return default;
|
||||
return (T)fields[key].Data;
|
||||
}
|
||||
|
||||
|
||||
public NetworkPacket Serialize() => new NetworkPacket(0x80, SerializeObject(this));
|
||||
|
||||
private byte[] SerializeObject(NetworkObject obj) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)18);
|
||||
data.WriteValue(Convert.ToInt16(obj.fields.Count));
|
||||
foreach (string key in obj.fields.Keys) {
|
||||
data.WriteValue(key);
|
||||
data.WriteChunk(EncodeObject(obj.fields[key]));
|
||||
}
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
private void Deserialize(NetworkData data) {
|
||||
if (data.ReadByte() != 0x12)
|
||||
throw new Exception("Invalid object type");
|
||||
|
||||
short count = data.ReadShort();
|
||||
for (short i = 0; i < count; i++) {
|
||||
string label = data.ReadString();
|
||||
DataWrapper obj = DecodeObject(data);
|
||||
Add(label, obj);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] EncodeObject(DataWrapper obj) {
|
||||
switch ((NetworkDataType)obj.Type) {
|
||||
case NetworkDataType.Null:
|
||||
return DataEncoder.EncodeNull();
|
||||
case NetworkDataType.Bool:
|
||||
return DataEncoder.EncodeBool((bool)obj.Data);
|
||||
case NetworkDataType.Byte:
|
||||
return DataEncoder.EncodeByte((byte)obj.Data);
|
||||
case NetworkDataType.Short:
|
||||
return DataEncoder.EncodeShort((short)obj.Data);
|
||||
case NetworkDataType.Int:
|
||||
return DataEncoder.EncodeInt((int)obj.Data);
|
||||
case NetworkDataType.Long:
|
||||
return DataEncoder.EncodeLong((long)obj.Data);
|
||||
case NetworkDataType.Float:
|
||||
return DataEncoder.EncodeFloat((float)obj.Data);
|
||||
case NetworkDataType.Double:
|
||||
return DataEncoder.EncodeDouble((double)obj.Data);
|
||||
case NetworkDataType.String:
|
||||
return DataEncoder.EncodeString((string)obj.Data);
|
||||
case NetworkDataType.BoolArray:
|
||||
return DataEncoder.EncodeBoolArray((bool[])obj.Data);
|
||||
case NetworkDataType.ByteArray:
|
||||
return DataEncoder.EncodeByteArray((byte[])obj.Data);
|
||||
case NetworkDataType.ShortArray:
|
||||
return DataEncoder.EncodeShortArray((short[])obj.Data);
|
||||
case NetworkDataType.IntArray:
|
||||
return DataEncoder.EncodeIntArray((int[])obj.Data);
|
||||
case NetworkDataType.LongArray:
|
||||
return DataEncoder.EncodeLongArray((long[])obj.Data);
|
||||
case NetworkDataType.FloatArray:
|
||||
return DataEncoder.EncodeFloatArray((float[])obj.Data);
|
||||
case NetworkDataType.DoubleArray:
|
||||
return DataEncoder.EncodeDoubleArray((double[])obj.Data);
|
||||
case NetworkDataType.StringArray:
|
||||
return DataEncoder.EncodeStringArray((string[])obj.Data);
|
||||
case NetworkDataType.NetworkArray:
|
||||
return EncodeNetworkArray((NetworkArray)obj.Data);
|
||||
case NetworkDataType.NetworkObject:
|
||||
return SerializeObject((NetworkObject)obj.Data);
|
||||
default: throw new Exception("Invalid object");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] EncodeNetworkArray(NetworkArray arr) {
|
||||
NetworkData data = new();
|
||||
data.WriteValue((byte)17);
|
||||
data.WriteValue(Convert.ToInt16(arr.Length));
|
||||
for (int i = 0; i < arr.Length; i++)
|
||||
data.WriteChunk(EncodeObject(arr[i]));
|
||||
return data.Data;
|
||||
}
|
||||
|
||||
private DataWrapper DecodeObject(NetworkData data) {
|
||||
switch ((NetworkDataType)data.ReadByte()) {
|
||||
case NetworkDataType.Null:
|
||||
return DataDecoder.DecodeNull(data);
|
||||
case NetworkDataType.Bool:
|
||||
return DataDecoder.DecodeBool(data);
|
||||
case NetworkDataType.Byte:
|
||||
return DataDecoder.DecodeByte(data);
|
||||
case NetworkDataType.Short:
|
||||
return DataDecoder.DecodeShort(data);
|
||||
case NetworkDataType.Int:
|
||||
return DataDecoder.DecodeInt(data);
|
||||
case NetworkDataType.Long:
|
||||
return DataDecoder.DecodeLong(data);
|
||||
case NetworkDataType.Float:
|
||||
return DataDecoder.DecodeFloat(data);
|
||||
case NetworkDataType.Double:
|
||||
return DataDecoder.DecodeDouble(data);
|
||||
case NetworkDataType.String:
|
||||
return DataDecoder.DecodeString(data);
|
||||
case NetworkDataType.BoolArray:
|
||||
return DataDecoder.DecodeBoolArray(data);
|
||||
case NetworkDataType.ByteArray:
|
||||
return DataDecoder.DecodeByteArray(data);
|
||||
case NetworkDataType.ShortArray:
|
||||
return DataDecoder.DecodeShortArray(data);
|
||||
case NetworkDataType.IntArray:
|
||||
return DataDecoder.DecodeIntArray(data);
|
||||
case NetworkDataType.LongArray:
|
||||
return DataDecoder.DecodeLongArray(data);
|
||||
case NetworkDataType.FloatArray:
|
||||
return DataDecoder.DecodeFloatArray(data);
|
||||
case NetworkDataType.DoubleArray:
|
||||
return DataDecoder.DecodeDoubleArray(data);
|
||||
case NetworkDataType.StringArray:
|
||||
return DataDecoder.DecodeStringArray(data);
|
||||
case NetworkDataType.NetworkArray:
|
||||
return DecodeNetworkArray(data);
|
||||
case NetworkDataType.NetworkObject:
|
||||
data.Seek(-1);
|
||||
return new DataWrapper(NetworkDataType.NetworkObject, new NetworkObject(data));
|
||||
default: throw new Exception("Invalid object");
|
||||
}
|
||||
}
|
||||
|
||||
private DataWrapper DecodeNetworkArray(NetworkData data) {
|
||||
NetworkArray array = new NetworkArray();
|
||||
short count = data.ReadShort();
|
||||
for (short i = 0; i < count; i++) {
|
||||
DataWrapper wrapper = DecodeObject(data);
|
||||
array.Add(wrapper);
|
||||
}
|
||||
return new DataWrapper(NetworkDataType.NetworkArray, array);
|
||||
}
|
||||
|
||||
public static NetworkObject WrapObject(byte c, short a, NetworkObject obj) {
|
||||
NetworkObject wrapper = new();
|
||||
wrapper.Add("c", c);
|
||||
wrapper.Add("a", a);
|
||||
wrapper.Add("p", obj);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public bool ContainsKey(string label) => fields.ContainsKey(label);
|
||||
}
|
||||
76
src/Data/NetworkPacket.cs
Normal file
76
src/Data/NetworkPacket.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using ComponentAce.Compression.Libs.zlib;
|
||||
|
||||
namespace sodoffmmo.Data;
|
||||
public class NetworkPacket {
|
||||
|
||||
byte header;
|
||||
byte[] data;
|
||||
bool compressed = false;
|
||||
|
||||
public int Length {
|
||||
get {
|
||||
return data.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] SendData {
|
||||
get {
|
||||
NetworkData sendData = new();
|
||||
sendData.WriteValue(header);
|
||||
sendData.WriteValue((short)data.Length);
|
||||
sendData.WriteChunk(data);
|
||||
return sendData.Data;
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkPacket() {
|
||||
data = new byte[0];
|
||||
}
|
||||
|
||||
public NetworkPacket(NetworkData data, bool compressed = false) {
|
||||
header = 0x80;
|
||||
if (compressed) {
|
||||
header = 0xa0;
|
||||
this.compressed = true;
|
||||
}
|
||||
this.data = new byte[data.Data.Length];
|
||||
Buffer.BlockCopy(data.Data, 0, this.data, 0, data.Data.Length);
|
||||
}
|
||||
|
||||
public NetworkPacket(byte header, byte[] data) {
|
||||
this.header = header;
|
||||
this.data = new byte[data.Length];
|
||||
Buffer.BlockCopy(data, 0, this.data, 0, data.Length);
|
||||
if (header == 0xa0)
|
||||
compressed = true;
|
||||
}
|
||||
|
||||
public NetworkObject GetObject() {
|
||||
if (compressed)
|
||||
Decompress();
|
||||
|
||||
NetworkObject obj = new NetworkObject(data);
|
||||
return obj;
|
||||
}
|
||||
|
||||
public void Compress() {
|
||||
if (compressed)
|
||||
return;
|
||||
MemoryStream outStream = new();
|
||||
using (ZOutputStream zstream = new(outStream, 9)) {
|
||||
zstream.Write(data);
|
||||
zstream.Flush();
|
||||
}
|
||||
data = outStream.ToArray();
|
||||
header = 0xa0;
|
||||
}
|
||||
|
||||
public void Decompress() {
|
||||
MemoryStream outStream = new();
|
||||
using (ZOutputStream zstream = new(outStream)) {
|
||||
zstream.Write(data);
|
||||
zstream.Flush();
|
||||
}
|
||||
data = outStream.ToArray();
|
||||
}
|
||||
}
|
||||
228
src/Data/PlayerData.cs
Normal file
228
src/Data/PlayerData.cs
Normal file
@ -0,0 +1,228 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Globalization;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Management;
|
||||
|
||||
namespace sodoffmmo.Data;
|
||||
public class PlayerData {
|
||||
public bool IsValid { get; set; } = false;
|
||||
|
||||
// viking uid
|
||||
public string Uid { get; set; } = "";
|
||||
// client token
|
||||
public string UNToken { get; set; } = "";
|
||||
// client zone
|
||||
public string ZoneName { get; set; } = "";
|
||||
|
||||
// rotation (eulerAngles.y)
|
||||
public float R { get; set; }
|
||||
// velocity x
|
||||
public float R1 { get; set; }
|
||||
// velocity y
|
||||
public float R2 { get; set; }
|
||||
// velocity z
|
||||
public float R3 { get; set; }
|
||||
// position x
|
||||
public float P1 { get; set; }
|
||||
// position y
|
||||
public float P2 { get; set; }
|
||||
// position z
|
||||
public float P3 { get; set; }
|
||||
// max speed
|
||||
public float Mx { get; set; } = 6;
|
||||
// flags
|
||||
public int F { get; set; }
|
||||
// animation bitfield (animations used by avatar, e.g. mounted, swim, ...)
|
||||
public int Mbf { get; set; }
|
||||
|
||||
public string DiplayName { get; set; } = "placeholder";
|
||||
public Role Role { get; set; } = Role.User;
|
||||
|
||||
public long last_ue_time { get; set; } = 0;
|
||||
|
||||
public static readonly string[] SupportedVariables = {
|
||||
"A", // avatar data
|
||||
"FP", // raised pet data
|
||||
"RA", // XP rank (points and level)
|
||||
"UDT", // UDT points
|
||||
"L", // location (level)
|
||||
"PU", // (not raised) pet data
|
||||
"RDE", // ride (int)
|
||||
"MU", // mood
|
||||
"MBR", // mount broom (bool)
|
||||
"GU", // goggles (bool)
|
||||
"LC", // livechat (int)
|
||||
"CU", // country id (int) (for flag?)
|
||||
"J", // join allowed
|
||||
"BU", // busy (?)
|
||||
"M", // membership status (bool)
|
||||
"P", // position vector (older games)
|
||||
"R", // rotation (older games - updated via SUV, not SPV)
|
||||
"F", // flags (older games - updated via SUV, not SPV)
|
||||
"UTI", // group/clan (EMD)
|
||||
"CLU", // group/clan (newer)
|
||||
"SPM", // drive mode (Eat My Dust)
|
||||
"H", // health
|
||||
};
|
||||
|
||||
// other variables (set and updated via SUV command)
|
||||
private Dictionary<string, string?> variables = new();
|
||||
|
||||
public string GetVariable(string varName) {
|
||||
return variables[varName];
|
||||
}
|
||||
|
||||
public string SetVariable(string varName, string value) {
|
||||
// do not store in variables directory
|
||||
if (varName == "UID") {
|
||||
return value;
|
||||
}
|
||||
if (varName == "R") {
|
||||
R = float.Parse(value, CultureInfo.InvariantCulture);
|
||||
return value;
|
||||
}
|
||||
if (varName == "F") {
|
||||
F = unchecked((int)Convert.ToUInt32(value, 16));
|
||||
return value;
|
||||
}
|
||||
|
||||
// fix variable value before store
|
||||
if (varName == "FP") {
|
||||
value = FixMountState(value);
|
||||
}
|
||||
|
||||
// store in directory
|
||||
variables[varName] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
public void InitFromNetworkData(NetworkObject suvData) {
|
||||
// set initial state for SPV data
|
||||
string? r = suvData.Get<string>("R"); // in Eat My Dust, rotation is sent in R1, R2, R3
|
||||
if (r != null) R = float.Parse(r, CultureInfo.InvariantCulture);
|
||||
string? p1 = suvData.Get<string>("P1");
|
||||
if (p1 != null) {
|
||||
P1 = float.Parse(p1, CultureInfo.InvariantCulture);
|
||||
P2 = float.Parse(suvData.Get<string>("P2"), CultureInfo.InvariantCulture);
|
||||
P3 = float.Parse(suvData.Get<string>("P3"), CultureInfo.InvariantCulture);
|
||||
R1 = float.Parse(suvData.Get<string>("R1"), CultureInfo.InvariantCulture);
|
||||
R2 = float.Parse(suvData.Get<string>("R2"), CultureInfo.InvariantCulture);
|
||||
R3 = float.Parse(suvData.Get<string>("R3"), CultureInfo.InvariantCulture);
|
||||
}
|
||||
string? mbf = suvData.Get<string>("MBF");
|
||||
if (mbf != null)
|
||||
Mbf = int.Parse(mbf);
|
||||
F = int.Parse(suvData.Get<string>("F"));
|
||||
|
||||
// reset all variables values
|
||||
// variables.Clear();
|
||||
|
||||
// set initial state for SUV data
|
||||
foreach (string varName in SupportedVariables) {
|
||||
string? value = suvData.Get<string>(varName);
|
||||
if (value != null) {
|
||||
SetVariable(varName, value);
|
||||
}
|
||||
}
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
public NetworkArray GetNetworkData(int clientID, out NetworkArray paramArr) {
|
||||
NetworkArray arr = new();
|
||||
arr.Add(clientID);
|
||||
arr.Add(UNToken);
|
||||
arr.Add((short)1);
|
||||
arr.Add((short)clientID);
|
||||
|
||||
paramArr = new();
|
||||
paramArr.Add(NetworkArray.Param("NT", (double)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()))); // network time (like PNG)
|
||||
paramArr.Add(NetworkArray.Param("t", (int)(Runtime.CurrentRuntime / 1000))); // timestamp (non-decreasing integer)
|
||||
|
||||
paramArr.Add(NetworkArray.Param("UID", Uid));
|
||||
addVariableToArray(paramArr, "A");
|
||||
addVariableToArray(paramArr, "FP");
|
||||
|
||||
if (IsValid) {
|
||||
paramArr.Add(NetworkArray.Param("R", R));
|
||||
paramArr.Add(NetworkArray.Param("R1", R1));
|
||||
paramArr.Add(NetworkArray.Param("R2", R2));
|
||||
paramArr.Add(NetworkArray.Param("R3", R3));
|
||||
paramArr.Add(NetworkArray.Param("P1", P1));
|
||||
paramArr.Add(NetworkArray.Param("P2", P2));
|
||||
paramArr.Add(NetworkArray.Param("P3", P3));
|
||||
paramArr.Add(NetworkArray.Param("MX", Mx));
|
||||
paramArr.Add(NetworkArray.Param("F", F));
|
||||
paramArr.Add(NetworkArray.Param("MBF", Mbf));
|
||||
|
||||
foreach (var v in variables) {
|
||||
if (v.Value is null || v.Key == "A" || v.Key == "FP")
|
||||
continue;
|
||||
paramArr.Add(NetworkArray.Param(v.Key, v.Value));
|
||||
}
|
||||
}
|
||||
|
||||
arr.Add(paramArr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
private void addVariableToArray(NetworkArray paramArr, string varName) {
|
||||
if (variables.TryGetValue (varName, out string tmp) && tmp != null) {
|
||||
paramArr.Add(NetworkArray.Param(varName, tmp));
|
||||
}
|
||||
}
|
||||
|
||||
private string FixMountState(string value) {
|
||||
// raised pet geometry - set from Fp
|
||||
PetGeometryType GeometryType = PetGeometryType.Default;
|
||||
// raised pet age - set from Fp
|
||||
PetAge PetAge = PetAge.Adult;
|
||||
// raised pet mounted - set from Fp
|
||||
bool PetMounted = false;
|
||||
|
||||
string[] array = value.Split('*');
|
||||
Dictionary<string, string> keyValPairs = new();
|
||||
foreach (string str in array) {
|
||||
string[] keyValPair = str.Split('$');
|
||||
if (keyValPair.Length == 2)
|
||||
keyValPairs[keyValPair[0]] = keyValPair[1];
|
||||
}
|
||||
if (keyValPairs.TryGetValue("G", out string geometry))
|
||||
if (geometry.ToLower().Contains("terribleterror"))
|
||||
GeometryType = PetGeometryType.Terror;
|
||||
if (keyValPairs.TryGetValue("A", out string age)) {
|
||||
switch (age) {
|
||||
case "E": PetAge = PetAge.EggInHand; break;
|
||||
case "B": PetAge = PetAge.Baby; break;
|
||||
case "C": PetAge = PetAge.Child; break;
|
||||
case "T": PetAge = PetAge.Teen; break;
|
||||
case "A": PetAge = PetAge.Adult; break;
|
||||
case "Ti": PetAge = PetAge.Titan; break;
|
||||
}
|
||||
}
|
||||
if (keyValPairs.TryGetValue("U", out string userdata)) {
|
||||
PetMounted = (userdata == "0" || userdata == "1");
|
||||
}
|
||||
if (PetMounted && !Configuration.ServerConfiguration.AllowChaos &&
|
||||
(GeometryType == PetGeometryType.Default && PetAge < PetAge.Teen
|
||||
|| GeometryType == PetGeometryType.Terror && PetAge < PetAge.Titan)
|
||||
) {
|
||||
return Regex.Replace(value, "^U\\$[01]\\*", "U$-1*");
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PetGeometryType {
|
||||
Default,
|
||||
Terror
|
||||
}
|
||||
public enum PetAge {
|
||||
EggInHand = 0,
|
||||
Baby = 1,
|
||||
Child = 2,
|
||||
Teen = 3,
|
||||
Adult = 4,
|
||||
Titan = 5
|
||||
}
|
||||
54
src/Data/SocketBuffer.cs
Normal file
54
src/Data/SocketBuffer.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace sodoffmmo.Data;
|
||||
internal class SocketBuffer {
|
||||
NetworkData data = new();
|
||||
Status status = Status.Header;
|
||||
byte header;
|
||||
short length;
|
||||
byte[] value = new byte[0];
|
||||
public void Write(byte[] buffer, int length) {
|
||||
data.WriteChunk(buffer, 0, length);
|
||||
}
|
||||
|
||||
public bool ReadPacket(out NetworkPacket packet) {
|
||||
packet = new();
|
||||
if (status == Status.Header)
|
||||
ReadHeader();
|
||||
|
||||
if (status == Status.Value) {
|
||||
ReadValue();
|
||||
if (status == Status.Header) {
|
||||
packet = new(header, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ReadHeader() {
|
||||
if (data.RemainingLength < 3)
|
||||
return;
|
||||
header = data.ReadByte();
|
||||
length = data.ReadShort();
|
||||
status = Status.Value;
|
||||
}
|
||||
|
||||
private void ReadValue() {
|
||||
if (data.RemainingLength < length)
|
||||
return;
|
||||
value = data.ReadChunk(length);
|
||||
data = new(data.ReadChunk(data.RemainingLength));
|
||||
status = Status.Header;
|
||||
}
|
||||
|
||||
enum Status {
|
||||
Header,
|
||||
Value
|
||||
}
|
||||
}
|
||||
18
src/Dockerfile
Normal file
18
src/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
# Use the official .NET SDK image for building the application
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Copy the source code
|
||||
COPY . .
|
||||
|
||||
# Restore dependencies and build the application
|
||||
RUN dotnet build -c Release -o /app
|
||||
|
||||
# Create clean run environment (without source and sdk)
|
||||
# FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
|
||||
# WORKDIR /app
|
||||
# COPY --from=build /app .
|
||||
|
||||
# Run the application
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["./sodoffmmo"]
|
||||
20
src/Management/AuthenticationInfo.cs
Normal file
20
src/Management/AuthenticationInfo.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace sodoffmmo.Management;
|
||||
|
||||
[Serializable]
|
||||
public class AuthenticationInfo {
|
||||
[XmlElement]
|
||||
public bool Authenticated { get; set; }
|
||||
|
||||
[XmlElement]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[XmlElement]
|
||||
public Role Role { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public enum Role {
|
||||
User = 0, Moderator = 1, Admin = 2
|
||||
}
|
||||
15
src/Management/Commands/AnnounceCommand.cs
Normal file
15
src/Management/Commands/AnnounceCommand.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("announce", Role.Admin)]
|
||||
class AnnounceCommand : IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
if (arguments.Length == 0) {
|
||||
client.Send(Utils.BuildServerSideMessage("Announce: No message to announce", "Server"));
|
||||
return;
|
||||
}
|
||||
client.Room.Send(Utils.BuildServerSideMessage(string.Join(' ', arguments), "Server"));
|
||||
}
|
||||
}
|
||||
27
src/Management/Commands/BypassCommand.cs
Normal file
27
src/Management/Commands/BypassCommand.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("bypass", Role.Admin)]
|
||||
class BypassCommand : IManagementCommand
|
||||
{
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
if (arguments.Length == 0) {
|
||||
client.Send(Utils.BuildServerSideMessage("Bypass: No message to send", "Server"));
|
||||
return;
|
||||
}
|
||||
|
||||
string message = string.Join(' ', arguments);
|
||||
client.Room.Send(Utils.BuildChatMessage(client.PlayerData.Uid, message, client.PlayerData.DiplayName), client);
|
||||
|
||||
NetworkObject cmd = new();
|
||||
NetworkObject data = new();
|
||||
data.Add("arr", new string[] { "SCA", "-1", "1", message, "", "1" });
|
||||
cmd.Add("c", "SCA");
|
||||
cmd.Add("p", data);
|
||||
NetworkPacket packet = NetworkObject.WrapObject(1, 13, cmd).Serialize();
|
||||
client.Send(packet);
|
||||
}
|
||||
}
|
||||
13
src/Management/Commands/DisableAllChatsCommand.cs
Normal file
13
src/Management/Commands/DisableAllChatsCommand.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("disableallchats", Role.Moderator)]
|
||||
class DisableAllChatsCommand : IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
Room.DisableAllChatOverrides();
|
||||
client.Room.Send(Utils.BuildServerSideMessage("All chat overrides have been disabled", "Server"));
|
||||
}
|
||||
}
|
||||
|
||||
12
src/Management/Commands/DisableChatCommand.cs
Normal file
12
src/Management/Commands/DisableChatCommand.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("disablechat", Role.Moderator)]
|
||||
class DisableChatCommand : IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
client.Room.AllowChatOverride = false;
|
||||
client.Room.Send(Utils.BuildServerSideMessage("Chat has been disabled", "Server"));
|
||||
}
|
||||
}
|
||||
12
src/Management/Commands/EnableChatCommand.cs
Normal file
12
src/Management/Commands/EnableChatCommand.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("enablechat", Role.Moderator)]
|
||||
class EnableChatCommand : IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
client.Room.AllowChatOverride = true;
|
||||
client.Room.Send(Utils.BuildServerSideMessage("Chat has been enabled", "Server"));
|
||||
}
|
||||
}
|
||||
12
src/Management/Commands/ListAllChatOverridesCommand.cs
Normal file
12
src/Management/Commands/ListAllChatOverridesCommand.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("listallchats", Role.Moderator)]
|
||||
class ListAllChatOverridesCommand : IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
client.Send(Utils.BuildServerSideMessage(string.Join(' ', Room.AllRooms().Where(x => x.AllowChatOverride).Select(x => x.Name)), "Server"));
|
||||
}
|
||||
}
|
||||
|
||||
16
src/Management/Commands/PlayerCount.cs
Normal file
16
src/Management/Commands/PlayerCount.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("playercount", Role.Admin)]
|
||||
class PlayerCount : IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
client.Send(Utils.BuildServerSideMessage($"Current room: {(client.Room?.ClientsCount.ToString() ?? "not in room")}, Server total: {Room.AllRooms().Sum(x => x.ClientsCount)}", "Server"));
|
||||
}
|
||||
}
|
||||
24
src/Management/Commands/TempMuteCommand.cs
Normal file
24
src/Management/Commands/TempMuteCommand.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
|
||||
namespace sodoffmmo.Management.Commands;
|
||||
|
||||
[ManagementCommand("tempmute", Role.Moderator)]
|
||||
class TempMuteCommand : IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments) {
|
||||
if (arguments.Length != 1) {
|
||||
client.Send(Utils.BuildServerSideMessage("TempMute: Invalid number of arguments", "Server"));
|
||||
return;
|
||||
}
|
||||
Client? target = client.Room.Clients.FirstOrDefault(x => x.PlayerData.DiplayName == arguments[0]);
|
||||
if (target == null) {
|
||||
client.Send(Utils.BuildServerSideMessage($"TempMute: user {arguments[0]} not found", "Server"));
|
||||
return;
|
||||
}
|
||||
target.TempMuted = !target.TempMuted;
|
||||
if (target.TempMuted)
|
||||
client.Send(Utils.BuildServerSideMessage($"TempMute: {arguments[0]} has been temporarily muted", "Server"));
|
||||
else
|
||||
client.Send(Utils.BuildServerSideMessage($"TempMute: {arguments[0]} has been unmuted", "Server"));
|
||||
}
|
||||
}
|
||||
11
src/Management/IManagementCommand.cs
Normal file
11
src/Management/IManagementCommand.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using sodoffmmo.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace sodoffmmo.Management;
|
||||
public interface IManagementCommand {
|
||||
public void Handle(Client client, string[] arguments);
|
||||
}
|
||||
52
src/Management/ManagementCommandProcessor.cs
Normal file
52
src/Management/ManagementCommandProcessor.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using sodoffmmo.Attributes;
|
||||
using sodoffmmo.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace sodoffmmo.Management;
|
||||
public class ManagementCommandProcessor {
|
||||
static Dictionary<Tuple<string, Role>, Type> commands = new();
|
||||
static bool initialized = false;
|
||||
|
||||
public static void Initialize() {
|
||||
if (Configuration.ServerConfiguration.Authentication == AuthenticationMode.Disabled)
|
||||
return;
|
||||
commands.Clear();
|
||||
var handlerTypes = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(type => typeof(IManagementCommand).IsAssignableFrom(type))
|
||||
.Where(type => type.GetCustomAttribute<ManagementCommandAttribute>() != null);
|
||||
|
||||
foreach (var handlerType in handlerTypes) {
|
||||
ManagementCommandAttribute attrib = (handlerType.GetCustomAttribute(typeof(ManagementCommandAttribute)) as ManagementCommandAttribute)!;
|
||||
commands[new Tuple<string, Role>(attrib.Name, attrib.Role)] = handlerType;
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public static bool ProcessCommand(string message, Client client) {
|
||||
if (!initialized)
|
||||
return false;
|
||||
if (!message.StartsWith("::") || message.Length < 3)
|
||||
return false;
|
||||
|
||||
string[] parts = message.Split(' ');
|
||||
string commandName = parts[0].Substring(2);
|
||||
string[] arguments = parts.Skip(1).ToArray();
|
||||
|
||||
for (int i = (int)client.PlayerData.Role; i >= 0; --i) {
|
||||
Role currentRole = (Role)i;
|
||||
|
||||
if (commands.TryGetValue(new Tuple<string, Role>(commandName, currentRole), out Type? commandType)) {
|
||||
IManagementCommand command = (IManagementCommand)Activator.CreateInstance(commandType)!;
|
||||
Console.WriteLine($"Management command {commandName} by {client.PlayerData.DiplayName} ({client.PlayerData.Uid}) in {client.Room.Name}");
|
||||
command.Handle(client, arguments);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
22
src/Program.cs
Normal file
22
src/Program.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using sodoffmmo;
|
||||
using sodoffmmo.Core;
|
||||
using System.Net;
|
||||
|
||||
Configuration.Initialize();
|
||||
|
||||
Server server;
|
||||
|
||||
if (String.IsNullOrEmpty(Configuration.ServerConfiguration.ListenIP) || Configuration.ServerConfiguration.ListenIP == "*") {
|
||||
server = new(
|
||||
IPAddress.IPv6Any,
|
||||
Configuration.ServerConfiguration.Port,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
server = new(
|
||||
IPAddress.Parse(Configuration.ServerConfiguration.ListenIP),
|
||||
Configuration.ServerConfiguration.Port,
|
||||
false
|
||||
);
|
||||
}
|
||||
await server.Run();
|
||||
165
src/Server.cs
Normal file
165
src/Server.cs
Normal file
@ -0,0 +1,165 @@
|
||||
using sodoffmmo.Core;
|
||||
using sodoffmmo.Data;
|
||||
using sodoffmmo.Management;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace sodoffmmo;
|
||||
public class Server {
|
||||
|
||||
readonly int port;
|
||||
readonly IPAddress ipAddress;
|
||||
readonly bool IPv6AndIPv4;
|
||||
ModuleManager moduleManager = new();
|
||||
|
||||
public static List<Client>? AllClients { get; private set; }
|
||||
public static Dictionary<IPEndPoint, Client>? UDPClients { get; private set; }
|
||||
public static UdpClient SharedUDPClient { get; private set; }
|
||||
|
||||
public Server(IPAddress ipAdress, int port, bool IPv6AndIPv4) {
|
||||
this.ipAddress = ipAdress;
|
||||
this.port = port;
|
||||
this.IPv6AndIPv4 = IPv6AndIPv4;
|
||||
|
||||
AllClients = [];
|
||||
UDPClients = [];
|
||||
}
|
||||
|
||||
public async Task Run() {
|
||||
moduleManager.RegisterModules();
|
||||
ManagementCommandProcessor.Initialize();
|
||||
using Socket listener = new(ipAddress.AddressFamily,
|
||||
SocketType.Stream,
|
||||
ProtocolType.Tcp);
|
||||
if (IPv6AndIPv4)
|
||||
listener.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
|
||||
listener.Bind(new IPEndPoint(ipAddress, port));
|
||||
|
||||
SpecialRoom.CreateRooms();
|
||||
|
||||
Task tcpTask = Listen(listener);
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
Task udpTask = ListenAndHandleUDP(cts.Token);
|
||||
|
||||
try { await Task.WhenAll(tcpTask, udpTask); } catch (OperationCanceledException) { Console.WriteLine($"UDP Listener Exited Gracefully"); }
|
||||
catch (Exception ex) { Console.WriteLine(ex.ToString()); }
|
||||
}
|
||||
|
||||
private async Task Listen(Socket listener) {
|
||||
Console.WriteLine($"MMO Server listening on port {port}");
|
||||
listener.Listen(100);
|
||||
while (true) {
|
||||
Socket handler = await listener.AcceptAsync();
|
||||
handler.SendTimeout = 200;
|
||||
Console.WriteLine($"New connection from {((IPEndPoint)handler.RemoteEndPoint!).Address}");
|
||||
_ = Task.Run(() => HandleClient(handler));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ListenAndHandleUDP(CancellationToken cancellationToken)
|
||||
{
|
||||
using var udpClient = new UdpClient(port);
|
||||
SharedUDPClient = udpClient; // this should get disposed when the server shuts down (maybe)
|
||||
|
||||
Console.WriteLine($"MMO UDP Listener listening on port {port}");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
UdpReceiveResult result = await udpClient.ReceiveAsync(cancellationToken);
|
||||
byte[] buffer = result.Buffer;
|
||||
IPEndPoint endPoint = result.RemoteEndPoint;
|
||||
|
||||
NetworkObject obj;
|
||||
try { obj = new(buffer[3..]); }
|
||||
catch { continue; }
|
||||
|
||||
if(obj.ContainsKey("h")) // init
|
||||
{
|
||||
int tcpClientId = obj.Get<int>("u");
|
||||
Client? tcpClient = AllClients?.FirstOrDefault(e => e.ClientID == tcpClientId);
|
||||
if (tcpClient == null) continue;
|
||||
|
||||
Console.WriteLine($"Received UDP handshake attempt from client with ID {tcpClient.ClientID}");
|
||||
|
||||
NetworkObject ackObj = new();
|
||||
ackObj.Add("h", (byte)1);
|
||||
|
||||
UDPClients?[endPoint] = tcpClient;
|
||||
tcpClient.UDPEndPoint = endPoint;
|
||||
|
||||
NetworkPacket ack = ackObj.Serialize();
|
||||
await SharedUDPClient.SendAsync(ack.SendData, ack.SendData.Length, endPoint); // for ACK, the same UdpClient instance needs to respond so the client doesn't keep pinging
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!UDPClients!.TryGetValue(endPoint, out Client? client))
|
||||
continue;
|
||||
|
||||
await HandleObjectsUDP([obj], client);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleClient(Socket handler) {
|
||||
Client client = new(handler);
|
||||
AllClients?.Add(client);
|
||||
try {
|
||||
while (client.Connected) {
|
||||
await client.Receive();
|
||||
List<NetworkObject> networkObjects = new();
|
||||
while (client.TryGetNextPacket(out NetworkPacket packet))
|
||||
networkObjects.Add(packet.GetObject());
|
||||
|
||||
await HandleObjects(networkObjects, client);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
client.SetRoom(null);
|
||||
AllClients?.Remove(client);
|
||||
UDPClients?.Remove(client.UDPEndPoint);
|
||||
} catch (Exception) { }
|
||||
client.Disconnect();
|
||||
Console.WriteLine("Socket disconnected IID: " + client.ClientID);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleObjects(List<NetworkObject> networkObjects, Client client) {
|
||||
foreach (var obj in networkObjects) {
|
||||
try {
|
||||
short commandId = obj.Get<short>("a");
|
||||
CommandHandler handler;
|
||||
if (commandId != 13) {
|
||||
if (commandId == 0 || commandId == 1)
|
||||
Console.WriteLine($"System command: {commandId} IID: {client.ClientID}");
|
||||
handler = moduleManager.GetCommandHandler(commandId);
|
||||
} else
|
||||
handler = moduleManager.GetCommandHandler(obj.Get<NetworkObject>("p").Get<string>("c"));
|
||||
Task task = handler.Handle(client, obj.Get<NetworkObject>("p"));
|
||||
if (!handler.RunInBackground)
|
||||
await task;
|
||||
} catch (Exception ex) {
|
||||
Console.WriteLine($"Exception IID: {client.ClientID} - {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleObjectsUDP(List<NetworkObject> networkObjects, Client client)
|
||||
{
|
||||
foreach (var obj in networkObjects)
|
||||
{
|
||||
try
|
||||
{
|
||||
// only handle extension commands for now as all system commands (other than UDP init) should be handled by the TCP connection
|
||||
CommandHandler handler = moduleManager.GetCommandHandler(obj.Get<NetworkObject>("p").Get<string>("c"));
|
||||
Task task = handler.Handle(client, obj.Get<NetworkObject>("p"));
|
||||
if (!handler.RunInBackground)
|
||||
await task;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Exception IID: {client.ClientID} - {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/appsettings.json
Normal file
73
src/appsettings.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"MMOServer": {
|
||||
"// ListenIP": "Listening IP address for the MMO server, default is '*' which represents all IPv4 and IPv6 addresses",
|
||||
"ListenIP": "*",
|
||||
|
||||
"// Port": "Listening port number for the MMO server",
|
||||
"Port": 9933,
|
||||
|
||||
"// PingDelay": "delay (in milliseconds) for PNG response",
|
||||
"PingDelay": 17,
|
||||
|
||||
"// EnableChat": "When true, in-game chat will be enabled",
|
||||
"EnableChat": true,
|
||||
"EnableCannedChat": true,
|
||||
|
||||
"// EventName": "World event name send to client (can be used to select ship type after modding WorldEventScoutAttack in client)",
|
||||
"EventName": "ScoutAttack",
|
||||
|
||||
"// FirstEventTimer": "time to start of first world event (battle ship event) after start MMO server",
|
||||
"FirstEventTimer": 3,
|
||||
|
||||
"// EventTimer": "time between start of world events (battle ship events), set both timer values (EventTimer and FirstEventTimer) to 0 to disable events",
|
||||
"EventTimer": 30,
|
||||
|
||||
"// RoomAlerts": "List of MMO rooms with alert function. Default empty (not used by SoD), bellow sample config for WoJS, MB and SS.",
|
||||
"// alert parameters": "alert type, duration [s], minimum time to start [s], maximum time to start [s], redAlertDuration (used for type '1'), number of songs (used for type '3')",
|
||||
"// alert types": "1 - Red Alert, 2 - Disco Alert, 3 - Dance Off",
|
||||
"RoomAlerts": {
|
||||
"LoungeInt" : [ ["3", 20.0, 30, 240, 0, 16] ],
|
||||
"Spaceport": [ ["1", 20.0, 300, 300, 60, 0], ["2", 120.0, 1800, 3600, 60, 0] ],
|
||||
"Academy": [ ["1", 20.0, 300, 300, 60, 0] ],
|
||||
"ClubSSInt" : [ ["3", 20.0, 30, 240, 0, 16] ],
|
||||
"JunkYardEMD": [ ["1", 20.0, 240, 300, 60, 0] ],
|
||||
},
|
||||
|
||||
"// AmbassadorRooms": "Rooms with ambassadors (MB funzones).",
|
||||
"AmbassadorRooms": ["Spaceport"],
|
||||
|
||||
"// AmbassadorGaugeStart": "The starting value for all ambassador gauges (MB funzones).",
|
||||
"AmbassadorGaugeStart": 75,
|
||||
|
||||
"// AmbassadorGaugeDecayRate": "Time in seconds before ambassador gauges decrease (MB funzones).",
|
||||
"AmbassadorGaugeDecayRate": 60,
|
||||
|
||||
"// AmbassadorGaugeDecayOnlyWhenInRoom": "Only decrease ambassador gauges when there is at least one player in the room (MB funzones).",
|
||||
"AmbassadorGaugeDecayOnlyWhenInRoom": true,
|
||||
|
||||
"// AmbassadorGaugePlayers": "Denominator for filling the ambassador gauges (MB funzones).",
|
||||
"AmbassadorGaugePlayers": 0.5,
|
||||
|
||||
"// RacingMaxPlayers": "maximum players allowed in Thunder Run Racing (no more than 6)",
|
||||
"RacingMaxPlayers": 6,
|
||||
|
||||
"// RacingMinPlayers": "minimum players to start Thunder Run Racing",
|
||||
"RacingMinPlayers": 2,
|
||||
|
||||
"// AllowChaos": "disable server side exploit protection",
|
||||
"AllowChaos": false,
|
||||
|
||||
"// Authentication": "Player authentication mode: Disabled, Optional, RequiredForChat, Required",
|
||||
"// Authentication Disabled": "authentication is disabled, anyone can connect to mmo",
|
||||
"// Authentication Optional": "authentication is required only for moderation activities",
|
||||
"// Authentication RequiredForChat": "authentication is required only for moderation activities and using chat (if chat is enabled)",
|
||||
"// Authentication Required": "authentication is required to connect to mmo",
|
||||
"Authentication": "Disabled",
|
||||
|
||||
"// ApiUrl": "SoDOff API server URL for authentication calls",
|
||||
"ApiUrl": "http://localhost:5000",
|
||||
|
||||
"// BypassToken": "Token allowed to connect without authentication",
|
||||
"BypassToken": ""
|
||||
}
|
||||
}
|
||||
21
src/sodoffmmo.csproj
Normal file
21
src/sodoffmmo.csproj
Normal file
@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ComponentAce.Compression.Libs.zlib" Version="1.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Loading…
x
Reference in New Issue
Block a user