diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..52fdd34
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,32 @@
+name: CI
+on:
+ workflow_dispatch:
+ push:
+ branches: [ main ]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ env:
+ PACK_NAME: ""
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Pack
+ run: |
+ if [ ! $PACK_NAME ]; then
+ PACK_NAME=${GITHUB_REPOSITORY##*/}
+ fi
+ echo "PACK_NAME=$PACK_NAME" >> $GITHUB_ENV
+ cd $PACK_NAME
+ zip -r ../$PACK_NAME.opk ./
+ - name: Upload
+ uses: actions/upload-artifact@v3
+ with:
+ name: Artifacts
+ path: ${{ env.PACK_NAME }}.opk
+ - name: Release
+ if: github.ref_type == 'tag'
+ uses: softprops/action-gh-release@v1
+ with:
+ token: ${{ secrets.GH_TOKEN }}
+ files: ${{ env.PACK_NAME }}.opk
diff --git a/LICENSE b/LICENSE
index e20b431..0ad25db 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-GNU AFFERO GENERAL PUBLIC LICENSE
+ GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
diff --git a/OlivaStoryCore/LICENSE b/OlivaStoryCore/LICENSE
new file mode 100644
index 0000000..0ad25db
--- /dev/null
+++ b/OlivaStoryCore/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
+
+
+ Copyright (C)
+
+ 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 .
+
+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
+.
diff --git a/OlivaStoryCore/__init__.py b/OlivaStoryCore/__init__.py
new file mode 100644
index 0000000..90858a6
--- /dev/null
+++ b/OlivaStoryCore/__init__.py
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+'''
+_______________________ _________________________________________
+__ __ \__ /____ _/_ | / /__ |__ __ \___ _/_ ____/__ ____/
+_ / / /_ / __ / __ | / /__ /| |_ / / /__ / _ / __ __/
+/ /_/ /_ /____/ / __ |/ / _ ___ | /_/ /__/ / / /___ _ /___
+\____/ /_____/___/ _____/ /_/ |_/_____/ /___/ \____/ /_____/
+
+@File : __init__.py
+@Author : lunzhiPenxil仑质
+@Contact : lunzhipenxil@gmail.com
+@License : AGPL
+@Copyright : (C) 2020-2021, OlivOS-Team
+@Desc : None
+'''
+
+import OlivaDiceCore
+from . import main
+from . import storyEngine
+from . import msgReply
+from . import msgCustom
+from . import msgCustomManager
+from . import data
+from . import userConfig
diff --git a/OlivaStoryCore/app.json b/OlivaStoryCore/app.json
new file mode 100644
index 0000000..56e181c
--- /dev/null
+++ b/OlivaStoryCore/app.json
@@ -0,0 +1,18 @@
+{
+ "name" : "OlivaStory核心模块",
+ "author" : "lunzhiPenxil",
+ "namespace" : "OlivaStoryCore",
+ "message_mode" : "old_string",
+ "info" : "本模块为OlivaStory的核心模块,实现了文游引擎的主要功能。",
+ "version" : "3.0.0",
+ "svn" : 1,
+ "compatible_svn" : 101,
+ "priority" : 20045,
+ "support" : [
+ {
+ "sdk" : "all",
+ "platform" : "all",
+ "model" : "all"
+ }
+ ]
+}
diff --git a/OlivaStoryCore/data.py b/OlivaStoryCore/data.py
new file mode 100644
index 0000000..ec2452f
--- /dev/null
+++ b/OlivaStoryCore/data.py
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+'''
+_______________________ _________________________________________
+__ __ \__ /____ _/_ | / /__ |__ __ \___ _/_ ____/__ ____/
+_ / / /_ / __ / __ | / /__ /| |_ / / /__ / _ / __ __/
+/ /_/ /_ /____/ / __ |/ / _ ___ | /_/ /__/ / / /___ _ /___
+\____/ /_____/___/ _____/ /_/ |_/_____/ /___/ \____/ /_____/
+
+@File : data.py
+@Author : lunzhiPenxil仑质
+@Contact : lunzhipenxil@gmail.com
+@License : AGPL
+@Copyright : (C) 2020-2021, OlivOS-Team
+@Desc : None
+'''
+
+
+OlivaStoryCore_ver = '3.0.0'
+OlivaStoryCore_svn = 1
+OlivaStoryCore_ver_short = '%s(%s)' % (str(OlivaStoryCore_ver), str(OlivaStoryCore_svn))
+
+dataDirRoot = './plugin/data/OlivaStory'
+
+gProc = None
diff --git a/OlivaStoryCore/main.py b/OlivaStoryCore/main.py
new file mode 100644
index 0000000..aa6bf49
--- /dev/null
+++ b/OlivaStoryCore/main.py
@@ -0,0 +1,37 @@
+# -*- encoding: utf-8 -*-
+'''
+_______________________ _________________________________________
+__ __ \__ /____ _/_ | / /__ |__ __ \___ _/_ ____/__ ____/
+_ / / /_ / __ / __ | / /__ /| |_ / / /__ / _ / __ __/
+/ /_/ /_ /____/ / __ |/ / _ ___ | /_/ /__/ / / /___ _ /___
+\____/ /_____/___/ _____/ /_/ |_/_____/ /___/ \____/ /_____/
+
+@File : main.py
+@Author : lunzhiPenxil仑质
+@Contact : lunzhipenxil@gmail.com
+@License : AGPL
+@Copyright : (C) 2020-2021, OlivOS-Team
+@Desc : None
+'''
+
+import OlivOS
+import OlivaStoryCore
+import OlivaDiceCore
+
+class Event(object):
+ def init(plugin_event, Proc):
+ OlivaStoryCore.msgReply.unity_init(plugin_event, Proc)
+
+ def init_after(plugin_event, Proc):
+ OlivaDiceCore.crossHook.dictHookList['model'].append(['OlivaStoryCore', OlivaStoryCore.data.OlivaStoryCore_ver_short])
+ OlivaStoryCore.msgReply.data_init(plugin_event, Proc)
+ OlivaStoryCore.storyEngine.data_init(plugin_event, Proc)
+
+ def private_message(plugin_event, Proc):
+ OlivaStoryCore.msgReply.unity_reply(plugin_event, Proc)
+
+ def group_message(plugin_event, Proc):
+ OlivaStoryCore.msgReply.unity_reply(plugin_event, Proc)
+
+ def poke(plugin_event, Proc):
+ pass
diff --git a/OlivaStoryCore/msgCustom.py b/OlivaStoryCore/msgCustom.py
new file mode 100644
index 0000000..b2d71c7
--- /dev/null
+++ b/OlivaStoryCore/msgCustom.py
@@ -0,0 +1,45 @@
+# -*- encoding: utf-8 -*-
+'''
+_______________________ _________________________________________
+__ __ \__ /____ _/_ | / /__ |__ __ \___ _/_ ____/__ ____/
+_ / / /_ / __ / __ | / /__ /| |_ / / /__ / _ / __ __/
+/ /_/ /_ /____/ / __ |/ / _ ___ | /_/ /__/ / / /___ _ /___
+\____/ /_____/___/ _____/ /_/ |_/_____/ /___/ \____/ /_____/
+
+@File : msgCustom.py
+@Author : lunzhiPenxil仑质
+@Contact : lunzhipenxil@gmail.com
+@License : AGPL
+@Copyright : (C) 2020-2021, OlivOS-Team
+@Desc : None
+'''
+
+import OlivOS
+import OlivaDiceCore
+import OlivaStoryCore
+
+dictConsoleSwitchTemplate = {
+ 'default' : {}
+}
+
+dictStrCustomDict = {}
+
+dictStrCustom = {
+ 'strStoryCoreStoryTall': '{tStoryCoreResult}\n\n{tStoryCoreSelection}',
+ 'strStoryCoreStoryTallNone': '故事不存在',
+ 'strStoryCoreStoryTallBreak': '故事中断了',
+ 'strStoryCoreStoryTallEnd': '故事结束了'
+}
+
+dictStrConst = {}
+
+dictGValue = {}
+
+dictTValue = {
+ 'tStoryCoreResult': 'N/A',
+ 'tStoryCoreSelection': 'N/A'
+}
+
+dictHelpDocTemp = {}
+
+dictUserConfigNoteDefault = {}
diff --git a/OlivaStoryCore/msgCustomManager.py b/OlivaStoryCore/msgCustomManager.py
new file mode 100644
index 0000000..24d6942
--- /dev/null
+++ b/OlivaStoryCore/msgCustomManager.py
@@ -0,0 +1,45 @@
+# -*- encoding: utf-8 -*-
+'''
+_______________________ _________________________________________
+__ __ \__ /____ _/_ | / /__ |__ __ \___ _/_ ____/__ ____/
+_ / / /_ / __ / __ | / /__ /| |_ / / /__ / _ / __ __/
+/ /_/ /_ /____/ / __ |/ / _ ___ | /_/ /__/ / / /___ _ /___
+\____/ /_____/___/ _____/ /_/ |_/_____/ /___/ \____/ /_____/
+
+@File : msgCustomManager.py
+@Author : lunzhiPenxil仑质
+@Contact : lunzhipenxil@gmail.com
+@License : AGPL
+@Copyright : (C) 2020-2021, OlivOS-Team
+@Desc : None
+'''
+
+import OlivOS
+import OlivaDiceCore
+import OlivaStoryCore
+
+import os
+import json
+
+def initMsgCustom(bot_info_dict):
+ for bot_info_dict_this in bot_info_dict:
+ if bot_info_dict_this not in OlivaDiceCore.msgCustom.dictStrCustomDict:
+ OlivaDiceCore.msgCustom.dictStrCustomDict[bot_info_dict_this] = {}
+ for dictStrCustom_this in OlivaStoryCore.msgCustom.dictStrCustom:
+ if dictStrCustom_this not in OlivaDiceCore.msgCustom.dictStrCustomDict[bot_info_dict_this]:
+ OlivaDiceCore.msgCustom.dictStrCustomDict[bot_info_dict_this][dictStrCustom_this] = OlivaStoryCore.msgCustom.dictStrCustom[dictStrCustom_this]
+ for dictHelpDoc_this in OlivaStoryCore.msgCustom.dictHelpDocTemp:
+ if dictHelpDoc_this not in OlivaDiceCore.helpDocData.dictHelpDoc[bot_info_dict_this]:
+ OlivaDiceCore.helpDocData.dictHelpDoc[bot_info_dict_this][dictHelpDoc_this] = OlivaStoryCore.msgCustom.dictHelpDocTemp[dictHelpDoc_this]
+ OlivaDiceCore.msgCustom.dictStrConst.update(OlivaStoryCore.msgCustom.dictStrConst)
+ OlivaDiceCore.msgCustom.dictGValue.update(OlivaStoryCore.msgCustom.dictGValue)
+ OlivaDiceCore.msgCustom.dictTValue.update(OlivaStoryCore.msgCustom.dictTValue)
+ OlivaDiceCore.userConfig.dictUserConfigNoteDefault.update(OlivaStoryCore.msgCustom.dictUserConfigNoteDefault)
+ for dictConsoleSwitchTemplate_this in OlivaStoryCore.msgCustom.dictConsoleSwitchTemplate:
+ if dictConsoleSwitchTemplate_this in OlivaDiceCore.console.dictConsoleSwitchTemplate:
+ OlivaDiceCore.console.dictConsoleSwitchTemplate[dictConsoleSwitchTemplate_this].update(
+ OlivaStoryCore.msgCustom.dictConsoleSwitchTemplate[dictConsoleSwitchTemplate_this]
+ )
+ OlivaDiceCore.console.initConsoleSwitchByBotDict(bot_info_dict)
+ OlivaDiceCore.console.readConsoleSwitch()
+ OlivaDiceCore.console.saveConsoleSwitch()
diff --git a/OlivaStoryCore/msgReply.py b/OlivaStoryCore/msgReply.py
new file mode 100644
index 0000000..086d9a2
--- /dev/null
+++ b/OlivaStoryCore/msgReply.py
@@ -0,0 +1,407 @@
+# -*- encoding: utf-8 -*-
+'''
+_______________________ _________________________________________
+__ __ \__ /____ _/_ | / /__ |__ __ \___ _/_ ____/__ ____/
+_ / / /_ / __ / __ | / /__ /| |_ / / /__ / _ / __ __/
+/ /_/ /_ /____/ / __ |/ / _ ___ | /_/ /__/ / / /___ _ /___
+\____/ /_____/___/ _____/ /_/ |_/_____/ /___/ \____/ /_____/
+
+@File : msgReply.py
+@Author : lunzhiPenxil仑质
+@Contact : lunzhipenxil@gmail.com
+@License : AGPL
+@Copyright : (C) 2020-2021, OlivOS-Team
+@Desc : None
+'''
+
+import OlivOS
+import OlivaStoryCore
+import OlivaDiceCore
+
+
+def unity_init(plugin_event, Proc):
+ pass
+
+def data_init(plugin_event, Proc):
+ OlivaStoryCore.data.gProc = Proc
+ OlivaStoryCore.msgCustomManager.initMsgCustom(Proc.Proc_data['bot_info_dict'])
+ OlivaStoryCore.userConfig.initUserConfigNoteDefault(Proc.Proc_data['bot_info_dict'])
+ if 'replyContextPrefixFliter' in OlivaDiceCore.crossHook.dictHookList:
+ OlivaDiceCore.crossHook.dictHookList['replyContextPrefixFliter'].append('story')
+
+def unity_reply(plugin_event, Proc):
+ OlivaDiceCore.userConfig.setMsgCount()
+ dictTValue = OlivaDiceCore.msgCustom.dictTValue.copy()
+ dictTValue['tName'] = plugin_event.data.sender['name']
+ dictStrCustom = OlivaDiceCore.msgCustom.dictStrCustomDict[plugin_event.bot_info.hash]
+ dictGValue = OlivaDiceCore.msgCustom.dictGValue
+ dictTValue.update(dictGValue)
+ dictTValue = OlivaDiceCore.msgCustomManager.dictTValueInit(plugin_event, dictTValue)
+
+ replyMsg = OlivaDiceCore.msgReply.replyMsg
+ isMatchWordStart = OlivaDiceCore.msgReply.isMatchWordStart
+ getMatchWordStartRight = OlivaDiceCore.msgReply.getMatchWordStartRight
+ skipSpaceStart = OlivaDiceCore.msgReply.skipSpaceStart
+ skipToRight = OlivaDiceCore.msgReply.skipToRight
+ msgIsCommand = OlivaDiceCore.msgReply.msgIsCommand
+
+ tmp_at_str = OlivOS.messageAPI.PARA.at(plugin_event.base_info['self_id']).CQ()
+ tmp_at_str_sub = None
+ if 'sub_self_id' in plugin_event.data.extend:
+ if plugin_event.data.extend['sub_self_id'] != None:
+ tmp_at_str_sub = OlivOS.messageAPI.PARA.at(plugin_event.data.extend['sub_self_id']).CQ()
+ tmp_command_str_1 = '.'
+ tmp_command_str_2 = '。'
+ tmp_command_str_3 = '/'
+ tmp_reast_str = plugin_event.data.message
+ flag_force_reply = False
+ flag_is_command = False
+ flag_is_from_host = False
+ flag_is_from_group = False
+ flag_is_from_group_admin = False
+ flag_is_from_group_have_admin = False
+ flag_is_from_master = False
+ if isMatchWordStart(tmp_reast_str, '[CQ:reply,id='):
+ tmp_reast_str = skipToRight(tmp_reast_str, ']')
+ tmp_reast_str = tmp_reast_str[1:]
+ if isMatchWordStart(tmp_reast_str, tmp_at_str):
+ tmp_reast_str = getMatchWordStartRight(tmp_reast_str, tmp_at_str)
+ tmp_reast_str = skipSpaceStart(tmp_reast_str)
+ flag_force_reply = True
+ if isMatchWordStart(tmp_reast_str, tmp_at_str):
+ tmp_reast_str = getMatchWordStartRight(tmp_reast_str, tmp_at_str)
+ tmp_reast_str = skipSpaceStart(tmp_reast_str)
+ flag_force_reply = True
+ if tmp_at_str_sub != None:
+ if isMatchWordStart(tmp_reast_str, tmp_at_str_sub):
+ tmp_reast_str = getMatchWordStartRight(tmp_reast_str, tmp_at_str_sub)
+ tmp_reast_str = skipSpaceStart(tmp_reast_str)
+ flag_force_reply = True
+ [tmp_reast_str, flag_is_command] = msgIsCommand(
+ tmp_reast_str,
+ OlivaDiceCore.crossHook.dictHookList['prefix']
+ )
+
+ tmp_hagID = None
+ tmp_userId = plugin_event.data.user_id
+ if plugin_event.plugin_info['func_type'] == 'group_message':
+ if plugin_event.data.host_id != None:
+ flag_is_from_host = True
+ flag_is_from_group = True
+ elif plugin_event.plugin_info['func_type'] == 'private_message':
+ flag_is_from_group = False
+ if flag_is_from_host and flag_is_from_group:
+ tmp_hagID = '%s|%s' % (str(plugin_event.data.host_id), str(plugin_event.data.group_id))
+ elif flag_is_from_group:
+ tmp_hagID = str(plugin_event.data.group_id)
+
+ if flag_is_command:
+ tmp_hagID = None
+ if plugin_event.plugin_info['func_type'] == 'group_message':
+ if plugin_event.data.host_id != None:
+ flag_is_from_host = True
+ flag_is_from_group = True
+ elif plugin_event.plugin_info['func_type'] == 'private_message':
+ flag_is_from_group = False
+ if flag_is_from_group:
+ if 'role' in plugin_event.data.sender:
+ flag_is_from_group_have_admin = True
+ if plugin_event.data.sender['role'] in ['owner', 'admin']:
+ flag_is_from_group_admin = True
+ elif plugin_event.data.sender['role'] in ['sub_admin']:
+ flag_is_from_group_admin = True
+ flag_is_from_group_sub_admin = True
+ if flag_is_from_host and flag_is_from_group:
+ tmp_hagID = '%s|%s' % (str(plugin_event.data.host_id), str(plugin_event.data.group_id))
+ elif flag_is_from_group:
+ tmp_hagID = str(plugin_event.data.group_id)
+ flag_hostEnable = True
+ if flag_is_from_host:
+ flag_hostEnable = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userId = plugin_event.data.host_id,
+ userType = 'host',
+ platform = plugin_event.platform['platform'],
+ userConfigKey = 'hostEnable',
+ botHash = plugin_event.bot_info.hash
+ )
+ flag_hostLocalEnable = True
+ if flag_is_from_host:
+ flag_hostLocalEnable = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userId = plugin_event.data.host_id,
+ userType = 'host',
+ platform = plugin_event.platform['platform'],
+ userConfigKey = 'hostLocalEnable',
+ botHash = plugin_event.bot_info.hash
+ )
+ flag_groupEnable = True
+ if flag_is_from_group:
+ if flag_is_from_host:
+ if flag_hostEnable:
+ flag_groupEnable = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userId = tmp_hagID,
+ userType = 'group',
+ platform = plugin_event.platform['platform'],
+ userConfigKey = 'groupEnable',
+ botHash = plugin_event.bot_info.hash
+ )
+ else:
+ flag_groupEnable = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userId = tmp_hagID,
+ userType = 'group',
+ platform = plugin_event.platform['platform'],
+ userConfigKey = 'groupWithHostEnable',
+ botHash = plugin_event.bot_info.hash
+ )
+ else:
+ flag_groupEnable = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userId = tmp_hagID,
+ userType = 'group',
+ platform = plugin_event.platform['platform'],
+ userConfigKey = 'groupEnable',
+ botHash = plugin_event.bot_info.hash
+ )
+ #此频道关闭时中断处理
+ if not flag_hostLocalEnable and not flag_force_reply:
+ return
+ #此群关闭时中断处理
+ if not flag_groupEnable and not flag_force_reply:
+ return
+ if isMatchWordStart(tmp_reast_str, 'story'):
+ tmp_reast_str = getMatchWordStartRight(tmp_reast_str, 'story')
+ tmp_reast_str = skipSpaceStart(tmp_reast_str)
+ tmp_platform = plugin_event.platform['platform']
+ tmp_botHash = plugin_event.bot_info.hash
+ tmp_chat_token = f'{tmp_platform}|{tmp_hagID}'
+ tmp_userId = plugin_event.data.user_id
+ data_storyRuntime = OlivaStoryCore.storyEngine.getStoryRuntime(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId
+ )
+ tmp_noteList = getNoteList(
+ chatToken = tmp_chat_token,
+ storyRuntime = data_storyRuntime
+ )
+ if type(data_storyRuntime) is not dict:
+ data_storyRuntime = {}
+ if isMatchWordStart(tmp_reast_str, 'end', isCommand = True):
+ OlivaStoryCore.storyEngine.runStoryBySelectionIndex(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ chatToken = tmp_chat_token,
+ selectionIndex = -1
+ )
+ tmp_reply_str = OlivaDiceCore.msgCustomManager.formatReplySTR(dictStrCustom['strStoryCoreStoryTallEnd'], dictTValue)
+ replyMsg(plugin_event, tmp_reply_str)
+ elif False and isMatchWordStart(tmp_reast_str, 'go'):
+ tmp_reast_str = getMatchWordStartRight(tmp_reast_str, 'go')
+ tmp_reast_str = skipSpaceStart(tmp_reast_str)
+ tmp_go_index = tmp_reast_str
+ try:
+ tmp_go_index = int(tmp_go_index)
+ except:
+ tmp_go_index = None
+ if tmp_go_index is not None:
+ tmp_go_index = tmp_go_index - 1
+ tmp_nodeData = OlivaStoryCore.storyEngine.runStoryBySelectionIndex(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ chatToken = tmp_chat_token,
+ selectionIndex = tmp_go_index
+ )
+ tmp_reply_str = getStoryTall(
+ dictStrCustom = dictStrCustom,
+ dictTValue = dictTValue,
+ nodeData = tmp_nodeData,
+ noteList = tmp_noteList
+ )
+ replyMsg(plugin_event, tmp_reply_str)
+ if OlivaStoryCore.storyEngine.isStoryEnd(tmp_nodeData):
+ OlivaStoryCore.storyEngine.runStoryBySelectionIndex(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ chatToken = tmp_chat_token,
+ selectionIndex = -1
+ )
+ elif len(tmp_reast_str) > 0:
+ tmp_reast_str = tmp_reast_str.strip(' ')
+ tmp_story_name = tmp_reast_str
+ tmp_nodeData = OlivaStoryCore.storyEngine.startStory(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ storyName = tmp_story_name,
+ chatToken = tmp_chat_token
+ )
+ tmp_reply_str = getStoryTall(
+ dictStrCustom = dictStrCustom,
+ dictTValue = dictTValue,
+ nodeData = tmp_nodeData,
+ flagIsStart = True,
+ noteList = tmp_noteList
+ )
+ replyMsg(plugin_event, tmp_reply_str)
+ else:
+ tmp_platform = plugin_event.platform['platform']
+ tmp_botHash = plugin_event.bot_info.hash
+ tmp_chat_token = f'{tmp_platform}|{tmp_hagID}'
+ data_storyRuntime = OlivaStoryCore.storyEngine.getStoryRuntime(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId
+ )
+ tmp_noteList = getNoteList(
+ chatToken = tmp_chat_token,
+ storyRuntime = data_storyRuntime
+ )
+ if type(data_storyRuntime) is dict \
+ and tmp_chat_token in data_storyRuntime \
+ and 'storyNameNow' in data_storyRuntime[tmp_chat_token] \
+ and type(data_storyRuntime[tmp_chat_token]['storyNameNow']) is str \
+ and 'storyFlagNow' in data_storyRuntime[tmp_chat_token] \
+ and type(data_storyRuntime[tmp_chat_token]['storyFlagNow']) is str:
+ tmp_storyNode = OlivaStoryCore.storyEngine.getStoryNodeByFlag(
+ storyName = data_storyRuntime[tmp_chat_token]['storyNameNow'],
+ flag = data_storyRuntime[tmp_chat_token]['storyFlagNow'],
+ botHash = tmp_botHash
+ )
+ tmp_storyNode_type = OlivaStoryCore.storyEngine.getStoryNodeDataForStoryData(
+ dataKey = 'type',
+ storyData = tmp_storyNode
+ )
+ if tmp_storyNode_type == 'ra' \
+ and isMatchWordStart(tmp_reast_str, 'ra'):
+ tmp_nodeData = OlivaStoryCore.storyEngine.runStoryBySelectionIndex(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ chatToken = tmp_chat_token,
+ selectionIndex = 'ra'
+ )
+ tmp_reply_str = getStoryTall(
+ dictStrCustom = dictStrCustom,
+ dictTValue = dictTValue,
+ nodeData = tmp_nodeData,
+ noteList = tmp_noteList
+ )
+ if tmp_reply_str is not None:
+ replyMsg(plugin_event, tmp_reply_str)
+ if OlivaStoryCore.storyEngine.isStoryEnd(tmp_nodeData):
+ OlivaStoryCore.storyEngine.runStoryBySelectionIndex(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ chatToken = tmp_chat_token,
+ selectionIndex = -1
+ )
+ else:
+ tmp_platform = plugin_event.platform['platform']
+ tmp_botHash = plugin_event.bot_info.hash
+ tmp_chat_token = f'{tmp_platform}|{tmp_hagID}'
+ data_storyRuntime = OlivaStoryCore.storyEngine.getStoryRuntime(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId
+ )
+ tmp_noteList = getNoteList(
+ chatToken = tmp_chat_token,
+ storyRuntime = data_storyRuntime
+ )
+ tmp_reply_str = None
+ if type(data_storyRuntime) is dict \
+ and tmp_chat_token in data_storyRuntime:
+ tmp_go_index = tmp_reast_str
+ try:
+ tmp_go_index = int(tmp_go_index)
+ except:
+ tmp_go_index = None
+ if tmp_go_index is not None:
+ tmp_go_index = tmp_go_index - 1
+ tmp_nodeData = OlivaStoryCore.storyEngine.runStoryBySelectionIndex(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ chatToken = tmp_chat_token,
+ selectionIndex = tmp_go_index
+ )
+ tmp_reply_str = getStoryTall(
+ dictStrCustom = dictStrCustom,
+ dictTValue = dictTValue,
+ nodeData = tmp_nodeData,
+ noteList = tmp_noteList
+ )
+ if tmp_reply_str is not None:
+ replyMsg(plugin_event, tmp_reply_str)
+ if OlivaStoryCore.storyEngine.isStoryEnd(tmp_nodeData):
+ OlivaStoryCore.storyEngine.runStoryBySelectionIndex(
+ botHash = tmp_botHash,
+ platform = tmp_platform,
+ userId = tmp_userId,
+ chatToken = tmp_chat_token,
+ selectionIndex = -1
+ )
+
+def getNoteList(
+ chatToken:str,
+ storyRuntime:dict
+):
+ res = None
+ if type(storyRuntime) is dict \
+ and chatToken in storyRuntime \
+ and type(storyRuntime[chatToken]) is dict \
+ and 'storyNoteList' in storyRuntime[chatToken] \
+ and type(storyRuntime[chatToken]['storyNoteList']) is list:
+ res = storyRuntime[chatToken]['storyNoteList']
+ return res
+
+def getStoryTall(
+ dictStrCustom,
+ dictTValue,
+ nodeData,
+ flagIsStart = False,
+ noteList = None
+):
+ res = OlivaDiceCore.msgCustomManager.formatReplySTR(dictStrCustom['strStoryCoreStoryTallBreak'], dictTValue)
+ if flagIsStart:
+ res = OlivaDiceCore.msgCustomManager.formatReplySTR(dictStrCustom['strStoryCoreStoryTallNone'], dictTValue)
+ if nodeData is not None:
+ dictTValue['tStoryCoreResult'] = OlivaStoryCore.storyEngine.getStoryNodeDataForStoryData(
+ dataKey = 'text',
+ storyData = nodeData
+ )
+ selection = OlivaStoryCore.storyEngine.getStoryNodeDataForStoryData(
+ dataKey = 'selection',
+ storyData = nodeData
+ )
+ tmp_nodeData_type = OlivaStoryCore.storyEngine.getStoryNodeDataForStoryData(
+ dataKey = 'type',
+ storyData = nodeData
+ )
+ if tmp_nodeData_type == None:
+ if len(selection) > 0:
+ tmp_selection_list = []
+ for i in range(len(selection)):
+ if noteList is None \
+ or (type(noteList) is list \
+ and OlivaStoryCore.storyEngine.haveStoryNote(
+ selectionData = selection[i],
+ storyNoteList = noteList
+ )
+ ):
+ tmp_selection_list.append(f"{i + 1} - {selection[i]['text']}")
+ if len(tmp_selection_list) > 0:
+ dictTValue['tStoryCoreSelection'] = '选项如下:\n%s' % ('\n'.join(tmp_selection_list))
+ else:
+ dictTValue['tStoryCoreSelection'] = '道路的尽头'
+ else:
+ dictTValue['tStoryCoreSelection'] = '选择的尽头'
+ elif tmp_nodeData_type == 'end':
+ dictTValue['tStoryCoreSelection'] = '道路的尽头'
+ elif tmp_nodeData_type == 'ra':
+ dictTValue['tStoryCoreSelection'] = '使用[.ra]指令完成检定以继续'
+ dictTValue['tStoryCoreResult'] = nodeData.get('text', [])
+ res = OlivaDiceCore.msgCustomManager.formatReplySTR(dictStrCustom['strStoryCoreStoryTall'], dictTValue)
+ return res
diff --git a/OlivaStoryCore/storyEngine.py b/OlivaStoryCore/storyEngine.py
new file mode 100644
index 0000000..c61226d
--- /dev/null
+++ b/OlivaStoryCore/storyEngine.py
@@ -0,0 +1,396 @@
+import OlivaDiceCore
+import OlivaStoryCore
+
+import os
+import json
+import traceback
+import copy
+import codecs
+try:
+ import pyjson5
+except:
+ pass
+
+storyList = {}
+
+storyNodeDefault = {
+ "flag": None,
+ "text": None,
+ "selection": [],
+ "type": None,
+ "endFlag": None,
+ "raSkillName": None,
+ "raSkillType": None,
+ "raMap": []
+}
+
+def data_init(plugin_event, Proc):
+ releaseDir(OlivaStoryCore.data.dataDirRoot)
+ initStoryList(Proc.Proc_data['bot_info_dict'])
+ pass
+
+def initStoryList(botInfo):
+ releaseDir(f'{OlivaStoryCore.data.dataDirRoot}/unity')
+ releaseDir(f'{OlivaStoryCore.data.dataDirRoot}/unity/extend')
+ releaseDir(f'{OlivaStoryCore.data.dataDirRoot}/unity/extend/story')
+ initStoryListByBotHash('unity')
+ for botHash in botInfo:
+ releaseDir(f'{OlivaStoryCore.data.dataDirRoot}/{botHash}')
+ releaseDir(f'{OlivaStoryCore.data.dataDirRoot}/{botHash}/extend')
+ releaseDir(f'{OlivaStoryCore.data.dataDirRoot}/{botHash}/extend/story')
+ initStoryListByBotHash(botHash)
+
+
+def initStoryListByBotHash(botHash:str):
+ storyList.setdefault(botHash, {})
+ fileStoryList = os.listdir(f'{OlivaStoryCore.data.dataDirRoot}/{botHash}/extend/story')
+ for filePath in fileStoryList:
+ objStoryThis = None
+ try:
+ with open(f'{OlivaStoryCore.data.dataDirRoot}/{botHash}/extend/story/{filePath}', 'rb') as filePath_f:
+ filePath_fs = formatUTF8WithBOM(filePath_f.read()).decode('utf-8')
+ try:
+ objStoryThis = pyjson5.loads(filePath_fs)
+ except:
+ objStoryThis = json.loads(filePath_fs)
+ except:
+ traceback.print_exc()
+ if type(objStoryThis) is dict \
+ and 'name' in objStoryThis \
+ and type(objStoryThis['name']) is str \
+ and 'ingress' in objStoryThis \
+ and 'story' in objStoryThis \
+ and type(objStoryThis['story']) is list:
+ storyList[botHash][objStoryThis['name']] = copy.deepcopy(objStoryThis)
+ if botHash != 'unity':
+ for objStoryName in storyList['unity']:
+ storyListThis = storyList['unity'][objStoryName]
+ storyList[botHash].setdefault(storyListThis['name'], storyListThis)
+
+def getStoryRuntime(
+ botHash,
+ platform,
+ userId
+):
+ res = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userId = userId,
+ userType = 'user',
+ platform = platform,
+ userConfigKey = 'storyRuntime',
+ botHash = botHash
+ )
+ if type(res) is not dict:
+ res = {}
+ return res
+
+def setStoryRuntime(
+ botHash,
+ platform,
+ userId,
+ storyRuntime
+):
+ res = OlivaDiceCore.userConfig.setUserConfigByKey(
+ userId = userId,
+ userType = 'user',
+ platform = platform,
+ userConfigKey = 'storyRuntime',
+ botHash = botHash,
+ userConfigValue = storyRuntime
+ )
+ OlivaDiceCore.userConfig.writeUserConfigByUserHash(
+ userHash = OlivaDiceCore.userConfig.getUserHash(
+ userId = userId,
+ userType = 'user',
+ platform = platform
+ )
+ )
+ return res
+
+def getStory(botHash, storyName):
+ res = None
+ if botHash in OlivaStoryCore.storyEngine.storyList \
+ and storyName in OlivaStoryCore.storyEngine.storyList[botHash] \
+ and type(OlivaStoryCore.storyEngine.storyList[botHash][storyName]) is dict \
+ and 'name' in OlivaStoryCore.storyEngine.storyList[botHash][storyName] \
+ and type(OlivaStoryCore.storyEngine.storyList[botHash][storyName]['name']) is str \
+ and 'ingress' in OlivaStoryCore.storyEngine.storyList[botHash][storyName] \
+ and 'story' in OlivaStoryCore.storyEngine.storyList[botHash][storyName] \
+ and type(OlivaStoryCore.storyEngine.storyList[botHash][storyName]['story']) is list:
+ res = OlivaStoryCore.storyEngine.storyList[botHash][storyName]
+ return res
+
+def getStoryNodeByFlagForData(
+ storyData,
+ flag
+):
+ res = None
+ story = storyData
+ if story is not None:
+ for storyNode in story['story']:
+ if type(storyNode) is dict \
+ and 'flag' in storyNode \
+ and 'text' in storyNode \
+ and str(storyNode['flag']) == str(flag):
+ res = storyNode
+ break
+ return res
+
+def getStoryNodeByFlag(
+ storyName,
+ flag,
+ botHash
+):
+ res = getStoryNodeByFlagForData(
+ storyData = getStory(
+ botHash = botHash,
+ storyName = storyName
+ ),
+ flag = flag
+ )
+ return res
+
+def getStoryNodeDataForStoryData(
+ dataKey:str,
+ storyData:dict
+):
+ global storyNodeDefault
+ res = None
+ if storyData is not None:
+ res = storyData.get(dataKey, copy.deepcopy(storyNodeDefault.get(dataKey, None)))
+ return res
+
+def getStoryNodeData(
+ storyName:str,
+ flag:str,
+ dataKey:str,
+ botHash:str
+):
+ res = getStoryNodeDataForStoryData(
+ storyData = getStoryNodeByFlag(
+ storyName = storyName,
+ flag = flag,
+ botHash = botHash
+ ),
+ dataKey = dataKey
+ )
+ return res
+
+def setStoryNoteList(listData:list, nodeData:dict):
+ res = listData
+ if type(listData) is not list:
+ res = []
+ if type(nodeData) is dict \
+ and 'hideNote' in nodeData \
+ and type(nodeData['hideNote']) is list:
+ for hideNote_item in nodeData['hideNote']:
+ if type(hideNote_item) is str \
+ and hideNote_item not in res:
+ res.append(hideNote_item)
+ return res
+
+def haveStoryNote(
+ selectionData:dict,
+ storyNoteList:list
+):
+ res = False
+ if type(selectionData) is dict \
+ and 'hideNote' in selectionData \
+ and type(selectionData['hideNote']) is str \
+ and type(storyNoteList) is list \
+ and selectionData['hideNote'] in storyNoteList:
+ res = True
+ elif type(selectionData) is dict \
+ and 'hideNote' not in selectionData:
+ res = True
+ return res
+
+def startStory(
+ botHash:str,
+ platform:str,
+ userId:str,
+ storyName:str,
+ chatToken:str
+):
+ res = None
+ story = getStory(botHash, storyName)
+ if story is not None:
+ tmp_story_runtime = getStoryRuntime(
+ botHash = botHash,
+ platform = platform,
+ userId = userId
+ )
+ tmp_storyNode = getStoryNodeByFlagForData(
+ storyData = story,
+ flag = story['ingress']
+ )
+ res = tmp_storyNode
+ if res is not None:
+ tmp_story_runtime[chatToken] = {
+ 'storyNameNow': storyName,
+ 'storyFlagNow': story['ingress'],
+ 'storyNoteList': setStoryNoteList([], tmp_storyNode)
+ }
+ setStoryRuntime(
+ botHash = botHash,
+ platform = platform,
+ userId = userId,
+ storyRuntime = tmp_story_runtime
+ )
+ return res
+
+def runStoryBySelectionIndex(
+ botHash:str,
+ platform:str,
+ userId:str,
+ chatToken:str,
+ selectionIndex:'None|str|int' = None
+):
+ res = None
+ tmp_story_runtime = getStoryRuntime(
+ botHash = botHash,
+ platform = platform,
+ userId = userId
+ )
+ storyFlagNext = None
+ flagNeedSave = False
+ if chatToken in tmp_story_runtime \
+ and type(tmp_story_runtime[chatToken]) is dict \
+ and 'storyNameNow' in tmp_story_runtime[chatToken] \
+ and type(tmp_story_runtime[chatToken]['storyNameNow']) is str \
+ and 'storyFlagNow' in tmp_story_runtime[chatToken] \
+ and type(tmp_story_runtime[chatToken]['storyFlagNow']) is str:
+ story = getStory(botHash, tmp_story_runtime[chatToken]['storyNameNow'])
+ if story is not None:
+ tmp_storyNode = getStoryNodeByFlagForData(
+ storyData = story,
+ flag = tmp_story_runtime[chatToken]['storyFlagNow']
+ )
+ tmp_story_runtime[chatToken]['storyNoteList'] = setStoryNoteList(
+ tmp_story_runtime[chatToken].get('storyNoteList', []),
+ tmp_storyNode
+ )
+ tmp_storyNode_selection = getStoryNodeDataForStoryData(
+ dataKey = 'selection',
+ storyData = tmp_storyNode
+ )
+ tmp_storyNode_type = getStoryNodeDataForStoryData(
+ dataKey = 'type',
+ storyData = tmp_storyNode
+ )
+ selectionIndex_new = None
+ if tmp_storyNode_type == None \
+ and type(selectionIndex) is int:
+ selectionIndex_new = selectionIndex
+ elif tmp_storyNode_type == 'ra' \
+ and selectionIndex == 'ra':
+ tmp_storyNode_raSkillName = getStoryNodeDataForStoryData(
+ dataKey = 'raSkillName',
+ storyData = tmp_storyNode
+ )
+ tmp_storyNode_raSkillValue = getStoryNodeDataForStoryData(
+ dataKey = 'raSkillValue',
+ storyData = tmp_storyNode
+ )
+ tmp_storyNode_raMap = getStoryNodeDataForStoryData(
+ dataKey = 'raMap',
+ storyData = tmp_storyNode
+ )
+ tmp_userData_raRollValue = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userConfigKey = 'RDRecordInt',
+ botHash = botHash,
+ userId = userId,
+ userType = 'user',
+ platform = platform
+ )
+ tmp_userData_raSkillValue = OlivaDiceCore.userConfig.getUserConfigByKey(
+ userConfigKey = 'RDRecordSkillInt',
+ botHash = botHash,
+ userId = userId,
+ userType = 'user',
+ platform = platform
+ )
+ if tmp_storyNode_raSkillName is not None \
+ and type(tmp_userData_raRollValue) is int \
+ and type(tmp_userData_raSkillValue) is int \
+ and ((type(tmp_storyNode_raSkillValue) is int \
+ and tmp_userData_raSkillValue == tmp_storyNode_raSkillValue) \
+ or tmp_storyNode_raSkillValue is None) \
+ and type(tmp_storyNode_raMap) is list:
+ for tmp_storyNode_raMap_item in tmp_storyNode_raMap:
+ if type(tmp_storyNode_raMap_item) is dict \
+ and 'para' in tmp_storyNode_raMap_item \
+ and type(tmp_storyNode_raMap_item['para']) is str \
+ and 'selection' in tmp_storyNode_raMap_item \
+ and type(tmp_storyNode_raMap_item['selection']) is int:
+ res_this = False
+ tmp_storyNode_raMap_item_para = tmp_storyNode_raMap_item['para']
+ tmp_value_dict = {
+ 'raRollValue': str(tmp_userData_raRollValue),
+ 'raSkillValue': str(tmp_userData_raSkillValue)
+ }
+ try:
+ tmp_storyNode_raMap_item_para = tmp_storyNode_raMap_item_para.format_map(tmp_value_dict)
+ res_this = eval(tmp_storyNode_raMap_item_para)
+ except:
+ res_this = False
+ traceback.print_exc()
+ if type(res_this) is not bool:
+ res_this = False
+ if res_this is True:
+ selectionIndex_new = tmp_storyNode_raMap_item['selection']
+ break
+ if type(selectionIndex_new) is int:
+ if len(tmp_storyNode_selection) > selectionIndex_new \
+ and 0 <= selectionIndex_new \
+ and type(tmp_storyNode_selection[selectionIndex_new]) is dict:
+ if 'to' in tmp_storyNode_selection[selectionIndex_new] \
+ and 'toType' in tmp_storyNode_selection[selectionIndex_new] \
+ and 'jump' == tmp_storyNode_selection[selectionIndex_new]['toType']:
+ if haveStoryNote(
+ selectionData = tmp_storyNode_selection[selectionIndex_new],
+ storyNoteList = tmp_story_runtime[chatToken].get('storyNoteList', [])
+ ):
+ res = getStoryNodeByFlagForData(
+ storyData = story,
+ flag = tmp_storyNode_selection[selectionIndex_new]['to']
+ )
+ if res is not None:
+ storyFlagNext = tmp_storyNode_selection[selectionIndex_new]['to']
+ if storyFlagNext is not None:
+ tmp_story_runtime[chatToken]['storyFlagNow'] = storyFlagNext
+ flagNeedSave = True
+ elif chatToken in tmp_story_runtime:
+ tmp_story_runtime.pop(chatToken)
+ flagNeedSave = True
+ if flagNeedSave:
+ setStoryRuntime(
+ botHash = botHash,
+ platform = platform,
+ userId = userId,
+ storyRuntime = tmp_story_runtime
+ )
+ return res
+
+def isStoryEnd(nodeData:dict):
+ res = False
+ if type(nodeData) is dict \
+ and 'flag' in nodeData \
+ and type(nodeData['flag']) is str \
+ and 'text' in nodeData \
+ and type(nodeData['text']) is str \
+ and 'type' in nodeData \
+ and nodeData['type'] == 'end' \
+ and 'endFlag' in nodeData \
+ and type(nodeData['endFlag']) is str:
+ res = True
+ return res
+
+def releaseDir(dir_path):
+ if not os.path.exists(dir_path):
+ os.makedirs(dir_path)
+
+def formatUTF8WithBOM(data:bytes):
+ res = data
+ if res[:3] == codecs.BOM_UTF8:
+ res = res[3:]
+ return res
diff --git a/OlivaStoryCore/userConfig.py b/OlivaStoryCore/userConfig.py
new file mode 100644
index 0000000..ca48af6
--- /dev/null
+++ b/OlivaStoryCore/userConfig.py
@@ -0,0 +1,27 @@
+# -*- encoding: utf-8 -*-
+'''
+_______________________ _________________________________________
+__ __ \__ /____ _/_ | / /__ |__ __ \___ _/_ ____/__ ____/
+_ / / /_ / __ / __ | / /__ /| |_ / / /__ / _ / __ __/
+/ /_/ /_ /____/ / __ |/ / _ ___ | /_/ /__/ / / /___ _ /___
+\____/ /_____/___/ _____/ /_/ |_/_____/ /___/ \____/ /_____/
+
+@File : userConfig.py
+@Author : lunzhiPenxil仑质
+@Contact : lunzhipenxil@gmail.com
+@License : AGPL
+@Copyright : (C) 2020-2021, OlivOS-Team
+@Desc : None
+'''
+
+import OlivaDiceCore
+import OlivaStoryCore
+
+dictUserConfigNoteDefault = {
+ 'storyRuntime' : None
+}
+
+def initUserConfigNoteDefault(bot_info_dict):
+ OlivaDiceCore.userConfig.dictUserConfigNoteDefault.update(
+ OlivaStoryCore.userConfig.dictUserConfigNoteDefault
+ )