fuck microslop
This commit is contained in:
commit
38fb825173
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
|
||||
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
.vs
|
||||
.vscode
|
||||
.DS_Store
|
||||
src/bin
|
||||
src/obj
|
||||
src/sodoff*.db
|
||||
src/sodoff*.db-shm
|
||||
src/sodoff*.db-wal
|
||||
src/assets/
|
||||
src/mods/
|
||||
src/assets-cache/
|
||||
src/Properties
|
||||
__pycache__/
|
||||
/src/Dlls/SmartFox2X.dll
|
||||
/src/appsettings.Development.json
|
||||
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/>.
|
||||
202
README.md
Normal file
202
README.md
Normal file
@ -0,0 +1,202 @@
|
||||
# SoDOff - School of Dragons, Offline
|
||||
|
||||
On 7th June 2023, School of Dragons announced they were "sunsetting" the game, and turning the servers off on the 30th of June.
|
||||
At that time, SoDOff was born. Currently, it is an almost complete implementation of the School of Dragons API server (and some other JS games).
|
||||
It allows you to run SoD offline as well as host private online servers.
|
||||
|
||||
We also provide MMO server implementation for SoD (https://github.com/SoDOff-Project/sodoff-mmo).
|
||||
It is recommended to use the MMO server even when offline, as some aspects of the single player game depend on MMO.
|
||||
|
||||
## License
|
||||
|
||||
SoDOff is open source, distributed under the [AGPL](LICENSE) license.
|
||||
This license does not cover resources obtained from the game or from responses of the original API distributed with the API server, especially:
|
||||
`missions.xml`, `items.xml`, `allranks.xml` and `store.xml` files from the `src/Resources` directory and files inside the `src/assets` directory.
|
||||
|
||||
## Getting started
|
||||
|
||||
To build the API server from sources, you'll need the dotnet 10 SDK. Simply run the following command to build and start the server:
|
||||
|
||||
```
|
||||
dotnet run --project src/sodoff.csproj
|
||||
```
|
||||
|
||||
### Modifying the Client
|
||||
|
||||
To play the game you need to modify the game client to use `http://localhost:5001/.com/conf/` instead of `http://media.jumpstart.com/DWADragonsUnity/`.
|
||||
You can use *ClientPatcher* tool form https://github.com/SoDOff-Project/sodoff-tools to do this. This tool also change correct ApiKey and 3DesKey values.
|
||||
You can also do this by manually editing `DOMain_Data/resources.assets` in a hex-editor and swapping the URLs (and keys if needed).
|
||||
|
||||
By default we use `conf` instead of `DWADragonsUnity` for XML configuration to allow the use of a longer server name/IP address (limitation of url length in resources.assets).
|
||||
Provided sample `DWADragonsMain.xml` file still requires the use of the `DWADragonsUnity` directory for assets.
|
||||
|
||||
### Server Configuration
|
||||
|
||||
Most of the server configuration is stored in `appsettings.json`. Check out the "// ..." keys in there for descriptions of different options.
|
||||
|
||||
#### Supported Clients
|
||||
|
||||
The server support multiple versions of School of Dragons and some other JS online games (like Magic & Mythies, Math Blaster and World of JumpStart).
|
||||
|
||||
For each supported SoD client version, there must be a corresponding file located at `assets/conf/{PLATFORM}/{VERSION}/DWADragonsMain.xml`.
|
||||
By default (modifiable in appsettings.json), files for version 2.5.0 and newer will be automatically encrypted to meet the client's requirements.
|
||||
|
||||
A sample file is provided for `{PLATFORM} = WIN`, `{VERSION} = 3.31.0`.
|
||||
It assumes that the server address are `localhost:5000` (API) and `localhost:5001` (assets).
|
||||
|
||||
To use this server with games other than School of Dragons you need to change the `LoadNonSoDData` value in `appsettings.json` to `true`.
|
||||
|
||||
#### Asset Server
|
||||
|
||||
Various settings for the asset server are customizable, with the key one being `ProviderURL`, which specifies the source for downloading assets in `partial` mode. By default, it's configured to use archive.org
|
||||
Please do not abuse the archive.org server, do not disable the `UseCache` option and do not delete the contents of the `asset-cache` directory.
|
||||
The [SoDOff-tools](https://github.com/SoDOff-Project/sodoff-tools) repository contains an *AssetsDownloader* tool to pre-download assets (this may be needed in case of a slow connection to `ProviderURL`).
|
||||
|
||||
#### Listening address/port
|
||||
|
||||
By default, the server listens on all IPv4 and IPv6 addresses on ports 5000 (API) and 5001 (assets).
|
||||
You can tweak this in `appsettings.json`, but it might also mean adjusting `DWADragonsMain.xml` and updating clients to reflect the changes in the asset server address.
|
||||
|
||||
### Server Side Modding
|
||||
|
||||
The server supports server side modifications, which includes adding new items and putting them in the store without having to modify the server source code.
|
||||
For more information, check out [src/mods/README-MODDING.md](src/mods/README-MODDING.md).
|
||||
|
||||
## Status
|
||||
|
||||
### What works
|
||||
|
||||
Almost everything:
|
||||
|
||||
- register/login
|
||||
- create profile
|
||||
- list profiles
|
||||
- tutorial
|
||||
- roaming in the open world
|
||||
- inventory
|
||||
- store
|
||||
- missions
|
||||
- hideouts
|
||||
- farms
|
||||
- minigames
|
||||
- clans
|
||||
- MMO (using sodoff-mmo)
|
||||
|
||||
### What doesn't work
|
||||
|
||||
- play as Guest
|
||||
- friends
|
||||
- in-game messaging system (Terrible Mail)
|
||||
|
||||
|
||||
### Methods
|
||||
|
||||
#### Fully implemented
|
||||
- AcceptMission
|
||||
- AddBattleItems
|
||||
- AssignRole
|
||||
- AuthenticateUser
|
||||
- CreateGroup
|
||||
- CreatePet
|
||||
- DeleteAccountNotification
|
||||
- DeleteProfile
|
||||
- EditGroup
|
||||
- FuseItems
|
||||
- GetAchievementsByUserID
|
||||
- GetAllActivePetsByuserId
|
||||
- GetAuthoritativeTime
|
||||
- GetChildList
|
||||
- GetCommonInventory (V2)
|
||||
- GetDefaultNameSuggestion
|
||||
- GetDetailedChildList
|
||||
- GetGameData
|
||||
- GetGameDataByUser
|
||||
- GetGroups
|
||||
- GetGroupsByGroupType
|
||||
- GetGroupsByUserID
|
||||
- GetImage
|
||||
- GetImageByUserId
|
||||
- GetItem
|
||||
- GetKeyValuePair
|
||||
- GetKeyValuePairByUserID
|
||||
- GetMembersByGroupID
|
||||
- GetMMOServerInfoWithZone (uses resource xml as response)
|
||||
- GetPendingJoinRequests
|
||||
- GetPetAchievementsByUserID
|
||||
- GetSelectedRaisedPet
|
||||
- GetStore
|
||||
- GetUnselectedPetByTypes
|
||||
- GetUserActiveMissionState
|
||||
- GetUserCompletedMissionState
|
||||
- GetUserInfoByApiToken
|
||||
- GetUserMissionState
|
||||
- GetUserProfile
|
||||
- GetUserProfileByUserID
|
||||
- GetUserRoomItemPositions
|
||||
- GetUserUpcomingMissionState
|
||||
- IsValidApiToken_V2
|
||||
- JoinGroup (V1 & V2) (User-given message for join request disabled, at least for now)
|
||||
- LeaveGroup
|
||||
- LoginChild
|
||||
- LoginParent
|
||||
- PurchaseItems (V1)
|
||||
- PurchaseItems (V2)
|
||||
- RedeemMysteryBoxItems
|
||||
- RegisterChild
|
||||
- RegisterParent
|
||||
- RerollUserItem
|
||||
- SetAchievementAndGetReward
|
||||
- SetAchievementByEntityIDs
|
||||
- SetAvatar
|
||||
- SetCommonInventory
|
||||
- SetDisplayName (V2)
|
||||
- SetDragonXP (used by account import tools)
|
||||
- SetImage
|
||||
- SetKeyValuePair
|
||||
- SetKeyValuePairByUserID
|
||||
- SetPlayerXP (used by account import tools)
|
||||
- SetRaisedPet
|
||||
- SetSelectedPet
|
||||
- SetUserRoomItemPositions
|
||||
- UseInventory
|
||||
|
||||
#### Implemented enough (probably)
|
||||
- GetCommonInventory (V1 - returns the viking's inventory if it is called with a viking; otherwise returns 8 viking slots)
|
||||
- GetQuestions (doesn't return all questions, probably doesn't need to)
|
||||
- GetRules (doesn't return any rules, probably doesn't need to)
|
||||
- GetSubscriptionInfo (always returns member, with end date 10 years from now)
|
||||
- SendRawGameData
|
||||
- SetNextItemState
|
||||
- SetTaskState (only the TaskCanBeDone status is supported; might contain a serious problem - see the MissionService class)
|
||||
- SetUserAchievementAndGetReward (works like SetAchievementAndGetReward)
|
||||
- SetUserRoom
|
||||
- ValidateName
|
||||
|
||||
#### Partially implemented
|
||||
- ApplyPayout (doesn't calculate rewards properly)
|
||||
- ApplyRewards
|
||||
- GetGameDataByGame (friend tab displays all players - friend filter is not yet implemented because friend lists are not implemented)
|
||||
- GetGameDataByGameForDateRange (friend tab displays all players)
|
||||
- GetTopAchievementPointUsers (ignores type [all, buddy, hall of fame, ...] and mode [overall, monthly, weekly] properties)
|
||||
- GetUserAchievements (used by Magic & Mythies)
|
||||
- GetUserRoomList (room categories are not implemented, but it's enough for SoD)
|
||||
- ProcessRewardedItems (gives gems, but doesn't give gold, gold is not yet implemented)
|
||||
- SellItems (gives gems, but doesn't give gold, gold is not yet implemented)
|
||||
- SetUserAchievementTask (returns a real reward but still use task placeholder)
|
||||
|
||||
#### Currently static or stubbed
|
||||
- GetAchievementTaskInfo (returns a static XML)
|
||||
- GetActiveChallenges (returns an empty array)
|
||||
- GetAllRanks (needs to be populated with what ranks the user has)
|
||||
- GetAllRewardTypeMultiplier (returns a static XML)
|
||||
- GetAllRewardTypeMultiplier (returns a static XML)
|
||||
- GetAnnouncementsByUser (returns no announcements, but that might be sufficient)
|
||||
- GetAverageRatingForRoom (return max rating)
|
||||
- GetBuddyList (returns an emtpy array)
|
||||
- GetProfileTagAll (returns an empty array - used by Magic & Mythies)
|
||||
- GetRankAttributeData (returns a static XML)
|
||||
- GetUserActivityByUserID (returns an empty array)
|
||||
- GetUserGameCurrency (return 65536 gems and 65536 coins)
|
||||
- GetUserMessageQueue (returns an emtpy array)
|
||||
- SaveMessage (doesn't do anything and returns false)
|
||||
- SendMessage (doesn't do anything and returns false)
|
||||
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
@ -0,0 +1,25 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
sodoff:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: src/Dockerfile
|
||||
# ports:
|
||||
# - 5000:5000
|
||||
networks:
|
||||
sodoff_network:
|
||||
# use static ip address for nginx proxy configuration
|
||||
# - if run multiple instances of this docker, each instance should use unique address
|
||||
# - if do not use proxy, port mapping as above should be enough
|
||||
ipv4_address: 172.16.99.10
|
||||
|
||||
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"
|
||||
121
mitm-redirect.py
Normal file
121
mitm-redirect.py
Normal file
@ -0,0 +1,121 @@
|
||||
from mitmproxy import ctx
|
||||
import mitmproxy.http
|
||||
|
||||
methods = [
|
||||
'GetRules',
|
||||
'LoginParent',
|
||||
'AuthenticateUser',
|
||||
'RegisterParent',
|
||||
'GetSubscriptionInfo',
|
||||
'GetUserInfoByApiToken',
|
||||
'IsValidApiToken_V2',
|
||||
'ValidateName',
|
||||
'GetDefaultNameSuggestion',
|
||||
'RegisterChild',
|
||||
'GetProfileByUserId',
|
||||
'LoginChild',
|
||||
'GetUserProfileByUserID',
|
||||
'GetKeyValuePair',
|
||||
'SetKeyValuePair',
|
||||
'GetKeyValuePairByUserID',
|
||||
'SetKeyValuePairByUserID',
|
||||
'GetUserProfile',
|
||||
'GetQuestions',
|
||||
'GetCommonInventory',
|
||||
'SetCommonInventory',
|
||||
'GetItem',
|
||||
'GetAuthoritativeTime',
|
||||
'SetAvatar',
|
||||
'GetPetAchievementsByUserID',
|
||||
'GetDetailedChildList',
|
||||
'GetStore',
|
||||
'GetAllRanks',
|
||||
'GetUserUpcomingMissionState',
|
||||
'GetUserActiveMissionState',
|
||||
'GetUserCompletedMissionState',
|
||||
'SetTaskState',
|
||||
'CreatePet',
|
||||
'SetRaisedPet',
|
||||
'SetSelectedPet',
|
||||
'GetAllActivePetsByuserId',
|
||||
'GetSelectedRaisedPet',
|
||||
'SetImage',
|
||||
'GetImage',
|
||||
'GetImageByUserId',
|
||||
'GetAchievementTaskInfo',
|
||||
'GetAllRewardTypeMultiplier',
|
||||
'GetBuddyList',
|
||||
'GetRankAttributeData',
|
||||
'GetUserMessageQueue',
|
||||
'SendMessage',
|
||||
'SaveMessage',
|
||||
'GetMMOServerInfoWithZone',
|
||||
'GetActiveChallenges',
|
||||
'GetAchievementsByUserID',
|
||||
'PurchaseItems',
|
||||
'AcceptMission',
|
||||
'GetUserMissionState',
|
||||
'SetAchievementAndGetReward',
|
||||
'SetUserAchievementTask',
|
||||
'GetAnnouncementsByUser',
|
||||
'GetUserRoomItemPositions',
|
||||
'SetUserRoomItemPositions',
|
||||
'GetAverageRatingForRoom',
|
||||
'GetUserRoomList',
|
||||
'GetUserActivityByUserID',
|
||||
'SetNextItemState',
|
||||
'SetUserRoom',
|
||||
'GetChildList',
|
||||
'GetUnselectedPetByTypes',
|
||||
'GetUserGameCurrency',
|
||||
'SetAchievementByEntityIDs',
|
||||
'UseInventory',
|
||||
'SellItems',
|
||||
'FuseItems',
|
||||
'RerollUserItem',
|
||||
'AddBattleItems',
|
||||
'ApplyRewards',
|
||||
'DeleteProfile',
|
||||
'DeleteAccountNotification',
|
||||
'SetUserAchievementAndGetReward',
|
||||
'ProcessRewardedItems',
|
||||
'ApplyPayout',
|
||||
'RedeemMysteryBoxItems',
|
||||
'SendRawGameData',
|
||||
'GetGameData',
|
||||
'GetGameDataByGame',
|
||||
'GetGameDataByUser',
|
||||
'GetGameDataByGameForDateRange',
|
||||
'GetTopAchievementPointUsers',
|
||||
'SetDisplayName',
|
||||
]
|
||||
|
||||
def routable(path):
|
||||
for method in methods:
|
||||
if method in path:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class LocalRedirect:
|
||||
def __init__(self):
|
||||
print('Loaded redirect addon')
|
||||
|
||||
def request(self, flow: mitmproxy.http.HTTPFlow):
|
||||
if ('api.sodoff.spirtix.com' in flow.request.pretty_host or 'api.jumpstart.com' in flow.request.pretty_host) and routable(flow.request.path):
|
||||
flow.request.host = "localhost"
|
||||
flow.request.scheme = 'http'
|
||||
flow.request.port = 5000
|
||||
|
||||
class RedirectMediaRequests:
|
||||
def __init__(self):
|
||||
print('Loaded media request redirector')
|
||||
|
||||
def request(self, flow: mitmproxy.http.HTTPFlow):
|
||||
if "media.jumpstart.com" in flow.request.pretty_host:
|
||||
flow.request.host = "media.sodoff.spirtix.com"
|
||||
|
||||
addons = [
|
||||
LocalRedirect(),
|
||||
RedirectMediaRequests()
|
||||
]
|
||||
4
run.sh
Normal file
4
run.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
dotnet run --project src/sodoff.csproj
|
||||
22
sodoff.sln
Normal file
22
sodoff.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}") = "sodoff", "src\sodoff.csproj", "{B0D39F12-DE8E-4289-82DD-C1F264CC280A}"
|
||||
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
|
||||
{B0D39F12-DE8E-4289-82DD-C1F264CC280A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B0D39F12-DE8E-4289-82DD-C1F264CC280A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B0D39F12-DE8E-4289-82DD-C1F264CC280A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B0D39F12-DE8E-4289-82DD-C1F264CC280A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
35
src/Attributes/DecryptRequest.cs
Normal file
35
src/Attributes/DecryptRequest.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class DecryptRequest : Attribute, IAsyncActionFilter {
|
||||
const string key = "56BB211B-CF06-48E1-9C1D-E40B5173D759";
|
||||
|
||||
private readonly string field;
|
||||
|
||||
public DecryptRequest(string field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
|
||||
if (context.HttpContext.Request.HasFormContentType) {
|
||||
var form = context.HttpContext.Request.Form;
|
||||
if (form.ContainsKey(field)) {
|
||||
// Get field from submitted form and decrypt
|
||||
var fieldValue = form[field].ToString();
|
||||
string result = TripleDES.DecryptUnicode(fieldValue, key);
|
||||
var dict = form.ToDictionary(x => x.Key, x => x.Value);
|
||||
dict[field] = result;
|
||||
var updatedForm = new FormCollection(dict);
|
||||
// Update the form in the request
|
||||
context.HttpContext.Request.Form = updatedForm;
|
||||
}
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
}
|
||||
27
src/Attributes/EncryptResponse.cs
Normal file
27
src/Attributes/EncryptResponse.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using sodoff.Util;
|
||||
using System.Text;
|
||||
|
||||
namespace sodoff.Attributes;
|
||||
public class EncryptResponse : Attribute, IAsyncResultFilter {
|
||||
const string key = "56BB211B-CF06-48E1-9C1D-E40B5173D759";
|
||||
|
||||
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) {
|
||||
var originalBodyStream = context.HttpContext.Response.Body;
|
||||
|
||||
using (var memoryStream = new MemoryStream()) {
|
||||
context.HttpContext.Response.Body = memoryStream;
|
||||
|
||||
await next();
|
||||
|
||||
// Read body and encrypt
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
var responseBody = await new StreamReader(memoryStream).ReadToEndAsync();
|
||||
var result = $"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string>{TripleDES.EncryptUnicode(responseBody, key)}</string>";
|
||||
context.HttpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(result);
|
||||
var newBody = new MemoryStream(Encoding.UTF8.GetBytes(result));
|
||||
// Override body with encrypted data
|
||||
await newBody.CopyToAsync(originalBodyStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/Attributes/SignResponse.cs
Normal file
34
src/Attributes/SignResponse.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Util;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace sodoff.Attributes;
|
||||
public class SignResponse : Attribute, IAsyncResultFilter {
|
||||
const string key = "11A0CC5A-C4DF-4A0E-931C-09A44C9966AE";
|
||||
public async System.Threading.Tasks.Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) {
|
||||
var originalBodyStream = context.HttpContext.Response.Body;
|
||||
|
||||
try {
|
||||
using (var memoryStream = new MemoryStream()) {
|
||||
context.HttpContext.Response.Body = memoryStream;
|
||||
|
||||
await next();
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
var responseBody = await new StreamReader(memoryStream).ReadToEndAsync();
|
||||
|
||||
var hash = Md5.GetMd5Hash(key + responseBody);
|
||||
|
||||
context.HttpContext.Response.Headers.Add("Signature", hash);
|
||||
|
||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||
await memoryStream.CopyToAsync(originalBodyStream);
|
||||
}
|
||||
} finally {
|
||||
context.HttpContext.Response.Body = originalBodyStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/Attributes/VikingSession.cs
Normal file
86
src/Attributes/VikingSession.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using sodoff.Model;
|
||||
|
||||
namespace sodoff.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class VikingSession : Attribute, IAsyncActionFilter {
|
||||
public enum Modes { VIKING, USER, VIKING_OR_USER };
|
||||
|
||||
public string ApiToken { get; set; } = "apiToken";
|
||||
public Modes Mode { get; set; } = Modes.VIKING;
|
||||
public bool UseLock = false;
|
||||
|
||||
private static Dictionary<string, SemaphoreSlim> semaphores = new();
|
||||
private static SemaphoreSlim dictSemaphore = new(1, 1);
|
||||
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
|
||||
DBContext ctx = context.HttpContext.RequestServices.GetService(typeof(DBContext)) as DBContext;
|
||||
|
||||
// get session from apiToken
|
||||
|
||||
if (!context.HttpContext.Request.Form.ContainsKey(ApiToken)) {
|
||||
context.Result = new UnauthorizedObjectResult("Unauthorized") { StatusCode = 403 };
|
||||
return;
|
||||
}
|
||||
|
||||
Session? session = ctx.Sessions.FirstOrDefault(x => x.ApiToken == Guid.Parse(context.HttpContext.Request.Form[ApiToken].ToString()));
|
||||
|
||||
// get viking / user id from session
|
||||
|
||||
string? userVikingId = null;
|
||||
if (Mode == Modes.VIKING || (Mode == Modes.VIKING_OR_USER && session?.UserId is null) ) {
|
||||
userVikingId = session?.VikingId?.ToString();
|
||||
} else {
|
||||
userVikingId = session?.UserId?.ToString();
|
||||
}
|
||||
|
||||
if (userVikingId is null) {
|
||||
context.Result = new UnauthorizedObjectResult("Unauthorized") { StatusCode = 403 };
|
||||
return;
|
||||
}
|
||||
|
||||
// call next (with lock if requested)
|
||||
|
||||
if (UseLock) {
|
||||
// NOTE: we can't refer to session.User / session.Viking here,
|
||||
// because it may cause to ignore modifications from the threads we are waiting for
|
||||
// we can use its only after vikingMutex.WaitOne()
|
||||
string semKey = "SoDOffViking:" + userVikingId;
|
||||
SemaphoreSlim semaphore = await GetSemaphore(semKey);
|
||||
try {
|
||||
await semaphore.WaitAsync();
|
||||
context.ActionArguments["user"] = session.User;
|
||||
context.ActionArguments["viking"] = session.Viking;
|
||||
await next();
|
||||
} finally {
|
||||
semaphore.Release();
|
||||
await RemoveSemaphore(semKey, semaphore);
|
||||
}
|
||||
} else {
|
||||
context.ActionArguments["user"] = session.User;
|
||||
context.ActionArguments["viking"] = session.Viking;
|
||||
await next();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<SemaphoreSlim> GetSemaphore(string key) {
|
||||
await dictSemaphore.WaitAsync();
|
||||
if (!semaphores.TryGetValue(key, out SemaphoreSlim semaphore)) {
|
||||
semaphore = new SemaphoreSlim(1, 1);
|
||||
semaphores.Add(key, semaphore);
|
||||
}
|
||||
dictSemaphore.Release();
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
private static async Task RemoveSemaphore(string key, SemaphoreSlim sem) {
|
||||
await dictSemaphore.WaitAsync();
|
||||
if (sem.CurrentCount == 1) {
|
||||
semaphores.Remove(key);
|
||||
}
|
||||
dictSemaphore.Release();
|
||||
}
|
||||
}
|
||||
18
src/Configuration/ApiServerConfig.cs
Normal file
18
src/Configuration/ApiServerConfig.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace sodoff.Configuration;
|
||||
public class ApiServerConfig {
|
||||
public string ResponseURL { get; set; } = string.Empty;
|
||||
|
||||
public string MMOAdress { get; set; } = "127.0.0.1";
|
||||
public int MMOPort { get; set; } = 9933;
|
||||
public uint MMOSupportMinVersion { get; set; } = 0;
|
||||
|
||||
public bool LoadNonSoDData { get; set; } = false;
|
||||
|
||||
public DbProviders DbProvider { get; set; } = DbProviders.SQLite;
|
||||
public string DbPath { get; set; } = string.Empty;
|
||||
public string DbConnection { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public enum DbProviders {
|
||||
SQLite, PostgreSQL, MySQL
|
||||
}
|
||||
18
src/Configuration/AssetServerConfig.cs
Normal file
18
src/Configuration/AssetServerConfig.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace sodoff.Configuration;
|
||||
public class AssetServerConfig {
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string ListenIP { get; set; } = string.Empty;
|
||||
public int Port { get; set; } = 5001;
|
||||
public string URLPrefix { get; set; } = string.Empty;
|
||||
public bool UseAnyURLPrefix { get; set; } = true;
|
||||
public AssetServerMode Mode { get; set; }
|
||||
public string ProviderURL { get; set; } = string.Empty;
|
||||
public string AutoEncryptRegexp { get; set; } = string.Empty;
|
||||
public string AutoEncryptKey { get; set; } = string.Empty;
|
||||
public bool SubstituteMissingLocalAssets { get; set; } = false;
|
||||
public bool UseCache { get; set; } = true;
|
||||
}
|
||||
|
||||
public enum AssetServerMode {
|
||||
None, Partial, Full
|
||||
}
|
||||
279
src/Controllers/Common/AchievementController.cs
Normal file
279
src/Controllers/Common/AchievementController.cs
Normal file
@ -0,0 +1,279 @@
|
||||
using System.Reflection;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class AchievementController : Controller {
|
||||
|
||||
public readonly DBContext ctx;
|
||||
private AchievementStoreSingleton achievementStore;
|
||||
private AchievementService achievementService;
|
||||
|
||||
public AchievementController(DBContext ctx, AchievementStoreSingleton achievementStore, AchievementService achievementService) {
|
||||
this.ctx = ctx;
|
||||
this.achievementStore = achievementStore;
|
||||
this.achievementService = achievementService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetPetAchievementsByUserID")]
|
||||
public IActionResult GetPetAchievementsByUserID([FromForm] string userId) {
|
||||
// NOTE: this is public info (for mmo) - no session check
|
||||
List<UserAchievementInfo> dragonsAchievement = new List<UserAchievementInfo>();
|
||||
foreach (Dragon dragon in ctx.Dragons.Where(d => d.Viking.Uid == Guid.Parse(userId))) {
|
||||
dragonsAchievement.Add(
|
||||
achievementService.CreateUserAchievementInfo(dragon.EntityId, dragon.PetXP, AchievementPointTypes.DragonXP)
|
||||
);
|
||||
}
|
||||
|
||||
ArrayOfUserAchievementInfo arrAchievements = new ArrayOfUserAchievementInfo {
|
||||
UserAchievementInfo = dragonsAchievement.ToArray()
|
||||
};
|
||||
|
||||
return Ok(arrAchievements);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
//[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetAllRanks")]
|
||||
public IActionResult GetAllRanks([FromForm] string apiKey) {
|
||||
uint gameID = ClientVersion.GetGameID(apiKey);
|
||||
if (gameID == ClientVersion.WoJS)
|
||||
return Ok(XmlUtil.ReadResourceXmlString("ranks.allranks_wojs"));
|
||||
if (gameID == ClientVersion.MB)
|
||||
return Ok(XmlUtil.ReadResourceXmlString("ranks.allranks_mb"));
|
||||
return Ok(XmlUtil.ReadResourceXmlString("ranks.allranks_sod"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetAchievementTaskInfo")]
|
||||
public IActionResult GetAchievementTaskInfo([FromForm] string achievementTaskIDList, [FromForm] string apiKey) {
|
||||
int[] achievementTaskIDs = XmlUtil.DeserializeXml<int[]>(achievementTaskIDList);
|
||||
List<AchievementTaskInfo> achievementTaskInfos = new();
|
||||
foreach (int taskId in achievementTaskIDs) {
|
||||
var achievementInfo = achievementStore.GetAllAchievementTaskInfo(taskId, ClientVersion.GetVersion(apiKey));
|
||||
if (achievementInfo != null)
|
||||
achievementTaskInfos.AddRange(achievementInfo);
|
||||
}
|
||||
return Ok(new ArrayOfAchievementTaskInfo {
|
||||
AchievementTaskInfo = achievementTaskInfos.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
//[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetAllRewardTypeMultiplier")]
|
||||
public IActionResult GetAllRewardTypeMultiplier() {
|
||||
// TODO
|
||||
return Ok(XmlUtil.ReadResourceXmlString("rewardmultiplier"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("AchievementWebService.asmx/SetDragonXP")] // used by dragonrescue-import
|
||||
[VikingSession]
|
||||
public IActionResult SetDragonXP(Viking viking, [FromForm] string dragonId, [FromForm] int value) {
|
||||
Dragon? dragon = viking.Dragons.FirstOrDefault(e => e.EntityId == Guid.Parse(dragonId));
|
||||
if (dragon is null) {
|
||||
return Conflict("Dragon not found");
|
||||
}
|
||||
|
||||
dragon.PetXP = value;
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok("OK");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("AchievementWebService.asmx/SetPlayerXP")] // used by dragonrescue-import
|
||||
[VikingSession]
|
||||
public IActionResult SetDragonXP(Viking viking, [FromForm] int type, [FromForm] int value) {
|
||||
if (!Enum.IsDefined(typeof(AchievementPointTypes), type)) {
|
||||
return Conflict("Invalid XP type");
|
||||
}
|
||||
|
||||
AchievementPointTypes xpType = (AchievementPointTypes)type;
|
||||
achievementService.SetAchievementPoints(viking, xpType, value);
|
||||
ctx.SaveChanges();
|
||||
return Ok("OK");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetUserAchievementInfo")] // used by World Of Jumpstart
|
||||
public IActionResult GetUserAchievementInfo([FromForm] Guid apiToken) {
|
||||
Viking? viking = ctx.Sessions.FirstOrDefault(x => x.ApiToken == apiToken).Viking;
|
||||
|
||||
if (viking != null) {
|
||||
return Ok(
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetAchievementsByUserID")]
|
||||
public IActionResult GetAchievementsByUserID([FromForm] string userId) {
|
||||
// NOTE: this is public info (for mmo) - no session check
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == Guid.Parse(userId));
|
||||
if (viking != null) {
|
||||
return Ok(new ArrayOfUserAchievementInfo {
|
||||
UserAchievementInfo = new UserAchievementInfo[]{
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerFarmingXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerFishingXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.UDTPoints),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Dragon? dragon = ctx.Dragons.FirstOrDefault(e => e.EntityId == Guid.Parse(userId));
|
||||
if (dragon != null) {
|
||||
return Ok(new ArrayOfUserAchievementInfo {
|
||||
UserAchievementInfo = new UserAchievementInfo[]{
|
||||
achievementService.CreateUserAchievementInfo(dragon.EntityId, dragon.PetXP, AchievementPointTypes.DragonXP),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetUserAchievements")] // used by Magic & Mythies
|
||||
[VikingSession]
|
||||
public IActionResult GetUserAchievements(Viking viking) {
|
||||
ArrayOfUserAchievementInfo arrAchievements = new ArrayOfUserAchievementInfo {
|
||||
UserAchievementInfo = new UserAchievementInfo[]{
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerFarmingXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerFishingXP),
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(arrAchievements);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/SetAchievementAndGetReward")]
|
||||
[Route("AchievementWebService.asmx/SetUserAchievementAndGetReward")]
|
||||
[VikingSession(UseLock=true)]
|
||||
public IActionResult SetAchievementAndGetReward(Viking viking, [FromForm] int achievementID) {
|
||||
var rewards = achievementService.ApplyAchievementRewardsByID(viking, achievementID);
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(rewards);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/AchievementWebService.asmx/SetUserAchievementTask")]
|
||||
[DecryptRequest("achievementTaskSetRequest")]
|
||||
[VikingSession(UseLock=true)]
|
||||
public IActionResult SetUserAchievementTask(Viking viking, [FromForm] string apiKey) {
|
||||
AchievementTaskSetRequest request = XmlUtil.DeserializeXml<AchievementTaskSetRequest>(Request.Form["achievementTaskSetRequest"]);
|
||||
|
||||
var response = new List<AchievementTaskSetResponse>();
|
||||
foreach (var task in request.AchievementTaskSet) {
|
||||
response.Add(
|
||||
achievementService.ApplyAchievementRewardsByTask(viking, task.TaskID, ClientVersion.GetVersion(apiKey))
|
||||
);
|
||||
}
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(new ArrayOfAchievementTaskSetResponse { AchievementTaskSetResponse = response.ToArray() });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/SetUserAchievementTask")] // used by World Of Jumpstart
|
||||
[VikingSession(UseLock=true)]
|
||||
public IActionResult SetUserAchievementTaskV1(Viking viking, [FromForm] int taskID, [FromForm] string apiKey) {
|
||||
var response = achievementService.ApplyAchievementRewardsByTask(viking, taskID, ClientVersion.GetVersion(apiKey));
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetUserAchievementTask")]
|
||||
// [VikingSession]
|
||||
public IActionResult GetUserAchievementTask([FromForm] string userId, [FromForm] string apiKey) {
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == Guid.Parse(userId));
|
||||
if (viking is null)
|
||||
return Ok(new ArrayOfUserAchievementTask());
|
||||
return Ok(new ArrayOfUserAchievementTask {
|
||||
UserAchievementTask = achievementService.GetUserAchievementTask(viking, ClientVersion.GetVersion(apiKey))
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/AchievementWebService.asmx/GetUserAchievementTaskRedeemableRewards")]
|
||||
// [VikingSession]
|
||||
public IActionResult GetUserAchievementTaskRedeemableRewards() {
|
||||
return Ok(); // TODO: this should be <UATRRS>
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/ApplyPayout")]
|
||||
[VikingSession]
|
||||
public IActionResult ApplyPayout(Viking viking, string ModuleName, int points) {
|
||||
// TODO: use args (ModuleName and points) to calculate reward
|
||||
var rewards = new AchievementReward[]{
|
||||
achievementService.AddAchievementPoints(viking, AchievementPointTypes.PlayerXP, 10),
|
||||
achievementService.AddAchievementPoints(viking, AchievementPointTypes.GameCurrency, 5),
|
||||
achievementService.AddAchievementPoints(viking, AchievementPointTypes.DragonXP, 6),
|
||||
achievementService.AddAchievementPoints(viking, AchievementPointTypes.UDTPoints, 6),
|
||||
};
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(rewards);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/SetAchievementByEntityIDs")]
|
||||
[VikingSession(UseLock=true)]
|
||||
public IActionResult SetAchievementByEntityIDs(Viking viking, [FromForm] int achievementID, [FromForm] string petIDs) {
|
||||
Guid[] petGuids = XmlUtil.DeserializeXml<Guid[]>(petIDs);
|
||||
|
||||
var rewards = achievementService.ApplyAchievementRewardsByID(viking, achievementID, petGuids);
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(rewards);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/AchievementWebService.asmx/GetTopAchievementPointUsers")]
|
||||
[VikingSession]
|
||||
public IActionResult GetTopAchievementPointUsers(string request) {
|
||||
UserAchievementInfoRequest infoRequest = XmlUtil.DeserializeXml<UserAchievementInfoRequest>(request);
|
||||
return Ok(achievementService.GetTopAchievementUsers(infoRequest));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AchievementWebService.asmx/GetTopAchievementPointBuddiesByType")] // Used by Math Blaster
|
||||
[VikingSession]
|
||||
public IActionResult GetTopAchievementPointBuddiesByType([FromForm] string userId, [FromForm] int pointTypeID) {
|
||||
UserAchievementInfoRequest infoRequest = new UserAchievementInfoRequest {
|
||||
UserID = new Guid(userId),
|
||||
PointTypeID = pointTypeID
|
||||
};
|
||||
return Ok(achievementService.GetTopAchievementBuddies(infoRequest));
|
||||
}
|
||||
}
|
||||
57
src/Controllers/Common/AnalyticsController.cs
Normal file
57
src/Controllers/Common/AnalyticsController.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Model;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common
|
||||
{
|
||||
public class AnalyticsController : Controller
|
||||
{
|
||||
private readonly DBContext ctx;
|
||||
public AnalyticsController(DBContext ctx)
|
||||
{
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("AnalyticsWebService.asmx/GetUserAndVikingCount")]
|
||||
public IActionResult GetUserAndVikingCount(bool vikingsOnly)
|
||||
{
|
||||
if (vikingsOnly) return Ok(new UserStatistics
|
||||
{
|
||||
VikingCount = ctx.Vikings.Count(e => e.Id > 0)
|
||||
});
|
||||
else return Ok(new UserStatistics
|
||||
{
|
||||
VikingCount = ctx.Vikings.Count(),
|
||||
UserCount = ctx.Users.Count(),
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AnalyticsWebService.asmx/LogEvent")]
|
||||
public IActionResult LogEvent([FromForm] string eventXml)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
Schema.LogEvent? eventClass = XmlUtil.DeserializeXml<Schema.LogEvent>(eventXml);
|
||||
string eventToLog = $"---\nEvent Logged At {now.ToLocalTime()}\nUserID - {eventClass.UserID}\nEvent Category ID - {eventClass.EventCategoryID}\nEvent Type ID - {eventClass.EventTypeID}\nEvent Data - {eventClass.Data}\n---";
|
||||
|
||||
Console.WriteLine(eventToLog);
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
[Route("ping")]
|
||||
[Route("ContentWebService.asmx")] // ping URL used by SoD
|
||||
public IActionResult Ping()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public class UserStatistics
|
||||
{
|
||||
public int VikingCount { get; set; }
|
||||
public int? UserCount { get; set; }
|
||||
}
|
||||
}
|
||||
256
src/Controllers/Common/AuthenticationController.cs
Normal file
256
src/Controllers/Common/AuthenticationController.cs
Normal file
@ -0,0 +1,256 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Util;
|
||||
using sodoff.Configuration;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
|
||||
[ApiController]
|
||||
public class AuthenticationController : Controller {
|
||||
|
||||
private readonly DBContext ctx;
|
||||
private readonly IOptions<ApiServerConfig> config;
|
||||
|
||||
public AuthenticationController(DBContext ctx, IOptions<ApiServerConfig> config) {
|
||||
this.ctx = ctx;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("v3/AuthenticationWebService.asmx/GetRules")]
|
||||
[EncryptResponse]
|
||||
public IActionResult GetRules() {
|
||||
GetProductRulesResponse response = new GetProductRulesResponse {
|
||||
GlobalSecretKey = "11A0CC5A-C4DF-4A0E-931C-09A44C9966AE"
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("v3/AuthenticationWebService.asmx/LoginParent")]
|
||||
[DecryptRequest("parentLoginData")]
|
||||
[EncryptResponse]
|
||||
public IActionResult LoginParent([FromForm] string apiKey) {
|
||||
ParentLoginData data = XmlUtil.DeserializeXml<ParentLoginData>(Request.Form["parentLoginData"]);
|
||||
|
||||
// Authenticate the user
|
||||
User? user = null;
|
||||
uint gameVersion = ClientVersion.GetVersion(apiKey);
|
||||
if (gameVersion <= ClientVersion.Max_OldJS) {
|
||||
user = ctx.Users.FirstOrDefault(e => e.Email == data.UserName);
|
||||
} else {
|
||||
user = ctx.Users.FirstOrDefault(e => e.Username == data.UserName);
|
||||
}
|
||||
|
||||
if (user is null) {
|
||||
return Ok(new ParentLoginInfo { Status = MembershipUserStatus.InvalidUserName });
|
||||
}
|
||||
|
||||
PasswordVerificationResult result = new PasswordHasher<object>().VerifyHashedPassword(null, user.Password, data.Password);
|
||||
if (result == PasswordVerificationResult.Failed) {
|
||||
return Ok(new ParentLoginInfo { Status = MembershipUserStatus.InvalidPassword });
|
||||
}
|
||||
|
||||
if (result == PasswordVerificationResult.SuccessRehashNeeded) {
|
||||
user.Password = new PasswordHasher<object>().HashPassword(null, data.Password);
|
||||
}
|
||||
|
||||
// Create session
|
||||
Session session = new Session {
|
||||
User = user,
|
||||
ApiToken = Guid.NewGuid(),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
ctx.Sessions.Add(session);
|
||||
ctx.SaveChanges();
|
||||
|
||||
var childList = new List<sodoff.Schema.UserLoginInfo>();
|
||||
foreach (var viking in user.Vikings) {
|
||||
childList.Add(new sodoff.Schema.UserLoginInfo{UserName = viking.Name, UserID = viking.Uid.ToString()});
|
||||
}
|
||||
|
||||
var response = new ParentLoginInfo {
|
||||
UserName = user.Username,
|
||||
//Email = user.Email, /* disabled to avoid put email in client debug logs */
|
||||
ApiToken = session.ApiToken.ToString(),
|
||||
UserID = user.Id.ToString(),
|
||||
Status = MembershipUserStatus.Success,
|
||||
SendActivationReminder = false,
|
||||
UnAuthorized = false,
|
||||
ChildList = childList.ToArray()
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("v3/AuthenticationWebService.asmx/AuthenticateUser")]
|
||||
[DecryptRequest("username")]
|
||||
[DecryptRequest("password")]
|
||||
public bool AuthenticateUser([FromForm] string apiKey) {
|
||||
String username = Request.Form["username"];
|
||||
String password = Request.Form["password"];
|
||||
|
||||
// Authenticate the user
|
||||
User? user = (ClientVersion.GetVersion(apiKey) <= ClientVersion.Max_OldJS)
|
||||
? ctx.Users.FirstOrDefault(e => e.Email == username)
|
||||
: ctx.Users.FirstOrDefault(e => e.Username == username);
|
||||
if (user is null || new PasswordHasher<object>().VerifyHashedPassword(null, user.Password, password) != PasswordVerificationResult.Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AuthenticationWebService.asmx/GetUserInfoByApiToken")]
|
||||
public IActionResult GetUserInfoByApiToken([FromForm] Guid apiToken, [FromForm] string apiKey) {
|
||||
// First check if this is a user session
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.User;
|
||||
if (user is not null) {
|
||||
return Ok(new UserInfo {
|
||||
UserID = user.Id.ToString(),
|
||||
Username = user.Username,
|
||||
MembershipID = "ef84db9-59c6-4950-b8ea-bbc1521f899b", // placeholder
|
||||
FacebookUserID = 0,
|
||||
MultiplayerEnabled = ClientVersion.GetVersion(apiKey) >= config.Value.MMOSupportMinVersion,
|
||||
IsApproved = true,
|
||||
Age = 24,
|
||||
OpenChatEnabled = true
|
||||
});
|
||||
}
|
||||
|
||||
// Then check if this is a viking session
|
||||
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking;
|
||||
if (viking is not null)
|
||||
{
|
||||
return Ok(new UserInfo {
|
||||
UserID = viking.Uid.ToString(),
|
||||
Username = viking.Name,
|
||||
FacebookUserID = 0,
|
||||
MultiplayerEnabled = ClientVersion.GetVersion(apiKey) >= config.Value.MMOSupportMinVersion,
|
||||
IsApproved = true,
|
||||
Age = 24,
|
||||
OpenChatEnabled = true
|
||||
});
|
||||
}
|
||||
|
||||
// Otherwise, this is a bad session, return empty UserInfo
|
||||
return Ok(new UserInfo {});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AuthenticationWebService.asmx/IsValidApiToken")] // used by World Of Jumpstart (FutureLand)
|
||||
public IActionResult IsValidApiToken_V1([FromForm] Guid? apiToken) {
|
||||
if (apiToken is null)
|
||||
return Ok(false);
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.User;
|
||||
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking;
|
||||
if (user is null && viking is null)
|
||||
return Ok(false);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AuthenticationWebService.asmx/IsValidApiToken_V2")]
|
||||
public IActionResult IsValidApiToken([FromForm] Guid? apiToken) {
|
||||
if (apiToken is null)
|
||||
return Ok(ApiTokenStatus.TokenNotFound);
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.User;
|
||||
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.Viking;
|
||||
if (user is null && viking is null)
|
||||
return Ok(ApiTokenStatus.TokenNotFound);
|
||||
return Ok(ApiTokenStatus.TokenValid);
|
||||
}
|
||||
|
||||
// This is more of a "create session for viking", rather than "login child"
|
||||
[Route("AuthenticationWebService.asmx/LoginChild")]
|
||||
[DecryptRequest("childUserID")]
|
||||
[EncryptResponse]
|
||||
public IActionResult LoginChild([FromForm] Guid parentApiToken, [FromForm] string apiKey) {
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == parentApiToken)?.User;
|
||||
if (user is null) {
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Find the viking
|
||||
string? childUserID = Request.Form["childUserID"];
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == Guid.Parse(childUserID));
|
||||
if (viking is null) {
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
uint gameVersion = ClientVersion.GetVersion(apiKey);
|
||||
if (viking.GameVersion is null)
|
||||
viking.GameVersion = gameVersion;
|
||||
if (
|
||||
(viking.GameVersion != gameVersion) &&
|
||||
!(viking.GameVersion >= ClientVersion.Min_SoD && gameVersion >= ClientVersion.Min_SoD) &&
|
||||
!(viking.GameVersion >= ClientVersion.WoJS && gameVersion >= ClientVersion.WoJS && viking.GameVersion < ClientVersion.WoJS_NewAvatar && gameVersion < ClientVersion.WoJS_NewAvatar)
|
||||
)
|
||||
return Unauthorized();
|
||||
// do not let players log into users from other games, exceptions:
|
||||
// 1) different version of SoD
|
||||
// 2) WoJS with old avatar and lands
|
||||
|
||||
// Check if user is viking parent
|
||||
if (user != viking.User) {
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
// Create session
|
||||
Session session = new Session {
|
||||
Viking = viking,
|
||||
ApiToken = Guid.NewGuid(),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
ctx.Sessions.Add(session);
|
||||
ctx.SaveChanges();
|
||||
|
||||
// Return back the api token
|
||||
return Ok(session.ApiToken.ToString());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("AuthenticationWebService.asmx/DeleteAccountNotification")]
|
||||
public IActionResult DeleteAccountNotification([FromForm] Guid apiToken) {
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.User;
|
||||
if (user is null)
|
||||
return Ok(MembershipUserStatus.ValidationError);
|
||||
|
||||
ctx.Users.Remove(user);
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(MembershipUserStatus.Success);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("Authentication/MMOAuthentication")]
|
||||
public IActionResult MMOAuthentication([FromForm] Guid token) {
|
||||
AuthenticationInfo info = new();
|
||||
info.Authenticated = false;
|
||||
var session = ctx.Sessions.FirstOrDefault(x => x.ApiToken == token);
|
||||
if (session != null) {
|
||||
info.Authenticated = true;
|
||||
info.DisplayName = session.Viking.Name;
|
||||
Role? role = session.Viking.MMORoles.FirstOrDefault()?.Role;
|
||||
if (role != null)
|
||||
info.Role = (Role)role;
|
||||
}
|
||||
return Ok(info);
|
||||
}
|
||||
}
|
||||
14
src/Controllers/Common/ChallengeController.cs
Normal file
14
src/Controllers/Common/ChallengeController.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Schema;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class ChallengeController : Controller {
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ChallengeWebService.asmx/GetActiveChallenges")]
|
||||
public IActionResult GetActiveChallenges() {
|
||||
// TODO: this is a placeholder
|
||||
return Ok(new ChallengeInfo[0]);
|
||||
}
|
||||
}
|
||||
53
src/Controllers/Common/ConfigurationController.cs
Normal file
53
src/Controllers/Common/ConfigurationController.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
|
||||
public class ConfigurationController : Controller {
|
||||
private MMOConfigService mmoConfigService;
|
||||
|
||||
public ConfigurationController(MMOConfigService mmoConfigService) {
|
||||
this.mmoConfigService = mmoConfigService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ConfigurationWebService.asmx/GetMMOServerInfoWithZone")]
|
||||
[Route("ConfigurationWebService.asmx/GetMMOServerInfo")] // used by SuperSecret
|
||||
public IActionResult GetMMOServerInfoWithZone([FromForm] string apiKey) {
|
||||
return Ok(mmoConfigService.GetMMOServerInformation(
|
||||
ClientVersion.GetVersion(apiKey.ToLower()) // NOTE: in this request apiKey is send uppercase
|
||||
));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ConfigurationWebService.asmx/GetContentByTypeByUser")] // used by World Of Jumpstart
|
||||
public IActionResult GetContentByTypeByUser([FromForm] int contentType)
|
||||
{
|
||||
if (contentType == 1) return Ok(new ContentInfo
|
||||
{
|
||||
ContentInfoArray = XmlUtil.DeserializeXml<ContentInfoData[]>(XmlUtil.ReadResourceXmlString("content_jukebox"))
|
||||
});
|
||||
if (contentType == 2) return Ok(new ContentInfo
|
||||
{
|
||||
ContentInfoArray = XmlUtil.DeserializeXml<ContentInfoData[]>(XmlUtil.ReadResourceXmlString("content_movie"))
|
||||
});
|
||||
if (contentType == 3) return Ok(new ContentInfo
|
||||
{
|
||||
ContentInfoArray = XmlUtil.DeserializeXml<ContentInfoData[]>(XmlUtil.ReadResourceXmlString("content_arcade"))
|
||||
});
|
||||
if (contentType == 4) return Ok(new ContentInfo
|
||||
{
|
||||
ContentInfoArray = XmlUtil.DeserializeXml<ContentInfoData[]>(XmlUtil.ReadResourceXmlString("content_learning"))
|
||||
});
|
||||
if (contentType == 5) return Ok(new ContentInfo
|
||||
{
|
||||
ContentInfoArray =XmlUtil.DeserializeXml<ContentInfoData[]>(XmlUtil.ReadResourceXmlString("content_blastermovie"))
|
||||
});
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
2659
src/Controllers/Common/ContentController.cs
Normal file
2659
src/Controllers/Common/ContentController.cs
Normal file
File diff suppressed because it is too large
Load Diff
565
src/Controllers/Common/GroupController.cs
Normal file
565
src/Controllers/Common/GroupController.cs
Normal file
@ -0,0 +1,565 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Util;
|
||||
using GroupMember = sodoff.Model.GroupMember;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
|
||||
public class GroupController : Controller {
|
||||
// Any permission that is commented out is not implemented.
|
||||
private static readonly List<string> PermissionsMember = [
|
||||
//"Delete Own Msg",
|
||||
//"Post Message"
|
||||
];
|
||||
private static readonly List<string> PermissionsElder = [
|
||||
//"Invite",
|
||||
"Approve Join Request",
|
||||
//"Post News",
|
||||
//"Delete Own Msg",
|
||||
//"Delete Any Msg",
|
||||
//"Delete News",
|
||||
//"Post Message",
|
||||
"Remove Member"
|
||||
];
|
||||
private static readonly List<string> PermissionsLeader = [
|
||||
//"Invite",
|
||||
"Approve Join Request",
|
||||
"Assign Leader",
|
||||
"Assign Elder",
|
||||
"Demote Elder",
|
||||
"Edit Group",
|
||||
//"Post News",
|
||||
//"Delete Own Msg",
|
||||
//"Delete Any Msg",
|
||||
//"Delete News",
|
||||
//"Post Message",
|
||||
"Remove Member"
|
||||
];
|
||||
|
||||
private static readonly List<RolePermission> RolePermissions = [
|
||||
new RolePermission {
|
||||
GroupType = GroupType.Public,
|
||||
Role = UserRole.Member,
|
||||
Permissions = PermissionsMember
|
||||
}, new RolePermission {
|
||||
GroupType = GroupType.Public,
|
||||
Role = UserRole.Elder,
|
||||
Permissions = PermissionsElder
|
||||
}, new RolePermission {
|
||||
GroupType = GroupType.Public,
|
||||
Role = UserRole.Leader,
|
||||
Permissions = PermissionsLeader
|
||||
},
|
||||
|
||||
new RolePermission {
|
||||
GroupType = GroupType.MembersOnly,
|
||||
Role = UserRole.Member,
|
||||
Permissions = PermissionsMember
|
||||
}, new RolePermission {
|
||||
GroupType = GroupType.MembersOnly,
|
||||
Role = UserRole.Elder,
|
||||
Permissions = PermissionsElder
|
||||
}, new RolePermission {
|
||||
GroupType = GroupType.MembersOnly,
|
||||
Role = UserRole.Leader,
|
||||
Permissions = PermissionsLeader
|
||||
},
|
||||
|
||||
new RolePermission {
|
||||
GroupType = GroupType.Private,
|
||||
Role = UserRole.Member,
|
||||
Permissions = PermissionsMember
|
||||
}, new RolePermission {
|
||||
GroupType = GroupType.Private,
|
||||
Role = UserRole.Elder,
|
||||
Permissions = PermissionsElder
|
||||
}, new RolePermission {
|
||||
GroupType = GroupType.Private,
|
||||
Role = UserRole.Leader,
|
||||
Permissions = PermissionsLeader
|
||||
}
|
||||
];
|
||||
|
||||
private readonly DBContext ctx;
|
||||
|
||||
public GroupController(DBContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/CreateGroup")]
|
||||
[VikingSession]
|
||||
public IActionResult CreateGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupCreateRequest) {
|
||||
uint gameId = ClientVersion.GetGameID(apiKey);
|
||||
|
||||
if (viking.GroupMembership != null)
|
||||
return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.CreatorIsNotApproved });
|
||||
|
||||
CreateGroupRequest request = XmlUtil.DeserializeXml<CreateGroupRequest>(groupCreateRequest);
|
||||
request.Name = request.Name.Trim();
|
||||
|
||||
// Cue the gauntlet of validity checks.
|
||||
if (request.Type <= GroupType.System)
|
||||
return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupTypeIsInvalid });
|
||||
//if (request.MaxMemberLimit < 4) return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupMaxMemberLimitInvalid }); // Not actually used by the game.
|
||||
if (request.Name.Length == 0)
|
||||
return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupNameIsEmpty });
|
||||
if (request.Description.Length == 0)
|
||||
return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupDescriptionIsEmpty });
|
||||
if (ctx.Groups.Any(g => g.Name.ToLower() == request.Name.ToLower()))
|
||||
return Ok(new CreateGroupResult { Success = false, Status = CreateGroupStatus.GroupNameIsDuplicate });
|
||||
|
||||
Model.Group group = new Model.Group {
|
||||
Name = request.Name,
|
||||
Description = request.Description,
|
||||
Logo = request.Logo,
|
||||
Color = request.Color,
|
||||
Type = request.Type,
|
||||
CreateDate = DateTime.UtcNow,
|
||||
GameID = gameId,
|
||||
MaxMemberLimit = request.MaxMemberLimit ?? 50,
|
||||
GroupID = Guid.NewGuid(),
|
||||
Vikings = new List<GroupMember>()
|
||||
};
|
||||
|
||||
ctx.Groups.Add(group);
|
||||
group.Vikings.Add(new GroupMember {
|
||||
Viking = viking,
|
||||
Group = group,
|
||||
UserRole = UserRole.Leader,
|
||||
JoinDate = group.CreateDate
|
||||
});
|
||||
group.LastActiveTime = group.CreateDate;
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(new CreateGroupResult {
|
||||
Success = true,
|
||||
Status = CreateGroupStatus.Success,
|
||||
Group = new Schema.Group {
|
||||
Name = group.Name,
|
||||
Description = group.Description,
|
||||
Logo = group.Logo,
|
||||
Color = group.Color,
|
||||
Type = group.Type,
|
||||
OwnerID = viking.Uid.ToString(),
|
||||
Points = 0,//group.Points,
|
||||
Active = false,//group.Vikings.Count >= 4,
|
||||
MemberLimit = group.MaxMemberLimit,
|
||||
GroupID = group.GroupID.ToString()
|
||||
}
|
||||
// TODO: Delete after 15 days of less than 4 members.
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/EditGroup")]
|
||||
[VikingSession]
|
||||
public IActionResult EditGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupEditRequest) {
|
||||
EditGroupRequest request = XmlUtil.DeserializeXml<EditGroupRequest>(groupEditRequest);
|
||||
request.Name = request.Name.Trim();
|
||||
|
||||
GroupMember? vikingRole = viking.GroupMembership;
|
||||
if (vikingRole == null || vikingRole.Group.GroupID.ToString() != request.GroupID)
|
||||
return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupNotFound });
|
||||
if (vikingRole.UserRole < UserRole.Elder)
|
||||
return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.PermissionDenied });
|
||||
|
||||
// Cue the gauntlet of validity checks.
|
||||
if (request.Type != null && request.Type <= GroupType.System)
|
||||
return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupTypeIsInvalid });
|
||||
//if (request.MaxMemberLimit < 4) return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupMaxMemberLimitInvalid }); // Not actually used by the game.
|
||||
if (string.IsNullOrEmpty(request.Name))
|
||||
request.Name = vikingRole.Group.Name;
|
||||
if (string.IsNullOrEmpty(request.Description))
|
||||
request.Description = vikingRole.Group.Description;
|
||||
if (request.Name != vikingRole.Group.Name && ctx.Groups.Any(g => g.Name.ToLower() == request.Name.ToLower()))
|
||||
return Ok(new EditGroupResult { Success = false, Status = EditGroupStatus.GroupNameIsDuplicate });
|
||||
|
||||
vikingRole.Group.Name = request.Name;
|
||||
vikingRole.Group.Description = request.Description;
|
||||
if (request.Type != null)
|
||||
vikingRole.Group.Type = (GroupType)request.Type;
|
||||
if (!string.IsNullOrEmpty(request.Color))
|
||||
vikingRole.Group.Color = request.Color;
|
||||
if (!string.IsNullOrEmpty(request.Logo))
|
||||
vikingRole.Group.Logo = request.Logo;
|
||||
if (request.MaxMemberLimit != null)
|
||||
vikingRole.Group.MaxMemberLimit = (int)request.MaxMemberLimit;
|
||||
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(new EditGroupResult {
|
||||
Success = true,
|
||||
Status = EditGroupStatus.Success,
|
||||
NewRolePermissions = RolePermissions
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("GroupWebService.asmx/JoinGroup")]
|
||||
[VikingSession]
|
||||
public IActionResult JoinGroupV1(Viking viking, [FromForm] string apiKey, [FromForm] string groupID) {
|
||||
Guid parsedGroupID = Guid.Parse(groupID);
|
||||
uint gameId = ClientVersion.GetGameID(apiKey);
|
||||
|
||||
// Check for loyalty.
|
||||
if (viking.GroupMembership != null)
|
||||
return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.SELF_BLOCKED });
|
||||
|
||||
Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID == parsedGroupID);
|
||||
if (group != null) {
|
||||
// This check is only on this side to prevent people from attempting to circumvent the join limit.
|
||||
if (group.Type <= GroupType.System || group.Vikings.Count < group.MaxMemberLimit) {
|
||||
group.Vikings.Add(new GroupMember {
|
||||
Viking = viking,
|
||||
Group = group,
|
||||
UserRole = UserRole.Member
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.APPROVED });
|
||||
}
|
||||
}
|
||||
return Ok(new JoinGroupResult { GroupStatus = GroupMembershipStatus.REJECTED });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/JoinGroup")]
|
||||
[VikingSession]
|
||||
public IActionResult JoinGroup(Viking viking, [FromForm] string apiKey, [FromForm] string groupJoinRequest) {
|
||||
uint gameId = ClientVersion.GetGameID(apiKey);
|
||||
|
||||
JoinGroupRequest request = XmlUtil.DeserializeXml<JoinGroupRequest>(groupJoinRequest);
|
||||
Guid parsedGroupID = Guid.Parse(request.GroupID);
|
||||
Model.Group? group = ctx.Groups.FirstOrDefault(g => g.GroupID == parsedGroupID);
|
||||
if (group == null) return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.Error });
|
||||
if (group.Type >= GroupType.Private) {
|
||||
return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupTypeIsNotPublic });
|
||||
}
|
||||
|
||||
GroupMember? existing = viking.GroupMembership;
|
||||
if (existing != null) {
|
||||
if (existing.Group == group)
|
||||
return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.UserAlreadyMemberOfTheGroup });
|
||||
|
||||
existing.Group.Vikings.Remove(existing);
|
||||
if (!existing.Group.Vikings.Any()) ctx.Groups.Remove(existing.Group);
|
||||
}
|
||||
|
||||
if (group.Type == GroupType.MembersOnly) {
|
||||
if (!group.JoinRequests.Any(r => r.Viking == viking))
|
||||
group.JoinRequests.Add(new GroupJoinRequest {
|
||||
Group = group,
|
||||
Viking = viking,
|
||||
Message = request.Message
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.JoinRequestPending });
|
||||
}
|
||||
|
||||
if (group.Vikings.Count >= group.MaxMemberLimit)
|
||||
return Ok(new GroupJoinResult { Success = false, Status = JoinGroupStatus.GroupIsFull });
|
||||
|
||||
GroupMember joinee = new GroupMember {
|
||||
Viking = viking,
|
||||
Group = group,
|
||||
UserRole = UserRole.Member,
|
||||
JoinDate = DateTime.UtcNow
|
||||
};
|
||||
group.Vikings.Add(joinee);
|
||||
group.LastActiveTime = joinee.JoinDate;
|
||||
ctx.SaveChanges();
|
||||
return Ok(new GroupJoinResult { Success = true, Status = JoinGroupStatus.Success });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/LeaveGroup")]
|
||||
[VikingSession]
|
||||
public IActionResult LeaveGroup(Viking viking, [FromForm] string groupLeaveRequest) {
|
||||
LeaveGroupRequest request = XmlUtil.DeserializeXml<LeaveGroupRequest>(groupLeaveRequest);
|
||||
GroupMember? vikingRole = viking.GroupMembership;
|
||||
if (vikingRole == null || vikingRole.Group.GroupID.ToString() != request.GroupID)
|
||||
return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error });
|
||||
GroupMember? targetRole;
|
||||
if (viking.Uid == Guid.Parse(request.UserID)) {
|
||||
targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking == viking);
|
||||
} else if (vikingRole.UserRole >= UserRole.Elder) {
|
||||
targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid == Guid.Parse(request.UserID));
|
||||
} else return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.Error });
|
||||
|
||||
if (targetRole == null)
|
||||
return Ok(new LeaveGroupResult { Success = false, Status = LeaveGroupStatus.UserNotAMemberOfTheGroup });
|
||||
vikingRole.Group.Vikings.Remove(targetRole);
|
||||
if (!vikingRole.Group.Vikings.Any()) ctx.Groups.Remove(vikingRole.Group);
|
||||
ctx.SaveChanges();
|
||||
return Ok(new LeaveGroupResult { Success = true, Status = LeaveGroupStatus.Success });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/GetGroups")]
|
||||
public IActionResult GetGroups([FromForm] string apiKey, [FromForm] string getGroupsRequest) {
|
||||
uint gameId = ClientVersion.GetGameID(apiKey);
|
||||
GetGroupsRequest request = XmlUtil.DeserializeXml<GetGroupsRequest>(getGroupsRequest);
|
||||
|
||||
IQueryable<Model.Group> groupsQuery = ctx.Groups.Where(g => g.GameID == gameId);
|
||||
if (request.ForUserID != null) {
|
||||
Viking? target = ctx.Vikings.FirstOrDefault(v => Guid.Parse(request.ForUserID) == v.Uid);
|
||||
if (target == null) return Ok(new GetGroupsResult { Success = false });
|
||||
if (target.GroupMembership?.Group == null) return Ok();
|
||||
groupsQuery = groupsQuery.Where(g => g.GroupID == target.GroupMembership.Group.GroupID);
|
||||
}
|
||||
|
||||
if (request.Name != null)
|
||||
groupsQuery = groupsQuery.Where(g => g.Name.ToLower().Contains(request.Name.ToLower()));
|
||||
|
||||
groupsQuery = groupsQuery.OrderByDescending(g => g.Points).ThenBy(g => g.Id);
|
||||
int skip = 0;
|
||||
if (request.PageSize != null) {
|
||||
if ((request.PageNo ?? 0) > 1)
|
||||
skip = (int)((request.PageNo! - 1) * request.PageSize);
|
||||
if (skip > 0)
|
||||
groupsQuery = groupsQuery.Skip(skip);
|
||||
groupsQuery = groupsQuery.Take((int) request.PageSize);
|
||||
}
|
||||
|
||||
if (request.IncludeMemberCount)
|
||||
groupsQuery = groupsQuery.Include(g => g.Vikings);
|
||||
|
||||
var groups = groupsQuery.ToList();
|
||||
return Ok(new GetGroupsResult {
|
||||
Success = true,
|
||||
Groups = groups.Select((g, i) => {
|
||||
Schema.Group group = new Schema.Group {
|
||||
Name = g.Name,
|
||||
Description = g.Description,
|
||||
GroupID = g.GroupID.ToString(),
|
||||
OwnerID = (g.Vikings.FirstOrDefault(v => v.UserRole == UserRole.Leader)?.Viking?.Uid ?? Guid.Empty).ToString(),
|
||||
Color = g.Color,
|
||||
Logo = g.Logo,
|
||||
Type = g.Type,
|
||||
Rank = i+skip+1,
|
||||
Points = g.Points,
|
||||
MemberLimit = g.MaxMemberLimit
|
||||
};
|
||||
if (request.IncludeMemberCount) group.TotalMemberCount = g.Vikings.Count;
|
||||
return group;
|
||||
}).ToArray(),
|
||||
RolePermissions = RolePermissions
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/RemoveMember")]
|
||||
[VikingSession]
|
||||
public IActionResult RemoveMember(Viking viking, [FromForm] string removeMemberRequest) {
|
||||
RemoveMemberRequest request = XmlUtil.DeserializeXml<RemoveMemberRequest>(removeMemberRequest);
|
||||
GroupMember? vikingRole = viking.GroupMembership;
|
||||
if (vikingRole == null || vikingRole.Group.GroupID != Guid.Parse(request.GroupID))
|
||||
return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.Error });
|
||||
|
||||
if (vikingRole.UserRole < UserRole.Elder)
|
||||
return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserHasNoPermission });
|
||||
|
||||
GroupMember? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid == Guid.Parse(request.RemoveUserID));
|
||||
if (targetRole == null)
|
||||
return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.UserNotAMemberOfTheGroup });
|
||||
|
||||
if (targetRole.UserRole >= vikingRole.UserRole)
|
||||
return Ok(new RemoveMemberResult { Success = false, Status = RemoveMemberStatus.InvalidParameters });
|
||||
|
||||
vikingRole.Group.Vikings.Remove(targetRole);
|
||||
if (!vikingRole.Group.Vikings.Any())
|
||||
ctx.Groups.Remove(vikingRole.Group);
|
||||
ctx.SaveChanges();
|
||||
return Ok(new RemoveMemberResult { Success = true, Status = RemoveMemberStatus.Success });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/AuthorizeJoinRequest")]
|
||||
[VikingSession]
|
||||
public IActionResult AuthorizeJoinRequest(Viking viking, [FromForm] string apiKey, [FromForm] string authorizeJoinRequest) {
|
||||
uint gameId = ClientVersion.GetGameID(apiKey);
|
||||
|
||||
AuthorizeJoinRequest request = XmlUtil.DeserializeXml<AuthorizeJoinRequest>(authorizeJoinRequest);
|
||||
GroupMember? vikingRole = viking.GroupMembership;
|
||||
if (vikingRole == null || vikingRole.Group.GroupID != Guid.Parse(request.GroupID))
|
||||
return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverNotInThisGroup });
|
||||
|
||||
if (vikingRole.UserRole < UserRole.Elder)
|
||||
return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.ApproverHasNoPermission });
|
||||
|
||||
Viking? target = ctx.Vikings.FirstOrDefault(v => v.Uid == Guid.Parse(request.JoineeID));
|
||||
if (target == null)
|
||||
return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.Error });
|
||||
|
||||
GroupMember? existing = target.GroupMembership;
|
||||
if (existing != null) {
|
||||
return Ok(new AuthorizeJoinResult {
|
||||
Success = false,
|
||||
Status = existing.Group == vikingRole.Group
|
||||
? AuthorizeJoinStatus.UserAlreadyMemberOfTheGroup
|
||||
: AuthorizeJoinStatus.UserHasNoJoinRequest
|
||||
});
|
||||
}
|
||||
|
||||
if (vikingRole.Group.Vikings.Count >= vikingRole.Group.MaxMemberLimit)
|
||||
return Ok(new AuthorizeJoinResult { Success = false, Status = AuthorizeJoinStatus.GroupIsFull });
|
||||
|
||||
if (request.Approved) {
|
||||
GroupMember joinee = new GroupMember {
|
||||
Viking = target,
|
||||
Group = vikingRole.Group,
|
||||
UserRole = UserRole.Member,
|
||||
JoinDate = DateTime.UtcNow
|
||||
};
|
||||
vikingRole.Group.Vikings.Add(joinee);
|
||||
vikingRole.Group.LastActiveTime = joinee.JoinDate;
|
||||
}
|
||||
|
||||
GroupJoinRequest? joinRequest = ctx.GroupJoinRequests.Find(target.Id, vikingRole.GroupID);
|
||||
if (joinRequest != null)
|
||||
ctx.GroupJoinRequests.Remove(joinRequest);
|
||||
ctx.SaveChanges();
|
||||
return Ok(new AuthorizeJoinResult { Success = true, Status = AuthorizeJoinStatus.Success });
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/AssignRole")]
|
||||
[VikingSession]
|
||||
public IActionResult AssignRole(Viking viking, [FromForm] string assignRoleRequest) {
|
||||
AssignRoleRequest request = XmlUtil.DeserializeXml<AssignRoleRequest>(assignRoleRequest);
|
||||
GroupMember? vikingRole = viking.GroupMembership;
|
||||
if (vikingRole == null || vikingRole.Group.GroupID != Guid.Parse(request.GroupID))
|
||||
return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverNotMemberOfTheGroup });
|
||||
|
||||
if (vikingRole.UserRole < UserRole.Elder)
|
||||
return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission });
|
||||
|
||||
GroupMember? targetRole = vikingRole.Group.Vikings.FirstOrDefault(gv => gv.Viking.Uid == Guid.Parse(request.MemberID));
|
||||
if (targetRole == null)
|
||||
return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.MemberNotPartOfTheGroup });
|
||||
|
||||
if (targetRole.UserRole == request.NewRole)
|
||||
return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.MemberAlreadyInTheRole });
|
||||
|
||||
if (vikingRole.UserRole == UserRole.Leader) {
|
||||
// Disallow leader from simply demoting themself.
|
||||
if (viking == targetRole.Viking)
|
||||
return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission });
|
||||
} else if (viking != targetRole.Viking || request.NewRole > vikingRole.UserRole) {
|
||||
// Disallow Elders from promoting themselves to leader, or promoting anyone else to elder, but allow them to demote themselves.
|
||||
return Ok(new AssignRoleResult { Success = false, Status = AssignRoleStatus.ApproverHasNoPermission });
|
||||
}
|
||||
|
||||
targetRole.UserRole = request.NewRole;
|
||||
if (request.NewRole == UserRole.Leader)
|
||||
vikingRole.UserRole = UserRole.Elder; // This is the only way a leader can demote themself.
|
||||
|
||||
ctx.SaveChanges();
|
||||
return Ok(new AssignRoleResult { Success = true, Status = AssignRoleStatus.Success });
|
||||
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/GroupWebService.asmx/GetPendingJoinRequest")]
|
||||
[VikingSession]
|
||||
public IActionResult GetPendingJoinRequests(Viking viking, [FromForm] string getPendingJoinRequest) {
|
||||
GetPendingJoinRequest request = XmlUtil.DeserializeXml<GetPendingJoinRequest>(getPendingJoinRequest);
|
||||
GroupMember? vikingRole = viking.GroupMembership;
|
||||
if (vikingRole?.Group.GroupID == Guid.Parse(request.GroupID) && vikingRole?.UserRole >= UserRole.Elder) {
|
||||
return Ok(new GetPendingJoinResult {
|
||||
Success = true,
|
||||
Requests = vikingRole.Group.JoinRequests
|
||||
.Select(r => {
|
||||
PendingJoinRequest req = new PendingJoinRequest {
|
||||
UserID = r.Viking.Uid.ToString(),
|
||||
GroupID = vikingRole.Group.GroupID.ToString(),
|
||||
StatusID = GroupJoinRequestStatus.Pending,
|
||||
Message = r.Message ?? "Hello! Please invite me to your Clan!"
|
||||
};
|
||||
req.FromUserID = req.UserID;
|
||||
return req;
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
return Ok(new GetPendingJoinResult { Success = false });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("GroupWebService.asmx/GetGroupsByUserID")]
|
||||
public Schema.Group[] GetGroupsByUserID([FromForm] string apiKey, [FromForm] Guid userId) {
|
||||
uint gameId = ClientVersion.GetGameID(apiKey);
|
||||
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(v => v.Uid == userId);
|
||||
if (viking == null) return [];
|
||||
|
||||
Model.Group? group = viking.GroupMembership.Group;
|
||||
return [
|
||||
new Schema.Group {
|
||||
GroupID = group.GroupID.ToString(),
|
||||
Name = group.Name,
|
||||
Color = group.Color,
|
||||
Logo = group.Logo,
|
||||
MemberLimit = group.MaxMemberLimit
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("GroupWebService.asmx/GetGroupsByGroupType")]
|
||||
public Schema.Group[] GetGroupsByGroupType([FromForm] string apiKey, [FromForm] string groupType) {
|
||||
uint gameId = ClientVersion.GetGameID(apiKey);
|
||||
|
||||
List<Schema.Group> groups = new List<Schema.Group>();
|
||||
foreach (Model.Group group in ctx.Groups) {
|
||||
if (group.GameID == gameId && group.Type.ToString() == groupType) groups.Add(new Schema.Group {
|
||||
GroupID = group.GroupID.ToString(),
|
||||
Name = group.Name,
|
||||
Color = group.Color,
|
||||
Logo = group.Logo,
|
||||
Type = group.Type,
|
||||
MemberLimit = group.MaxMemberLimit
|
||||
});
|
||||
}
|
||||
return groups.ToArray();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("GroupWebService.asmx/GetMembersByGroupID")]
|
||||
public Schema.GroupMember[] GetMembersByGroupID([FromForm] string groupID) {
|
||||
Guid parsedGroupID = Guid.Parse(groupID);
|
||||
Model.Group? group = ctx.Groups.FirstOrDefault(
|
||||
g => g.GroupID == parsedGroupID
|
||||
);
|
||||
if (group == null) return [];
|
||||
|
||||
return group.Vikings.Select(v => new Schema.GroupMember {
|
||||
DisplayName = group.GameID == ClientVersion.MB && v.Viking.AvatarSerialized != null
|
||||
? XmlUtil.DeserializeXml<AvatarData>(v.Viking.AvatarSerialized).DisplayName
|
||||
: v.Viking.Name,
|
||||
UserID = v.Viking.Uid.ToString(),
|
||||
JoinDate = v.JoinDate,
|
||||
RoleID = (int) v.UserRole,
|
||||
Online = false, // There's no way to check this.
|
||||
Rank = 0,
|
||||
GroupID = group.GroupID.ToString(),
|
||||
Points = group.Points
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
34
src/Controllers/Common/ImageController.cs
Normal file
34
src/Controllers/Common/ImageController.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
using sodoff.Util;
|
||||
using System;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class ImageController : Controller {
|
||||
|
||||
private readonly DBContext ctx;
|
||||
private KeyValueService keyValueService;
|
||||
public ImageController(DBContext ctx, KeyValueService keyValueService) {
|
||||
this.ctx = ctx;
|
||||
this.keyValueService = keyValueService;
|
||||
}
|
||||
|
||||
// SetImage and GetImage are defined in ContentController
|
||||
|
||||
[HttpGet]
|
||||
[Route("RawImage/{VikingId}/{ImageType}/{ImageSlot}.jpg")]
|
||||
public IActionResult RawImage(string VikingId, string ImageType, int ImageSlot) {
|
||||
Image? image = ctx.Images.FirstOrDefault(e => e.Viking.Uid == Guid.Parse(VikingId) && e.ImageType == ImageType && e.ImageSlot == ImageSlot);
|
||||
if (image is null || image.ImageData is null) {
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
byte[] imageBytes = Convert.FromBase64String(image.ImageData);
|
||||
var imageStream = new MemoryStream(imageBytes, 0, imageBytes.Length);
|
||||
return File(imageStream, "image/jpeg");
|
||||
}
|
||||
}
|
||||
203
src/Controllers/Common/ImportExportController.cs
Normal file
203
src/Controllers/Common/ImportExportController.cs
Normal file
@ -0,0 +1,203 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Encodings.Web;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class ExportController : ControllerBase {
|
||||
private readonly DBContext ctx;
|
||||
|
||||
public ExportController(DBContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Export")]
|
||||
public IActionResult Export([FromForm] string username, [FromForm] string password) {
|
||||
// Authenticate user by Username
|
||||
User? user = ctx.Users.FirstOrDefault(e => e.Username == username);
|
||||
if (user is null || new PasswordHasher<object>().VerifyHashedPassword(null, user.Password, password) == PasswordVerificationResult.Failed) {
|
||||
return Unauthorized("Invalid username or password.");
|
||||
}
|
||||
|
||||
// Serialize to JSON
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
WriteIndented = true
|
||||
};
|
||||
string jsonData = JsonSerializer.Serialize(user, options);
|
||||
|
||||
return Ok(jsonData);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Import")]
|
||||
public IActionResult Import([FromForm] string username, [FromForm] string password, [FromForm] string vikingName, [FromForm] IFormFile dataFile, [FromForm] string? newVikingName) {
|
||||
User? user = ctx.Users.FirstOrDefault(e => e.Username == username);
|
||||
if (user is null || new PasswordHasher<object>().VerifyHashedPassword(null, user.Password, password) == PasswordVerificationResult.Failed) {
|
||||
return Unauthorized("Invalid username or password.");
|
||||
}
|
||||
|
||||
User user_data;
|
||||
using (var reader = new StreamReader(dataFile.OpenReadStream())) {
|
||||
user_data = System.Text.Json.JsonSerializer.Deserialize<User>(reader.ReadToEnd());
|
||||
}
|
||||
|
||||
foreach (var v in user_data.Vikings) {
|
||||
if (v.Name == vikingName) {
|
||||
if (String.IsNullOrEmpty(newVikingName))
|
||||
newVikingName = vikingName;
|
||||
|
||||
if (ctx.Vikings.Count(e => e.Name == newVikingName) > 0) {
|
||||
return Conflict("Viking name already in use");
|
||||
}
|
||||
|
||||
if (newVikingName != vikingName) {
|
||||
AvatarData avatarData = XmlUtil.DeserializeXml<AvatarData>(v.AvatarSerialized);
|
||||
avatarData.DisplayName = newVikingName;
|
||||
v.AvatarSerialized = XmlUtil.SerializeXml(avatarData);
|
||||
}
|
||||
|
||||
Viking viking = new Viking {
|
||||
Uid = Guid.NewGuid(),
|
||||
Name = newVikingName,
|
||||
User = user,
|
||||
AvatarSerialized = v.AvatarSerialized,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
BirthDate = v.BirthDate,
|
||||
Gender = v.Gender,
|
||||
GameVersion = v.GameVersion
|
||||
};
|
||||
user.Vikings.Add(viking);
|
||||
|
||||
Dictionary<int, Guid> dragonIds = new();
|
||||
foreach (var x in v.Dragons) {
|
||||
x.Viking = viking;
|
||||
x.EntityId = Guid.NewGuid();
|
||||
dragonIds.Add(x.Id, x.EntityId);
|
||||
x.Id = 0;
|
||||
ctx.Dragons.Add(x);
|
||||
}
|
||||
Dictionary<int, int> itemIds = new();
|
||||
foreach (var x in v.InventoryItems) {
|
||||
itemIds.Add(x.Id, x.ItemId);
|
||||
x.Id = 0;
|
||||
x.Viking = viking;
|
||||
ctx.InventoryItems.Add(x);
|
||||
}
|
||||
|
||||
ctx.SaveChanges(); // need for get new ids of dragons and items
|
||||
|
||||
HashSet<int> usedItemIds = new();
|
||||
foreach (var x in v.Rooms) {
|
||||
x.Viking = viking;
|
||||
if (int.TryParse(x.RoomId, out int roomID)) {
|
||||
// numeric room name is inventory item id
|
||||
// remap old value to new value based on item id value
|
||||
roomID = viking.InventoryItems.FirstOrDefault(e => e.ItemId == itemIds[roomID] && !usedItemIds.Contains(e.Id)).Id;
|
||||
usedItemIds.Add(roomID);
|
||||
x.RoomId = roomID.ToString();
|
||||
}
|
||||
ctx.Rooms.Add(x);
|
||||
}
|
||||
foreach (var x in v.PairData) {
|
||||
x.Viking = viking;
|
||||
if (x.PairId == 2014) { // stables data
|
||||
foreach (var p in x.Pairs.Where(e => e.Key.StartsWith("Stable"))) {
|
||||
StableData stableData = XmlUtil.DeserializeXml<StableData>(p.Value);
|
||||
stableData.InventoryID = viking.InventoryItems.FirstOrDefault(e => e.ItemId == stableData.ItemID).Id;
|
||||
usedItemIds.Add(stableData.InventoryID);
|
||||
foreach (var n in stableData.NestList) {
|
||||
if (n.PetID != 0)
|
||||
n.PetID = viking.Dragons.FirstOrDefault(d => d.EntityId == dragonIds[n.PetID]).Id;
|
||||
}
|
||||
p.Value = XmlUtil.SerializeXml(stableData);
|
||||
}
|
||||
}
|
||||
ctx.PairData.Add(x);
|
||||
}
|
||||
|
||||
foreach (var x in v.Images) {
|
||||
x.Viking = viking;
|
||||
ctx.Images.Add(x);
|
||||
}
|
||||
foreach (var x in v.MissionStates) {
|
||||
x.Viking = viking;
|
||||
ctx.MissionStates.Add(x);
|
||||
}
|
||||
foreach (var x in v.TaskStatuses) {
|
||||
x.Viking = viking;
|
||||
ctx.TaskStatuses.Add(x);
|
||||
}
|
||||
foreach (var x in v.AchievementTaskStates) {
|
||||
x.Viking = viking;
|
||||
ctx.AchievementTaskState.Add(x);
|
||||
}
|
||||
foreach (var x in v.AchievementPoints) {
|
||||
x.Viking = viking;
|
||||
ctx.AchievementPoints.Add(x);
|
||||
}
|
||||
foreach (var x in v.ProfileAnswers) {
|
||||
x.Viking = viking;
|
||||
ctx.ProfileAnswers.Add(x);
|
||||
}
|
||||
foreach (var x in v.GameData) {
|
||||
x.Viking = viking;
|
||||
ctx.GameData.Add(x);
|
||||
}
|
||||
foreach (var x in v.SavedData) {
|
||||
x.Viking = viking;
|
||||
ctx.SavedData.Add(x);
|
||||
}
|
||||
foreach (var x in v.Parties) {
|
||||
x.Viking = viking;
|
||||
ctx.Parties.Add(x);
|
||||
}
|
||||
foreach (var x in v.UserMissions) {
|
||||
x.Viking = viking;
|
||||
ctx.UserMissions.Add(x);
|
||||
}
|
||||
foreach (var x in v.UserBadgesCompleted) {
|
||||
x.Viking = viking;
|
||||
ctx.UserBadgesCompleted.Add(x);
|
||||
}
|
||||
if (v.Ratings.Count > 0) {
|
||||
viking.Ratings = new List<Rating>();
|
||||
foreach (var x in v.Ratings) {
|
||||
// TODO (non-SoD) add rating via SetRating(viking, x.Rank.CategoryID, x.Rank.RatedEntityID, x.Rank.RatedUserID, x.Value);
|
||||
}
|
||||
}
|
||||
if (v.Neighborhood != null) {
|
||||
v.Neighborhood.Viking = viking;
|
||||
ctx.Neighborhoods.Add(v.Neighborhood);
|
||||
}
|
||||
|
||||
if (v.SelectedDragon != null)
|
||||
viking.SelectedDragon = viking.Dragons.FirstOrDefault(d => d.EntityId == dragonIds[v.SelectedDragon.Id]);
|
||||
|
||||
ctx.SaveChanges();
|
||||
return Ok("OK");
|
||||
}
|
||||
}
|
||||
return Ok("Viking Not Found");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Export")]
|
||||
public IActionResult Export() {
|
||||
return Content(XmlUtil.ReadResourceXmlString("html.export"), "application/xhtml+xml");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Import")]
|
||||
public IActionResult Import() {
|
||||
return Content(XmlUtil.ReadResourceXmlString("html.import"), "application/xhtml+xml");
|
||||
}
|
||||
}
|
||||
89
src/Controllers/Common/ItemStoreController.cs
Normal file
89
src/Controllers/Common/ItemStoreController.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System.Reflection;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class ItemStoreController : Controller {
|
||||
|
||||
private readonly DBContext ctx;
|
||||
private StoreService storeService;
|
||||
private ItemService itemService;
|
||||
|
||||
public ItemStoreController(DBContext ctx, StoreService storeService, ItemService itemService) {
|
||||
this.ctx = ctx;
|
||||
this.storeService = storeService;
|
||||
this.itemService = itemService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ItemStoreWebService.asmx/GetStore")]
|
||||
public IActionResult GetStore([FromForm] string getStoreRequest) {
|
||||
GetStoreRequest request = XmlUtil.DeserializeXml<GetStoreRequest>(getStoreRequest);
|
||||
|
||||
ItemsInStoreData[] stores = new ItemsInStoreData[request.StoreIDs.Length];
|
||||
for (int i = 0; i < request.StoreIDs.Length; i++) {
|
||||
stores[i] = storeService.GetStore(request.StoreIDs[i]);
|
||||
}
|
||||
|
||||
GetStoreResponse response = new GetStoreResponse {
|
||||
Stores = stores
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ItemStoreWebService.asmx/GetItem")]
|
||||
public IActionResult GetItem([FromForm] int itemId) {
|
||||
if (itemId == 0) // For a null item, return an empty item
|
||||
return Ok(new ItemData());
|
||||
return Ok(itemService.GetItem(itemId));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ItemStoreWebService.asmx/GetItemsInStore")] // used by World Of Jumpstart
|
||||
public IActionResult GetItemsInStore([FromForm] int storeId, [FromForm] string apiKey) {
|
||||
return Ok(storeService.GetStore(storeId, ClientVersion.GetVersion(apiKey)));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
//[Produces("application/xml")]
|
||||
[Route("ItemStoreWebService.asmx/GetRankAttributeData")]
|
||||
public IActionResult GetRankAttributeData([FromForm] string apiKey) {
|
||||
uint gameVersion = ClientVersion.GetVersion(apiKey);
|
||||
if (gameVersion == ClientVersion.MB)
|
||||
return Ok(XmlUtil.ReadResourceXmlString("ranks.rankattrib_mb"));
|
||||
// TODO, this is a placeholder
|
||||
return Ok(XmlUtil.ReadResourceXmlString("ranks.rankattrib"));
|
||||
}
|
||||
|
||||
[Route("ItemStoreWebService.asmx/GetAssetVersions")]
|
||||
public IActionResult GetAssetVersions() {
|
||||
// TODO return AssetVersion[]
|
||||
return Ok(null);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ItemStoreWebService.asmx/GetAnnouncementsByUser")]
|
||||
//[VikingSession(UseLock=false)]
|
||||
public IActionResult GetAnnouncements([FromForm] string apiKey, [FromForm] int worldObjectID) {
|
||||
// TODO: This is a placeholder, although this endpoint seems to be only used to send announcements to the user (such as the server shutdown), so this might be sufficient.
|
||||
|
||||
uint gameID = ClientVersion.GetGameID(apiKey);
|
||||
if (gameID == ClientVersion.WoJS) {
|
||||
return Ok(XmlUtil.DeserializeXml<AnnouncementList>(XmlUtil.ReadResourceXmlString("announcements_wojs")));
|
||||
} else if (gameID == ClientVersion.SS && worldObjectID == 6) {
|
||||
return Ok(XmlUtil.DeserializeXml<AnnouncementList>(XmlUtil.ReadResourceXmlString("announcements_ss")));
|
||||
}
|
||||
|
||||
return Ok(new AnnouncementList());
|
||||
}
|
||||
}
|
||||
39
src/Controllers/Common/MembershipController.cs
Normal file
39
src/Controllers/Common/MembershipController.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class MembershipController : Controller {
|
||||
|
||||
private readonly DBContext ctx;
|
||||
public MembershipController(DBContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MembershipWebService.asmx/GetSubscriptionInfo")]
|
||||
public IActionResult GetSubscriptionInfo() {
|
||||
return Ok(new SubscriptionInfo {
|
||||
Recurring = true,
|
||||
Status = "Member",
|
||||
SubscriptionTypeID = 1,
|
||||
SubscriptionEndDate = new DateTime(2033, 6, 13, 16, 17, 18)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MembershipWebService.asmx/GetChildList")] // used by old SoD (e.g. 2.9)
|
||||
[VikingSession(Mode=VikingSession.Modes.USER, UseLock=false)]
|
||||
public IActionResult GetChildList(User user) {
|
||||
if (user.Vikings.Count <= 0)
|
||||
return Ok();
|
||||
|
||||
ChildList profiles = new ChildList();
|
||||
profiles.strings = user.Vikings.Select(viking => viking.Uid + ", " + viking.Name).ToArray();
|
||||
|
||||
return Ok(profiles);
|
||||
}
|
||||
}
|
||||
109
src/Controllers/Common/MessagingController.cs
Normal file
109
src/Controllers/Common/MessagingController.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class MessagingController : Controller {
|
||||
|
||||
private readonly MessagingService messagingService;
|
||||
private readonly DBContext ctx;
|
||||
public MessagingController(MessagingService messagingService, DBContext ctx)
|
||||
{
|
||||
this.messagingService = messagingService;
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MessagingWebService.asmx/GetUserMessageQueue")]
|
||||
[VikingSession]
|
||||
public ArrayOfMessageInfo? GetUserMessageQueue(Viking viking, [FromForm] bool showOldMessages, [FromForm] bool showDeletedMessages) {
|
||||
return messagingService.ConstructUserMessageInfoArray(viking, showOldMessages, showDeletedMessages);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MessagingWebService.asmx/SendMessage")]
|
||||
[VikingSession]
|
||||
public IActionResult SendMessage(Viking viking, [FromForm] Guid toUser, [FromForm] int messageID, [FromForm] string data) {
|
||||
// find viking to send to
|
||||
Viking? toViking = ctx.Vikings.FirstOrDefault(e => e.Uid == toUser);
|
||||
if (toViking == null) return Ok(false);
|
||||
|
||||
// clients (at least older ones) don't send what typeID the message is, its encoded in data
|
||||
ArrayOfKeyValuePairOfStringString arrayOfKeyValuePairOfStringString = XmlUtil.DeserializeXml<ArrayOfKeyValuePairOfStringString>(data);
|
||||
MessageTypeID typeID = MessageTypeID.Unknown;
|
||||
string typeText = "";
|
||||
|
||||
switch(arrayOfKeyValuePairOfStringString.KeyValuePairOfStringString![0].Value)
|
||||
{
|
||||
case "Photo":
|
||||
typeID = MessageTypeID.Photo;
|
||||
typeText = "Photo";
|
||||
break;
|
||||
case "Drawing":
|
||||
typeID = MessageTypeID.GreetingCard;
|
||||
typeText = "Card";
|
||||
break;
|
||||
}
|
||||
|
||||
var msg = messagingService.AddMessageToViking(viking, toViking, MessageType.Data, typeID, MessageLevel.WhiteList, data, "[[Line1]]=[[{{BuddyUserName}} has sent you a " + typeText + "!]]", "[[Line1]]=[[{{BuddyUserName}} has sent you a " + typeText + "!]]");
|
||||
if (msg != null) return Ok(true);
|
||||
else return Ok(false);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MessagingWebService.asmx/SaveMessage")]
|
||||
public IActionResult SaveMessage([FromForm] int userMessageQueueId, [FromForm] bool isNew, [FromForm] bool IsDeleted) {
|
||||
var updatedMessage = messagingService.UpdateMessageByQueueID(userMessageQueueId, isNew, IsDeleted);
|
||||
if (updatedMessage != null) return Ok(true);
|
||||
else return Ok(false);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MessageWebService.asmx/GetCombinedListMessage")]
|
||||
[VikingSession(UseLock = false)]
|
||||
public ArrayOfCombinedListMessage? GetCombinedListMessage(Viking viking, [FromForm] Guid userId)
|
||||
{
|
||||
// find viking
|
||||
Viking? uidViking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
|
||||
|
||||
if (viking != null && uidViking != null) return messagingService.ConstructCombinedMessageArray(viking, uidViking);
|
||||
else return new ArrayOfCombinedListMessage();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MessageWebService.asmx/RemoveMessageFromBoard")]
|
||||
public IActionResult RemoveMessageFromBoard([FromForm] int messageID)
|
||||
{
|
||||
messagingService.RemoveMessage(messageID);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Messaging/PostTextMessage")]
|
||||
public IActionResult PostTextMessage([FromForm] Guid token, [FromForm] Guid userId, [FromForm] string data, [FromForm] int messageLevel, [FromForm] int replyMessageId)
|
||||
{
|
||||
// check if session is valid
|
||||
Session? session = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token);
|
||||
|
||||
// find viking
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
|
||||
|
||||
if (session != null && viking != null)
|
||||
{
|
||||
// post text message
|
||||
var message = messagingService.AddMessageToViking(session.Viking, viking, MessageType.Post, MessageTypeID.Messaging, (MessageLevel)messageLevel, data, isReply: replyMessageId > 0, parentMessageId: replyMessageId);
|
||||
|
||||
if (message != null) return Ok(true);
|
||||
}
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
56
src/Controllers/Common/PrecenseController.cs
Normal file
56
src/Controllers/Common/PrecenseController.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
|
||||
namespace sodoff.Controllers.Common
|
||||
{
|
||||
[ApiController]
|
||||
public class PrecenseController : Controller
|
||||
{
|
||||
private readonly DBContext ctx;
|
||||
public PrecenseController(DBContext ctx) => this.ctx = ctx;
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/json")]
|
||||
[Route("Precense/SetVikingOnline")]
|
||||
public IActionResult SetVikingOnline([FromForm] Guid token, [FromForm] bool online)
|
||||
{
|
||||
// get viking from session
|
||||
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token)?.Viking;
|
||||
|
||||
if (viking != null)
|
||||
{
|
||||
viking.Online = online;
|
||||
ctx.SaveChanges();
|
||||
return Ok(viking.Online);
|
||||
} else return Ok(false);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/json")]
|
||||
[Route("Precense/SetVikingRoom")]
|
||||
public IActionResult SetVikingRoom([FromForm] Guid token, [FromForm] int roomId, [FromForm] string zoneName)
|
||||
{
|
||||
// get viking from session
|
||||
Viking? viking = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token)?.Viking;
|
||||
|
||||
if (viking != null)
|
||||
{
|
||||
if (roomId <= 0 && string.IsNullOrEmpty(zoneName))
|
||||
{
|
||||
viking.CurrentRoomId = null;
|
||||
viking.CurrentZone = null;
|
||||
ctx.SaveChanges();
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
viking.CurrentRoomId = roomId;
|
||||
viking.CurrentZone = zoneName;
|
||||
ctx.SaveChanges();
|
||||
return Ok(true);
|
||||
}
|
||||
else return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
186
src/Controllers/Common/ProfileController.cs
Normal file
186
src/Controllers/Common/ProfileController.cs
Normal file
@ -0,0 +1,186 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
using sodoff.Util;
|
||||
using sodoff.Configuration;
|
||||
using GroupMember = sodoff.Model.GroupMember;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
public class ProfileController : Controller {
|
||||
|
||||
private readonly DBContext ctx;
|
||||
private AchievementService achievementService;
|
||||
private ProfileService profileService;
|
||||
private readonly IOptions<ApiServerConfig> config;
|
||||
|
||||
public ProfileController(DBContext ctx, AchievementService achievementService, ProfileService profileService, IOptions<ApiServerConfig> config) {
|
||||
this.ctx = ctx;
|
||||
this.achievementService = achievementService;
|
||||
this.profileService = profileService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ProfileWebService.asmx/GetUserProfileByUserID")]
|
||||
public IActionResult GetUserProfileByUserID([FromForm] Guid userId, [FromForm] string apiKey) {
|
||||
// NOTE: this is public info (for mmo) - no session check
|
||||
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
|
||||
if (viking is null) {
|
||||
return Ok(new UserProfileData());
|
||||
// NOTE: do not return `Conflict("Viking not found")` due to client side error handling
|
||||
// (not Ok response cause soft-lock client - can't close error message)
|
||||
}
|
||||
|
||||
return Ok(GetProfileDataFromViking(viking, apiKey));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ProfileWebService.asmx/GetUserProfile")]
|
||||
[VikingSession(UseLock=false)]
|
||||
public IActionResult GetUserProfile(Viking viking, [FromForm] string apiKey) {
|
||||
return Ok(GetProfileDataFromViking(viking, apiKey));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ProfileWebService.asmx/GetDetailedChildList")]
|
||||
[VikingSession(Mode=VikingSession.Modes.USER, ApiToken="parentApiToken", UseLock=false)]
|
||||
public Schema.UserProfileDataList? GetDetailedChildList(User user, [FromForm] string apiKey) {
|
||||
if (user.Vikings.Count <= 0)
|
||||
return null;
|
||||
|
||||
UserProfileData[] profiles = user.Vikings.Select(v => GetProfileDataFromViking(v, apiKey)).ToArray();
|
||||
return new UserProfileDataList {
|
||||
UserProfiles = profiles
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
// [Produces("application/xml")]
|
||||
[Route("ProfileWebService.asmx/GetQuestions")]
|
||||
public IActionResult GetQuestions() {
|
||||
return Ok(XmlUtil.ReadResourceXmlString("questiondata"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ProfileWebService.asmx/SetUserProfileAnswers")]
|
||||
[VikingSession]
|
||||
public IActionResult SetUserProfileAnswers(Viking viking, [FromForm] int profileAnswerIDs)
|
||||
{
|
||||
ProfileQuestion questionFromaId = profileService.GetQuestionFromAnswerId(profileAnswerIDs);
|
||||
return Ok(profileService.SetAnswer(viking, questionFromaId.ID, profileAnswerIDs));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
//[Produces("application/xml")]
|
||||
[Route("ProfileWebService.asmx/GetProfileTagAll")] // used by Magic & Mythies
|
||||
public IActionResult GetProfileTagAll() {
|
||||
return Ok(XmlUtil.ReadResourceXmlString("profiletags"));
|
||||
}
|
||||
|
||||
private UserProfileData GetProfileDataFromViking(Viking viking, [FromForm] string apiKey) {
|
||||
// Get the avatar data
|
||||
AvatarData avatarData = null;
|
||||
Gender? gender = null;
|
||||
if (viking.AvatarSerialized is not null) {
|
||||
avatarData = XmlUtil.DeserializeXml<AvatarData>(viking.AvatarSerialized);
|
||||
avatarData.Id = viking.Id;
|
||||
if (gender is null)
|
||||
gender = avatarData.GenderType;
|
||||
}
|
||||
if (gender is null)
|
||||
gender = Gender.Unknown;
|
||||
|
||||
if (avatarData != null && ClientVersion.GetVersion(apiKey) == 0xa3a12a0a) { // TODO adjust version number: we don't know for which versions it is required (for 3.12 it is, for 3.19 and 3.0 it's not)
|
||||
if (avatarData.Part.FirstOrDefault(e => e.PartType == "Sword") is null) {
|
||||
var extraParts = new AvatarDataPart[] {
|
||||
new AvatarDataPart {
|
||||
PartType = "Sword",
|
||||
Geometries = new string[] {"NULL"},
|
||||
Textures = new string[] {"__EMPTY__"},
|
||||
UserInventoryId = null,
|
||||
}
|
||||
};
|
||||
avatarData.Part = extraParts.Concat(avatarData.Part).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// Build the AvatarDisplayData
|
||||
AvatarDisplayData avatar = new AvatarDisplayData {
|
||||
AvatarData = avatarData,
|
||||
UserInfo = new UserInfo {
|
||||
MembershipID = "ef84db9-59c6-4950-b8ea-bbc1521f899b", // placeholder
|
||||
UserID = viking.Uid.ToString(),
|
||||
ParentUserID = viking.UserId.ToString(),
|
||||
Username = viking.Name,
|
||||
FirstName = viking.Name,
|
||||
MultiplayerEnabled = ClientVersion.GetVersion(apiKey) >= config.Value.MMOSupportMinVersion,
|
||||
Locale = "en-US", // placeholder
|
||||
GenderID = gender,
|
||||
OpenChatEnabled = true,
|
||||
IsApproved = true,
|
||||
RegistrationDate = viking.CreationDate,
|
||||
CreationDate = viking.CreationDate,
|
||||
FacebookUserID = 0,
|
||||
BirthDate = viking.BirthDate
|
||||
},
|
||||
UserSubscriptionInfo = new UserSubscriptionInfo {
|
||||
UserID = viking.UserId.ToString(),
|
||||
MembershipID = 130687131, // placeholder
|
||||
SubscriptionTypeID = 1, // placeholder
|
||||
SubscriptionDisplayName = "Member", // placeholder
|
||||
SubscriptionPlanID = 41, // placeholder
|
||||
SubscriptionID = -3, // placeholder
|
||||
IsActive = true, // placeholder
|
||||
},
|
||||
RankID = 0, // placeholder
|
||||
AchievementInfo = null, // placeholder
|
||||
Achievements = new UserAchievementInfo[] {
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerFarmingXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.PlayerFishingXP),
|
||||
achievementService.CreateUserAchievementInfo(viking, AchievementPointTypes.UDTPoints),
|
||||
}
|
||||
};
|
||||
|
||||
UserGameCurrency currency = achievementService.GetUserCurrency(viking);
|
||||
|
||||
GroupMember? groupRole = viking.GroupMembership;
|
||||
return new UserProfileData {
|
||||
ID = viking.Uid.ToString(),
|
||||
AvatarInfo = avatar,
|
||||
AchievementCount = 0,
|
||||
MythieCount = 0,
|
||||
AnswerData = new UserAnswerData { UserID = viking.Uid.ToString(), Answers = profileService.GetUserAnswers(viking) },
|
||||
GameCurrency = currency.GameCurrency,
|
||||
CashCurrency = currency.CashCurrency,
|
||||
ActivityCount = 0,
|
||||
BuddyCount = viking.BuddyList.Count + viking.BuddiesMade.Count,
|
||||
UserGradeData = new UserGrade { UserGradeID = 0 },
|
||||
UserProfileTag = new UserProfileTag() {
|
||||
CreateDate = new DateTime(DateTime.Now.Ticks),
|
||||
ProductGroupID = 1,
|
||||
ProfileTags = new List<ProfileTag>(),
|
||||
UserID = viking.Uid,
|
||||
UserProfileTagID = 1
|
||||
},
|
||||
Groups = groupRole != null ? [
|
||||
new UserProfileGroupData {
|
||||
GroupID = groupRole.Group.GroupID.ToString(),
|
||||
Name = groupRole.Group.Name,
|
||||
Color = groupRole.Group.Color,
|
||||
Logo = groupRole.Group.Logo,
|
||||
TypeID = (int)groupRole.Group.Type,
|
||||
RoleID = groupRole.UserRole
|
||||
}] : new UserProfileGroupData[]{}
|
||||
};
|
||||
}
|
||||
}
|
||||
296
src/Controllers/Common/RatingController.cs
Normal file
296
src/Controllers/Common/RatingController.cs
Normal file
@ -0,0 +1,296 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
|
||||
public class RatingController : Controller
|
||||
{
|
||||
private readonly DBContext ctx;
|
||||
|
||||
public RatingController(DBContext ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("MissionWebService.asmx/GetPayout")] // used by World Of Jumpstart
|
||||
public IActionResult GetPayout([FromForm] int points, [FromForm] string ModuleName) {
|
||||
switch (ModuleName)
|
||||
{
|
||||
case string x when x.StartsWith("MSBejeweled"):
|
||||
return Ok(points / (350 / 3));
|
||||
|
||||
case string x when x.StartsWith("MSDodgeNDash"):
|
||||
return Ok((int)Math.Floor(Math.Pow(1.145, (double)points / 10)));
|
||||
|
||||
case string x when x.StartsWith("MSBuggyRacer"):
|
||||
return Ok(points / 4.7);
|
||||
|
||||
case string x when x.StartsWith("MSSoundBop"):
|
||||
return Ok(points * 10);
|
||||
|
||||
case string x when x.StartsWith("MSGhostTownGrab"):
|
||||
return Ok(points / 20);
|
||||
|
||||
case string x when x.StartsWith("BubbleTrouble"):
|
||||
return Ok(points / 10);
|
||||
|
||||
case string x when x.StartsWith("HopsJetPack"):
|
||||
return Ok(points / 25);
|
||||
|
||||
case string x when x.StartsWith("HallowsBubbleMaze"):
|
||||
return Ok(points / 10);
|
||||
|
||||
case string x when x.StartsWith("MSRoboAGoGo"):
|
||||
return Ok(points / 20);
|
||||
|
||||
case string x when x.StartsWith("Volleyball"):
|
||||
return Ok(points / 1.8);
|
||||
|
||||
case string x when x.StartsWith("Football"):
|
||||
return Ok(points / 3.2);
|
||||
|
||||
case string x when x.StartsWith("Basketball"):
|
||||
return Ok(points / 1.8);
|
||||
|
||||
case string x when x.StartsWith("FruitSaladChop"):
|
||||
return Ok(points / 60);
|
||||
|
||||
case string x when x.StartsWith("JSGardenDefense"):
|
||||
return Ok(points / (350/3));
|
||||
|
||||
case string x when x.StartsWith("JSSushiChop"):
|
||||
return Ok(points / 120);
|
||||
|
||||
case string x when x.StartsWith("JSDanceOff"):
|
||||
return Ok(points / (350/3));
|
||||
|
||||
case string x when x.StartsWith("MSPnCow"):
|
||||
return Ok(60);
|
||||
|
||||
case string x when x.StartsWith("MSPnBunny"):
|
||||
return Ok(60);
|
||||
|
||||
case string x when x.StartsWith("MSPnMonkey"):
|
||||
return Ok(60);
|
||||
|
||||
case string x when x.StartsWith("MSCnKite"):
|
||||
return Ok(60);
|
||||
|
||||
case string x when x.StartsWith("MSCnLadyBug"):
|
||||
return Ok(60);
|
||||
|
||||
case string x when x.StartsWith("MSCnDucky"):
|
||||
return Ok(60);
|
||||
|
||||
case string x when x.StartsWith("Calendar"):
|
||||
return Ok(points / 40);
|
||||
|
||||
case string x when x.StartsWith("PearlPush"):
|
||||
return Ok(points / 20);
|
||||
|
||||
case string x when x.StartsWith("FLLetItRide"):
|
||||
return Ok(points <= 28 ? 2 : 5);
|
||||
|
||||
case string x when x.StartsWith("FLLetItRideMastered"):
|
||||
return Ok(points <= 15 ? 4 :
|
||||
points <= 25 ? 6 :
|
||||
points <= 35 ? 9 :
|
||||
points <= 45 ? 12 : 15);
|
||||
|
||||
case string x when x.StartsWith("FashionShowWin"):
|
||||
return Ok(points >= 1 ? 20 : 0);
|
||||
|
||||
case string x when x.StartsWith("FashionShowLose"):
|
||||
return Ok(points >= 0 ? 10 : 0);
|
||||
|
||||
case string x when x.StartsWith("FashionShowTie"):
|
||||
return Ok(points >= 2 ? 15 : 0);
|
||||
}
|
||||
|
||||
return Ok(points / (350 / 3));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("ScoreWebService.asmx/SetScore")] // used by World Of Jumpstart
|
||||
public IActionResult SetScore()
|
||||
{
|
||||
// TODO - placeholder
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V2/Ratingwebservice.asmx/GetAverageRatingForRoom")]
|
||||
//[VikingSession(UseLock=false)]
|
||||
public IActionResult GetAverageRatingForRoom(/*Viking viking,*/ [FromForm] string request)
|
||||
{
|
||||
// TODO: This is a placeholder
|
||||
return Ok(5);
|
||||
}
|
||||
|
||||
// This method is the only thing that adds ratings.
|
||||
private RatingInfo SetRating(Viking viking, int category, int? eID, string? uID, int value) {
|
||||
RatingRank? rank;
|
||||
Rating? rating = viking.Ratings.FirstOrDefault(
|
||||
r => category == r.Rank.CategoryID && r.Rank.RatedEntityID == eID && r.Rank.RatedUserID == uID
|
||||
);
|
||||
if (rating == null) {
|
||||
rank = ctx.RatingRanks.FirstOrDefault(rr => rr.CategoryID == category && rr.RatedEntityID == eID && rr.RatedUserID == uID);
|
||||
if (rank == null) {
|
||||
rank = new RatingRank {
|
||||
CategoryID = category,
|
||||
RatedEntityID = eID,
|
||||
RatedUserID = uID,
|
||||
Rank = 0,
|
||||
Ratings = new List<Rating>()
|
||||
};
|
||||
ctx.RatingRanks.Add(rank);
|
||||
}
|
||||
rating = new Rating {
|
||||
VikingId = viking.Id,
|
||||
Rank = rank
|
||||
};
|
||||
ctx.Ratings.Add(rating);
|
||||
} else {
|
||||
rank = rating.Rank;
|
||||
}
|
||||
|
||||
rating.Value = value;
|
||||
rank.RatingAverage = 0;
|
||||
foreach (Rating r in rank.Ratings) {
|
||||
rank.RatingAverage += (float)((decimal)r.Value / (decimal)rank.Ratings.Count);
|
||||
}
|
||||
|
||||
if (eID != -1 || uID != null) { // do not sort "single item" (eID == -1 and no uID) category
|
||||
RatingRank[] ranks = ctx.RatingRanks
|
||||
.Where(rr => rr != rank && rr.CategoryID == category) // Only rank by category.
|
||||
.OrderBy(rr => rr.Rank)
|
||||
.ToArray();
|
||||
bool resortOthers = false;
|
||||
rank.Rank = 1; // Start here, work way down.
|
||||
for (int i=0;i<ranks.Length;i++) {
|
||||
if (!resortOthers && ranks[i].RatingAverage < rank.RatingAverage) {
|
||||
rank.Rank = i+1;
|
||||
resortOthers = true;
|
||||
}
|
||||
if (resortOthers) ranks[i].Rank = i+2;
|
||||
else ranks[i].Rank = i+1;
|
||||
}
|
||||
if (!resortOthers) rank.Rank = ranks.Length+1;
|
||||
}
|
||||
|
||||
rating.Date = DateTime.UtcNow;
|
||||
rank.UpdateDate = rating.Date;
|
||||
ctx.SaveChanges();
|
||||
|
||||
return new RatingInfo() {
|
||||
Id = rating.Id,
|
||||
OwnerUid = viking.Uid,
|
||||
CategoryID = category,
|
||||
RatedEntityID = eID,
|
||||
Value = value,
|
||||
Date = rating.Date
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/SetRating")]
|
||||
[VikingSession]
|
||||
public IActionResult SetRating(Viking viking, [FromForm] int categoryID, [FromForm] int ratedEntityID, [FromForm] int ratedValue) {
|
||||
return Ok(SetRating(viking, categoryID, ratedEntityID, null, ratedValue));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/SetUserRating")]
|
||||
[VikingSession]
|
||||
public IActionResult SetUserRating(Viking viking, [FromForm] int categoryID, [FromForm] string ratedUserID, [FromForm] int ratedValue) {
|
||||
return Ok(SetRating(viking, categoryID, null, ratedUserID, ratedValue));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/GetRatingByRatedEntity")]
|
||||
[VikingSession]
|
||||
public RatingInfo[] GetRatingByRatedEntity(Viking viking, [FromForm] int categoryID, [FromForm] int ratedEntityID) {
|
||||
return ctx.Ratings
|
||||
.Where(r => r.Viking == viking && r.Rank.CategoryID == categoryID && r.Rank.RatedEntityID == ratedEntityID && r.Rank.RatedUserID == null)
|
||||
.Select(r => new RatingInfo {
|
||||
Id = r.Id,
|
||||
OwnerUid = r.Viking.Uid,
|
||||
CategoryID = r.Rank.CategoryID,
|
||||
RatedEntityID = r.Rank.RatedEntityID,
|
||||
Value = r.Value,
|
||||
Date = r.Date
|
||||
}
|
||||
).ToArray();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/GetTopRatedByCategoryID")]
|
||||
public RatingRankInfo[] GetTopRatedByCategoryID([FromForm] int categoryID, [FromForm] int numberOfRecord) {
|
||||
return ctx.RatingRanks
|
||||
.Where(rr => categoryID == rr.CategoryID)
|
||||
.Take(numberOfRecord)
|
||||
.ToList()
|
||||
.Select(rr => new RatingRankInfo(rr))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/GetTopRatedUserByCategoryID")]
|
||||
public IActionResult GetTopRatedUserByCategoryID([FromForm] int categoryID, [FromForm] int numberOfRecord) {
|
||||
return Ok(new ArrayOfUserRatingRankInfo {
|
||||
UserRatingRankInfo = ctx.RatingRanks
|
||||
.Where(rr => rr.RatedUserID != null && (categoryID == rr.CategoryID
|
||||
|| (categoryID == 4 && rr.CategoryID == 5) // The party board searches for 4 but the pod rating is set in 5.
|
||||
))
|
||||
.OrderBy(rr => rr.Rank)
|
||||
.Take(numberOfRecord)
|
||||
.ToList()
|
||||
.Select(rr => new UserRatingRankInfo { RankInfo = new RatingRankInfo(rr), RatedUserID = new Guid(rr.RatedUserID) })
|
||||
.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/GetEntityRatedRank")]
|
||||
public IActionResult GetEntityRatedRank([FromForm] int categoryID, [FromForm] int ratedEntityID) {
|
||||
// TODO: Add a shortcut here for shipwreck lagoon tracks.
|
||||
RatingRank? rank = ctx.RatingRanks.FirstOrDefault(rr => categoryID == rr.CategoryID && rr.RatedEntityID == ratedEntityID);
|
||||
if (rank == null) return Ok();
|
||||
return Ok(new RatingRankInfo(rank));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/GetRatingForRatedUser")]
|
||||
[VikingSession]
|
||||
public IActionResult GetRatingForRatedUser(Viking viking, [FromForm] int categoryID, [FromForm] string ratedUserID) {
|
||||
Rating? rating = ctx.Ratings.FirstOrDefault(
|
||||
r => r.Viking == viking && categoryID == r.Rank.CategoryID && r.Rank.RatedEntityID == null && r.Rank.RatedUserID == ratedUserID
|
||||
);
|
||||
return Ok(rating?.Value ?? 0);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("RatingWebService.asmx/GetRatingForRatedEntity")]
|
||||
[VikingSession]
|
||||
public IActionResult GetRatingForRatedEntity(Viking viking, [FromForm] int categoryID, [FromForm] int ratedEntityID) {
|
||||
Rating? rating = ctx.Ratings.FirstOrDefault(
|
||||
r => r.Viking == viking && categoryID == r.Rank.CategoryID && r.Rank.RatedEntityID == ratedEntityID && r.Rank.RatedUserID == null
|
||||
);
|
||||
return Ok(rating?.Value ?? 0);
|
||||
}
|
||||
}
|
||||
202
src/Controllers/Common/RegistrationController.cs
Normal file
202
src/Controllers/Common/RegistrationController.cs
Normal file
@ -0,0 +1,202 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Attributes;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Controllers.Common;
|
||||
|
||||
public class RegistrationController : Controller {
|
||||
|
||||
private readonly DBContext ctx;
|
||||
private ItemService itemService;
|
||||
private MissionService missionService;
|
||||
private RoomService roomService;
|
||||
private KeyValueService keyValueService;
|
||||
|
||||
public RegistrationController(DBContext ctx, ItemService itemService, MissionService missionService, RoomService roomService, KeyValueService keyValueService) {
|
||||
this.ctx = ctx;
|
||||
this.itemService = itemService;
|
||||
this.missionService = missionService;
|
||||
this.roomService = roomService;
|
||||
this.keyValueService = keyValueService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("v3/RegistrationWebService.asmx/DeleteProfile")]
|
||||
public IActionResult DeleteProfile([FromForm] Guid apiToken, [FromForm] Guid userID) {
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == apiToken)?.User;
|
||||
if (user is null) {
|
||||
return Ok(DeleteProfileStatus.OWNER_ID_NOT_FOUND);
|
||||
}
|
||||
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userID);
|
||||
if (viking is null) {
|
||||
return Ok(DeleteProfileStatus.PROFILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (user != viking.User) {
|
||||
return Ok(DeleteProfileStatus.PROFILE_NOT_OWNED_BY_THIS_OWNER);
|
||||
}
|
||||
|
||||
ctx.Vikings.Remove(viking);
|
||||
ctx.SaveChanges();
|
||||
|
||||
return Ok(DeleteProfileStatus.SUCCESS);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("v3/RegistrationWebService.asmx/RegisterParent")]
|
||||
[DecryptRequest("parentRegistrationData")]
|
||||
[EncryptResponse]
|
||||
public IActionResult RegisterParent([FromForm] string apiKey) {
|
||||
ParentRegistrationData data = XmlUtil.DeserializeXml<ParentRegistrationData>(Request.Form["parentRegistrationData"]);
|
||||
User u = new User {
|
||||
Id = Guid.NewGuid(),
|
||||
Username = data.ChildList[0].ChildName,
|
||||
Password = new PasswordHasher<object>().HashPassword(null, data.Password),
|
||||
Email = data.Email
|
||||
};
|
||||
|
||||
// Check if user exists
|
||||
uint gameVersion = ClientVersion.GetVersion(apiKey);
|
||||
if (gameVersion <= ClientVersion.Max_OldJS) {
|
||||
if (ctx.Users.Count(e => e.Email == u.Email) > 0) {
|
||||
return Ok(new RegistrationResult { Status = MembershipUserStatus.DuplicateEmail });
|
||||
}
|
||||
}
|
||||
if (ctx.Users.Count(e => e.Username== u.Username) > 0) {
|
||||
return Ok(new RegistrationResult { Status = MembershipUserStatus.DuplicateUserName });
|
||||
}
|
||||
|
||||
ctx.Users.Add(u);
|
||||
ctx.SaveChanges();
|
||||
|
||||
if (gameVersion <= ClientVersion.Max_OldJS) {
|
||||
CreateViking(u, data.ChildList[0], gameVersion);
|
||||
}
|
||||
|
||||
ParentLoginInfo pli = new ParentLoginInfo {
|
||||
UserName = u.Username,
|
||||
ApiToken = Guid.NewGuid().ToString(),
|
||||
UserID = u.Id.ToString(),
|
||||
Status = MembershipUserStatus.Success,
|
||||
UnAuthorized = false
|
||||
};
|
||||
|
||||
var response = new RegistrationResult {
|
||||
ParentLoginInfo = pli,
|
||||
UserID = u.Id.ToString(),
|
||||
Status = MembershipUserStatus.Success,
|
||||
ApiToken = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V4/RegistrationWebService.asmx/RegisterChild")]
|
||||
[DecryptRequest("childRegistrationData")]
|
||||
[EncryptResponse]
|
||||
public IActionResult RegisterChild([FromForm] Guid parentApiToken, [FromForm] string apiKey) {
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == parentApiToken)?.User;
|
||||
if (user is null) {
|
||||
return Ok(new RegistrationResult{
|
||||
Status = MembershipUserStatus.InvalidApiToken
|
||||
});
|
||||
}
|
||||
|
||||
// Check if name populated
|
||||
ChildRegistrationData data = XmlUtil.DeserializeXml<ChildRegistrationData>(Request.Form["childRegistrationData"]);
|
||||
if (String.IsNullOrWhiteSpace(data.ChildName)) {
|
||||
return Ok(new RegistrationResult { Status = MembershipUserStatus.ValidationError });
|
||||
}
|
||||
|
||||
// Check if viking exists
|
||||
if (ctx.Vikings.Count(e => e.Name == data.ChildName) > 0) {
|
||||
return Ok(new RegistrationResult { Status = MembershipUserStatus.DuplicateUserName });
|
||||
}
|
||||
|
||||
Viking v = CreateViking(user, data, ClientVersion.GetVersion(apiKey));
|
||||
|
||||
return Ok(new RegistrationResult {
|
||||
UserID = v.Uid.ToString(),
|
||||
Status = MembershipUserStatus.Success
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Produces("application/xml")]
|
||||
[Route("V3/RegistrationWebService.asmx/RegisterChild")] // used by SoD 1.13 and Magic & Mythies
|
||||
[DecryptRequest("childRegistrationData")]
|
||||
public IActionResult RegisterChildV3([FromForm] Guid parentApiToken, [FromForm] string apiKey) {
|
||||
User? user = ctx.Sessions.FirstOrDefault(e => e.ApiToken == parentApiToken)?.User;
|
||||
if (user is null) {
|
||||
return Ok(new RegistrationResult{
|
||||
Status = MembershipUserStatus.InvalidApiToken
|
||||
});
|
||||
}
|
||||
|
||||
// Check if name populated
|
||||
ChildRegistrationData data = XmlUtil.DeserializeXml<ChildRegistrationData>(Request.Form["childRegistrationData"]);
|
||||
if (String.IsNullOrWhiteSpace(data.ChildName)) {
|
||||
return Ok(MembershipUserStatus.ValidationError);
|
||||
}
|
||||
|
||||
// Check if viking exists
|
||||
if (ctx.Vikings.Count(e => e.Name == data.ChildName) > 0) {
|
||||
return Ok(MembershipUserStatus.DuplicateUserName);
|
||||
}
|
||||
|
||||
Viking v = CreateViking(user, data, ClientVersion.GetVersion(apiKey));
|
||||
|
||||
return Ok(MembershipUserStatus.Success);
|
||||
}
|
||||
|
||||
private Viking CreateViking(User user, ChildRegistrationData data, uint gameVersion) {
|
||||
List<InventoryItem> items = new();
|
||||
if (gameVersion >= ClientVersion.Min_SoD) {
|
||||
items.Add( new InventoryItem { ItemId = 8977, Quantity = 1 } ); // DragonStableINTDO - Dragons Dragon Stable
|
||||
}
|
||||
|
||||
Viking v = new Viking {
|
||||
Uid = Guid.NewGuid(),
|
||||
Name = data.ChildName,
|
||||
User = user,
|
||||
InventoryItems = items,
|
||||
AchievementPoints = new List<AchievementPoints>(),
|
||||
Rooms = new List<Room>(),
|
||||
CreationDate = DateTime.UtcNow,
|
||||
BirthDate = data.BirthDate
|
||||
};
|
||||
|
||||
missionService.SetUpMissions(v, gameVersion);
|
||||
|
||||
if (data.Gender == "Boy") v.Gender = Gender.Male;
|
||||
else if (data.Gender == "Girl") v.Gender = Gender.Female;
|
||||
|
||||
ctx.Vikings.Add(v);
|
||||
ctx.SaveChanges();
|
||||
|
||||
if (gameVersion >= ClientVersion.MaM && gameVersion < 0xa2a09a0a) {
|
||||
keyValueService.SetPairData(null, v, null, 2017, new Schema.PairData {
|
||||
Pairs = new Schema.Pair[]{
|
||||
new Schema.Pair {
|
||||
// avoid show change viking name dialog
|
||||
PairKey = "AvatarNameCustomizationDone",
|
||||
PairValue = "1"
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
roomService.CreateRoom(v, "MyRoomINT");
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
91
src/Controllers/Internal/ModerationController.cs
Normal file
91
src/Controllers/Internal/ModerationController.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using sodoff.Model;
|
||||
using sodoff.Schema;
|
||||
using sodoff.Services;
|
||||
|
||||
namespace sodoff.Controllers.Internal
|
||||
{
|
||||
[ApiController]
|
||||
public class ModerationController : Controller
|
||||
{
|
||||
public readonly DBContext ctx;
|
||||
public readonly ModerationService moderationService;
|
||||
public ModerationController(DBContext ctx, ModerationService moderationService)
|
||||
{
|
||||
this.ctx = ctx;
|
||||
this.moderationService = moderationService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Moderation/AddBanToVikingByGuid")]
|
||||
public IActionResult AddBanToVikingByGuid([FromForm] Guid token, [FromForm] Guid userId, [FromForm] int banType, [FromForm] int days = 0)
|
||||
{
|
||||
var validationResult = ValidateSession(token);
|
||||
|
||||
if(validationResult)
|
||||
{
|
||||
// get the viking
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
|
||||
|
||||
// get execution timestamp
|
||||
DateTime timestamp = DateTime.UtcNow;
|
||||
|
||||
DateTime expiration = new DateTime();
|
||||
|
||||
if (days == 0) expiration = new DateTime(9999, 99, 99); // a days value of 0 should mean indefinite ban (might change later)
|
||||
else expiration = timestamp.AddDays(days);
|
||||
|
||||
if (viking != null) return Ok(moderationService.AddBanToViking(viking, (UserBanType)banType, expiration));
|
||||
else return NotFound();
|
||||
}
|
||||
|
||||
return Unauthorized("You Do Not Have Sufficient Permissions To Moderate Users");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Moderation/RemoveBansFromVikingByGuidAndType")]
|
||||
public IActionResult RemoveBanFromVikingByGuid([FromForm] Guid token, [FromForm] Guid userId, [FromForm] int banType)
|
||||
{
|
||||
var validationResult = ValidateSession(token);
|
||||
|
||||
if (validationResult)
|
||||
{
|
||||
// get the viking
|
||||
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
|
||||
|
||||
// remove all bans of type
|
||||
if (viking != null) return Ok(moderationService.RemoveBansFromVikingByType(viking, (UserBanType)banType));
|
||||
else return NotFound();
|
||||
}
|
||||
|
||||
return Unauthorized("You Do Not Have Sufficient Permissions To Moderate Users");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Moderation/CheckForVikingBan")]
|
||||
public IActionResult CheckForVikingBan([FromForm] Guid token)
|
||||
{
|
||||
// get viking session
|
||||
var session = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token);
|
||||
|
||||
if (session != null && session.Viking != null) return Ok(moderationService.IsVikingBanned(session.Viking));
|
||||
else return Ok(UserBanType.NotBanned); // invalid session, for now just return not banned
|
||||
}
|
||||
|
||||
private bool ValidateSession(Guid token)
|
||||
{
|
||||
// get active session
|
||||
var session = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token);
|
||||
|
||||
if (session != null)
|
||||
{
|
||||
// most endpoints here should only be activated by a 'Moderator' or above
|
||||
Role? vikingRole = session.Viking?.MMORoles.FirstOrDefault()?.Role;
|
||||
if (vikingRole != null && (vikingRole == Role.Moderator || vikingRole == Role.Admin)) return true;
|
||||
else return false;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Dockerfile
Normal file
22
src/Dockerfile
Normal file
@ -0,0 +1,22 @@
|
||||
# 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
|
||||
|
||||
# move mods and assets directors from sources to /app
|
||||
RUN mv src/mods /app && ln -s /app/mods src/
|
||||
RUN mv src/assets /app && ln -s /app/assets src/
|
||||
|
||||
# Create clean run environment (without source and sdk)
|
||||
# FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||
# WORKDIR /app
|
||||
# COPY --from=build /app .
|
||||
|
||||
# Run the application
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["./sodoff"]
|
||||
176
src/Middleware/AssetMiddleware.cs
Normal file
176
src/Middleware/AssetMiddleware.cs
Normal file
@ -0,0 +1,176 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using sodoff.Configuration;
|
||||
using sodoff.Util;
|
||||
|
||||
namespace sodoff.Middleware;
|
||||
public class AssetMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IOptions<AssetServerConfig> config;
|
||||
private readonly Regex autoEncryptRegexp;
|
||||
|
||||
public AssetMiddleware(RequestDelegate next, IOptions<AssetServerConfig> config)
|
||||
{
|
||||
_next = next;
|
||||
this.config = config;
|
||||
if (config.Value.AutoEncryptRegexp != "")
|
||||
autoEncryptRegexp = new Regex(config.Value.AutoEncryptRegexp);
|
||||
else
|
||||
autoEncryptRegexp = null;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context.Connection.LocalPort == config.Value.Port)
|
||||
await GetAssetAsync(context);
|
||||
else
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private async Task GetAssetAsync(HttpContext context)
|
||||
{
|
||||
string path = context.Request.Path;
|
||||
|
||||
if (path is null || config.Value.Mode == AssetServerMode.None) {
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
string assetPath;
|
||||
if (config.Value.UseAnyURLPrefix) {
|
||||
int firstSlash = path.IndexOf('/', 1);
|
||||
if (firstSlash < 0) {
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
assetPath = path.Remove(0, firstSlash + 1);
|
||||
} else {
|
||||
if (!string.IsNullOrEmpty(config.Value.URLPrefix) && !path.StartsWith("/" + config.Value.URLPrefix)) {
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
assetPath = path.Remove(0, config.Value.URLPrefix.Length + 1);
|
||||
}
|
||||
|
||||
string localPath = GetLocalPath("assets/" + assetPath);
|
||||
|
||||
if (localPath == string.Empty && config.Value.Mode == AssetServerMode.Partial && config.Value.UseCache)
|
||||
localPath = GetLocalPath("assets-cache/" + assetPath);
|
||||
|
||||
if (localPath == string.Empty) {
|
||||
if (config.Value.Mode == AssetServerMode.Partial)
|
||||
await GetRemoteAsset(context, assetPath);
|
||||
else
|
||||
context.Response.StatusCode = 404;
|
||||
}
|
||||
else {
|
||||
context.Response.Headers["Content-Type"] = "application/octet-stream";
|
||||
bool needEncrypt = autoEncryptRegexp != null && autoEncryptRegexp.IsMatch(localPath);
|
||||
if (needEncrypt) {
|
||||
await context.Response.WriteAsync(
|
||||
TripleDES.EncryptASCII(
|
||||
System.IO.File.ReadAllText(localPath),
|
||||
config.Value.AutoEncryptKey
|
||||
)
|
||||
);
|
||||
} else {
|
||||
await context.Response.SendFileAsync(Path.GetFullPath(localPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetRemoteAsset(HttpContext context, string path)
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
string filePath = Path.GetFullPath("assets-cache/" + path);
|
||||
string filePathTmp = filePath + Path.GetRandomFileName().Substring(0, 8);
|
||||
try {
|
||||
using (var response = await client.GetAsync(
|
||||
config.Value.ProviderURL + path,
|
||||
HttpCompletionOption.ResponseHeadersRead
|
||||
)) {
|
||||
if (response.IsSuccessStatusCode) {
|
||||
if (response.Content.Headers.ContentType?.MediaType != null)
|
||||
context.Response.Headers["Content-Type"] = response.Content.Headers.ContentType?.MediaType;
|
||||
|
||||
if (response.Content.Headers.ContentLength != null)
|
||||
context.Response.Headers["Content-Length"] = response.Content.Headers.ContentLength.ToString();
|
||||
|
||||
using (var inputStream = await response.Content.ReadAsStreamAsync()) {
|
||||
if (config.Value.UseCache) {
|
||||
string dirPath = Path.GetDirectoryName(filePath);
|
||||
if (!Directory.Exists(dirPath)) {
|
||||
Directory.CreateDirectory(dirPath);
|
||||
}
|
||||
|
||||
// copy data retrieved from upstream server to file and to response for game client
|
||||
using (var fileStream = File.Open(filePathTmp, FileMode.Create)) {
|
||||
// read response from upstream server
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0) {
|
||||
// write to temporary file
|
||||
var task1 = fileStream.WriteAsync(buffer, 0, bytesRead);
|
||||
// send to client
|
||||
var task2 = context.Response.Body.WriteAsync(buffer, 0, bytesRead);
|
||||
// wait for finish both writes
|
||||
await Task.WhenAll(task1, task2);
|
||||
}
|
||||
}
|
||||
|
||||
// after successfully write data to temporary file, rename it to proper asset filename
|
||||
try {
|
||||
File.Move(filePathTmp, filePath);
|
||||
} catch (System.IO.IOException) {
|
||||
// this can happen for example if two clients request the same file in the same time
|
||||
// TODO: avoid redundant download in those cases
|
||||
File.Delete(filePathTmp);
|
||||
}
|
||||
} else {
|
||||
await inputStream.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.Response.StatusCode = 404;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception err) {
|
||||
Console.WriteLine($"{err}");
|
||||
if (File.Exists(filePathTmp))
|
||||
File.Delete(filePathTmp);
|
||||
if (!context.Response.HasStarted)
|
||||
context.Response.StatusCode = 404;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLocalPath(string path)
|
||||
{
|
||||
if (File.Exists(path)) return path;
|
||||
|
||||
string[] qualityTiers = { "/High/", "/Mid/", "/Low/" };
|
||||
|
||||
if (config.Value.SubstituteMissingLocalAssets)
|
||||
{
|
||||
foreach (var tier in qualityTiers)
|
||||
{
|
||||
if (path.Contains(tier))
|
||||
{
|
||||
foreach (var otherTier in qualityTiers)
|
||||
{
|
||||
if (otherTier != tier)
|
||||
{
|
||||
string otherPath = path.Replace(tier, otherTier);
|
||||
if (File.Exists(otherPath)) return otherPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
15
src/Model/AchievementPoints.cs
Normal file
15
src/Model/AchievementPoints.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
public class AchievementPoints {
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public int Type { get; set; }
|
||||
|
||||
public int Value { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
18
src/Model/AchievementTaskState.cs
Normal file
18
src/Model/AchievementTaskState.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
[PrimaryKey(nameof(TaskId), nameof(VikingId))]
|
||||
public class AchievementTaskState {
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public int TaskId { get; set; }
|
||||
|
||||
public int Points { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
25
src/Model/Buddy.cs
Normal file
25
src/Model/Buddy.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using sodoff.Schema;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class Buddy
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
public int VikingId { get; set; }
|
||||
public int BuddyVikingId { get; set; }
|
||||
|
||||
public BuddyStatus BuddyStatus1 { get; set; }
|
||||
public BuddyStatus BuddyStatus2 { get; set; }
|
||||
|
||||
public bool IsBestFriend1 { get; set; }
|
||||
public bool IsBestFriend2 { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public virtual Viking? Viking { get; set; }
|
||||
public virtual Viking? BuddyViking { get; set; }
|
||||
}
|
||||
393
src/Model/DBContext.cs
Normal file
393
src/Model/DBContext.cs
Normal file
@ -0,0 +1,393 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using sodoff.Configuration;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace sodoff.Model;
|
||||
public class DBContext : DbContext {
|
||||
public DbSet<User> Users { get; set; } = null!;
|
||||
public DbSet<Viking> Vikings { get; set; } = null!;
|
||||
public DbSet<Dragon> Dragons { get; set; } = null!;
|
||||
public DbSet<Image> Images { get; set; } = null!;
|
||||
public DbSet<Session> Sessions { get; set; } = null!;
|
||||
public DbSet<Pair> Pairs { get; set; } = null!;
|
||||
public DbSet<PairData> PairData { get; set; } = null!;
|
||||
public DbSet<TaskStatus> TaskStatuses { get; set; } = null!;
|
||||
public DbSet<InventoryItem> InventoryItems { get; set; } = null!;
|
||||
public DbSet<MissionState> MissionStates { get; set; } = null!;
|
||||
public DbSet<Room> Rooms { get; set; } = null!;
|
||||
public DbSet<SceneData> SceneData { get; set; } = null!;
|
||||
public DbSet<RoomItem> RoomItems { get; set; } = null!;
|
||||
public DbSet<GameData> GameData { get; set; } = null!;
|
||||
public DbSet<GameDataPair> GameDataPairs { get; set; } = null!;
|
||||
public DbSet<AchievementPoints> AchievementPoints { get; set; } = null!;
|
||||
public DbSet<ProfileAnswer> ProfileAnswers { get; set; } = null!;
|
||||
public DbSet<MMORole> MMORoles { get; set; } = null!;
|
||||
public DbSet<Party> Parties { get; set; } = null!;
|
||||
public DbSet<AchievementTaskState> AchievementTaskState { get; set; } = null!;
|
||||
public DbSet<SavedData> SavedData { get; set; } = null!;
|
||||
public DbSet<UserMissionData> UserMissions { get; set; } = null!;
|
||||
public DbSet<Neighborhood> Neighborhoods { get; set; } = null!;
|
||||
// we had a brief debate on whether it's neighborhoods or neighborheed
|
||||
public DbSet<Group> Groups { get; set; } = null!;
|
||||
public DbSet<GroupMember> GroupMembers { get; set; } = null!;
|
||||
public DbSet<GroupJoinRequest> GroupJoinRequests { get; set; } = null!;
|
||||
public DbSet<Rating> Ratings { get; set; } = null!;
|
||||
public DbSet<RatingRank> RatingRanks { get; set; } = null!;
|
||||
public DbSet<UserMissionData> UserMissionData { get; set; } = null!;
|
||||
public DbSet<UserBadgeCompleteData> UserBadgesCompleted { get; set; } = null!;
|
||||
public DbSet<UserBan> Bans { get; set; } = null!;
|
||||
public DbSet<Message> Messages { get; set; } = null!;
|
||||
public DbSet<Buddy> Buddies { get; set; } = null!;
|
||||
|
||||
private readonly IOptions<ApiServerConfig> config;
|
||||
|
||||
public DBContext(IOptions<ApiServerConfig> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
|
||||
#if USE_POSTGRESQL
|
||||
if (config.Value.DbProvider == DbProviders.PostgreSQL) {
|
||||
optionsBuilder.UseNpgsql(config.Value.DbConnection).UseLazyLoadingProxies();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if USE_MYSQL
|
||||
if (config.Value.DbProvider == DbProviders.MySQL) {
|
||||
optionsBuilder.UseMySQL(config.Value.DbConnection).UseLazyLoadingProxies();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if USE_SQLITE
|
||||
if (config.Value.DbProvider == DbProviders.SQLite) {
|
||||
string DbPath;
|
||||
if (String.IsNullOrEmpty(config.Value.DbPath)) {
|
||||
DbPath = Path.Join(Directory.GetCurrentDirectory(), "sodoff.db");
|
||||
} else {
|
||||
DbPath = config.Value.DbPath;
|
||||
}
|
||||
optionsBuilder.UseSqlite($"Data Source={DbPath}").UseLazyLoadingProxies();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
throw new Exception($"Unsupported DbProvider {config.Value.DbProvider}");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder) {
|
||||
builder.Entity<GroupMember>().HasKey(["VikingID", "GroupID"]);
|
||||
builder.Entity<GroupJoinRequest>().HasKey(["VikingID", "GroupID"]);
|
||||
|
||||
// Sessions
|
||||
builder.Entity<Session>().HasOne(s => s.User)
|
||||
.WithMany(e => e.Sessions)
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Session>().HasOne(s => s.Viking)
|
||||
.WithMany(e => e.Sessions)
|
||||
.HasForeignKey(e => e.VikingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Users
|
||||
builder.Entity<User>().HasMany(u => u.Sessions)
|
||||
.WithOne(e => e.User);
|
||||
|
||||
builder.Entity<User>().HasMany(u => u.PairData)
|
||||
.WithOne(e => e.User);
|
||||
|
||||
builder.Entity<User>().HasMany(u => u.Vikings)
|
||||
.WithOne(e => e.User);
|
||||
|
||||
// Vikings
|
||||
builder.Entity<Viking>().HasOne(v => v.User)
|
||||
.WithMany(e => e.Vikings)
|
||||
.HasForeignKey(e => e.UserId);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.Dragons)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.Sessions)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.MissionStates)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.Rooms)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.SceneData)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.AchievementPoints)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.PairData)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.Images)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.TaskStatuses)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.AchievementTaskStates)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasOne(v => v.SelectedDragon)
|
||||
.WithOne()
|
||||
.HasForeignKey<Viking>(e => e.SelectedDragonId);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.GameData)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.SavedData)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.ProfileAnswers)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.Parties)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.MMORoles)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasOne(v => v.Neighborhood)
|
||||
.WithOne(e => e.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasOne(v => v.GroupMembership)
|
||||
.WithOne(g => g.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.Ratings)
|
||||
.WithOne(r => r.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.UserMissions)
|
||||
.WithOne(r => r.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.UserBadgesCompleted)
|
||||
.WithOne(r => r.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.UserBans)
|
||||
.WithOne(r => r.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.MessageBoard)
|
||||
.WithOne(r => r.ToViking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.MessagesMade)
|
||||
.WithOne(r => r.Viking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.BuddyList)
|
||||
.WithOne(r => r.BuddyViking);
|
||||
|
||||
builder.Entity<Viking>().HasMany(v => v.BuddiesMade)
|
||||
.WithOne(r => r.Viking);
|
||||
|
||||
// Dragons
|
||||
builder.Entity<Dragon>().HasOne(d => d.Viking)
|
||||
.WithMany(e => e.Dragons)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<Dragon>().HasMany(d => d.PairData)
|
||||
.WithOne(e => e.Dragon);
|
||||
|
||||
// PairData & Pair
|
||||
builder.Entity<PairData>().HasMany(p => p.Pairs)
|
||||
.WithOne(e => e.PairData);
|
||||
|
||||
builder.Entity<PairData>().HasOne(p => p.Viking)
|
||||
.WithMany(e => e.PairData)
|
||||
.HasForeignKey(e => e.VikingId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<PairData>().HasOne(p => p.User)
|
||||
.WithMany(e => e.PairData)
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<PairData>().HasOne(p => p.Dragon)
|
||||
.WithMany(e => e.PairData)
|
||||
.HasForeignKey(e => e.DragonId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Pair>()
|
||||
.HasOne(p => p.PairData)
|
||||
.WithMany(pd => pd.Pairs)
|
||||
.HasForeignKey(p => p.MasterId)
|
||||
.HasPrincipalKey(e => e.Id);
|
||||
|
||||
// Inventory & InventoryItem
|
||||
builder.Entity<Viking>()
|
||||
.HasMany(v => v.InventoryItems)
|
||||
.WithOne(i => i.Viking);
|
||||
|
||||
builder.Entity<InventoryItem>()
|
||||
.HasOne(e => e.Viking)
|
||||
.WithMany(e => e.InventoryItems)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
// Room & RoomItem
|
||||
builder.Entity<Room>().HasOne(r => r.Viking)
|
||||
.WithMany(e => e.Rooms)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<Room>().HasMany(r => r.Items)
|
||||
.WithOne(e => e.Room);
|
||||
|
||||
builder.Entity<RoomItem>().HasOne(i => i.Room)
|
||||
.WithMany(r => r.Items)
|
||||
.HasForeignKey(e => e.RoomId);
|
||||
|
||||
// GameData
|
||||
|
||||
builder.Entity<GameData>().HasOne(e => e.Viking)
|
||||
.WithMany(e => e.GameData);
|
||||
|
||||
builder.Entity<GameData>().HasMany(e => e.GameDataPairs)
|
||||
.WithOne(e => e.GameData);
|
||||
|
||||
builder.Entity<GameDataPair>().HasOne(e => e.GameData)
|
||||
.WithMany(e => e.GameDataPairs);
|
||||
|
||||
// Others ..
|
||||
builder.Entity<Image>().HasOne(s => s.Viking)
|
||||
.WithMany(e => e.Images)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<TaskStatus>().HasKey(e => new { e.Id, e.VikingId, e.MissionId });
|
||||
|
||||
builder.Entity<TaskStatus>()
|
||||
.HasOne(t => t.Viking)
|
||||
.WithMany(v => v.TaskStatuses)
|
||||
.HasForeignKey(t => t.VikingId);
|
||||
|
||||
builder.Entity<MissionState>().HasOne(m => m.Viking)
|
||||
.WithMany(e => e.MissionStates)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<AchievementTaskState>().HasOne(m => m.Viking)
|
||||
.WithMany(e => e.AchievementTaskStates)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<AchievementPoints>().HasKey(e => new { e.VikingId, e.Type });
|
||||
|
||||
builder.Entity<AchievementPoints>()
|
||||
.HasOne(e => e.Viking)
|
||||
.WithMany(e => e.AchievementPoints)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<SavedData>().HasKey(e => new { e.VikingId, e.SaveId });
|
||||
|
||||
builder.Entity<SavedData>()
|
||||
.HasOne(e => e.Viking)
|
||||
.WithMany(v => v.SavedData)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<Party>().HasOne(i => i.Viking)
|
||||
.WithMany(i => i.Parties);
|
||||
|
||||
builder.Entity<ProfileAnswer>().HasOne(i => i.Viking)
|
||||
.WithMany(i => i.ProfileAnswers)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
builder.Entity<SceneData>().HasOne(i => i.Viking)
|
||||
.WithMany(i => i.SceneData)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
// MMO Roles
|
||||
builder.Entity<MMORole>().HasOne(r => r.Viking)
|
||||
.WithMany(e => e.MMORoles)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
// Neighborhoods
|
||||
builder.Entity<Neighborhood>().HasOne(r => r.Viking)
|
||||
.WithOne(e => e.Neighborhood)
|
||||
.HasForeignKey<Neighborhood>(e => e.VikingId);
|
||||
|
||||
// Groups
|
||||
#if SEED_NON_SOD_DATA
|
||||
builder.Entity<Group>().HasData(
|
||||
new Model.Group {
|
||||
Id = 1,
|
||||
GroupID = new Guid("db0aa225-2f0e-424c-83a7-73783fe63fef"),
|
||||
Name = "Dragons",
|
||||
Color = "234,57,23",
|
||||
Logo = "RS_DATA/Content/PlayerData/EMD/IcoEMDTeamDragons.png",
|
||||
Type = Schema.GroupType.System,
|
||||
GameID = Util.ClientVersion.EMD,
|
||||
MaxMemberLimit = int.MaxValue
|
||||
},
|
||||
new Model.Group {
|
||||
Id = 2,
|
||||
GroupID = new Guid("db0aa225-2f0e-424c-83a7-73783fe63fef"),
|
||||
Name = "Scorpions",
|
||||
Color = "120,183,53",
|
||||
Logo = "RS_DATA/Content/PlayerData/EMD/IcoEMDTeamScorpions.png",
|
||||
Type = Schema.GroupType.System,
|
||||
GameID = Util.ClientVersion.EMD,
|
||||
MaxMemberLimit = int.MaxValue
|
||||
}
|
||||
);
|
||||
#endif
|
||||
builder.Entity<Group>().HasMany(r => r.Vikings)
|
||||
.WithOne(v => v.Group);
|
||||
builder.Entity<GroupMember>().HasOne(r => r.Group)
|
||||
.WithMany(g => g.Vikings);
|
||||
builder.Entity<GroupMember>().HasOne(r => r.Viking)
|
||||
.WithOne(g => g.GroupMembership);
|
||||
builder.Entity<Group>().HasMany(r => r.JoinRequests)
|
||||
.WithOne(r => r.Group);
|
||||
builder.Entity<GroupJoinRequest>().HasOne(r => r.Group)
|
||||
.WithMany(g => g.JoinRequests);
|
||||
|
||||
// Rating
|
||||
builder.Entity<Rating>().HasOne(r => r.Viking)
|
||||
.WithMany(v => v.Ratings)
|
||||
.HasForeignKey(r => r.VikingId);
|
||||
|
||||
builder.Entity<Rating>().HasOne(r => r.Rank)
|
||||
.WithMany(rr => rr.Ratings)
|
||||
.HasForeignKey(r => r.RankId);
|
||||
|
||||
builder.Entity<RatingRank>().HasMany(rr => rr.Ratings)
|
||||
.WithOne(r => r.Rank);
|
||||
|
||||
// old ("step") missions
|
||||
builder.Entity<UserMissionData>().HasOne(r => r.Viking)
|
||||
.WithMany(v => v.UserMissions)
|
||||
.HasForeignKey(r => r.VikingId);
|
||||
|
||||
builder.Entity<UserBadgeCompleteData>().HasOne(r => r.Viking)
|
||||
.WithMany(v => v.UserBadgesCompleted)
|
||||
.HasForeignKey(r => r.VikingId);
|
||||
|
||||
// Bans
|
||||
builder.Entity<UserBan>().HasOne(r => r.Viking)
|
||||
.WithMany(e => e.UserBans)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
|
||||
// Messages
|
||||
builder.Entity<Message>().HasOne(r => r.Viking)
|
||||
.WithMany(e => e.MessagesMade)
|
||||
.HasForeignKey(e => e.VikingId)
|
||||
.IsRequired(false); // system messages usually don't have am author
|
||||
|
||||
builder.Entity<Message>().HasOne(r => r.ToViking)
|
||||
.WithMany(e => e.MessageBoard)
|
||||
.HasForeignKey(e => e.ToVikingId);
|
||||
|
||||
builder.Entity<Message>().HasMany(e => e.Replies)
|
||||
.WithOne(e => e.ParentMessage)
|
||||
.HasForeignKey(e => e.ParentMessageId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Buddies
|
||||
builder.Entity<Buddy>().HasOne(r => r.BuddyViking)
|
||||
.WithMany(e => e.BuddyList)
|
||||
.HasForeignKey(e => e.BuddyVikingId);
|
||||
|
||||
builder.Entity<Buddy>().HasOne(r => r.Viking)
|
||||
.WithMany(e => e.BuddiesMade)
|
||||
.HasForeignKey(e => e.VikingId);
|
||||
}
|
||||
}
|
||||
29
src/Model/Dragon.cs
Normal file
29
src/Model/Dragon.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
[Index(nameof(EntityId), IsUnique = true)]
|
||||
public class Dragon {
|
||||
[Key]
|
||||
// [JsonIgnore] used in serialised xml (stables)
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid EntityId { get; set; }
|
||||
|
||||
[Required]
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public string? RaisedPetData { get; set; }
|
||||
|
||||
public int? PetXP { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
public virtual ICollection<PairData> PairData { get; set; } = null!;
|
||||
}
|
||||
23
src/Model/GameData.cs
Normal file
23
src/Model/GameData.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
public class GameData {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public int GameId { get; set; }
|
||||
public int Difficulty { get; set; }
|
||||
public int GameLevel { get; set; }
|
||||
public DateTime DatePlayed { get; set; }
|
||||
public bool IsMultiplayer { get; set; }
|
||||
public bool Win { get; set; }
|
||||
public bool Loss { get; set; }
|
||||
public virtual ICollection<GameDataPair> GameDataPairs { get; set; } = null!;
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
}
|
||||
15
src/Model/GameDataPair.cs
Normal file
15
src/Model/GameDataPair.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
public class GameDataPair {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
[JsonIgnore]
|
||||
public int GameDataId { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
public int Value { get; set; }
|
||||
[JsonIgnore]
|
||||
public virtual GameData GameData { get; set; } = null!;
|
||||
}
|
||||
35
src/Model/Group.cs
Normal file
35
src/Model/Group.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using sodoff.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class Group {
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public Guid GroupID { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public GroupType Type { get; set; }
|
||||
|
||||
public string? Logo { get; set; }
|
||||
|
||||
public string? Color { get; set; }
|
||||
|
||||
public int Points { get; set; }
|
||||
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
public DateTime? LastActiveTime { get; set; }
|
||||
|
||||
public uint GameID { get; set; }
|
||||
|
||||
public int MaxMemberLimit { get; set; }
|
||||
|
||||
public virtual ICollection<GroupMember> Vikings { get; set; } = null!;
|
||||
|
||||
public virtual ICollection<GroupJoinRequest> JoinRequests { get; set; } = null!;
|
||||
}
|
||||
15
src/Model/GroupJoinRequest.cs
Normal file
15
src/Model/GroupJoinRequest.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class GroupJoinRequest {
|
||||
[Key]
|
||||
public int VikingID { get; set; }
|
||||
[Key]
|
||||
public int GroupID { get; set; }
|
||||
|
||||
public string? Message { get; set; }
|
||||
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
public virtual Group Group { get; set; } = null!;
|
||||
}
|
||||
16
src/Model/GroupMember.cs
Normal file
16
src/Model/GroupMember.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using sodoff.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class GroupMember {
|
||||
[Key]
|
||||
public int VikingID { get; set; }
|
||||
[Key]
|
||||
public int GroupID { get; set; }
|
||||
public UserRole UserRole { get; set; }
|
||||
public DateTime JoinDate { get; set; }
|
||||
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
public virtual Group Group { get; set; } = null!;
|
||||
}
|
||||
25
src/Model/Image.cs
Normal file
25
src/Model/Image.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
[PrimaryKey(nameof(ImageType), nameof(ImageSlot), nameof(VikingId))]
|
||||
public class Image {
|
||||
[Required]
|
||||
public string ImageType { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public int ImageSlot { get; set; }
|
||||
|
||||
[Required]
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public string? ImageData { get; set; }
|
||||
|
||||
public string? TemplateName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
}
|
||||
24
src/Model/InventoryItem.cs
Normal file
24
src/Model/InventoryItem.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model {
|
||||
public class InventoryItem {
|
||||
[Key]
|
||||
// [JsonIgnore] used as room id, used in serialised xml (pairs, ...)
|
||||
public int Id { get; set; }
|
||||
|
||||
public int ItemId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public string? StatsSerialized { get; set; }
|
||||
|
||||
public string? AttributesSerialized { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
}
|
||||
18
src/Model/MMORole.cs
Normal file
18
src/Model/MMORole.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using sodoff.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class MMORole {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public Role Role { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
39
src/Model/Message.cs
Normal file
39
src/Model/Message.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using sodoff.Schema;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class Message
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int? VikingId { get; set; }
|
||||
public int ToVikingId { get; set; }
|
||||
|
||||
public int QueueID { get; set; }
|
||||
public int? ConversationID { get; set; }
|
||||
public int? ParentMessageId { get; set; }
|
||||
|
||||
public MessageType? MessageType { get; set; }
|
||||
public MessageTypeID? MessageTypeID { get; set; }
|
||||
public MessageLevel MessageLevel { get; set; }
|
||||
|
||||
public string? Data { get; set; }
|
||||
public string? MemberMessage { get; set; }
|
||||
public string? NonMemberMessage { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? LastUpdatedAt { get; set; }
|
||||
|
||||
public bool IsDeleted { get; set; }
|
||||
public bool IsNew { get; set; }
|
||||
public bool IsPrivate { get; set; }
|
||||
|
||||
public virtual Viking? Viking { get; set; }
|
||||
public virtual Viking? ToViking { get; set; }
|
||||
|
||||
public virtual Message? ParentMessage { get; set; }
|
||||
public virtual ICollection<Message> Replies { get; set; } = null!;
|
||||
}
|
||||
26
src/Model/MissionState.cs
Normal file
26
src/Model/MissionState.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class MissionState {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int MissionId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
|
||||
public MissionStatus MissionStatus { get; set; }
|
||||
|
||||
public bool? UserAccepted { get; set; }
|
||||
}
|
||||
|
||||
public enum MissionStatus {
|
||||
Upcoming,Active,Completed
|
||||
}
|
||||
35
src/Model/Neighborhood.cs
Normal file
35
src/Model/Neighborhood.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using sodoff.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model
|
||||
{
|
||||
public class Neighborhood
|
||||
{
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid Slot0 { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid Slot1 { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid Slot2 { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid Slot3 { get; set; }
|
||||
|
||||
[Required]
|
||||
public Guid Slot4 { get; set; }
|
||||
}
|
||||
}
|
||||
22
src/Model/Pair.cs
Normal file
22
src/Model/Pair.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class Pair {
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Key { get; set; } = null!;
|
||||
|
||||
public string Value { get; set; } = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public int MasterId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual PairData PairData { get; set; } = null!;
|
||||
}
|
||||
37
src/Model/PairData.cs
Normal file
37
src/Model/PairData.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
[Index(nameof(UserId))]
|
||||
[Index(nameof(VikingId))]
|
||||
public class PairData {
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int PairId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Guid? UserId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int? VikingId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int? DragonId { get; set; }
|
||||
|
||||
public virtual ICollection<Pair> Pairs { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual User? User { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Dragon? Dragon { get; set; }
|
||||
}
|
||||
21
src/Model/Party.cs
Normal file
21
src/Model/Party.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model
|
||||
{
|
||||
public class Party
|
||||
{
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
public string Location { get; set; } = null!;
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
public DateTime ExpirationDate { get; set; } = DateTime.UtcNow;
|
||||
public bool? PrivateParty { get; set; }
|
||||
public string LocationIconAsset { get; set; } = null!;
|
||||
public string AssetBundle { get; set; } = null!;
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
}
|
||||
19
src/Model/ProfileAnswer.cs
Normal file
19
src/Model/ProfileAnswer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model
|
||||
{
|
||||
public class ProfileAnswer
|
||||
{
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
public int QuestionID { get; set; }
|
||||
public int AnswerID { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
21
src/Model/Rating.cs
Normal file
21
src/Model/Rating.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class Rating {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
public int RankId { get; set; }
|
||||
|
||||
public int Value { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; }
|
||||
public virtual RatingRank Rank { get; set; }
|
||||
}
|
||||
21
src/Model/RatingRank.cs
Normal file
21
src/Model/RatingRank.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class RatingRank {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int CategoryID { get; set; }
|
||||
public int? RatedEntityID { get; set; }
|
||||
public string? RatedUserID { get; set; }
|
||||
|
||||
public int Rank { get; set; }
|
||||
public float RatingAverage { get; set; } // On a scale of 1-5
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<Rating> Ratings { get; set; } = null!;
|
||||
}
|
||||
22
src/Model/Room.cs
Normal file
22
src/Model/Room.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class Room {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string RoomId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
|
||||
public virtual ICollection<RoomItem> Items { get; set; } = null!;
|
||||
}
|
||||
17
src/Model/RoomItem.cs
Normal file
17
src/Model/RoomItem.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
public class RoomItem {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int RoomId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Room Room { get; set; }
|
||||
|
||||
public string RoomItemData { get; set; }
|
||||
}
|
||||
12
src/Model/SaveData.cs
Normal file
12
src/Model/SaveData.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
public class SavedData {
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
public uint SaveId { get; set; }
|
||||
public string? SerializedData { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
}
|
||||
18
src/Model/SceneData.cs
Normal file
18
src/Model/SceneData.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model
|
||||
{
|
||||
public class SceneData
|
||||
{
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
public int VikingId { get; set; }
|
||||
public string SceneName { get; set; } = null!;
|
||||
public string XmlData { get; set; } = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking Viking { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
17
src/Model/Session.cs
Normal file
17
src/Model/Session.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace sodoff.Model;
|
||||
public class Session {
|
||||
[Key]
|
||||
public Guid ApiToken { get; set; }
|
||||
|
||||
public Guid? UserId { get; set; }
|
||||
|
||||
public int? VikingId { get; set; }
|
||||
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
public virtual User? User { get; set; }
|
||||
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
19
src/Model/TaskStatus.cs
Normal file
19
src/Model/TaskStatus.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model {
|
||||
public class TaskStatus {
|
||||
public int Id { get; set; }
|
||||
|
||||
public int MissionId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
|
||||
public string? Payload { get; set; }
|
||||
|
||||
public bool Completed { get; set; } = false;
|
||||
}
|
||||
}
|
||||
25
src/Model/User.cs
Normal file
25
src/Model/User.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
[Index(nameof(Username), IsUnique = true)]
|
||||
public class User {
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Email { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public string Username { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public string Password { get; set; } = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<Session> Sessions { get; set; } = null!;
|
||||
public virtual ICollection<Viking> Vikings { get; set; } = null!;
|
||||
public virtual ICollection<PairData> PairData { get; set; } = null!;
|
||||
}
|
||||
16
src/Model/UserBadgeCompleteData.cs
Normal file
16
src/Model/UserBadgeCompleteData.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace sodoff.Model
|
||||
{
|
||||
public class UserBadgeCompleteData
|
||||
{
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
public int BadgeId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
}
|
||||
19
src/Model/UserBan.cs
Normal file
19
src/Model/UserBan.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using sodoff.Schema;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
public class UserBan
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int VikingId { get; set; }
|
||||
|
||||
public UserBanType UserBanType { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? ExpiresOn { get; set; }
|
||||
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
21
src/Model/UserMissionData.cs
Normal file
21
src/Model/UserMissionData.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace sodoff.Model
|
||||
{
|
||||
[PrimaryKey(nameof(VikingId), nameof(WorldId), nameof(MissionId))]
|
||||
public class UserMissionData
|
||||
{
|
||||
[JsonIgnore]
|
||||
public int VikingId { get; set; }
|
||||
public int WorldId { get; set; }
|
||||
public int MissionId { get; set; }
|
||||
public int StepId { get; set; }
|
||||
public int TaskId { get; set; }
|
||||
public bool IsCompleted { get; set; } = false;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual Viking? Viking { get; set; }
|
||||
}
|
||||
}
|
||||
71
src/Model/Viking.cs
Normal file
71
src/Model/Viking.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using sodoff.Schema;
|
||||
|
||||
namespace sodoff.Model;
|
||||
|
||||
[Index(nameof(Uid), IsUnique = true)]
|
||||
public class Viking {
|
||||
[Key]
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
|
||||
public Guid Uid { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[JsonIgnore]
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public string? AvatarSerialized { get; set; }
|
||||
|
||||
public string? BuddyCode { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int? SelectedDragonId { get; set; }
|
||||
|
||||
public bool? Online { get; set; }
|
||||
public int? CurrentRoomId { get; set; }
|
||||
public string? CurrentZone { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<Session> Sessions { get; set; } = null!;
|
||||
[JsonIgnore]
|
||||
public virtual User User { get; set; } = null!;
|
||||
public virtual ICollection<Dragon> Dragons { get; set; } = null!;
|
||||
public virtual ICollection<Image> Images { get; set; } = null!;
|
||||
public virtual ICollection<MissionState> MissionStates { get; set; } = null!;
|
||||
public virtual ICollection<TaskStatus> TaskStatuses { get; set; } = null!;
|
||||
public virtual ICollection<AchievementTaskState> AchievementTaskStates { get; set; } = null!;
|
||||
public virtual ICollection<Room> Rooms { get; set; } = null!;
|
||||
public virtual ICollection<SceneData> SceneData { get; set; } = null!;
|
||||
public virtual ICollection<AchievementPoints> AchievementPoints { get; set; } = null!;
|
||||
public virtual ICollection<PairData> PairData { get; set; } = null!;
|
||||
public virtual ICollection<InventoryItem> InventoryItems { get; set; } = null!;
|
||||
public virtual ICollection<GameData> GameData { get; set; } = null!;
|
||||
public virtual ICollection<ProfileAnswer> ProfileAnswers { get; set; } = null!;
|
||||
public virtual ICollection<SavedData> SavedData { get; set; } = null!;
|
||||
public virtual ICollection<Party> Parties { get; set; } = null!;
|
||||
[JsonIgnore]
|
||||
public virtual ICollection<MMORole> MMORoles { get; set; } = null!;
|
||||
public virtual Neighborhood? Neighborhood { get; set; } = null!;
|
||||
[JsonIgnore]
|
||||
public virtual GroupMember? GroupMembership { get; set; } = null!;
|
||||
public virtual ICollection<Rating> Ratings { get; set; } = null!;
|
||||
public virtual Dragon? SelectedDragon { get; set; }
|
||||
public virtual ICollection<UserMissionData> UserMissions { get; set; } = null!;
|
||||
public virtual ICollection<UserBadgeCompleteData> UserBadgesCompleted { get; set; } = null!;
|
||||
public virtual ICollection<UserBan> UserBans { get; set; } = null!;
|
||||
public virtual ICollection<Message> MessageBoard { get; set; } = null!;
|
||||
public virtual ICollection<Message> MessagesMade { get; set; } = null!;
|
||||
public virtual ICollection<Buddy> BuddyList { get; set; } = null!;
|
||||
public virtual ICollection<Buddy> BuddiesMade { get; set; } = null!;
|
||||
|
||||
public DateTime? CreationDate { get; set; }
|
||||
public DateTime? BirthDate { get; set; }
|
||||
public Gender? Gender { get; set; }
|
||||
public uint? GameVersion { get; set; }
|
||||
}
|
||||
76
src/Program.cs
Normal file
76
src/Program.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using sodoff.Configuration;
|
||||
using sodoff.Middleware;
|
||||
using sodoff.Model;
|
||||
using sodoff.Services;
|
||||
using sodoff.Utils;
|
||||
using System.Xml;
|
||||
using System.Net;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.Configure<AssetServerConfig>(builder.Configuration.GetSection("AssetServer"));
|
||||
builder.Services.Configure<ApiServerConfig>(builder.Configuration.GetSection("ApiServer"));
|
||||
builder.Services.AddControllers(options => {
|
||||
options.OutputFormatters.Add(new XmlSerializerOutputFormatter(new XmlWriterSettings() { OmitXmlDeclaration = false }));
|
||||
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
|
||||
options.Filters.Add<LogRequestOnError>();
|
||||
});
|
||||
builder.Services.AddDbContext<DBContext>();
|
||||
|
||||
// create Modding Service singleton here ... do this before start http server to avoid serve invalid assets list
|
||||
builder.Services.AddSingleton<ModdingService>(new ModdingService());
|
||||
// other singletons will be created at first use ...
|
||||
builder.Services.AddSingleton<MissionStoreSingleton>();
|
||||
builder.Services.AddSingleton<AchievementStoreSingleton>();
|
||||
builder.Services.AddSingleton<ItemService>();
|
||||
builder.Services.AddSingleton<StoreService>();
|
||||
builder.Services.AddSingleton<XmlDataService>();
|
||||
builder.Services.AddSingleton<MMOConfigService>();
|
||||
builder.Services.AddSingleton<MMOClientService>();
|
||||
|
||||
builder.Services.AddScoped<KeyValueService>();
|
||||
builder.Services.AddScoped<MissionService>();
|
||||
builder.Services.AddScoped<RoomService>();
|
||||
builder.Services.AddScoped<InventoryService>();
|
||||
builder.Services.AddScoped<AchievementService>();
|
||||
builder.Services.AddScoped<GameDataService>();
|
||||
builder.Services.AddScoped<ProfileService>();
|
||||
builder.Services.AddScoped<NeighborhoodService>();
|
||||
builder.Services.AddScoped<MessagingService>();
|
||||
builder.Services.AddScoped<BuddyService>();
|
||||
builder.Services.AddScoped<WorldIdService>();
|
||||
builder.Services.AddScoped<ModerationService>();
|
||||
|
||||
builder.Services.AddHostedService<MMOClientService>(provider => provider.GetService<MMOClientService>());
|
||||
|
||||
bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue<bool>("Enabled");
|
||||
string assetIP = builder.Configuration.GetSection("AssetServer").GetValue<string>("ListenIP");
|
||||
int assetPort = builder.Configuration.GetSection("AssetServer").GetValue<int>("Port");
|
||||
if (assetServer)
|
||||
builder.Services.Configure<KestrelServerOptions>(options => {
|
||||
if (String.IsNullOrEmpty(assetIP) || assetIP == "*")
|
||||
options.ListenAnyIP(assetPort);
|
||||
else
|
||||
options.Listen(IPAddress.Parse(assetIP), assetPort);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
using var scope = app.Services.CreateScope();
|
||||
|
||||
scope.ServiceProvider.GetRequiredService<DBContext>().Database.EnsureCreated();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
|
||||
if (assetServer)
|
||||
app.UseMiddleware<AssetMiddleware>();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
10059
src/Resources/achievements/achievementid_sod.xml
Normal file
10059
src/Resources/achievements/achievementid_sod.xml
Normal file
File diff suppressed because it is too large
Load Diff
6599
src/Resources/achievements/achievementtaskinfo_emd.xml
Normal file
6599
src/Resources/achievements/achievementtaskinfo_emd.xml
Normal file
File diff suppressed because it is too large
Load Diff
17710
src/Resources/achievements/achievementtaskinfo_mam.xml
Normal file
17710
src/Resources/achievements/achievementtaskinfo_mam.xml
Normal file
File diff suppressed because it is too large
Load Diff
17943
src/Resources/achievements/achievementtaskinfo_mb.xml
Normal file
17943
src/Resources/achievements/achievementtaskinfo_mb.xml
Normal file
File diff suppressed because it is too large
Load Diff
32864
src/Resources/achievements/achievementtaskinfo_sod.xml
Normal file
32864
src/Resources/achievements/achievementtaskinfo_sod.xml
Normal file
File diff suppressed because it is too large
Load Diff
6767
src/Resources/achievements/achievementtaskinfo_ss.xml
Normal file
6767
src/Resources/achievements/achievementtaskinfo_ss.xml
Normal file
File diff suppressed because it is too large
Load Diff
16725
src/Resources/achievements/achievementtaskinfo_wojs.xml
Normal file
16725
src/Resources/achievements/achievementtaskinfo_wojs.xml
Normal file
File diff suppressed because it is too large
Load Diff
12
src/Resources/announcements_ss.xml
Normal file
12
src/Resources/announcements_ss.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<Announcements>
|
||||
<Announcement>
|
||||
<AnnouncementID>1</AnnouncementID>
|
||||
<Description>SanctuaryBabySS</Description>
|
||||
<AnnouncementText>[[Message]]=[[Wanna raise and train a Dragon? Go to the Enchanted Sanctuary today!]]
|
||||
[[SceneName]]=[[SanctuaryBabySS]]
|
||||
</AnnouncementText>
|
||||
<Type>3</Type>
|
||||
<StartDate>2024-02-03</StartDate>
|
||||
<EndDate>2034-02-04</EndDate>
|
||||
</Announcement>
|
||||
</Announcements>
|
||||
17
src/Resources/announcements_wojs.xml
Normal file
17
src/Resources/announcements_wojs.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<Announcements>
|
||||
<Announcement>
|
||||
<AnnouncementID>10</AnnouncementID>
|
||||
<Description>Concert Song</Description>
|
||||
<AnnouncementText>[[Song1]]=[[RS_DATA/PfConcertSong02.unity3d/PfConcertSong02;181;0]]
|
||||
[[Song2]]=[[RS_DATA/PfConcertSong01.unity3d/PfConcertSong01;189;0]]
|
||||
[[Song3]]=[[RS_DATA/PfConcertSong03.unity3d/PfConcertSong03;268;0]]
|
||||
[[Song4]]=[[RS_DATA/PfConcertSong04.unity3d/PfConcertSong04;209;0]]
|
||||
[[Song5]]=[[RS_DATA/PfConcertSong05.unity3d/PfConcertSong05;61;0]]
|
||||
[[Song6]]=[[RS_DATA/PfConcertSong06.unity3d/PfConcertSong06;90;0]]
|
||||
[[Interval]]=[[30]]
|
||||
</AnnouncementText>
|
||||
<Type>3</Type>
|
||||
<StartDate>2024-02-03</StartDate>
|
||||
<EndDate>2034-02-04</EndDate>
|
||||
</Announcement>
|
||||
</Announcements>
|
||||
98
src/Resources/content_arcade.xml
Normal file
98
src/Resources/content_arcade.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<ArrayOfContentInfoData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ContentInfoData>
|
||||
<DisplayName>Punk Punk Drop</DisplayName>
|
||||
<Description>Punk Punk Drop</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Arcade_PunkPunkDrop_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>Bejeweled</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/Arcade_PunkPunkDrop_01.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Buggy Racer</DisplayName>
|
||||
<Description>Buggy Racer game inside Main Street.</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Arcade_BuggyRacer_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>DuneBuggyMenu</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/ArcadeBuggyRacer256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>GhostTown Grab</DisplayName>
|
||||
<Description>GhostTown Grab game inside Main Street.</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Arcade_GhostGrab_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>WesternWorldMain</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/ArcadeGhostGrab256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Dodge n Dash</DisplayName>
|
||||
<Description>Dodge Bot game inside Main Street.</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Arcade_DodgeBot_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>DgDodge</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/ArcadeDodgeBot256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Sound Bop</DisplayName>
|
||||
<Description>Sound Bop game inside Main Street.</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Arcade_SoundBop_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>SsSimon</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/ArcadeSoundBop256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Dragon Playtime</DisplayName>
|
||||
<Description>Dragon Playtime</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Arcade_dragon_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>JSPetMainStreet</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/ArcadeDragon256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>3</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Robo-A-Go-Go</DisplayName>
|
||||
<Description>Robo-A-Go-Go</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Arcade_Robo_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>JSRoboAGoGoMainStreet</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/ArcadeRobo256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>3</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Fruit Salad Chop</DisplayName>
|
||||
<Description>Fruit Salad Chop</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/FruitSaladChopScreen.png</ThumbnailUrl>
|
||||
<LinkUrl>FruitSaladChop</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/FruitSaladChop.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>3</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
</ArrayOfContentInfoData>
|
||||
52
src/Resources/content_blastermovie.xml
Normal file
52
src/Resources/content_blastermovie.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<ArrayOfContentInfoData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<!-- Blaster B.F.F's -->
|
||||
<ContentInfoData>
|
||||
<DisplayName>Blaster B.F.F's</DisplayName>
|
||||
<ThumbnailUrl>RS_MOVIES/BlasterMovieBestFriend.jpg</ThumbnailUrl>
|
||||
<LinkUrl>RS_MOVIES/BlasterMovieBestFriend.ogg</LinkUrl>
|
||||
<ContentType>5</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<CategoryUrl>RS_MOVIES/MasterBasicsIntro.jpg</CategoryUrl>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
<!-- The Terrible Monkey King -->
|
||||
<ContentInfoData>
|
||||
<DisplayName>The Terrible Monkey King</DisplayName>
|
||||
<ThumbnailUrl>RS_MOVIES/BlasterMovie9-12.jpg</ThumbnailUrl>
|
||||
<LinkUrl>RS_MOVIES/BlasterMovie9-12.ogg</LinkUrl>
|
||||
<ContentType>5</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<CategoryUrl>RS_MOVIES/MasterBasicsIntro.jpg</CategoryUrl>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
<!-- Master the Basics -->
|
||||
<ContentInfoData>
|
||||
<DisplayName>Master the Basics</DisplayName>
|
||||
<ThumbnailUrl>RS_MOVIES/MasterBasicsIntro.jpg</ThumbnailUrl>
|
||||
<LinkUrl>RS_MOVIES/MasterBasicsIntro.ogg</LinkUrl>
|
||||
<ContentType>5</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<CategoryUrl>RS_MOVIES/MasterBasicsIntro.jpg</CategoryUrl>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
<!-- Gelator the Brain Drainer -->
|
||||
<ContentInfoData>
|
||||
<DisplayName>Gelator the Brain Drainer</DisplayName>
|
||||
<ThumbnailUrl>RS_MOVIES/BlasterMovie6-9.jpg</ThumbnailUrl>
|
||||
<LinkUrl>RS_MOVIES/BlasterMovie6-9.ogg</LinkUrl>
|
||||
<ContentType>5</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<CategoryUrl>RS_MOVIES/MasterBasicsIntro.jpg</CategoryUrl>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
<!-- Bugs in the System -->
|
||||
<ContentInfoData>
|
||||
<DisplayName>Bugs in the System</DisplayName>
|
||||
<ThumbnailUrl>RS_MOVIES/MB89Intro.jpg</ThumbnailUrl>
|
||||
<LinkUrl>RS_MOVIES/MB89Intro.ogg</LinkUrl>
|
||||
<ContentType>5</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<CategoryUrl>RS_MOVIES/MasterBasicsIntro.jpg</CategoryUrl>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
</ArrayOfContentInfoData>
|
||||
602
src/Resources/content_jukebox.xml
Normal file
602
src/Resources/content_jukebox.xml
Normal file
@ -0,0 +1,602 @@
|
||||
<ArrayOfContentInfoData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ContentInfoData>
|
||||
<DisplayName>The BFF Song by Miya Mackenzie</DisplayName>
|
||||
<Description>The BFF Song</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_The_BFF_Song.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>The Tide is High by Miya Mackenzie</DisplayName>
|
||||
<Description>The Tide is High</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_Tide_Is_High.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Won't Back Down by Miya Mackenzie</DisplayName>
|
||||
<Description>Won't Back Down</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_Won't_Back_Down.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Addicted to You by Miya Mackenzie</DisplayName>
|
||||
<Description>Addicted to You</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_Addcited_to_You.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Before and After by Miya Mackenzie</DisplayName>
|
||||
<Description>Before and After</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_Before_And_After.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Gonna Be Alright by Miya Mackenzie</DisplayName>
|
||||
<Description>Gonna Be Alright</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_Gonna_Be_Alright.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>I Feel Free by Miya Mackenzie</DisplayName>
|
||||
<Description>I Feel Free</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_I_Feel_Free.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>I'm So Done by Miya Mackenzie</DisplayName>
|
||||
<Description>I'm So Done</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_I'm_So_Done.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>One Wish by Miya Mackenzie</DisplayName>
|
||||
<Description>One Wish</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_One_Wish.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Only You by Miya Mackenzie</DisplayName>
|
||||
<Description>Only You</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Miya_Mackenzie_Only_You.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>A Fisherman</DisplayName>
|
||||
<Description>A Fisherman</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Fisher.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>ABC Harmony</DisplayName>
|
||||
<Description>ABC Harmony</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/AllInHarmony.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Art Friends</DisplayName>
|
||||
<Description>Art Friends</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/KRFWMV1_song.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Baa-baa Black Sheep</DisplayName>
|
||||
<Description>Baa-baa Black Sheep</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/SHEEP.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Barn House Rock</DisplayName>
|
||||
<Description>Barn House Rock</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/ELVIS.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Be Your Best</DisplayName>
|
||||
<Description>Be Your Best</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Responsiblity.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Come on Down to the Barnyard</DisplayName>
|
||||
<Description>Come on Down to the Barnyard</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/RHYTHM.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Count in Spanish</DisplayName>
|
||||
<Description>Count in Spanish</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/CountInEnglishAndSpanish.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Egyptian Treasure Map</DisplayName>
|
||||
<Description>Egyptian Treasure Map</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Egypt.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Fire Safety Tips</DisplayName>
|
||||
<Description>Fire Safety Tips</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/FireSafety.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Fish in the Pond</DisplayName>
|
||||
<Description>Fish in the Pond</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Pond.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Frere Jacques, Dormez Vous?</DisplayName>
|
||||
<Description>Frere Jacques, Dormez Vous?</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/SLEEP.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Get Ready for JumpStart</DisplayName>
|
||||
<Description>Get Ready for JumpStart</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/WelcomeToJumpStartWorld.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Grandma's Garden</DisplayName>
|
||||
<Description>Grandma's Garden</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/InGrandmasGarden.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Grandma's House</DisplayName>
|
||||
<Description>Grandma's House</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Mmsong.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Help Me Find My Cat</DisplayName>
|
||||
<Description>Help Me Find My Cat</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Gatito.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Home on the Range</DisplayName>
|
||||
<Description>Home on the Range</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/GiveMeAHome.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>I Am a Horse</DisplayName>
|
||||
<Description>I Am a Horse</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/HORSE.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Just Ask</DisplayName>
|
||||
<Description>Just Ask</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/SC_RM06_Ask.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Learn Your ABCs</DisplayName>
|
||||
<Description>Learn Your ABCs</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/ABCsAreFor.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Letters of the Alphabet</DisplayName>
|
||||
<Description>Letters of the Alphabet</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Tssong.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Make a Collage</DisplayName>
|
||||
<Description>Make a Collage</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/KRFHMV1_song.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>My Waterfall</DisplayName>
|
||||
<Description>My Waterfall</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/AMusicWaterfall.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Old MacDonald Had a Farm</DisplayName>
|
||||
<Description>Old MacDonald Had a Farm</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/OLDMAC.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Read to Tell a Story</DisplayName>
|
||||
<Description>Read to Tell a Story</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Read.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Rhyme with Coat</DisplayName>
|
||||
<Description>Rhyme with Coat</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/RainCoat_Mixed.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Rock 'N' Roll Verbs</DisplayName>
|
||||
<Description>Rock 'N' Roll Verbs</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/RockNRollVerbs.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Science Is Fab</DisplayName>
|
||||
<Description>Science Is Fab</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/LearningScience.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Shapes of Life</DisplayName>
|
||||
<Description>Shapes of Life</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/AllTheShapes.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Sing in Spanish</DisplayName>
|
||||
<Description>Sing in Spanish</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Colores.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Song About a Line</DisplayName>
|
||||
<Description>Song About a Line</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/KRRRMV1_song.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Space Super Hero</DisplayName>
|
||||
<Description>Space Super Hero</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Blaster.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Textures Oh Yeah</DisplayName>
|
||||
<Description>Textures Oh Yeah</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/KRCSMV1_song.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Turtle Footprints</DisplayName>
|
||||
<Description>Turtle Footprints</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Foot.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Two Little Mice</DisplayName>
|
||||
<Description>Two Little Mice</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/CAT.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>What Is a Noun?</DisplayName>
|
||||
<Description>What Is a Noun?</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/WhatIsANoun.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>What Is a Rhyme?</DisplayName>
|
||||
<Description>What Is a Rhyme?</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Rhyme.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>What Is Art?</DisplayName>
|
||||
<Description>What Is Art?</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/BSSNG1.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Where Are the Animals?</DisplayName>
|
||||
<Description>Where Are the Animals?</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/Wdsong.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Where Did It Come From?</DisplayName>
|
||||
<Description>Where Did It Come From?</Description>
|
||||
<ThumbnailUrl />
|
||||
<LinkUrl>RS_DATA/Content/Music/WhereDidItComeFrom.ogg</LinkUrl>
|
||||
<TextureUrl />
|
||||
<RolloverUrl />
|
||||
<CategoryUrl />
|
||||
<ContentType>1</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>0</LinkType>
|
||||
</ContentInfoData>
|
||||
</ArrayOfContentInfoData>
|
||||
122
src/Resources/content_learning.xml
Normal file
122
src/Resources/content_learning.xml
Normal file
@ -0,0 +1,122 @@
|
||||
<ArrayOfContentInfoData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ContentInfoData>
|
||||
<DisplayName>Monkey Madness</DisplayName>
|
||||
<Description>Monkey Madness</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_Monkey_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>PnMonkeyMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnMonkey256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge3_5.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Lucky Ducky</DisplayName>
|
||||
<Description>Lucky Ducky</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_Duckies_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>CnDuckyMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnDuckies256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge3_5.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Hops' JetPack</DisplayName>
|
||||
<Description>Hops' JetPack MS</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_JetPack_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>JetPackMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnJetPack256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge5_8.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Bubble Trouble</DisplayName>
|
||||
<Description>Bubble Trouble MS</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_Bubble_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>BubbleTroubleMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnBubble256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge5_8.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Bunny Hop</DisplayName>
|
||||
<Description>Bunny Hop MS</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_Bunny_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>PnBunnyMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnBunny256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge3_5.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>True</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Ladybug Learning</DisplayName>
|
||||
<Description>Ladybug Learning MS</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_Ladybug_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>CnLadyBugMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnLadybug256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge3_5.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>True</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Cow-abunga</DisplayName>
|
||||
<Description>Cow-abunga MS</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_Cow_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>PnCowMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnCow256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge3_5.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>True</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Kite Delight</DisplayName>
|
||||
<Description>Kite Delight MS</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/Learning_Kite_screen_01.png</ThumbnailUrl>
|
||||
<LinkUrl>CnKiteMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/LearnKite256.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge3_5.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>True</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Blast Off Math</DisplayName>
|
||||
<Description>Blast Off Math</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/JS_Blastoff_Math.png</ThumbnailUrl>
|
||||
<LinkUrl>BlastOffMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/BlastOffMath.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge5_8.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Blast Off Reading</DisplayName>
|
||||
<Description>Blast Off Reading</Description>
|
||||
<ThumbnailUrl>RS_DATA/Content/MSArcade/JS_Blastoff_Reading.png</ThumbnailUrl>
|
||||
<LinkUrl>BlastOffMS</LinkUrl>
|
||||
<TextureUrl>RS_DATA/Content/MSArcade/BlastOffReading.png</TextureUrl>
|
||||
<RolloverUrl />
|
||||
<CategoryUrl>RS_DATA/Content/MSArcade/IcoAge5_8.png</CategoryUrl>
|
||||
<ContentType>4</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>4</LinkType>
|
||||
</ContentInfoData>
|
||||
</ArrayOfContentInfoData>
|
||||
42
src/Resources/content_movie.xml
Normal file
42
src/Resources/content_movie.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<ArrayOfContentInfoData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ContentInfoData>
|
||||
<DisplayName>JumpStart Commercial - Dragons (2009)</DisplayName>
|
||||
<LinkUrl>RS_DATA/Content/MovieTheaterContent/jscom01.ogg</LinkUrl>
|
||||
<RolloverUrl/>
|
||||
<CategoryUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsshield.jpg</CategoryUrl>
|
||||
<ThumbnailUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsshield.jpg</ThumbnailUrl>
|
||||
<ContentType>2</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>JumpStart Commercial - Madagascar</DisplayName>
|
||||
<LinkUrl>RS_DATA/Content/MovieTheaterContent/jscom02.ogg</LinkUrl>
|
||||
<RolloverUrl/>
|
||||
<CategoryUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsshield.jpg</CategoryUrl>
|
||||
<ThumbnailUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsshield.jpg</ThumbnailUrl>
|
||||
<ContentType>2</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>JumpStart 2009 Intro</DisplayName>
|
||||
<LinkUrl>RS_DATA/Content/MovieTheaterContent/jsintro.ogg</LinkUrl>
|
||||
<RolloverUrl/>
|
||||
<CategoryUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsshield.jpg</CategoryUrl>
|
||||
<ThumbnailUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsshield.jpg</ThumbnailUrl>
|
||||
<ContentType>2</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
<ContentInfoData>
|
||||
<DisplayName>Big Cat</DisplayName>
|
||||
<LinkUrl>RS_DATA/Content/MovieTheaterContent/bigcat.ogg</LinkUrl>
|
||||
<RolloverUrl/>
|
||||
<CategoryUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsrlogo.png</CategoryUrl>
|
||||
<ThumbnailUrl>RS_DATA/Content/MovieTheaterContent/Thumbnails/jsrlogo.png</ThumbnailUrl>
|
||||
<ContentType>2</ContentType>
|
||||
<MemberOnly>false</MemberOnly>
|
||||
<LinkType>2</LinkType>
|
||||
</ContentInfoData>
|
||||
</ArrayOfContentInfoData>
|
||||
151
src/Resources/defaulthouse.xml
Normal file
151
src/Resources/defaulthouse.xml
Normal file
@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<HouseData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Part>
|
||||
<PartTypes>Foundation_GEO</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseFoundations01.unity3d/PfFoundationCottage</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Foundation_STL</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseFoundations01.unity3d/StyleFoundationsCottageLogTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Foundation_SKN</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Foundation_GLS</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Foundation_TRM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Stairs_GEO</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseStairs01.unity3d/PfStairCottage</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Stairs_STL</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseStairs01.unity3d/StyleStairsCottageLogTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Stairs_SKN</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Stairs_GLS</PartTypes>
|
||||
<PartNames>EMPTY</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Stairs_TRM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Wall_GEO</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseWalls01.unity3d/PfWallCottage</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Wall_STL</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseWalls01.unity3d/StyleWallsCottageLogTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Wall_SKN</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Wall_GLS</PartTypes>
|
||||
<PartNames>EMPTY</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Wall_TRM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Roof_GEO</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseRoofs01.unity3d/PfRoofCottage</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Roof_STL</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseRoofs01.unity3d/StyleRoofsCottageHauntedTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Roof_SKN</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Roof_GLS</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Roof_TRM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Window_GEO</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseWindows01.unity3d/PfWindowCottage</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Window_STL</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseWindows01.unity3d/StyleWindowsCottageLogTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Window_SKN</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Window_GLS</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Window_TRM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Door_GEO</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseDoors01.unity3d/PfDoorCottage</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Door_STL</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseDoors01.unity3d/StyleDoorsCottageLogTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Door_SKN</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Door_GLS</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Door_TRM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Chimney_GEO</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseChimneys01.unity3d/PfChimneyCottage</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Chimney_STL</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseChimneys01.unity3d/StyleChimneysCottageHauntedTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Chimney_SKN</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseSkin01.unity3d/SkinCastleClassicTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Chimney_GLS</PartTypes>
|
||||
<PartNames>EMPTY</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>Chimney_TRM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>DEFAULT_GLASS</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseGlass01.unity3d/GlassBlueTex</PartNames>
|
||||
</Part>
|
||||
<Part>
|
||||
<PartTypes>DEFAULT_TRIM</PartTypes>
|
||||
<PartNames>RS_SHARED/MyHouseTrims01.unity3d/TrimStoneIvyTex</PartNames>
|
||||
</Part>
|
||||
</HouseData>
|
||||
1463
src/Resources/displaynames.xml
Normal file
1463
src/Resources/displaynames.xml
Normal file
File diff suppressed because it is too large
Load Diff
438
src/Resources/dtrewards.xml
Normal file
438
src/Resources/dtrewards.xml
Normal file
@ -0,0 +1,438 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ArrayOfInt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<int>12374</int>
|
||||
<int>12375</int>
|
||||
<int>12377</int>
|
||||
<int>12378</int>
|
||||
<int>12379</int>
|
||||
<int>12380</int>
|
||||
<int>12381</int>
|
||||
<int>12382</int>
|
||||
<int>12383</int>
|
||||
<int>12392</int>
|
||||
<int>12393</int>
|
||||
<int>12394</int>
|
||||
<int>12396</int>
|
||||
<int>12397</int>
|
||||
<int>12398</int>
|
||||
<int>13660</int>
|
||||
<int>13661</int>
|
||||
<int>13684</int>
|
||||
<int>13685</int>
|
||||
<int>13686</int>
|
||||
<int>13687</int>
|
||||
<int>13700</int>
|
||||
<int>13701</int>
|
||||
<int>13718</int>
|
||||
<int>13719</int>
|
||||
<int>13720</int>
|
||||
<int>13721</int>
|
||||
<int>13722</int>
|
||||
<int>13723</int>
|
||||
<int>13724</int>
|
||||
<int>13725</int>
|
||||
<int>13726</int>
|
||||
<int>13727</int>
|
||||
<int>13728</int>
|
||||
<int>13747</int>
|
||||
<int>13748</int>
|
||||
<int>13749</int>
|
||||
<int>13750</int>
|
||||
<int>13751</int>
|
||||
<int>13752</int>
|
||||
<int>13753</int>
|
||||
<int>13754</int>
|
||||
<int>13755</int>
|
||||
<int>13757</int>
|
||||
<int>13758</int>
|
||||
<int>13759</int>
|
||||
<int>13760</int>
|
||||
<int>13761</int>
|
||||
<int>13762</int>
|
||||
<int>13763</int>
|
||||
<int>13764</int>
|
||||
<int>13765</int>
|
||||
<int>13766</int>
|
||||
<int>13767</int>
|
||||
<int>13768</int>
|
||||
<int>13769</int>
|
||||
<int>13770</int>
|
||||
<int>13771</int>
|
||||
<int>13772</int>
|
||||
<int>13773</int>
|
||||
<int>14842</int>
|
||||
<int>14843</int>
|
||||
<int>14844</int>
|
||||
<int>14845</int>
|
||||
<int>14846</int>
|
||||
<int>14847</int>
|
||||
<int>14848</int>
|
||||
<int>14849</int>
|
||||
<int>14850</int>
|
||||
<int>14851</int>
|
||||
<int>14852</int>
|
||||
<int>14853</int>
|
||||
<int>14854</int>
|
||||
<int>14855</int>
|
||||
<int>14856</int>
|
||||
<int>14857</int>
|
||||
<int>14858</int>
|
||||
<int>14859</int>
|
||||
<int>14860</int>
|
||||
<int>14861</int>
|
||||
<int>14862</int>
|
||||
<int>14863</int>
|
||||
<int>14864</int>
|
||||
<int>14865</int>
|
||||
<int>14866</int>
|
||||
<int>14867</int>
|
||||
<int>14868</int>
|
||||
<int>14869</int>
|
||||
<int>14870</int>
|
||||
<int>14871</int>
|
||||
<int>14873</int>
|
||||
<int>14874</int>
|
||||
<int>14875</int>
|
||||
<int>14876</int>
|
||||
<int>14877</int>
|
||||
<int>14878</int>
|
||||
<int>14879</int>
|
||||
<int>14880</int>
|
||||
<int>14881</int>
|
||||
<int>14882</int>
|
||||
<int>14883</int>
|
||||
<int>14908</int>
|
||||
<int>14921</int>
|
||||
<int>14923</int>
|
||||
<int>14933</int>
|
||||
<int>15778</int>
|
||||
<int>15781</int>
|
||||
<int>15784</int>
|
||||
<int>15785</int>
|
||||
<int>15899</int>
|
||||
<int>16249</int>
|
||||
<int>16669</int>
|
||||
<int>16670</int>
|
||||
<int>16671</int>
|
||||
<int>16729</int>
|
||||
<int>16730</int>
|
||||
<int>16731</int>
|
||||
<int>16735</int>
|
||||
<int>16806</int>
|
||||
<int>17864</int>
|
||||
<int>17865</int>
|
||||
<int>17885</int>
|
||||
<int>17886</int>
|
||||
<int>17943</int>
|
||||
<int>17997</int>
|
||||
<int>18064</int>
|
||||
<int>18099</int>
|
||||
<int>18111</int>
|
||||
<int>18274</int>
|
||||
<int>18276</int>
|
||||
<int>18278</int>
|
||||
<int>18289</int>
|
||||
<int>18290</int>
|
||||
<int>18291</int>
|
||||
<int>18339</int>
|
||||
<int>18340</int>
|
||||
<int>18467</int>
|
||||
<int>18468</int>
|
||||
<int>18482</int>
|
||||
<int>18483</int>
|
||||
<int>18489</int>
|
||||
<int>18490</int>
|
||||
<int>18491</int>
|
||||
<int>18492</int>
|
||||
<int>18493</int>
|
||||
<int>18494</int>
|
||||
<int>18495</int>
|
||||
<int>18496</int>
|
||||
<int>18497</int>
|
||||
<int>18498</int>
|
||||
<int>18501</int>
|
||||
<int>18751</int>
|
||||
<int>18752</int>
|
||||
<int>18779</int>
|
||||
<int>18780</int>
|
||||
<int>18781</int>
|
||||
<int>18782</int>
|
||||
<int>18783</int>
|
||||
<int>18784</int>
|
||||
<int>18785</int>
|
||||
<int>18812</int>
|
||||
<int>18813</int>
|
||||
<int>18814</int>
|
||||
<int>19002</int>
|
||||
<int>19021</int>
|
||||
<int>19022</int>
|
||||
<int>19023</int>
|
||||
<int>19024</int>
|
||||
<int>19025</int>
|
||||
<int>19026</int>
|
||||
<int>19027</int>
|
||||
<int>19028</int>
|
||||
<int>19029</int>
|
||||
<int>19030</int>
|
||||
<int>19046</int>
|
||||
<int>19047</int>
|
||||
<int>19112</int>
|
||||
<int>19113</int>
|
||||
<int>19181</int>
|
||||
<int>20261</int>
|
||||
<int>20262</int>
|
||||
<int>20263</int>
|
||||
<int>20304</int>
|
||||
<int>20476</int>
|
||||
<int>20477</int>
|
||||
<int>20478</int>
|
||||
<int>20492</int>
|
||||
<int>20493</int>
|
||||
<int>20496</int>
|
||||
<int>20497</int>
|
||||
<int>20498</int>
|
||||
<int>20499</int>
|
||||
<int>20500</int>
|
||||
<int>20501</int>
|
||||
<int>20502</int>
|
||||
<int>20503</int>
|
||||
<int>20504</int>
|
||||
<int>20505</int>
|
||||
<int>20506</int>
|
||||
<int>20507</int>
|
||||
<int>20508</int>
|
||||
<int>20509</int>
|
||||
<int>20510</int>
|
||||
<int>20511</int>
|
||||
<int>20512</int>
|
||||
<int>20513</int>
|
||||
<int>20515</int>
|
||||
<int>20516</int>
|
||||
<int>20517</int>
|
||||
<int>20518</int>
|
||||
<int>20519</int>
|
||||
<int>20520</int>
|
||||
<int>20521</int>
|
||||
<int>20522</int>
|
||||
<int>20523</int>
|
||||
<int>20524</int>
|
||||
<int>20525</int>
|
||||
<int>20526</int>
|
||||
<int>20527</int>
|
||||
<int>20528</int>
|
||||
<int>20529</int>
|
||||
<int>20530</int>
|
||||
<int>20531</int>
|
||||
<int>20532</int>
|
||||
<int>20533</int>
|
||||
<int>20534</int>
|
||||
<int>20535</int>
|
||||
<int>20536</int>
|
||||
<int>20537</int>
|
||||
<int>20538</int>
|
||||
<int>20539</int>
|
||||
<int>20540</int>
|
||||
<int>20541</int>
|
||||
<int>20542</int>
|
||||
<int>20543</int>
|
||||
<int>20544</int>
|
||||
<int>20545</int>
|
||||
<int>20546</int>
|
||||
<int>20547</int>
|
||||
<int>20548</int>
|
||||
<int>20549</int>
|
||||
<int>20550</int>
|
||||
<int>20551</int>
|
||||
<int>20552</int>
|
||||
<int>20553</int>
|
||||
<int>20554</int>
|
||||
<int>20555</int>
|
||||
<int>20556</int>
|
||||
<int>20557</int>
|
||||
<int>20558</int>
|
||||
<int>20559</int>
|
||||
<int>20560</int>
|
||||
<int>20561</int>
|
||||
<int>20562</int>
|
||||
<int>20563</int>
|
||||
<int>20564</int>
|
||||
<int>20565</int>
|
||||
<int>20566</int>
|
||||
<int>20567</int>
|
||||
<int>20568</int>
|
||||
<int>20569</int>
|
||||
<int>20570</int>
|
||||
<int>20571</int>
|
||||
<int>20572</int>
|
||||
<int>20573</int>
|
||||
<int>20574</int>
|
||||
<int>20575</int>
|
||||
<int>20576</int>
|
||||
<int>20577</int>
|
||||
<int>20578</int>
|
||||
<int>20579</int>
|
||||
<int>20580</int>
|
||||
<int>20581</int>
|
||||
<int>20582</int>
|
||||
<int>20583</int>
|
||||
<int>20584</int>
|
||||
<int>20585</int>
|
||||
<int>20586</int>
|
||||
<int>20587</int>
|
||||
<int>20588</int>
|
||||
<int>20589</int>
|
||||
<int>20590</int>
|
||||
<int>20591</int>
|
||||
<int>20592</int>
|
||||
<int>20593</int>
|
||||
<int>20594</int>
|
||||
<int>20595</int>
|
||||
<int>20596</int>
|
||||
<int>20597</int>
|
||||
<int>20598</int>
|
||||
<int>20599</int>
|
||||
<int>20600</int>
|
||||
<int>20601</int>
|
||||
<int>20602</int>
|
||||
<int>20603</int>
|
||||
<int>20604</int>
|
||||
<int>20605</int>
|
||||
<int>20606</int>
|
||||
<int>20607</int>
|
||||
<int>20608</int>
|
||||
<int>20609</int>
|
||||
<int>20610</int>
|
||||
<int>20857</int>
|
||||
<int>20858</int>
|
||||
<!--<int>7149</int> broken -->
|
||||
<!--<int>7150</int> broken -->
|
||||
<int>8820</int>
|
||||
<int>8821</int>
|
||||
<int>8824</int>
|
||||
<int>8825</int>
|
||||
<int>8826</int>
|
||||
<int>8822</int>
|
||||
|
||||
<!-- blueprints -->
|
||||
<int>18508</int>
|
||||
<int>16191</int>
|
||||
<int>16212</int>
|
||||
<int>16138</int>
|
||||
<int>18328</int>
|
||||
<int>16216</int>
|
||||
<int>18338</int>
|
||||
<int>16124</int>
|
||||
<int>16187</int>
|
||||
<int>18400</int>
|
||||
<int>16057</int>
|
||||
<int>16179</int>
|
||||
<int>18677</int>
|
||||
<int>16166</int>
|
||||
<int>18395</int>
|
||||
<int>16172</int>
|
||||
<int>18331</int>
|
||||
<int>18686</int>
|
||||
<int>16054</int>
|
||||
<int>16147</int>
|
||||
<int>16132</int>
|
||||
<int>16155</int>
|
||||
<int>18397</int>
|
||||
<int>16211</int>
|
||||
<int>18334</int>
|
||||
<!--<int>18510</int> broken bp - no output item -->
|
||||
<int>18332</int>
|
||||
<int>18969</int>
|
||||
<int>16176</int>
|
||||
<int>18386</int>
|
||||
<int>18343</int>
|
||||
<int>18382</int>
|
||||
<int>18381</int>
|
||||
<int>16210</int>
|
||||
<int>18682</int>
|
||||
<int>18347</int>
|
||||
<int>16214</int>
|
||||
<int>16204</int>
|
||||
<int>16136</int>
|
||||
<int>18390</int>
|
||||
<int>18684</int>
|
||||
<int>16149</int>
|
||||
<int>18384</int>
|
||||
<int>16213</int>
|
||||
<!--<int>18511</int> broken bp - no output item -->
|
||||
<int>16183</int>
|
||||
<int>18393</int>
|
||||
<int>18392</int>
|
||||
<int>18399</int>
|
||||
<int>18335</int>
|
||||
<int>16125</int>
|
||||
<int>16202</int>
|
||||
<int>16128</int>
|
||||
<int>18678</int>
|
||||
<int>18388</int>
|
||||
<int>16174</int>
|
||||
<int>18330</int>
|
||||
<int>16170</int>
|
||||
<int>18680</int>
|
||||
<int>16153</int>
|
||||
<int>16139</int>
|
||||
<int>16201</int>
|
||||
<int>18398</int>
|
||||
<int>16157</int>
|
||||
<int>16164</int>
|
||||
<int>18383</int>
|
||||
<int>16205</int>
|
||||
<int>18341</int>
|
||||
<int>16181</int>
|
||||
<int>16208</int>
|
||||
<int>16130</int>
|
||||
<int>18683</int>
|
||||
<int>18344</int>
|
||||
<int>18342</int>
|
||||
<int>16189</int>
|
||||
<!--<int>18512</int> broken bp - no output item -->
|
||||
<int>18350</int>
|
||||
<int>18685</int>
|
||||
<int>16134</int>
|
||||
<int>18337</int>
|
||||
<int>16200</int>
|
||||
<int>16207</int>
|
||||
<int>18394</int>
|
||||
<int>18679</int>
|
||||
<int>18385</int>
|
||||
<int>16143</int>
|
||||
<int>16168</int>
|
||||
<int>18376</int>
|
||||
<int>18389</int>
|
||||
<int>16185</int>
|
||||
<int>18509</int>
|
||||
<int>19184</int>
|
||||
<int>18681</int>
|
||||
<int>18387</int>
|
||||
<int>18396</int>
|
||||
<int>18336</int>
|
||||
<int>16145</int>
|
||||
<int>18687</int>
|
||||
<int>16215</int>
|
||||
<int>18375</int>
|
||||
<int>16151</int>
|
||||
<int>16217</int>
|
||||
<int>18391</int>
|
||||
<int>16141</int>
|
||||
<int>18345</int>
|
||||
<int>16203</int>
|
||||
<int>18351</int>
|
||||
<int>16206</int>
|
||||
<int>18329</int>
|
||||
<int>18333</int>
|
||||
<int>18346</int>
|
||||
<int>16160</int>
|
||||
<int>16209</int>
|
||||
<int>16162</int>
|
||||
<int>8443</int>
|
||||
|
||||
<!-- runes (~1.2% probability) -->
|
||||
<int>18794</int>
|
||||
<int>18794</int>
|
||||
<int>18794</int>
|
||||
<int>18794</int>
|
||||
</ArrayOfInt>
|
||||
16
src/Resources/html/export.xml
Normal file
16
src/Resources/html/export.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Export SoDOff account</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/Export" method="post">
|
||||
<label for="username">Username:</label><br />
|
||||
<input type="text" id="username" name="username" /><br />
|
||||
<label for="password">Password:</label><br />
|
||||
<input type="password" id="password" name="password" /><br /><br />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
24
src/Resources/html/import.xml
Normal file
24
src/Resources/html/import.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Import SoDOff account</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/Import" method="post" enctype="multipart/form-data">
|
||||
<label for="username">Username:</label><br />
|
||||
<input type="text" id="username" name="username" /><br />
|
||||
<label for="password">Password:</label><br />
|
||||
<input type="password" id="password" name="password" /><br /><br />
|
||||
|
||||
<label for="vikingName">Viking name (in imported file):</label><br />
|
||||
<input type="text" id="vikingName" name="vikingName" /><br />
|
||||
<label for="newVikingName">New viking name:</label><br />
|
||||
<input type="text" id="newVikingName" name="newVikingName" /><br />
|
||||
<label for="dataFile">Account data:</label><br />
|
||||
<input type="file" id="dataFile" name="dataFile" /><br /><br />
|
||||
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
1217846
src/Resources/items.xml
Normal file
1217846
src/Resources/items.xml
Normal file
File diff suppressed because it is too large
Load Diff
23
src/Resources/missions/badge_wojs_al.xml
Normal file
23
src/Resources/missions/badge_wojs_al.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<BadgeData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Badge>
|
||||
<BadgeId>1</BadgeId>
|
||||
<Name>Explorer Badge</Name>
|
||||
<Description>Adventure Land Badge 01</Description>
|
||||
<Experience>1</Experience>
|
||||
<Pieces>10</Pieces>
|
||||
<Mask>Badges01.unity3d/MskBadge10</Mask>
|
||||
<Color>Badges01.unity3d/BadgeK07Color</Color>
|
||||
<Grey>Badges01.unity3d/BadgeK07Grey</Grey>
|
||||
<PieceDialog>
|
||||
<FileName>DlgFrankieB1Piece</FileName>
|
||||
<NPC>NPCs.unity3d/PfFrankie</NPC>
|
||||
<Bundle>RS_DATA/Badges01.unity3d</Bundle>
|
||||
</PieceDialog>
|
||||
<CompleteDialog>
|
||||
<FileName>DlgFrankieB1Complete</FileName>
|
||||
<NPC>NPCs.unity3d/PfFrankie</NPC>
|
||||
<Bundle>RS_DATA/Badges01.unity3d</Bundle>
|
||||
</CompleteDialog>
|
||||
</Badge>
|
||||
</BadgeData>
|
||||
765
src/Resources/missions/defaultmissionlist.xml
Normal file
765
src/Resources/missions/defaultmissionlist.xml
Normal file
@ -0,0 +1,765 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<DefaultMissions>
|
||||
<!-- list of default mission for most versions (2.9, 3.12, 3.31) -->
|
||||
<Active>
|
||||
<id>999</id>
|
||||
<id>1035</id>
|
||||
<id>1036</id>
|
||||
<id>1037</id>
|
||||
<id>1046</id>
|
||||
<id>1047</id>
|
||||
<id>1048</id>
|
||||
<id>1185</id>
|
||||
<id>1186</id>
|
||||
<id>1187</id>
|
||||
<id>1188</id>
|
||||
<id>1189</id>
|
||||
<id>1191</id>
|
||||
<id>1192</id>
|
||||
<id>1193</id>
|
||||
<id>1194</id>
|
||||
<id>1195</id>
|
||||
<id>1196</id>
|
||||
<id>1197</id>
|
||||
<id>1198</id>
|
||||
<id>1199</id>
|
||||
<id>1200</id>
|
||||
<id>1201</id>
|
||||
<id>1202</id>
|
||||
<id>1203</id>
|
||||
<id>1204</id>
|
||||
<id>1205</id>
|
||||
<id>1206</id>
|
||||
<id>1207</id>
|
||||
<id>1208</id>
|
||||
<id>1209</id>
|
||||
<id>1210</id>
|
||||
<id>1211</id>
|
||||
<id>1212</id>
|
||||
<id>1213</id>
|
||||
<id>1214</id>
|
||||
<id>1215</id>
|
||||
<id>1216</id>
|
||||
<id>1217</id>
|
||||
<id>1218</id>
|
||||
<id>1219</id>
|
||||
<id>1220</id>
|
||||
<id>1221</id>
|
||||
<id>1222</id>
|
||||
<id>1223</id>
|
||||
<id>1224</id>
|
||||
<id>1225</id>
|
||||
<id>1226</id>
|
||||
<id>1227</id>
|
||||
<id>1228</id>
|
||||
<id>1229</id>
|
||||
<id>1230</id>
|
||||
<id>1231</id>
|
||||
<id>1232</id>
|
||||
<id>1233</id>
|
||||
<id>1234</id>
|
||||
<id>1235</id>
|
||||
<id>1236</id>
|
||||
<id>1237</id>
|
||||
<id>1238</id>
|
||||
<id>1239</id>
|
||||
<id>1240</id>
|
||||
<id>1241</id>
|
||||
<id>1242</id>
|
||||
<id>1243</id>
|
||||
<id>1244</id>
|
||||
<id>1245</id>
|
||||
<id>1250</id>
|
||||
<id>1251</id>
|
||||
<id>1252</id>
|
||||
<id>1253</id>
|
||||
<id>1254</id>
|
||||
<id>1255</id>
|
||||
<id>1256</id>
|
||||
<id>1257</id>
|
||||
<id>1258</id>
|
||||
<id>1259</id>
|
||||
<id>1260</id>
|
||||
<id>1261</id>
|
||||
<id>1262</id>
|
||||
<id>1263</id>
|
||||
<id>1264</id>
|
||||
<id>1265</id>
|
||||
<id>1266</id>
|
||||
<id>1267</id>
|
||||
<id>1268</id>
|
||||
<id>1269</id>
|
||||
<id>1270</id>
|
||||
<id>1271</id>
|
||||
<id>1272</id>
|
||||
<id>1273</id>
|
||||
<id>1274</id>
|
||||
<id>1275</id>
|
||||
<id>1276</id>
|
||||
<id>1277</id>
|
||||
<id>1278</id>
|
||||
<id>1279</id>
|
||||
<id>1280</id>
|
||||
<id>1281</id>
|
||||
<id>1282</id>
|
||||
<id>1283</id>
|
||||
<id>1284</id>
|
||||
<id>1285</id>
|
||||
<id>1286</id>
|
||||
<id>1287</id>
|
||||
<id>1288</id>
|
||||
<id>1289</id>
|
||||
<id>1290</id>
|
||||
<id>1291</id>
|
||||
<id>1292</id>
|
||||
<id>1293</id>
|
||||
<id>1294</id>
|
||||
<id>1295</id>
|
||||
<id>1307</id>
|
||||
<id>1308</id>
|
||||
<id>1309</id>
|
||||
<id>1310</id>
|
||||
<id>1311</id>
|
||||
<id>1312</id>
|
||||
<id>1313</id>
|
||||
<id>1314</id>
|
||||
<id>1315</id>
|
||||
<id>1316</id>
|
||||
<id>1321</id>
|
||||
<id>1322</id>
|
||||
<id>1323</id>
|
||||
<id>1324</id>
|
||||
<id>1325</id>
|
||||
<id>1326</id>
|
||||
<id>1327</id>
|
||||
<id>1328</id>
|
||||
<id>1329</id>
|
||||
<id>1330</id>
|
||||
<id>1345</id>
|
||||
<id>1346</id>
|
||||
<id>1347</id>
|
||||
<id>1348</id>
|
||||
<id>1349</id>
|
||||
<id>1350</id>
|
||||
<id>1351</id>
|
||||
<id>1352</id>
|
||||
<id>1353</id>
|
||||
<id>1354</id>
|
||||
<id>1689</id>
|
||||
<id>1690</id>
|
||||
<id>1691</id>
|
||||
<id>1692</id>
|
||||
<id>1693</id>
|
||||
<id>1694</id>
|
||||
<id>1695</id>
|
||||
<id>1696</id>
|
||||
<id>1697</id>
|
||||
<id>1698</id>
|
||||
<id>1699</id>
|
||||
<id>1700</id>
|
||||
<id>1701</id>
|
||||
<id>1702</id>
|
||||
<id>1703</id>
|
||||
<id>1704</id>
|
||||
<id>1705</id>
|
||||
<id>1706</id>
|
||||
<id>1707</id>
|
||||
<id>1708</id>
|
||||
<id>1709</id>
|
||||
<id>1710</id>
|
||||
<id>1711</id>
|
||||
<id>1712</id>
|
||||
<id>1713</id>
|
||||
<id>1714</id>
|
||||
<id>1715</id>
|
||||
<id>1716</id>
|
||||
<id>1717</id>
|
||||
<id>1718</id>
|
||||
<id>1719</id>
|
||||
<id>1720</id>
|
||||
<id>1721</id>
|
||||
<id>1722</id>
|
||||
<id>1723</id>
|
||||
<id>1724</id>
|
||||
<id>1725</id>
|
||||
<id>1726</id>
|
||||
<id>1727</id>
|
||||
<id>1728</id>
|
||||
<id>1729</id>
|
||||
<id>1730</id>
|
||||
<id>2176</id>
|
||||
<id>2287</id>
|
||||
<id>2303</id>
|
||||
<id>2304</id>
|
||||
<id>2309</id>
|
||||
<id>2392</id>
|
||||
<id>2393</id>
|
||||
<id>2394</id>
|
||||
<id>2395</id>
|
||||
<id>2396</id>
|
||||
<id>2397</id>
|
||||
<id>2398</id>
|
||||
<id>2399</id>
|
||||
<id>2400</id>
|
||||
<id>2401</id>
|
||||
<id>2402</id>
|
||||
<id>2403</id>
|
||||
<id>2404</id>
|
||||
<id>2405</id>
|
||||
<id>2406</id>
|
||||
<id>2407</id>
|
||||
<id>2421</id>
|
||||
<id>2422</id>
|
||||
<id>2424</id>
|
||||
<id>2425</id>
|
||||
<id>2426</id>
|
||||
<id>2427</id>
|
||||
<id>2428</id>
|
||||
<id>2429</id>
|
||||
<id>2430</id>
|
||||
<id>2431</id>
|
||||
<id>2432</id>
|
||||
<id>2433</id>
|
||||
<id>2434</id>
|
||||
<id>2435</id>
|
||||
<id>2436</id>
|
||||
<id>2437</id>
|
||||
<id>2438</id>
|
||||
<id>2439</id>
|
||||
<id>2457</id>
|
||||
<id>2458</id>
|
||||
<id>2510</id>
|
||||
<id>2511</id>
|
||||
<id>2512</id>
|
||||
<id>2513</id>
|
||||
<id>2514</id>
|
||||
<id>2515</id>
|
||||
<id>2516</id>
|
||||
<id>2517</id>
|
||||
<id>2518</id>
|
||||
<id>2519</id>
|
||||
<id>2520</id>
|
||||
<id>2521</id>
|
||||
<id>2562</id>
|
||||
<id>2563</id>
|
||||
<id>2564</id>
|
||||
<id>2565</id>
|
||||
<id>2566</id>
|
||||
<id>2567</id>
|
||||
<id>2568</id>
|
||||
<id>2569</id>
|
||||
<id>2570</id>
|
||||
<id>2571</id>
|
||||
<id>2572</id>
|
||||
<id>2573</id>
|
||||
<id>2578</id>
|
||||
<id>2579</id>
|
||||
<id>2580</id>
|
||||
<id>2581</id>
|
||||
<id>2582</id>
|
||||
<id>2583</id>
|
||||
<id>2584</id>
|
||||
<id>2585</id>
|
||||
<id>2586</id>
|
||||
<id>2587</id>
|
||||
<id>2588</id>
|
||||
<id>2606</id>
|
||||
<id>2607</id>
|
||||
<id>2631</id>
|
||||
<id>2632</id>
|
||||
<id>2633</id>
|
||||
<id>2634</id>
|
||||
<id>2635</id>
|
||||
<id>2636</id>
|
||||
<id>2637</id>
|
||||
<id>2638</id>
|
||||
<id>2639</id>
|
||||
<id>2640</id>
|
||||
<id>2645</id>
|
||||
<id>2646</id>
|
||||
<id>2649</id>
|
||||
<id>2650</id>
|
||||
<id>2652</id>
|
||||
<id>2653</id>
|
||||
<id>2655</id>
|
||||
<id>2656</id>
|
||||
<id>2661</id>
|
||||
<id>2662</id>
|
||||
<id>2663</id>
|
||||
<id>2664</id>
|
||||
<id>2665</id>
|
||||
<id>2666</id>
|
||||
<id>2667</id>
|
||||
<id>2668</id>
|
||||
<id>2669</id>
|
||||
<id>2670</id>
|
||||
<id>2673</id>
|
||||
<id>2674</id>
|
||||
<id>2675</id>
|
||||
<id>2676</id>
|
||||
<id>2677</id>
|
||||
<id>2678</id>
|
||||
<id>2679</id>
|
||||
<id>2680</id>
|
||||
<id>2681</id>
|
||||
<id>2682</id>
|
||||
<id>2683</id>
|
||||
<id>2684</id>
|
||||
<id>2685</id>
|
||||
<id>2686</id>
|
||||
<id>2687</id>
|
||||
<id>2786</id>
|
||||
<id>2787</id>
|
||||
<id>2790</id>
|
||||
<id>2806</id>
|
||||
<id>2807</id>
|
||||
<id>2808</id>
|
||||
<id>2809</id>
|
||||
<id>2810</id>
|
||||
<id>2811</id>
|
||||
<id>2812</id>
|
||||
<id>2813</id>
|
||||
<id>2814</id>
|
||||
<id>2815</id>
|
||||
<id>2816</id>
|
||||
<id>2817</id>
|
||||
<id>2818</id>
|
||||
<id>2819</id>
|
||||
<id>2843</id>
|
||||
<id>2844</id>
|
||||
<id>2845</id>
|
||||
<id>2878</id>
|
||||
<id>2879</id>
|
||||
<id>2894</id>
|
||||
<id>2895</id>
|
||||
<id>2931</id>
|
||||
<id>2932</id>
|
||||
<id>2946</id>
|
||||
<id>2947</id>
|
||||
<id>2972</id>
|
||||
<id>2973</id>
|
||||
<id>3022</id>
|
||||
<id>3023</id>
|
||||
<id>3036</id>
|
||||
<id>3037</id>
|
||||
<id>3038</id>
|
||||
<id>3052</id>
|
||||
<id>3053</id>
|
||||
<id>3069</id>
|
||||
<id>3071</id>
|
||||
<id>3072</id>
|
||||
<id>3073</id>
|
||||
<id>3074</id>
|
||||
<id>3075</id>
|
||||
<id>3076</id>
|
||||
<id>3077</id>
|
||||
<id>3078</id>
|
||||
<id>3079</id>
|
||||
<id>3080</id>
|
||||
<id>3099</id>
|
||||
<id>3100</id>
|
||||
<id>3113</id>
|
||||
<id>3114</id>
|
||||
<id>3115</id>
|
||||
<id>3116</id>
|
||||
<id>3117</id>
|
||||
<id>3118</id>
|
||||
<id>3119</id>
|
||||
<id>3120</id>
|
||||
<id>3121</id>
|
||||
<id>3122</id>
|
||||
<id>3123</id>
|
||||
<id>3124</id>
|
||||
<id>3127</id>
|
||||
<id>3128</id>
|
||||
<id>3129</id>
|
||||
<id>3130</id>
|
||||
<id>3131</id>
|
||||
<id>3132</id>
|
||||
<id>3133</id>
|
||||
<id>3134</id>
|
||||
<id>3135</id>
|
||||
<id>3136</id>
|
||||
<id>3148</id>
|
||||
<id>3149</id>
|
||||
</Active>
|
||||
<Upcoming>
|
||||
<id>1003</id>
|
||||
<id>1014</id>
|
||||
<id>1015</id>
|
||||
<id>1016</id>
|
||||
<id>1017</id>
|
||||
<id>1027</id>
|
||||
<id>1028</id>
|
||||
<id>1029</id>
|
||||
<id>1031</id>
|
||||
<id>1033</id>
|
||||
<id>1038</id>
|
||||
<id>1044</id>
|
||||
<id>1053</id>
|
||||
<id>1054</id>
|
||||
<id>1055</id>
|
||||
<id>1057</id>
|
||||
<id>1058</id>
|
||||
<id>1062</id>
|
||||
<id>1067</id>
|
||||
<id>1074</id>
|
||||
<id>1085</id>
|
||||
<id>1089</id>
|
||||
<id>1090</id>
|
||||
<id>1091</id>
|
||||
<id>1093</id>
|
||||
<id>1095</id>
|
||||
<id>1096</id>
|
||||
<id>1097</id>
|
||||
<id>1099</id>
|
||||
<id>1101</id>
|
||||
<id>1102</id>
|
||||
<id>1106</id>
|
||||
<id>1108</id>
|
||||
<id>1110</id>
|
||||
<id>1111</id>
|
||||
<id>1114</id>
|
||||
<id>1120</id>
|
||||
<id>1121</id>
|
||||
<id>1128</id>
|
||||
<id>1134</id>
|
||||
<id>1140</id>
|
||||
<id>1143</id>
|
||||
<id>1144</id>
|
||||
<id>1150</id>
|
||||
<id>1153</id>
|
||||
<id>1155</id>
|
||||
<id>1159</id>
|
||||
<id>1163</id>
|
||||
<id>1164</id>
|
||||
<id>1166</id>
|
||||
<id>1167</id>
|
||||
<id>1168</id>
|
||||
<id>1169</id>
|
||||
<id>1171</id>
|
||||
<id>1173</id>
|
||||
<id>1179</id>
|
||||
<id>1247</id>
|
||||
<id>1296</id>
|
||||
<id>1304</id>
|
||||
<id>1305</id>
|
||||
<id>1317</id>
|
||||
<id>1318</id>
|
||||
<id>1331</id>
|
||||
<id>1333</id>
|
||||
<id>1335</id>
|
||||
<id>1338</id>
|
||||
<id>1343</id>
|
||||
<id>1344</id>
|
||||
<id>1357</id>
|
||||
<id>1361</id>
|
||||
<id>1362</id>
|
||||
<id>1390</id>
|
||||
<id>1508</id>
|
||||
<id>1529</id>
|
||||
<id>1530</id>
|
||||
<id>1575</id>
|
||||
<id>1579</id>
|
||||
<id>1605</id>
|
||||
<id>1606</id>
|
||||
<id>1607</id>
|
||||
<id>1608</id>
|
||||
<id>1611</id>
|
||||
<id>1612</id>
|
||||
<id>1613</id>
|
||||
<id>1614</id>
|
||||
<id>1615</id>
|
||||
<id>1617</id>
|
||||
<id>1618</id>
|
||||
<id>1619</id>
|
||||
<id>1620</id>
|
||||
<id>1622</id>
|
||||
<id>1623</id>
|
||||
<id>1624</id>
|
||||
<id>1625</id>
|
||||
<id>1626</id>
|
||||
<id>1627</id>
|
||||
<id>1628</id>
|
||||
<id>1629</id>
|
||||
<id>1630</id>
|
||||
<id>1632</id>
|
||||
<id>1633</id>
|
||||
<id>1634</id>
|
||||
<id>1636</id>
|
||||
<id>1638</id>
|
||||
<id>1640</id>
|
||||
<id>1641</id>
|
||||
<id>1642</id>
|
||||
<id>1646</id>
|
||||
<id>1648</id>
|
||||
<id>1652</id>
|
||||
<id>1655</id>
|
||||
<id>1656</id>
|
||||
<id>1657</id>
|
||||
<id>1658</id>
|
||||
<id>1660</id>
|
||||
<id>1661</id>
|
||||
<id>1663</id>
|
||||
<id>1666</id>
|
||||
<id>1667</id>
|
||||
<id>1669</id>
|
||||
<id>1671</id>
|
||||
<id>1672</id>
|
||||
<id>1673</id>
|
||||
<id>1674</id>
|
||||
<id>1675</id>
|
||||
<id>1676</id>
|
||||
<id>1678</id>
|
||||
<id>1681</id>
|
||||
<id>1683</id>
|
||||
<id>1736</id>
|
||||
<id>1749</id>
|
||||
<id>1769</id>
|
||||
<id>1771</id>
|
||||
<id>1777</id>
|
||||
<id>1781</id>
|
||||
<id>1787</id>
|
||||
<id>1788</id>
|
||||
<id>1813</id>
|
||||
<id>1815</id>
|
||||
<id>1818</id>
|
||||
<id>1822</id>
|
||||
<id>1828</id>
|
||||
<id>1961</id>
|
||||
<id>1967</id>
|
||||
<id>1969</id>
|
||||
<id>1970</id>
|
||||
<id>1971</id>
|
||||
<id>1972</id>
|
||||
<id>1973</id>
|
||||
<id>1974</id>
|
||||
<id>1978</id>
|
||||
<id>2175</id>
|
||||
<id>2178</id>
|
||||
<id>2180</id>
|
||||
<id>2182</id>
|
||||
<id>2195</id>
|
||||
<id>2196</id>
|
||||
<id>2199</id>
|
||||
<id>2206</id>
|
||||
<id>2207</id>
|
||||
<id>2208</id>
|
||||
<id>2212</id>
|
||||
<id>2213</id>
|
||||
<id>2215</id>
|
||||
<id>2217</id>
|
||||
<id>2218</id>
|
||||
<id>2219</id>
|
||||
<id>2223</id>
|
||||
<id>2225</id>
|
||||
<id>2226</id>
|
||||
<id>2228</id>
|
||||
<id>2229</id>
|
||||
<id>2232</id>
|
||||
<id>2233</id>
|
||||
<id>2235</id>
|
||||
<id>2284</id>
|
||||
<id>2288</id>
|
||||
<id>2300</id>
|
||||
<id>2302</id>
|
||||
<id>2307</id>
|
||||
<id>2308</id>
|
||||
<id>2311</id>
|
||||
<id>2314</id>
|
||||
<id>2315</id>
|
||||
<id>2318</id>
|
||||
<id>2319</id>
|
||||
<id>2320</id>
|
||||
<id>2324</id>
|
||||
<id>2328</id>
|
||||
<id>2329</id>
|
||||
<id>2330</id>
|
||||
<id>2331</id>
|
||||
<id>2332</id>
|
||||
<id>2333</id>
|
||||
<id>2335</id>
|
||||
<id>2337</id>
|
||||
<id>2338</id>
|
||||
<id>2339</id>
|
||||
<id>2344</id>
|
||||
<id>2346</id>
|
||||
<id>2347</id>
|
||||
<id>2353</id>
|
||||
<id>2354</id>
|
||||
<id>2357</id>
|
||||
<id>2363</id>
|
||||
<id>2372</id>
|
||||
<id>2389</id>
|
||||
<id>2408</id>
|
||||
<id>2415</id>
|
||||
<id>2416</id>
|
||||
<id>2423</id>
|
||||
<id>2444</id>
|
||||
<id>2459</id>
|
||||
<id>2472</id>
|
||||
<id>2474</id>
|
||||
<id>2475</id>
|
||||
<id>2476</id>
|
||||
<id>2477</id>
|
||||
<id>2478</id>
|
||||
<id>2479</id>
|
||||
<id>2481</id>
|
||||
<id>2482</id>
|
||||
<id>2483</id>
|
||||
<id>2485</id>
|
||||
<id>2486</id>
|
||||
<id>2493</id>
|
||||
<id>2494</id>
|
||||
<id>2496</id>
|
||||
<id>2498</id>
|
||||
<id>2499</id>
|
||||
<id>2500</id>
|
||||
<id>2503</id>
|
||||
<id>2506</id>
|
||||
<id>2522</id>
|
||||
<id>2526</id>
|
||||
<id>2528</id>
|
||||
<id>2529</id>
|
||||
<id>2532</id>
|
||||
<id>2533</id>
|
||||
<id>2536</id>
|
||||
<id>2538</id>
|
||||
<id>2542</id>
|
||||
<id>2548</id>
|
||||
<id>2551</id>
|
||||
<id>2554</id>
|
||||
<id>2556</id>
|
||||
<id>2557</id>
|
||||
<id>2574</id>
|
||||
<id>2589</id>
|
||||
<id>2590</id>
|
||||
<id>2591</id>
|
||||
<id>2592</id>
|
||||
<id>2593</id>
|
||||
<id>2594</id>
|
||||
<id>2596</id>
|
||||
<id>2597</id>
|
||||
<id>2598</id>
|
||||
<id>2603</id>
|
||||
<id>2604</id>
|
||||
<id>2605</id>
|
||||
<id>2608</id>
|
||||
<id>2613</id>
|
||||
<id>2614</id>
|
||||
<id>2622</id>
|
||||
<id>2626</id>
|
||||
<id>2641</id>
|
||||
<id>2643</id>
|
||||
<id>2659</id>
|
||||
<id>2671</id>
|
||||
<id>2688</id>
|
||||
<id>2689</id>
|
||||
<id>2690</id>
|
||||
<id>2691</id>
|
||||
<id>2692</id>
|
||||
<id>2693</id>
|
||||
<id>2694</id>
|
||||
<id>2695</id>
|
||||
<id>2697</id>
|
||||
<id>2699</id>
|
||||
<id>2700</id>
|
||||
<id>2705</id>
|
||||
<id>2707</id>
|
||||
<id>2708</id>
|
||||
<id>2711</id>
|
||||
<id>2714</id>
|
||||
<id>2717</id>
|
||||
<id>2718</id>
|
||||
<id>2719</id>
|
||||
<id>2723</id>
|
||||
<id>2730</id>
|
||||
<id>2731</id>
|
||||
<id>2776</id>
|
||||
<id>2781</id>
|
||||
<id>2782</id>
|
||||
<id>2783</id>
|
||||
<id>2836</id>
|
||||
<id>2837</id>
|
||||
<id>2838</id>
|
||||
<id>2839</id>
|
||||
<id>2874</id>
|
||||
<id>2875</id>
|
||||
<id>2876</id>
|
||||
<id>2884</id>
|
||||
<id>2887</id>
|
||||
<id>2889</id>
|
||||
<id>2892</id>
|
||||
<id>2893</id>
|
||||
<id>2897</id>
|
||||
<id>2898</id>
|
||||
<id>2899</id>
|
||||
<id>2937</id>
|
||||
<id>2956</id>
|
||||
<id>2957</id>
|
||||
<id>2958</id>
|
||||
<id>2959</id>
|
||||
<id>2961</id>
|
||||
<id>2962</id>
|
||||
<id>2963</id>
|
||||
<id>2966</id>
|
||||
<id>2968</id>
|
||||
<id>2969</id>
|
||||
<id>2974</id>
|
||||
<id>2983</id>
|
||||
<id>2984</id>
|
||||
<id>2985</id>
|
||||
<id>2986</id>
|
||||
<id>2988</id>
|
||||
<id>2989</id>
|
||||
<id>2990</id>
|
||||
<id>2992</id>
|
||||
<id>2993</id>
|
||||
<id>2996</id>
|
||||
<id>3013</id>
|
||||
<id>3015</id>
|
||||
<id>3018</id>
|
||||
<id>3024</id>
|
||||
<id>3025</id>
|
||||
<id>3031</id>
|
||||
<id>3032</id>
|
||||
<id>3034</id>
|
||||
<id>3035</id>
|
||||
<id>3039</id>
|
||||
<id>3041</id>
|
||||
<id>3043</id>
|
||||
<id>3054</id>
|
||||
<id>3055</id>
|
||||
<id>3056</id>
|
||||
<id>3057</id>
|
||||
<id>3067</id>
|
||||
<id>3068</id>
|
||||
<id>3081</id>
|
||||
<id>3091</id>
|
||||
<id>3093</id>
|
||||
<id>3095</id>
|
||||
<id>3101</id>
|
||||
<id>3102</id>
|
||||
<id>3103</id>
|
||||
<id>3104</id>
|
||||
<id>3108</id>
|
||||
<id>3111</id>
|
||||
<id>3112</id>
|
||||
<id>3125</id>
|
||||
<id>3126</id>
|
||||
<id>3137</id>
|
||||
<id>3141</id>
|
||||
<id>3145</id>
|
||||
<id>3150</id>
|
||||
<id>3158</id>
|
||||
<id>3167</id>
|
||||
<id>3171</id>
|
||||
<id>3172</id>
|
||||
<id>3173</id>
|
||||
<id>3174</id>
|
||||
<id>3175</id>
|
||||
<id>3176</id>
|
||||
<id>3177</id>
|
||||
<id>3178</id>
|
||||
</Upcoming>
|
||||
</DefaultMissions>
|
||||
409
src/Resources/missions/defaultmissionlist_mam.xml
Normal file
409
src/Resources/missions/defaultmissionlist_mam.xml
Normal file
@ -0,0 +1,409 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<DefaultMissions>
|
||||
<!-- list of default mission for Magic and Mythies -->
|
||||
<Active>
|
||||
<id>2290</id>
|
||||
</Active>
|
||||
<Upcoming>
|
||||
<id>1739</id>
|
||||
<id>1750</id>
|
||||
<id>1751</id>
|
||||
<id>1752</id>
|
||||
<id>1753</id>
|
||||
<id>1754</id>
|
||||
<id>1756</id>
|
||||
<id>1757</id>
|
||||
<id>1761</id>
|
||||
<id>1763</id>
|
||||
<id>1764</id>
|
||||
<id>1766</id>
|
||||
<id>1767</id>
|
||||
<id>1768</id>
|
||||
<id>1770</id>
|
||||
<id>1772</id>
|
||||
<id>1773</id>
|
||||
<id>1776</id>
|
||||
<id>1792</id>
|
||||
<id>1793</id>
|
||||
<id>1795</id>
|
||||
<id>1798</id>
|
||||
<id>1799</id>
|
||||
<id>1800</id>
|
||||
<id>1803</id>
|
||||
<id>1804</id>
|
||||
<id>1805</id>
|
||||
<id>1807</id>
|
||||
<id>1808</id>
|
||||
<id>1809</id>
|
||||
<id>1811</id>
|
||||
<id>1814</id>
|
||||
<id>1816</id>
|
||||
<id>1820</id>
|
||||
<id>1825</id>
|
||||
<id>1826</id>
|
||||
<id>1827</id>
|
||||
<id>1829</id>
|
||||
<id>1830</id>
|
||||
<id>1831</id>
|
||||
<id>1832</id>
|
||||
<id>1833</id>
|
||||
<id>1834</id>
|
||||
<id>1835</id>
|
||||
<id>1836</id>
|
||||
<id>1837</id>
|
||||
<id>1838</id>
|
||||
<id>1839</id>
|
||||
<id>1840</id>
|
||||
<id>1841</id>
|
||||
<id>1842</id>
|
||||
<id>1843</id>
|
||||
<id>1844</id>
|
||||
<id>1845</id>
|
||||
<id>1846</id>
|
||||
<id>1847</id>
|
||||
<id>1848</id>
|
||||
<id>1849</id>
|
||||
<id>1850</id>
|
||||
<id>1851</id>
|
||||
<id>1852</id>
|
||||
<id>1853</id>
|
||||
<id>1854</id>
|
||||
<id>1855</id>
|
||||
<id>1856</id>
|
||||
<id>1857</id>
|
||||
<id>1858</id>
|
||||
<id>1859</id>
|
||||
<id>1860</id>
|
||||
<id>1861</id>
|
||||
<id>1862</id>
|
||||
<id>1863</id>
|
||||
<id>1864</id>
|
||||
<id>1865</id>
|
||||
<id>1866</id>
|
||||
<id>1867</id>
|
||||
<id>1868</id>
|
||||
<id>1869</id>
|
||||
<id>1870</id>
|
||||
<id>1871</id>
|
||||
<id>1872</id>
|
||||
<id>1873</id>
|
||||
<id>1874</id>
|
||||
<id>1875</id>
|
||||
<id>1876</id>
|
||||
<id>1877</id>
|
||||
<id>1878</id>
|
||||
<id>1879</id>
|
||||
<id>1880</id>
|
||||
<id>1881</id>
|
||||
<id>1882</id>
|
||||
<id>1883</id>
|
||||
<id>1884</id>
|
||||
<id>1885</id>
|
||||
<id>1886</id>
|
||||
<id>1887</id>
|
||||
<id>1888</id>
|
||||
<id>1889</id>
|
||||
<id>1890</id>
|
||||
<id>1891</id>
|
||||
<id>1892</id>
|
||||
<id>1893</id>
|
||||
<id>1894</id>
|
||||
<id>1895</id>
|
||||
<id>1896</id>
|
||||
<id>1897</id>
|
||||
<id>1898</id>
|
||||
<id>1899</id>
|
||||
<id>1900</id>
|
||||
<id>1901</id>
|
||||
<id>1902</id>
|
||||
<id>1903</id>
|
||||
<id>1904</id>
|
||||
<id>1905</id>
|
||||
<id>1906</id>
|
||||
<id>1907</id>
|
||||
<id>1908</id>
|
||||
<id>1909</id>
|
||||
<id>1910</id>
|
||||
<id>1911</id>
|
||||
<id>1912</id>
|
||||
<id>1913</id>
|
||||
<id>1914</id>
|
||||
<id>1915</id>
|
||||
<id>1916</id>
|
||||
<id>1917</id>
|
||||
<id>1918</id>
|
||||
<id>1919</id>
|
||||
<id>1920</id>
|
||||
<id>1921</id>
|
||||
<id>1922</id>
|
||||
<id>1923</id>
|
||||
<id>1924</id>
|
||||
<id>1925</id>
|
||||
<id>1926</id>
|
||||
<id>1927</id>
|
||||
<id>1928</id>
|
||||
<id>1929</id>
|
||||
<id>1930</id>
|
||||
<id>1931</id>
|
||||
<id>1932</id>
|
||||
<id>1933</id>
|
||||
<id>1934</id>
|
||||
<id>1935</id>
|
||||
<id>1936</id>
|
||||
<id>1937</id>
|
||||
<id>1938</id>
|
||||
<id>1939</id>
|
||||
<id>1940</id>
|
||||
<id>1941</id>
|
||||
<id>1942</id>
|
||||
<id>1943</id>
|
||||
<id>1944</id>
|
||||
<id>1945</id>
|
||||
<id>1946</id>
|
||||
<id>1947</id>
|
||||
<id>1948</id>
|
||||
<id>1949</id>
|
||||
<id>1950</id>
|
||||
<id>1951</id>
|
||||
<id>1952</id>
|
||||
<id>1953</id>
|
||||
<id>1954</id>
|
||||
<id>1955</id>
|
||||
<id>1956</id>
|
||||
<id>1957</id>
|
||||
<id>1958</id>
|
||||
<id>1959</id>
|
||||
<id>1960</id>
|
||||
<id>1981</id>
|
||||
<id>1982</id>
|
||||
<id>1983</id>
|
||||
<id>1988</id>
|
||||
<id>1989</id>
|
||||
<id>1992</id>
|
||||
<id>1993</id>
|
||||
<id>1994</id>
|
||||
<id>1995</id>
|
||||
<id>1996</id>
|
||||
<id>1997</id>
|
||||
<id>1998</id>
|
||||
<id>1999</id>
|
||||
<id>2000</id>
|
||||
<id>2001</id>
|
||||
<id>2002</id>
|
||||
<id>2003</id>
|
||||
<id>2004</id>
|
||||
<id>2005</id>
|
||||
<id>2006</id>
|
||||
<id>2007</id>
|
||||
<id>2008</id>
|
||||
<id>2009</id>
|
||||
<id>2010</id>
|
||||
<id>2011</id>
|
||||
<id>2012</id>
|
||||
<id>2013</id>
|
||||
<id>2014</id>
|
||||
<id>2015</id>
|
||||
<id>2016</id>
|
||||
<id>2017</id>
|
||||
<id>2018</id>
|
||||
<id>2019</id>
|
||||
<id>2020</id>
|
||||
<id>2021</id>
|
||||
<id>2022</id>
|
||||
<id>2023</id>
|
||||
<id>2024</id>
|
||||
<id>2025</id>
|
||||
<id>2026</id>
|
||||
<id>2027</id>
|
||||
<id>2028</id>
|
||||
<id>2029</id>
|
||||
<id>2030</id>
|
||||
<id>2031</id>
|
||||
<id>2032</id>
|
||||
<id>2033</id>
|
||||
<id>2034</id>
|
||||
<id>2035</id>
|
||||
<id>2036</id>
|
||||
<id>2037</id>
|
||||
<id>2038</id>
|
||||
<id>2039</id>
|
||||
<id>2040</id>
|
||||
<id>2041</id>
|
||||
<id>2042</id>
|
||||
<id>2043</id>
|
||||
<id>2044</id>
|
||||
<id>2045</id>
|
||||
<id>2046</id>
|
||||
<id>2047</id>
|
||||
<id>2048</id>
|
||||
<id>2049</id>
|
||||
<id>2050</id>
|
||||
<id>2051</id>
|
||||
<id>2052</id>
|
||||
<id>2053</id>
|
||||
<id>2054</id>
|
||||
<id>2055</id>
|
||||
<id>2056</id>
|
||||
<id>2057</id>
|
||||
<id>2058</id>
|
||||
<id>2059</id>
|
||||
<id>2060</id>
|
||||
<id>2061</id>
|
||||
<id>2062</id>
|
||||
<id>2063</id>
|
||||
<id>2064</id>
|
||||
<id>2065</id>
|
||||
<id>2066</id>
|
||||
<id>2067</id>
|
||||
<id>2068</id>
|
||||
<id>2069</id>
|
||||
<id>2070</id>
|
||||
<id>2071</id>
|
||||
<id>2072</id>
|
||||
<id>2073</id>
|
||||
<id>2074</id>
|
||||
<id>2075</id>
|
||||
<id>2076</id>
|
||||
<id>2077</id>
|
||||
<id>2078</id>
|
||||
<id>2079</id>
|
||||
<id>2080</id>
|
||||
<id>2081</id>
|
||||
<id>2082</id>
|
||||
<id>2083</id>
|
||||
<id>2084</id>
|
||||
<id>2085</id>
|
||||
<id>2086</id>
|
||||
<id>2087</id>
|
||||
<id>2088</id>
|
||||
<id>2089</id>
|
||||
<id>2090</id>
|
||||
<id>2091</id>
|
||||
<id>2092</id>
|
||||
<id>2093</id>
|
||||
<id>2094</id>
|
||||
<id>2095</id>
|
||||
<id>2096</id>
|
||||
<id>2097</id>
|
||||
<id>2098</id>
|
||||
<id>2099</id>
|
||||
<id>2100</id>
|
||||
<id>2101</id>
|
||||
<id>2102</id>
|
||||
<id>2103</id>
|
||||
<id>2104</id>
|
||||
<id>2105</id>
|
||||
<id>2106</id>
|
||||
<id>2107</id>
|
||||
<id>2108</id>
|
||||
<id>2109</id>
|
||||
<id>2110</id>
|
||||
<id>2111</id>
|
||||
<id>2112</id>
|
||||
<id>2113</id>
|
||||
<id>2114</id>
|
||||
<id>2115</id>
|
||||
<id>2116</id>
|
||||
<id>2117</id>
|
||||
<id>2118</id>
|
||||
<id>2119</id>
|
||||
<id>2120</id>
|
||||
<id>2121</id>
|
||||
<id>2122</id>
|
||||
<id>2123</id>
|
||||
<id>2124</id>
|
||||
<id>2125</id>
|
||||
<id>2126</id>
|
||||
<id>2127</id>
|
||||
<id>2128</id>
|
||||
<id>2129</id>
|
||||
<id>2130</id>
|
||||
<id>2131</id>
|
||||
<id>2132</id>
|
||||
<id>2133</id>
|
||||
<id>2134</id>
|
||||
<id>2135</id>
|
||||
<id>2136</id>
|
||||
<id>2137</id>
|
||||
<id>2138</id>
|
||||
<id>2140</id>
|
||||
<id>2142</id>
|
||||
<id>2145</id>
|
||||
<id>2146</id>
|
||||
<id>2149</id>
|
||||
<id>2151</id>
|
||||
<id>2152</id>
|
||||
<id>2158</id>
|
||||
<id>2160</id>
|
||||
<id>2161</id>
|
||||
<id>2162</id>
|
||||
<id>2163</id>
|
||||
<id>2164</id>
|
||||
<id>2165</id>
|
||||
<id>2166</id>
|
||||
<id>2167</id>
|
||||
<id>2168</id>
|
||||
<id>2169</id>
|
||||
<id>2170</id>
|
||||
<id>2171</id>
|
||||
<id>2172</id>
|
||||
<id>2173</id>
|
||||
<id>2189</id>
|
||||
<id>2190</id>
|
||||
<id>2192</id>
|
||||
<id>2193</id>
|
||||
<id>2202</id>
|
||||
<id>2236</id>
|
||||
<id>2237</id>
|
||||
<id>2238</id>
|
||||
<id>2239</id>
|
||||
<id>2240</id>
|
||||
<id>2241</id>
|
||||
<id>2242</id>
|
||||
<id>2243</id>
|
||||
<id>2244</id>
|
||||
<id>2245</id>
|
||||
<id>2246</id>
|
||||
<id>2247</id>
|
||||
<id>2248</id>
|
||||
<id>2249</id>
|
||||
<id>2250</id>
|
||||
<id>2251</id>
|
||||
<id>2252</id>
|
||||
<id>2253</id>
|
||||
<id>2254</id>
|
||||
<id>2255</id>
|
||||
<id>2256</id>
|
||||
<id>2257</id>
|
||||
<id>2258</id>
|
||||
<id>2259</id>
|
||||
<id>2260</id>
|
||||
<id>2261</id>
|
||||
<id>2262</id>
|
||||
<id>2263</id>
|
||||
<id>2264</id>
|
||||
<id>2265</id>
|
||||
<id>2266</id>
|
||||
<id>2267</id>
|
||||
<id>2268</id>
|
||||
<id>2269</id>
|
||||
<id>2270</id>
|
||||
<id>2271</id>
|
||||
<id>2272</id>
|
||||
<id>2273</id>
|
||||
<id>2274</id>
|
||||
<id>2275</id>
|
||||
<id>2276</id>
|
||||
<id>2277</id>
|
||||
<id>2278</id>
|
||||
<id>2279</id>
|
||||
<id>2280</id>
|
||||
<id>2281</id>
|
||||
<id>2282</id>
|
||||
<id>2283</id>
|
||||
<id>2297</id>
|
||||
<id>2301</id>
|
||||
<id>2305</id>
|
||||
<id>2306</id>
|
||||
</Upcoming>
|
||||
</DefaultMissions>
|
||||
635
src/Resources/missions/defaultmissionlist_sod_v1.xml
Normal file
635
src/Resources/missions/defaultmissionlist_sod_v1.xml
Normal file
@ -0,0 +1,635 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<DefaultMissions>
|
||||
<!-- list of default mission for oldest versions (1.13)
|
||||
* must not contain contain missions with Type == 7 <Prerequisites> (causes xml deserialize error in client)
|
||||
* "New Student" (999) must be Upcoming not Active mission
|
||||
* should not contain any visible missions not supported in oldest versions
|
||||
* TODO: remove all missions not supported in oldest versions
|
||||
-->
|
||||
<Active>
|
||||
<id>1000</id>
|
||||
</Active>
|
||||
<Upcoming>
|
||||
<id>999</id>
|
||||
<id>1003</id>
|
||||
<id>1014</id>
|
||||
<id>1015</id>
|
||||
<id>1016</id>
|
||||
<id>1017</id>
|
||||
<id>1027</id>
|
||||
<id>1028</id>
|
||||
<id>1029</id>
|
||||
<id>1031</id>
|
||||
<id>1033</id>
|
||||
<id>1038</id>
|
||||
<id>1044</id>
|
||||
<id>1053</id>
|
||||
<id>1054</id>
|
||||
<id>1055</id>
|
||||
<id>1057</id>
|
||||
<id>1058</id>
|
||||
<id>1062</id>
|
||||
<id>1067</id>
|
||||
<id>1074</id>
|
||||
<id>1085</id>
|
||||
<id>1089</id>
|
||||
<id>1090</id>
|
||||
<id>1091</id>
|
||||
<id>1093</id>
|
||||
<id>1095</id>
|
||||
<id>1096</id>
|
||||
<id>1097</id>
|
||||
<id>1099</id>
|
||||
<id>1101</id>
|
||||
<id>1102</id>
|
||||
<id>1106</id>
|
||||
<id>1108</id>
|
||||
<id>1110</id>
|
||||
<id>1111</id>
|
||||
<id>1114</id>
|
||||
<id>1120</id>
|
||||
<id>1121</id>
|
||||
<id>1128</id>
|
||||
<id>1134</id>
|
||||
<id>1140</id>
|
||||
<id>1143</id>
|
||||
<id>1144</id>
|
||||
<id>1150</id>
|
||||
<id>1153</id>
|
||||
<id>1155</id>
|
||||
<id>1159</id>
|
||||
<id>1163</id>
|
||||
<id>1164</id>
|
||||
<id>1166</id>
|
||||
<id>1167</id>
|
||||
<id>1168</id>
|
||||
<id>1169</id>
|
||||
<id>1171</id>
|
||||
<id>1173</id>
|
||||
<id>1179</id>
|
||||
<id>1247</id>
|
||||
<id>1296</id>
|
||||
<id>1304</id>
|
||||
<id>1305</id>
|
||||
<id>1317</id>
|
||||
<id>1318</id>
|
||||
<id>1331</id>
|
||||
<id>1333</id>
|
||||
<id>1335</id>
|
||||
<id>1338</id>
|
||||
<id>1343</id>
|
||||
<id>1344</id>
|
||||
<id>1357</id>
|
||||
<id>1361</id>
|
||||
<id>1362</id>
|
||||
<id>1390</id>
|
||||
<id>1508</id>
|
||||
<id>1529</id>
|
||||
<id>1530</id>
|
||||
<id>1575</id>
|
||||
<id>1579</id>
|
||||
<id>1605</id>
|
||||
<id>1606</id>
|
||||
<id>1607</id>
|
||||
<id>1608</id>
|
||||
<id>1611</id>
|
||||
<id>1612</id>
|
||||
<id>1613</id>
|
||||
<id>1614</id>
|
||||
<id>1615</id>
|
||||
<id>1617</id>
|
||||
<id>1618</id>
|
||||
<id>1619</id>
|
||||
<id>1620</id>
|
||||
<id>1622</id>
|
||||
<id>1623</id>
|
||||
<id>1624</id>
|
||||
<id>1625</id>
|
||||
<id>1626</id>
|
||||
<id>1627</id>
|
||||
<id>1628</id>
|
||||
<id>1629</id>
|
||||
<id>1630</id>
|
||||
<id>1632</id>
|
||||
<id>1633</id>
|
||||
<id>1634</id>
|
||||
<id>1636</id>
|
||||
<id>1638</id>
|
||||
<id>1640</id>
|
||||
<id>1641</id>
|
||||
<id>1642</id>
|
||||
<id>1646</id>
|
||||
<id>1648</id>
|
||||
<id>1652</id>
|
||||
<id>1655</id>
|
||||
<id>1656</id>
|
||||
<id>1657</id>
|
||||
<id>1658</id>
|
||||
<id>1660</id>
|
||||
<id>1661</id>
|
||||
<id>1663</id>
|
||||
<id>1666</id>
|
||||
<id>1667</id>
|
||||
<id>1669</id>
|
||||
<id>1671</id>
|
||||
<id>1672</id>
|
||||
<id>1673</id>
|
||||
<id>1674</id>
|
||||
<id>1675</id>
|
||||
<id>1676</id>
|
||||
<id>1678</id>
|
||||
<id>1681</id>
|
||||
<id>1683</id>
|
||||
<id>1736</id>
|
||||
<id>1749</id>
|
||||
<id>1769</id>
|
||||
<id>1771</id>
|
||||
<id>1777</id>
|
||||
<id>1781</id>
|
||||
<id>1787</id>
|
||||
<id>1788</id>
|
||||
<id>1813</id>
|
||||
<id>1815</id>
|
||||
<id>1818</id>
|
||||
<id>1822</id>
|
||||
<id>1828</id>
|
||||
<id>1961</id>
|
||||
<id>1967</id>
|
||||
<id>1969</id>
|
||||
<id>1970</id>
|
||||
<id>1971</id>
|
||||
<id>1972</id>
|
||||
<id>1973</id>
|
||||
<id>1974</id>
|
||||
<id>1978</id>
|
||||
<id>2175</id>
|
||||
<id>2178</id>
|
||||
<id>2180</id>
|
||||
<id>2182</id>
|
||||
<id>2195</id>
|
||||
<id>2196</id>
|
||||
<id>2199</id>
|
||||
<id>2206</id>
|
||||
<id>2207</id>
|
||||
<id>2208</id>
|
||||
<id>2212</id>
|
||||
<id>2213</id>
|
||||
<id>2215</id>
|
||||
<id>2217</id>
|
||||
<id>2218</id>
|
||||
<id>2219</id>
|
||||
<id>2223</id>
|
||||
<id>2225</id>
|
||||
<id>2226</id>
|
||||
<id>2228</id>
|
||||
<id>2229</id>
|
||||
<id>2232</id>
|
||||
<id>2233</id>
|
||||
<id>2235</id>
|
||||
<id>2284</id>
|
||||
<id>2288</id>
|
||||
<id>2300</id>
|
||||
<id>2302</id>
|
||||
<id>2307</id>
|
||||
<id>2308</id>
|
||||
<id>2311</id>
|
||||
<id>2314</id>
|
||||
<id>2315</id>
|
||||
<id>2318</id>
|
||||
<id>2319</id>
|
||||
<id>2320</id>
|
||||
<id>2324</id>
|
||||
<id>2328</id>
|
||||
<id>2329</id>
|
||||
<id>2330</id>
|
||||
<id>2331</id>
|
||||
<id>2332</id>
|
||||
<id>2333</id>
|
||||
<id>2335</id>
|
||||
<id>2337</id>
|
||||
<id>2338</id>
|
||||
<id>2339</id>
|
||||
<id>2344</id>
|
||||
<id>2346</id>
|
||||
<id>2347</id>
|
||||
<id>2353</id>
|
||||
<id>2354</id>
|
||||
<id>2357</id>
|
||||
<id>2363</id>
|
||||
<id>2372</id>
|
||||
<id>2389</id>
|
||||
<id>2408</id>
|
||||
<id>2415</id>
|
||||
<id>2416</id>
|
||||
<id>2423</id>
|
||||
<id>2444</id>
|
||||
<id>2459</id>
|
||||
<id>2472</id>
|
||||
<id>2474</id>
|
||||
<id>2475</id>
|
||||
<id>2476</id>
|
||||
<id>2477</id>
|
||||
<id>2478</id>
|
||||
<id>2479</id>
|
||||
<id>2481</id>
|
||||
<id>2482</id>
|
||||
<id>2483</id>
|
||||
<id>2485</id>
|
||||
<id>2486</id>
|
||||
<id>2493</id>
|
||||
<id>2494</id>
|
||||
<id>2496</id>
|
||||
<id>2498</id>
|
||||
<id>2499</id>
|
||||
<id>2500</id>
|
||||
<id>2503</id>
|
||||
<id>2506</id>
|
||||
<id>2522</id>
|
||||
<id>2526</id>
|
||||
<id>2528</id>
|
||||
<id>2529</id>
|
||||
<id>2532</id>
|
||||
<id>2533</id>
|
||||
<id>2536</id>
|
||||
<id>2538</id>
|
||||
<id>2542</id>
|
||||
<id>2548</id>
|
||||
<id>2551</id>
|
||||
<id>2554</id>
|
||||
<id>2556</id>
|
||||
<id>2557</id>
|
||||
<id>2574</id>
|
||||
<id>2589</id>
|
||||
<id>2590</id>
|
||||
<id>2591</id>
|
||||
<id>2592</id>
|
||||
<id>2593</id>
|
||||
<id>2594</id>
|
||||
<id>2596</id>
|
||||
<id>2597</id>
|
||||
<id>2598</id>
|
||||
<id>2608</id>
|
||||
<id>2613</id>
|
||||
<id>2614</id>
|
||||
<id>2622</id>
|
||||
<id>2626</id>
|
||||
<id>2641</id>
|
||||
<id>2643</id>
|
||||
<id>2659</id>
|
||||
<id>2689</id>
|
||||
<id>2690</id>
|
||||
<id>2691</id>
|
||||
<id>2692</id>
|
||||
<id>2693</id>
|
||||
<id>2694</id>
|
||||
<id>2695</id>
|
||||
<id>2697</id>
|
||||
<id>2699</id>
|
||||
<id>2700</id>
|
||||
<id>2705</id>
|
||||
<id>2707</id>
|
||||
<id>2708</id>
|
||||
<id>2711</id>
|
||||
<id>2714</id>
|
||||
<id>2717</id>
|
||||
<id>2718</id>
|
||||
<id>2719</id>
|
||||
<id>2723</id>
|
||||
<id>2730</id>
|
||||
<id>2731</id>
|
||||
<id>2874</id>
|
||||
<id>2884</id>
|
||||
<id>2887</id>
|
||||
<id>2889</id>
|
||||
<id>2892</id>
|
||||
<id>2893</id>
|
||||
<id>2937</id>
|
||||
<id>2974</id>
|
||||
<id>2986</id>
|
||||
<id>2988</id>
|
||||
<id>2989</id>
|
||||
<id>2990</id>
|
||||
<id>2992</id>
|
||||
<id>2993</id>
|
||||
<id>2996</id>
|
||||
<id>3013</id>
|
||||
<id>3057</id>
|
||||
<id>3150</id>
|
||||
<id>3167</id>
|
||||
<id>3176</id>
|
||||
<id>3177</id>
|
||||
<id>3178</id>
|
||||
|
||||
<!-- fishing board -->
|
||||
<id>1035</id>
|
||||
<id>1036</id>
|
||||
<id>1037</id>
|
||||
<id>1046</id>
|
||||
<id>1047</id>
|
||||
<id>1048</id>
|
||||
|
||||
<!-- farming board -->
|
||||
<id>1185</id>
|
||||
<id>1186</id>
|
||||
<id>1187</id>
|
||||
<id>1188</id>
|
||||
<id>1189</id>
|
||||
<id>1191</id>
|
||||
<id>1192</id>
|
||||
<id>1193</id>
|
||||
<id>1194</id>
|
||||
<id>1195</id>
|
||||
<id>1196</id>
|
||||
<id>1197</id>
|
||||
<id>1198</id>
|
||||
<id>1199</id>
|
||||
<id>1200</id>
|
||||
<id>1201</id>
|
||||
<id>1202</id>
|
||||
<id>1203</id>
|
||||
<id>1204</id>
|
||||
<id>1205</id>
|
||||
<id>1206</id>
|
||||
<id>1207</id>
|
||||
<id>1208</id>
|
||||
<id>1209</id>
|
||||
<id>1210</id>
|
||||
<id>1211</id>
|
||||
<id>1212</id>
|
||||
<id>1213</id>
|
||||
<id>1214</id>
|
||||
<id>1215</id>
|
||||
<id>1216</id>
|
||||
<id>1217</id>
|
||||
<id>1218</id>
|
||||
<id>1219</id>
|
||||
<id>1220</id>
|
||||
<id>1221</id>
|
||||
<id>1222</id>
|
||||
<id>1223</id>
|
||||
<id>1224</id>
|
||||
<id>1225</id>
|
||||
<id>1226</id>
|
||||
<id>1227</id>
|
||||
<id>1228</id>
|
||||
<id>1229</id>
|
||||
<id>1230</id>
|
||||
<id>1231</id>
|
||||
<id>1232</id>
|
||||
<id>1233</id>
|
||||
<id>1234</id>
|
||||
<id>1235</id>
|
||||
<id>1236</id>
|
||||
<id>1237</id>
|
||||
<id>1238</id>
|
||||
<id>1239</id>
|
||||
<id>1240</id>
|
||||
<id>1241</id>
|
||||
<id>1242</id>
|
||||
<id>1243</id>
|
||||
<id>1244</id>
|
||||
<id>1245</id>
|
||||
<id>1250</id>
|
||||
<id>1251</id>
|
||||
<id>1252</id>
|
||||
<id>1253</id>
|
||||
<id>1254</id>
|
||||
<id>1255</id>
|
||||
<id>1256</id>
|
||||
<id>1257</id>
|
||||
<id>1258</id>
|
||||
<id>1259</id>
|
||||
<id>1260</id>
|
||||
<id>1261</id>
|
||||
<id>1262</id>
|
||||
<id>1263</id>
|
||||
<id>1264</id>
|
||||
<id>1265</id>
|
||||
<id>1266</id>
|
||||
<id>1267</id>
|
||||
<id>1268</id>
|
||||
<id>1269</id>
|
||||
<id>1270</id>
|
||||
<id>1271</id>
|
||||
<id>1272</id>
|
||||
<id>1273</id>
|
||||
<id>1274</id>
|
||||
<id>1275</id>
|
||||
<id>1276</id>
|
||||
<id>1277</id>
|
||||
<id>1278</id>
|
||||
<id>1279</id>
|
||||
<id>1280</id>
|
||||
<id>1281</id>
|
||||
<id>1282</id>
|
||||
<id>1283</id>
|
||||
<id>1284</id>
|
||||
<id>1285</id>
|
||||
<id>1286</id>
|
||||
<id>1287</id>
|
||||
<id>1288</id>
|
||||
<id>1289</id>
|
||||
<id>1290</id>
|
||||
<id>1291</id>
|
||||
<id>1292</id>
|
||||
<id>1293</id>
|
||||
<id>1294</id>
|
||||
<id>1295</id>
|
||||
<id>1321</id>
|
||||
<id>1322</id>
|
||||
<id>1323</id>
|
||||
<id>1324</id>
|
||||
<id>1325</id>
|
||||
<id>1326</id>
|
||||
<id>1327</id>
|
||||
<id>1328</id>
|
||||
<id>1329</id>
|
||||
<id>1330</id>
|
||||
<id>1345</id>
|
||||
<id>1346</id>
|
||||
<id>1347</id>
|
||||
<id>1348</id>
|
||||
<id>1349</id>
|
||||
<id>1350</id>
|
||||
<id>1351</id>
|
||||
<id>1352</id>
|
||||
<id>1353</id>
|
||||
<id>1354</id>
|
||||
<id>1689</id>
|
||||
<id>1690</id>
|
||||
<id>1691</id>
|
||||
<id>1692</id>
|
||||
<id>1693</id>
|
||||
<id>1694</id>
|
||||
<id>1695</id>
|
||||
<id>1696</id>
|
||||
<id>1697</id>
|
||||
<id>1698</id>
|
||||
<id>1699</id>
|
||||
<id>1700</id>
|
||||
<id>1701</id>
|
||||
<id>1702</id>
|
||||
<id>1703</id>
|
||||
<id>1704</id>
|
||||
<id>1705</id>
|
||||
<id>1706</id>
|
||||
<id>1707</id>
|
||||
<id>1708</id>
|
||||
<id>1709</id>
|
||||
<id>1710</id>
|
||||
<id>1711</id>
|
||||
<id>1712</id>
|
||||
<id>1713</id>
|
||||
<id>1714</id>
|
||||
<id>1715</id>
|
||||
<id>1716</id>
|
||||
<id>1717</id>
|
||||
<id>1718</id>
|
||||
<id>1719</id>
|
||||
<id>1720</id>
|
||||
<id>1721</id>
|
||||
<id>1722</id>
|
||||
<id>1723</id>
|
||||
<id>1724</id>
|
||||
<id>1725</id>
|
||||
<id>1726</id>
|
||||
<id>1727</id>
|
||||
<id>1728</id>
|
||||
<id>1729</id>
|
||||
<id>1730</id>
|
||||
<id>2392</id>
|
||||
<id>2393</id>
|
||||
<id>2394</id>
|
||||
<id>2395</id>
|
||||
<id>2396</id>
|
||||
<id>2397</id>
|
||||
<id>2398</id>
|
||||
<id>2399</id>
|
||||
<id>2400</id>
|
||||
<id>2401</id>
|
||||
<id>2402</id>
|
||||
<id>2403</id>
|
||||
<id>2404</id>
|
||||
<id>2405</id>
|
||||
<id>2406</id>
|
||||
<id>2407</id>
|
||||
<id>2424</id>
|
||||
<id>2425</id>
|
||||
<id>2426</id>
|
||||
<id>2427</id>
|
||||
<id>2428</id>
|
||||
<id>2429</id>
|
||||
<id>2430</id>
|
||||
<id>2431</id>
|
||||
<id>2432</id>
|
||||
<id>2433</id>
|
||||
<id>2434</id>
|
||||
<id>2435</id>
|
||||
<id>2436</id>
|
||||
<id>2437</id>
|
||||
<id>2438</id>
|
||||
<id>2439</id>
|
||||
<id>2512</id>
|
||||
<id>2513</id>
|
||||
<id>2514</id>
|
||||
<id>2515</id>
|
||||
<id>2516</id>
|
||||
<id>2517</id>
|
||||
<id>2518</id>
|
||||
<id>2519</id>
|
||||
<id>2520</id>
|
||||
<id>2521</id>
|
||||
<id>2562</id>
|
||||
<id>2563</id>
|
||||
<id>2564</id>
|
||||
<id>2565</id>
|
||||
<id>2566</id>
|
||||
<id>2567</id>
|
||||
<id>2568</id>
|
||||
<id>2569</id>
|
||||
<id>2570</id>
|
||||
<id>2571</id>
|
||||
<id>2579</id>
|
||||
<id>2580</id>
|
||||
<id>2581</id>
|
||||
<id>2582</id>
|
||||
<id>2583</id>
|
||||
<id>2584</id>
|
||||
<id>2585</id>
|
||||
<id>2586</id>
|
||||
<id>2587</id>
|
||||
<id>2588</id>
|
||||
<id>2631</id>
|
||||
<id>2632</id>
|
||||
<id>2633</id>
|
||||
<id>2634</id>
|
||||
<id>2635</id>
|
||||
<id>2636</id>
|
||||
<id>2637</id>
|
||||
<id>2638</id>
|
||||
<id>2639</id>
|
||||
<id>2640</id>
|
||||
<id>2661</id>
|
||||
<id>2662</id>
|
||||
<id>2663</id>
|
||||
<id>2664</id>
|
||||
<id>2665</id>
|
||||
<id>2666</id>
|
||||
<id>2667</id>
|
||||
<id>2668</id>
|
||||
<id>2669</id>
|
||||
<id>2670</id>
|
||||
<id>2675</id>
|
||||
<id>2676</id>
|
||||
<id>2677</id>
|
||||
<id>2678</id>
|
||||
<id>2679</id>
|
||||
<id>2680</id>
|
||||
<id>2681</id>
|
||||
<id>2682</id>
|
||||
<id>2683</id>
|
||||
<id>2684</id>
|
||||
<id>3071</id>
|
||||
<id>3072</id>
|
||||
<id>3073</id>
|
||||
<id>3074</id>
|
||||
<id>3075</id>
|
||||
<id>3076</id>
|
||||
<id>3077</id>
|
||||
<id>3078</id>
|
||||
<id>3079</id>
|
||||
<id>3080</id>
|
||||
<id>3113</id>
|
||||
<id>3114</id>
|
||||
<id>3115</id>
|
||||
<id>3116</id>
|
||||
<id>3117</id>
|
||||
<id>3118</id>
|
||||
<id>3119</id>
|
||||
<id>3120</id>
|
||||
<id>3121</id>
|
||||
<id>3122</id>
|
||||
<id>3127</id>
|
||||
<id>3128</id>
|
||||
<id>3129</id>
|
||||
<id>3130</id>
|
||||
<id>3131</id>
|
||||
<id>3132</id>
|
||||
<id>3133</id>
|
||||
<id>3134</id>
|
||||
<id>3135</id>
|
||||
<id>3136</id>
|
||||
|
||||
<!-- Cauldron -->
|
||||
<id>1307</id>
|
||||
<id>1308</id>
|
||||
<id>1309</id>
|
||||
<id>1310</id>
|
||||
<id>1311</id>
|
||||
<id>1312</id>
|
||||
<id>1313</id>
|
||||
<id>1314</id>
|
||||
<id>1315</id>
|
||||
<id>1316</id>
|
||||
</Upcoming>
|
||||
</DefaultMissions>
|
||||
15
src/Resources/missions/defaultmissionlist_wojs.xml
Normal file
15
src/Resources/missions/defaultmissionlist_wojs.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<DefaultMissions>
|
||||
<!-- list of default mission for World of Jump Start -->
|
||||
<Active>
|
||||
<id>1509</id>
|
||||
</Active>
|
||||
<Upcoming>
|
||||
<id>1743</id>
|
||||
<id>1512</id>
|
||||
<id>1513</id>
|
||||
<id>1514</id>
|
||||
<id>1651</id>
|
||||
<id>1653</id>
|
||||
</Upcoming>
|
||||
</DefaultMissions>
|
||||
40218
src/Resources/missions/missions_mam.xml
Normal file
40218
src/Resources/missions/missions_mam.xml
Normal file
File diff suppressed because it is too large
Load Diff
125777
src/Resources/missions/missions_sod.xml
Normal file
125777
src/Resources/missions/missions_sod.xml
Normal file
File diff suppressed because it is too large
Load Diff
1130
src/Resources/missions/missions_wojs.xml
Normal file
1130
src/Resources/missions/missions_wojs.xml
Normal file
File diff suppressed because it is too large
Load Diff
3546
src/Resources/missions/missions_wojs_jr.xml
Normal file
3546
src/Resources/missions/missions_wojs_jr.xml
Normal file
File diff suppressed because it is too large
Load Diff
3834
src/Resources/missions/step_missions.xml
Normal file
3834
src/Resources/missions/step_missions.xml
Normal file
File diff suppressed because it is too large
Load Diff
23706
src/Resources/missions/step_missions_steps.xml
Normal file
23706
src/Resources/missions/step_missions_steps.xml
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user