Various Mod Updates for SPT 3.11
This commit is contained in:
parent
306af72daf
commit
37519a6093
|
@ -0,0 +1,402 @@
|
||||||
|
Attribution-NonCommercial-NoDerivatives 4.0 International
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||||
|
does not provide legal services or legal advice. Distribution of
|
||||||
|
Creative Commons public licenses does not create a lawyer-client or
|
||||||
|
other relationship. Creative Commons makes its licenses and related
|
||||||
|
information available on an "as-is" basis. Creative Commons gives no
|
||||||
|
warranties regarding its licenses, any material licensed under their
|
||||||
|
terms and conditions, or any related information. Creative Commons
|
||||||
|
disclaims all liability for damages resulting from their use to the
|
||||||
|
fullest extent possible.
|
||||||
|
|
||||||
|
Using Creative Commons Public Licenses
|
||||||
|
|
||||||
|
Creative Commons public licenses provide a standard set of terms and
|
||||||
|
conditions that creators and other rights holders may use to share
|
||||||
|
original works of authorship and other material subject to copyright
|
||||||
|
and certain other rights specified in the public license below. The
|
||||||
|
following considerations are for informational purposes only, are not
|
||||||
|
exhaustive, and do not form part of our licenses.
|
||||||
|
|
||||||
|
Considerations for licensors: Our public licenses are
|
||||||
|
intended for use by those authorized to give the public
|
||||||
|
permission to use material in ways otherwise restricted by
|
||||||
|
copyright and certain other rights. Our licenses are
|
||||||
|
irrevocable. Licensors should read and understand the terms
|
||||||
|
and conditions of the license they choose before applying it.
|
||||||
|
Licensors should also secure all rights necessary before
|
||||||
|
applying our licenses so that the public can reuse the
|
||||||
|
material as expected. Licensors should clearly mark any
|
||||||
|
material not subject to the license. This includes other CC-
|
||||||
|
licensed material, or material used under an exception or
|
||||||
|
limitation to copyright. More considerations for licensors:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensors
|
||||||
|
|
||||||
|
Considerations for the public: By using one of our public
|
||||||
|
licenses, a licensor grants the public permission to use the
|
||||||
|
licensed material under specified terms and conditions. If
|
||||||
|
the licensor's permission is not necessary for any reason--for
|
||||||
|
example, because of any applicable exception or limitation to
|
||||||
|
copyright--then that use is not regulated by the license. Our
|
||||||
|
licenses grant only permissions under copyright and certain
|
||||||
|
other rights that a licensor has authority to grant. Use of
|
||||||
|
the licensed material may still be restricted for other
|
||||||
|
reasons, including because others have copyright or other
|
||||||
|
rights in the material. A licensor may make special requests,
|
||||||
|
such as asking that all changes be marked or described.
|
||||||
|
Although not required by our licenses, you are encouraged to
|
||||||
|
respect those requests where reasonable. More considerations
|
||||||
|
for the public:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensees
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
|
||||||
|
International Public License
|
||||||
|
|
||||||
|
By exercising the Licensed Rights (defined below), You accept and agree
|
||||||
|
to be bound by the terms and conditions of this Creative Commons
|
||||||
|
Attribution-NonCommercial-NoDerivatives 4.0 International Public
|
||||||
|
License ("Public License"). To the extent this Public License may be
|
||||||
|
interpreted as a contract, You are granted the Licensed Rights in
|
||||||
|
consideration of Your acceptance of these terms and conditions, and the
|
||||||
|
Licensor grants You such rights in consideration of benefits the
|
||||||
|
Licensor receives from making the Licensed Material available under
|
||||||
|
these terms and conditions.
|
||||||
|
|
||||||
|
|
||||||
|
Section 1 -- Definitions.
|
||||||
|
|
||||||
|
a. Adapted Material means material subject to Copyright and Similar
|
||||||
|
Rights that is derived from or based upon the Licensed Material
|
||||||
|
and in which the Licensed Material is translated, altered,
|
||||||
|
arranged, transformed, or otherwise modified in a manner requiring
|
||||||
|
permission under the Copyright and Similar Rights held by the
|
||||||
|
Licensor. For purposes of this Public License, where the Licensed
|
||||||
|
Material is a musical work, performance, or sound recording,
|
||||||
|
Adapted Material is always produced where the Licensed Material is
|
||||||
|
synched in timed relation with a moving image.
|
||||||
|
|
||||||
|
b. Copyright and Similar Rights means copyright and/or similar rights
|
||||||
|
closely related to copyright including, without limitation,
|
||||||
|
performance, broadcast, sound recording, and Sui Generis Database
|
||||||
|
Rights, without regard to how the rights are labeled or
|
||||||
|
categorized. For purposes of this Public License, the rights
|
||||||
|
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||||
|
Rights.
|
||||||
|
|
||||||
|
c. Effective Technological Measures means those measures that, in the
|
||||||
|
absence of proper authority, may not be circumvented under laws
|
||||||
|
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||||
|
Treaty adopted on December 20, 1996, and/or similar international
|
||||||
|
agreements.
|
||||||
|
|
||||||
|
d. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||||
|
any other exception or limitation to Copyright and Similar Rights
|
||||||
|
that applies to Your use of the Licensed Material.
|
||||||
|
|
||||||
|
e. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
f. Licensed Rights means the rights granted to You subject to the
|
||||||
|
terms and conditions of this Public License, which are limited to
|
||||||
|
all Copyright and Similar Rights that apply to Your use of the
|
||||||
|
Licensed Material and that the Licensor has authority to license.
|
||||||
|
|
||||||
|
g. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
h. NonCommercial means not primarily intended for or directed towards
|
||||||
|
commercial advantage or monetary compensation. For purposes of
|
||||||
|
this Public License, the exchange of the Licensed Material for
|
||||||
|
other material subject to Copyright and Similar Rights by digital
|
||||||
|
file-sharing or similar means is NonCommercial provided there is
|
||||||
|
no payment of monetary compensation in connection with the
|
||||||
|
exchange.
|
||||||
|
|
||||||
|
i. Share means to provide material to the public by any means or
|
||||||
|
process that requires permission under the Licensed Rights, such
|
||||||
|
as reproduction, public display, public performance, distribution,
|
||||||
|
dissemination, communication, or importation, and to make material
|
||||||
|
available to the public including in ways that members of the
|
||||||
|
public may access the material from a place and at a time
|
||||||
|
individually chosen by them.
|
||||||
|
|
||||||
|
j. Sui Generis Database Rights means rights other than copyright
|
||||||
|
resulting from Directive 96/9/EC of the European Parliament and of
|
||||||
|
the Council of 11 March 1996 on the legal protection of databases,
|
||||||
|
as amended and/or succeeded, as well as other essentially
|
||||||
|
equivalent rights anywhere in the world.
|
||||||
|
|
||||||
|
k. You means the individual or entity exercising the Licensed Rights
|
||||||
|
under this Public License. Your has a corresponding meaning.
|
||||||
|
|
||||||
|
|
||||||
|
Section 2 -- Scope.
|
||||||
|
|
||||||
|
a. License grant.
|
||||||
|
|
||||||
|
1. Subject to the terms and conditions of this Public License,
|
||||||
|
the Licensor hereby grants You a worldwide, royalty-free,
|
||||||
|
non-sublicensable, non-exclusive, irrevocable license to
|
||||||
|
exercise the Licensed Rights in the Licensed Material to:
|
||||||
|
|
||||||
|
a. reproduce and Share the Licensed Material, in whole or
|
||||||
|
in part, for NonCommercial purposes only; and
|
||||||
|
|
||||||
|
b. produce and reproduce, but not Share, Adapted Material
|
||||||
|
for NonCommercial purposes only.
|
||||||
|
|
||||||
|
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||||
|
Exceptions and Limitations apply to Your use, this Public
|
||||||
|
License does not apply, and You do not need to comply with
|
||||||
|
its terms and conditions.
|
||||||
|
|
||||||
|
3. Term. The term of this Public License is specified in Section
|
||||||
|
6(a).
|
||||||
|
|
||||||
|
4. Media and formats; technical modifications allowed. The
|
||||||
|
Licensor authorizes You to exercise the Licensed Rights in
|
||||||
|
all media and formats whether now known or hereafter created,
|
||||||
|
and to make technical modifications necessary to do so. The
|
||||||
|
Licensor waives and/or agrees not to assert any right or
|
||||||
|
authority to forbid You from making technical modifications
|
||||||
|
necessary to exercise the Licensed Rights, including
|
||||||
|
technical modifications necessary to circumvent Effective
|
||||||
|
Technological Measures. For purposes of this Public License,
|
||||||
|
simply making modifications authorized by this Section 2(a)
|
||||||
|
(4) never produces Adapted Material.
|
||||||
|
|
||||||
|
5. Downstream recipients.
|
||||||
|
|
||||||
|
a. Offer from the Licensor -- Licensed Material. Every
|
||||||
|
recipient of the Licensed Material automatically
|
||||||
|
receives an offer from the Licensor to exercise the
|
||||||
|
Licensed Rights under the terms and conditions of this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
b. No downstream restrictions. You may not offer or impose
|
||||||
|
any additional or different terms or conditions on, or
|
||||||
|
apply any Effective Technological Measures to, the
|
||||||
|
Licensed Material if doing so restricts exercise of the
|
||||||
|
Licensed Rights by any recipient of the Licensed
|
||||||
|
Material.
|
||||||
|
|
||||||
|
6. No endorsement. Nothing in this Public License constitutes or
|
||||||
|
may be construed as permission to assert or imply that You
|
||||||
|
are, or that Your use of the Licensed Material is, connected
|
||||||
|
with, or sponsored, endorsed, or granted official status by,
|
||||||
|
the Licensor or others designated to receive attribution as
|
||||||
|
provided in Section 3(a)(1)(A)(i).
|
||||||
|
|
||||||
|
b. Other rights.
|
||||||
|
|
||||||
|
1. Moral rights, such as the right of integrity, are not
|
||||||
|
licensed under this Public License, nor are publicity,
|
||||||
|
privacy, and/or other similar personality rights; however, to
|
||||||
|
the extent possible, the Licensor waives and/or agrees not to
|
||||||
|
assert any such rights held by the Licensor to the limited
|
||||||
|
extent necessary to allow You to exercise the Licensed
|
||||||
|
Rights, but not otherwise.
|
||||||
|
|
||||||
|
2. Patent and trademark rights are not licensed under this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
3. To the extent possible, the Licensor waives any right to
|
||||||
|
collect royalties from You for the exercise of the Licensed
|
||||||
|
Rights, whether directly or through a collecting society
|
||||||
|
under any voluntary or waivable statutory or compulsory
|
||||||
|
licensing scheme. In all other cases the Licensor expressly
|
||||||
|
reserves any right to collect such royalties, including when
|
||||||
|
the Licensed Material is used other than for NonCommercial
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
|
||||||
|
Section 3 -- License Conditions.
|
||||||
|
|
||||||
|
Your exercise of the Licensed Rights is expressly made subject to the
|
||||||
|
following conditions.
|
||||||
|
|
||||||
|
a. Attribution.
|
||||||
|
|
||||||
|
1. If You Share the Licensed Material, You must:
|
||||||
|
|
||||||
|
a. retain the following if it is supplied by the Licensor
|
||||||
|
with the Licensed Material:
|
||||||
|
|
||||||
|
i. identification of the creator(s) of the Licensed
|
||||||
|
Material and any others designated to receive
|
||||||
|
attribution, in any reasonable manner requested by
|
||||||
|
the Licensor (including by pseudonym if
|
||||||
|
designated);
|
||||||
|
|
||||||
|
ii. a copyright notice;
|
||||||
|
|
||||||
|
iii. a notice that refers to this Public License;
|
||||||
|
|
||||||
|
iv. a notice that refers to the disclaimer of
|
||||||
|
warranties;
|
||||||
|
|
||||||
|
v. a URI or hyperlink to the Licensed Material to the
|
||||||
|
extent reasonably practicable;
|
||||||
|
|
||||||
|
b. indicate if You modified the Licensed Material and
|
||||||
|
retain an indication of any previous modifications; and
|
||||||
|
|
||||||
|
c. indicate the Licensed Material is licensed under this
|
||||||
|
Public License, and include the text of, or the URI or
|
||||||
|
hyperlink to, this Public License.
|
||||||
|
|
||||||
|
For the avoidance of doubt, You do not have permission under
|
||||||
|
this Public License to Share Adapted Material.
|
||||||
|
|
||||||
|
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||||
|
reasonable manner based on the medium, means, and context in
|
||||||
|
which You Share the Licensed Material. For example, it may be
|
||||||
|
reasonable to satisfy the conditions by providing a URI or
|
||||||
|
hyperlink to a resource that includes the required
|
||||||
|
information.
|
||||||
|
|
||||||
|
3. If requested by the Licensor, You must remove any of the
|
||||||
|
information required by Section 3(a)(1)(A) to the extent
|
||||||
|
reasonably practicable.
|
||||||
|
|
||||||
|
|
||||||
|
Section 4 -- Sui Generis Database Rights.
|
||||||
|
|
||||||
|
Where the Licensed Rights include Sui Generis Database Rights that
|
||||||
|
apply to Your use of the Licensed Material:
|
||||||
|
|
||||||
|
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||||
|
to extract, reuse, reproduce, and Share all or a substantial
|
||||||
|
portion of the contents of the database for NonCommercial purposes
|
||||||
|
only and provided You do not Share Adapted Material;
|
||||||
|
|
||||||
|
b. if You include all or a substantial portion of the database
|
||||||
|
contents in a database in which You have Sui Generis Database
|
||||||
|
Rights, then the database in which You have Sui Generis Database
|
||||||
|
Rights (but not its individual contents) is Adapted Material; and
|
||||||
|
|
||||||
|
c. You must comply with the conditions in Section 3(a) if You Share
|
||||||
|
all or a substantial portion of the contents of the database.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 4 supplements and does not
|
||||||
|
replace Your obligations under this Public License where the Licensed
|
||||||
|
Rights include other Copyright and Similar Rights.
|
||||||
|
|
||||||
|
|
||||||
|
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||||
|
|
||||||
|
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||||
|
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||||
|
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||||
|
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||||
|
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||||
|
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||||
|
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||||
|
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||||
|
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||||
|
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||||
|
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||||
|
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||||
|
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||||
|
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||||
|
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||||
|
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
c. The disclaimer of warranties and limitation of liability provided
|
||||||
|
above shall be interpreted in a manner that, to the extent
|
||||||
|
possible, most closely approximates an absolute disclaimer and
|
||||||
|
waiver of all liability.
|
||||||
|
|
||||||
|
|
||||||
|
Section 6 -- Term and Termination.
|
||||||
|
|
||||||
|
a. This Public License applies for the term of the Copyright and
|
||||||
|
Similar Rights licensed here. However, if You fail to comply with
|
||||||
|
this Public License, then Your rights under this Public License
|
||||||
|
terminate automatically.
|
||||||
|
|
||||||
|
b. Where Your right to use the Licensed Material has terminated under
|
||||||
|
Section 6(a), it reinstates:
|
||||||
|
|
||||||
|
1. automatically as of the date the violation is cured, provided
|
||||||
|
it is cured within 30 days of Your discovery of the
|
||||||
|
violation; or
|
||||||
|
|
||||||
|
2. upon express reinstatement by the Licensor.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||||
|
right the Licensor may have to seek remedies for Your violations
|
||||||
|
of this Public License.
|
||||||
|
|
||||||
|
c. For the avoidance of doubt, the Licensor may also offer the
|
||||||
|
Licensed Material under separate terms or conditions or stop
|
||||||
|
distributing the Licensed Material at any time; however, doing so
|
||||||
|
will not terminate this Public License.
|
||||||
|
|
||||||
|
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 7 -- Other Terms and Conditions.
|
||||||
|
|
||||||
|
a. The Licensor shall not be bound by any additional or different
|
||||||
|
terms or conditions communicated by You unless expressly agreed.
|
||||||
|
|
||||||
|
b. Any arrangements, understandings, or agreements regarding the
|
||||||
|
Licensed Material not stated herein are separate from and
|
||||||
|
independent of the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 8 -- Interpretation.
|
||||||
|
|
||||||
|
a. For the avoidance of doubt, this Public License does not, and
|
||||||
|
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||||
|
conditions on any use of the Licensed Material that could lawfully
|
||||||
|
be made without permission under this Public License.
|
||||||
|
|
||||||
|
b. To the extent possible, if any provision of this Public License is
|
||||||
|
deemed unenforceable, it shall be automatically reformed to the
|
||||||
|
minimum extent necessary to make it enforceable. If the provision
|
||||||
|
cannot be reformed, it shall be severed from this Public License
|
||||||
|
without affecting the enforceability of the remaining terms and
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
c. No term or condition of this Public License will be waived and no
|
||||||
|
failure to comply consented to unless expressly agreed to by the
|
||||||
|
Licensor.
|
||||||
|
|
||||||
|
d. Nothing in this Public License constitutes or may be interpreted
|
||||||
|
as a limitation upon, or waiver of, any privileges and immunities
|
||||||
|
that apply to the Licensor or You, including from the legal
|
||||||
|
processes of any jurisdiction or authority.
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons is not a party to its public
|
||||||
|
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||||
|
its public licenses to material it publishes and in those instances
|
||||||
|
will be considered the “Licensor.” The text of the Creative Commons
|
||||||
|
public licenses is dedicated to the public domain under the CC0 Public
|
||||||
|
Domain Dedication. Except for the limited purpose of indicating that
|
||||||
|
material is shared under a Creative Commons public license or as
|
||||||
|
otherwise permitted by the Creative Commons policies published at
|
||||||
|
creativecommons.org/policies, Creative Commons does not authorize the
|
||||||
|
use of the trademark "Creative Commons" or any other trademark or logo
|
||||||
|
of Creative Commons without its prior written consent including,
|
||||||
|
without limitation, in connection with any unauthorized modifications
|
||||||
|
to any of its public licenses or any other arrangements,
|
||||||
|
understandings, or agreements concerning use of licensed material. For
|
||||||
|
the avoidance of doubt, this paragraph does not form part of the
|
||||||
|
public licenses.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at creativecommons.org.
|
Binary file not shown.
|
@ -1,11 +1,11 @@
|
||||||
[General]
|
[General]
|
||||||
gameName=spt
|
gameName=spt
|
||||||
modid=0
|
modid=0
|
||||||
version=d2025.1.17.0
|
version=d2025.5.13.0
|
||||||
newestVersion=
|
newestVersion=
|
||||||
category="1,2"
|
category="-1,"
|
||||||
nexusFileStatus=1
|
nexusFileStatus=1
|
||||||
installationFile=DrakiaXYZ-Waypoints-1.6.1.7z
|
installationFile=acidphantasm-botplacementsystem.zip
|
||||||
repository=Nexus
|
repository=Nexus
|
||||||
ignoredVersion=
|
ignoredVersion=
|
||||||
comments=
|
comments=
|
||||||
|
@ -15,7 +15,7 @@ url=
|
||||||
hasCustomURL=false
|
hasCustomURL=false
|
||||||
lastNexusQuery=
|
lastNexusQuery=
|
||||||
lastNexusUpdate=
|
lastNexusUpdate=
|
||||||
nexusLastModified=2024-12-16T06:41:17Z
|
nexusLastModified=2025-05-13T08:46:56Z
|
||||||
nexusCategory=0
|
nexusCategory=0
|
||||||
converted=false
|
converted=false
|
||||||
validated=false
|
validated=false
|
BIN
mods/Acids Bot Placement System/user/mods/acidphantasm-botplacementsystem/ABPSConfig.exe (Stored with Git LFS)
Normal file
BIN
mods/Acids Bot Placement System/user/mods/acidphantasm-botplacementsystem/ABPSConfig.exe (Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -0,0 +1,402 @@
|
||||||
|
Attribution-NonCommercial-NoDerivatives 4.0 International
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||||
|
does not provide legal services or legal advice. Distribution of
|
||||||
|
Creative Commons public licenses does not create a lawyer-client or
|
||||||
|
other relationship. Creative Commons makes its licenses and related
|
||||||
|
information available on an "as-is" basis. Creative Commons gives no
|
||||||
|
warranties regarding its licenses, any material licensed under their
|
||||||
|
terms and conditions, or any related information. Creative Commons
|
||||||
|
disclaims all liability for damages resulting from their use to the
|
||||||
|
fullest extent possible.
|
||||||
|
|
||||||
|
Using Creative Commons Public Licenses
|
||||||
|
|
||||||
|
Creative Commons public licenses provide a standard set of terms and
|
||||||
|
conditions that creators and other rights holders may use to share
|
||||||
|
original works of authorship and other material subject to copyright
|
||||||
|
and certain other rights specified in the public license below. The
|
||||||
|
following considerations are for informational purposes only, are not
|
||||||
|
exhaustive, and do not form part of our licenses.
|
||||||
|
|
||||||
|
Considerations for licensors: Our public licenses are
|
||||||
|
intended for use by those authorized to give the public
|
||||||
|
permission to use material in ways otherwise restricted by
|
||||||
|
copyright and certain other rights. Our licenses are
|
||||||
|
irrevocable. Licensors should read and understand the terms
|
||||||
|
and conditions of the license they choose before applying it.
|
||||||
|
Licensors should also secure all rights necessary before
|
||||||
|
applying our licenses so that the public can reuse the
|
||||||
|
material as expected. Licensors should clearly mark any
|
||||||
|
material not subject to the license. This includes other CC-
|
||||||
|
licensed material, or material used under an exception or
|
||||||
|
limitation to copyright. More considerations for licensors:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensors
|
||||||
|
|
||||||
|
Considerations for the public: By using one of our public
|
||||||
|
licenses, a licensor grants the public permission to use the
|
||||||
|
licensed material under specified terms and conditions. If
|
||||||
|
the licensor's permission is not necessary for any reason--for
|
||||||
|
example, because of any applicable exception or limitation to
|
||||||
|
copyright--then that use is not regulated by the license. Our
|
||||||
|
licenses grant only permissions under copyright and certain
|
||||||
|
other rights that a licensor has authority to grant. Use of
|
||||||
|
the licensed material may still be restricted for other
|
||||||
|
reasons, including because others have copyright or other
|
||||||
|
rights in the material. A licensor may make special requests,
|
||||||
|
such as asking that all changes be marked or described.
|
||||||
|
Although not required by our licenses, you are encouraged to
|
||||||
|
respect those requests where reasonable. More considerations
|
||||||
|
for the public:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensees
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Attribution-NonCommercial-NoDerivatives 4.0
|
||||||
|
International Public License
|
||||||
|
|
||||||
|
By exercising the Licensed Rights (defined below), You accept and agree
|
||||||
|
to be bound by the terms and conditions of this Creative Commons
|
||||||
|
Attribution-NonCommercial-NoDerivatives 4.0 International Public
|
||||||
|
License ("Public License"). To the extent this Public License may be
|
||||||
|
interpreted as a contract, You are granted the Licensed Rights in
|
||||||
|
consideration of Your acceptance of these terms and conditions, and the
|
||||||
|
Licensor grants You such rights in consideration of benefits the
|
||||||
|
Licensor receives from making the Licensed Material available under
|
||||||
|
these terms and conditions.
|
||||||
|
|
||||||
|
|
||||||
|
Section 1 -- Definitions.
|
||||||
|
|
||||||
|
a. Adapted Material means material subject to Copyright and Similar
|
||||||
|
Rights that is derived from or based upon the Licensed Material
|
||||||
|
and in which the Licensed Material is translated, altered,
|
||||||
|
arranged, transformed, or otherwise modified in a manner requiring
|
||||||
|
permission under the Copyright and Similar Rights held by the
|
||||||
|
Licensor. For purposes of this Public License, where the Licensed
|
||||||
|
Material is a musical work, performance, or sound recording,
|
||||||
|
Adapted Material is always produced where the Licensed Material is
|
||||||
|
synched in timed relation with a moving image.
|
||||||
|
|
||||||
|
b. Copyright and Similar Rights means copyright and/or similar rights
|
||||||
|
closely related to copyright including, without limitation,
|
||||||
|
performance, broadcast, sound recording, and Sui Generis Database
|
||||||
|
Rights, without regard to how the rights are labeled or
|
||||||
|
categorized. For purposes of this Public License, the rights
|
||||||
|
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||||
|
Rights.
|
||||||
|
|
||||||
|
c. Effective Technological Measures means those measures that, in the
|
||||||
|
absence of proper authority, may not be circumvented under laws
|
||||||
|
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||||
|
Treaty adopted on December 20, 1996, and/or similar international
|
||||||
|
agreements.
|
||||||
|
|
||||||
|
d. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||||
|
any other exception or limitation to Copyright and Similar Rights
|
||||||
|
that applies to Your use of the Licensed Material.
|
||||||
|
|
||||||
|
e. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
f. Licensed Rights means the rights granted to You subject to the
|
||||||
|
terms and conditions of this Public License, which are limited to
|
||||||
|
all Copyright and Similar Rights that apply to Your use of the
|
||||||
|
Licensed Material and that the Licensor has authority to license.
|
||||||
|
|
||||||
|
g. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
h. NonCommercial means not primarily intended for or directed towards
|
||||||
|
commercial advantage or monetary compensation. For purposes of
|
||||||
|
this Public License, the exchange of the Licensed Material for
|
||||||
|
other material subject to Copyright and Similar Rights by digital
|
||||||
|
file-sharing or similar means is NonCommercial provided there is
|
||||||
|
no payment of monetary compensation in connection with the
|
||||||
|
exchange.
|
||||||
|
|
||||||
|
i. Share means to provide material to the public by any means or
|
||||||
|
process that requires permission under the Licensed Rights, such
|
||||||
|
as reproduction, public display, public performance, distribution,
|
||||||
|
dissemination, communication, or importation, and to make material
|
||||||
|
available to the public including in ways that members of the
|
||||||
|
public may access the material from a place and at a time
|
||||||
|
individually chosen by them.
|
||||||
|
|
||||||
|
j. Sui Generis Database Rights means rights other than copyright
|
||||||
|
resulting from Directive 96/9/EC of the European Parliament and of
|
||||||
|
the Council of 11 March 1996 on the legal protection of databases,
|
||||||
|
as amended and/or succeeded, as well as other essentially
|
||||||
|
equivalent rights anywhere in the world.
|
||||||
|
|
||||||
|
k. You means the individual or entity exercising the Licensed Rights
|
||||||
|
under this Public License. Your has a corresponding meaning.
|
||||||
|
|
||||||
|
|
||||||
|
Section 2 -- Scope.
|
||||||
|
|
||||||
|
a. License grant.
|
||||||
|
|
||||||
|
1. Subject to the terms and conditions of this Public License,
|
||||||
|
the Licensor hereby grants You a worldwide, royalty-free,
|
||||||
|
non-sublicensable, non-exclusive, irrevocable license to
|
||||||
|
exercise the Licensed Rights in the Licensed Material to:
|
||||||
|
|
||||||
|
a. reproduce and Share the Licensed Material, in whole or
|
||||||
|
in part, for NonCommercial purposes only; and
|
||||||
|
|
||||||
|
b. produce and reproduce, but not Share, Adapted Material
|
||||||
|
for NonCommercial purposes only.
|
||||||
|
|
||||||
|
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||||
|
Exceptions and Limitations apply to Your use, this Public
|
||||||
|
License does not apply, and You do not need to comply with
|
||||||
|
its terms and conditions.
|
||||||
|
|
||||||
|
3. Term. The term of this Public License is specified in Section
|
||||||
|
6(a).
|
||||||
|
|
||||||
|
4. Media and formats; technical modifications allowed. The
|
||||||
|
Licensor authorizes You to exercise the Licensed Rights in
|
||||||
|
all media and formats whether now known or hereafter created,
|
||||||
|
and to make technical modifications necessary to do so. The
|
||||||
|
Licensor waives and/or agrees not to assert any right or
|
||||||
|
authority to forbid You from making technical modifications
|
||||||
|
necessary to exercise the Licensed Rights, including
|
||||||
|
technical modifications necessary to circumvent Effective
|
||||||
|
Technological Measures. For purposes of this Public License,
|
||||||
|
simply making modifications authorized by this Section 2(a)
|
||||||
|
(4) never produces Adapted Material.
|
||||||
|
|
||||||
|
5. Downstream recipients.
|
||||||
|
|
||||||
|
a. Offer from the Licensor -- Licensed Material. Every
|
||||||
|
recipient of the Licensed Material automatically
|
||||||
|
receives an offer from the Licensor to exercise the
|
||||||
|
Licensed Rights under the terms and conditions of this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
b. No downstream restrictions. You may not offer or impose
|
||||||
|
any additional or different terms or conditions on, or
|
||||||
|
apply any Effective Technological Measures to, the
|
||||||
|
Licensed Material if doing so restricts exercise of the
|
||||||
|
Licensed Rights by any recipient of the Licensed
|
||||||
|
Material.
|
||||||
|
|
||||||
|
6. No endorsement. Nothing in this Public License constitutes or
|
||||||
|
may be construed as permission to assert or imply that You
|
||||||
|
are, or that Your use of the Licensed Material is, connected
|
||||||
|
with, or sponsored, endorsed, or granted official status by,
|
||||||
|
the Licensor or others designated to receive attribution as
|
||||||
|
provided in Section 3(a)(1)(A)(i).
|
||||||
|
|
||||||
|
b. Other rights.
|
||||||
|
|
||||||
|
1. Moral rights, such as the right of integrity, are not
|
||||||
|
licensed under this Public License, nor are publicity,
|
||||||
|
privacy, and/or other similar personality rights; however, to
|
||||||
|
the extent possible, the Licensor waives and/or agrees not to
|
||||||
|
assert any such rights held by the Licensor to the limited
|
||||||
|
extent necessary to allow You to exercise the Licensed
|
||||||
|
Rights, but not otherwise.
|
||||||
|
|
||||||
|
2. Patent and trademark rights are not licensed under this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
3. To the extent possible, the Licensor waives any right to
|
||||||
|
collect royalties from You for the exercise of the Licensed
|
||||||
|
Rights, whether directly or through a collecting society
|
||||||
|
under any voluntary or waivable statutory or compulsory
|
||||||
|
licensing scheme. In all other cases the Licensor expressly
|
||||||
|
reserves any right to collect such royalties, including when
|
||||||
|
the Licensed Material is used other than for NonCommercial
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
|
||||||
|
Section 3 -- License Conditions.
|
||||||
|
|
||||||
|
Your exercise of the Licensed Rights is expressly made subject to the
|
||||||
|
following conditions.
|
||||||
|
|
||||||
|
a. Attribution.
|
||||||
|
|
||||||
|
1. If You Share the Licensed Material, You must:
|
||||||
|
|
||||||
|
a. retain the following if it is supplied by the Licensor
|
||||||
|
with the Licensed Material:
|
||||||
|
|
||||||
|
i. identification of the creator(s) of the Licensed
|
||||||
|
Material and any others designated to receive
|
||||||
|
attribution, in any reasonable manner requested by
|
||||||
|
the Licensor (including by pseudonym if
|
||||||
|
designated);
|
||||||
|
|
||||||
|
ii. a copyright notice;
|
||||||
|
|
||||||
|
iii. a notice that refers to this Public License;
|
||||||
|
|
||||||
|
iv. a notice that refers to the disclaimer of
|
||||||
|
warranties;
|
||||||
|
|
||||||
|
v. a URI or hyperlink to the Licensed Material to the
|
||||||
|
extent reasonably practicable;
|
||||||
|
|
||||||
|
b. indicate if You modified the Licensed Material and
|
||||||
|
retain an indication of any previous modifications; and
|
||||||
|
|
||||||
|
c. indicate the Licensed Material is licensed under this
|
||||||
|
Public License, and include the text of, or the URI or
|
||||||
|
hyperlink to, this Public License.
|
||||||
|
|
||||||
|
For the avoidance of doubt, You do not have permission under
|
||||||
|
this Public License to Share Adapted Material.
|
||||||
|
|
||||||
|
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||||
|
reasonable manner based on the medium, means, and context in
|
||||||
|
which You Share the Licensed Material. For example, it may be
|
||||||
|
reasonable to satisfy the conditions by providing a URI or
|
||||||
|
hyperlink to a resource that includes the required
|
||||||
|
information.
|
||||||
|
|
||||||
|
3. If requested by the Licensor, You must remove any of the
|
||||||
|
information required by Section 3(a)(1)(A) to the extent
|
||||||
|
reasonably practicable.
|
||||||
|
|
||||||
|
|
||||||
|
Section 4 -- Sui Generis Database Rights.
|
||||||
|
|
||||||
|
Where the Licensed Rights include Sui Generis Database Rights that
|
||||||
|
apply to Your use of the Licensed Material:
|
||||||
|
|
||||||
|
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||||
|
to extract, reuse, reproduce, and Share all or a substantial
|
||||||
|
portion of the contents of the database for NonCommercial purposes
|
||||||
|
only and provided You do not Share Adapted Material;
|
||||||
|
|
||||||
|
b. if You include all or a substantial portion of the database
|
||||||
|
contents in a database in which You have Sui Generis Database
|
||||||
|
Rights, then the database in which You have Sui Generis Database
|
||||||
|
Rights (but not its individual contents) is Adapted Material; and
|
||||||
|
|
||||||
|
c. You must comply with the conditions in Section 3(a) if You Share
|
||||||
|
all or a substantial portion of the contents of the database.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 4 supplements and does not
|
||||||
|
replace Your obligations under this Public License where the Licensed
|
||||||
|
Rights include other Copyright and Similar Rights.
|
||||||
|
|
||||||
|
|
||||||
|
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||||
|
|
||||||
|
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||||
|
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||||
|
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||||
|
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||||
|
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||||
|
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||||
|
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||||
|
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||||
|
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||||
|
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||||
|
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||||
|
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||||
|
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||||
|
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||||
|
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||||
|
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
c. The disclaimer of warranties and limitation of liability provided
|
||||||
|
above shall be interpreted in a manner that, to the extent
|
||||||
|
possible, most closely approximates an absolute disclaimer and
|
||||||
|
waiver of all liability.
|
||||||
|
|
||||||
|
|
||||||
|
Section 6 -- Term and Termination.
|
||||||
|
|
||||||
|
a. This Public License applies for the term of the Copyright and
|
||||||
|
Similar Rights licensed here. However, if You fail to comply with
|
||||||
|
this Public License, then Your rights under this Public License
|
||||||
|
terminate automatically.
|
||||||
|
|
||||||
|
b. Where Your right to use the Licensed Material has terminated under
|
||||||
|
Section 6(a), it reinstates:
|
||||||
|
|
||||||
|
1. automatically as of the date the violation is cured, provided
|
||||||
|
it is cured within 30 days of Your discovery of the
|
||||||
|
violation; or
|
||||||
|
|
||||||
|
2. upon express reinstatement by the Licensor.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||||
|
right the Licensor may have to seek remedies for Your violations
|
||||||
|
of this Public License.
|
||||||
|
|
||||||
|
c. For the avoidance of doubt, the Licensor may also offer the
|
||||||
|
Licensed Material under separate terms or conditions or stop
|
||||||
|
distributing the Licensed Material at any time; however, doing so
|
||||||
|
will not terminate this Public License.
|
||||||
|
|
||||||
|
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 7 -- Other Terms and Conditions.
|
||||||
|
|
||||||
|
a. The Licensor shall not be bound by any additional or different
|
||||||
|
terms or conditions communicated by You unless expressly agreed.
|
||||||
|
|
||||||
|
b. Any arrangements, understandings, or agreements regarding the
|
||||||
|
Licensed Material not stated herein are separate from and
|
||||||
|
independent of the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 8 -- Interpretation.
|
||||||
|
|
||||||
|
a. For the avoidance of doubt, this Public License does not, and
|
||||||
|
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||||
|
conditions on any use of the Licensed Material that could lawfully
|
||||||
|
be made without permission under this Public License.
|
||||||
|
|
||||||
|
b. To the extent possible, if any provision of this Public License is
|
||||||
|
deemed unenforceable, it shall be automatically reformed to the
|
||||||
|
minimum extent necessary to make it enforceable. If the provision
|
||||||
|
cannot be reformed, it shall be severed from this Public License
|
||||||
|
without affecting the enforceability of the remaining terms and
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
c. No term or condition of this Public License will be waived and no
|
||||||
|
failure to comply consented to unless expressly agreed to by the
|
||||||
|
Licensor.
|
||||||
|
|
||||||
|
d. Nothing in this Public License constitutes or may be interpreted
|
||||||
|
as a limitation upon, or waiver of, any privileges and immunities
|
||||||
|
that apply to the Licensor or You, including from the legal
|
||||||
|
processes of any jurisdiction or authority.
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons is not a party to its public
|
||||||
|
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||||
|
its public licenses to material it publishes and in those instances
|
||||||
|
will be considered the “Licensor.” The text of the Creative Commons
|
||||||
|
public licenses is dedicated to the public domain under the CC0 Public
|
||||||
|
Domain Dedication. Except for the limited purpose of indicating that
|
||||||
|
material is shared under a Creative Commons public license or as
|
||||||
|
otherwise permitted by the Creative Commons policies published at
|
||||||
|
creativecommons.org/policies, Creative Commons does not authorize the
|
||||||
|
use of the trademark "Creative Commons" or any other trademark or logo
|
||||||
|
of Creative Commons without its prior written consent including,
|
||||||
|
without limitation, in connection with any unauthorized modifications
|
||||||
|
to any of its public licenses or any other arrangements,
|
||||||
|
understandings, or agreements concerning use of licensed material. For
|
||||||
|
the avoidance of doubt, this paragraph does not form part of the
|
||||||
|
public licenses.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at creativecommons.org.
|
|
@ -0,0 +1,640 @@
|
||||||
|
{
|
||||||
|
"pmcDifficulty": {
|
||||||
|
"easy": 10,
|
||||||
|
"normal": 50,
|
||||||
|
"hard": 30,
|
||||||
|
"impossible": 10
|
||||||
|
},
|
||||||
|
"pmcConfig": {
|
||||||
|
"startingPMCs": {
|
||||||
|
"enable": true,
|
||||||
|
"ignoreMaxBotCaps": true,
|
||||||
|
"groupChance": 25,
|
||||||
|
"maxGroupSize": 3,
|
||||||
|
"maxGroupCount": 4,
|
||||||
|
"mapLimits": {
|
||||||
|
"bigmap": {
|
||||||
|
"min": 8,
|
||||||
|
"max": 10
|
||||||
|
},
|
||||||
|
"factory4_day": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 7
|
||||||
|
},
|
||||||
|
"factory4_night": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 7
|
||||||
|
},
|
||||||
|
"interchange": {
|
||||||
|
"min": 9,
|
||||||
|
"max": 13
|
||||||
|
},
|
||||||
|
"laboratory": {
|
||||||
|
"min": 7,
|
||||||
|
"max": 9
|
||||||
|
},
|
||||||
|
"lighthouse": {
|
||||||
|
"min": 7,
|
||||||
|
"max": 10
|
||||||
|
},
|
||||||
|
"rezervbase": {
|
||||||
|
"min": 8,
|
||||||
|
"max": 10
|
||||||
|
},
|
||||||
|
"sandbox": {
|
||||||
|
"min": 8,
|
||||||
|
"max": 11
|
||||||
|
},
|
||||||
|
"sandbox_high": {
|
||||||
|
"min": 8,
|
||||||
|
"max": 11
|
||||||
|
},
|
||||||
|
"shoreline": {
|
||||||
|
"min": 9,
|
||||||
|
"max": 13
|
||||||
|
},
|
||||||
|
"tarkovstreets": {
|
||||||
|
"min": 7,
|
||||||
|
"max": 10
|
||||||
|
},
|
||||||
|
"woods": {
|
||||||
|
"min": 9,
|
||||||
|
"max": 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"waves": {
|
||||||
|
"enable": false,
|
||||||
|
"ignoreMaxBotCaps": false,
|
||||||
|
"groupChance": 10,
|
||||||
|
"maxGroupSize": 2,
|
||||||
|
"maxGroupCount": 3,
|
||||||
|
"maxBotsPerWave": 5,
|
||||||
|
"delayBeforeFirstWave": 500,
|
||||||
|
"secondsBetweenWaves": 360,
|
||||||
|
"stopWavesBeforeEndOfRaidLimit": 300
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scavConfig": {
|
||||||
|
"startingScavs": {
|
||||||
|
"enable": true,
|
||||||
|
"maxBotSpawns": {
|
||||||
|
"bigmap": 5,
|
||||||
|
"factory4_day": 3,
|
||||||
|
"factory4_night": 3,
|
||||||
|
"interchange": 5,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 5,
|
||||||
|
"rezervbase": 5,
|
||||||
|
"sandbox": 5,
|
||||||
|
"sandbox_high": 5,
|
||||||
|
"shoreline": 5,
|
||||||
|
"tarkovstreets": 5,
|
||||||
|
"woods": 5
|
||||||
|
},
|
||||||
|
"maxBotsPerZone": 1,
|
||||||
|
"startingMarksman": true
|
||||||
|
},
|
||||||
|
"waves": {
|
||||||
|
"enable": true,
|
||||||
|
"enableCustomFactory": true,
|
||||||
|
"startSpawns": 60,
|
||||||
|
"stopSpawns": 600,
|
||||||
|
"activeTimeMin": 180,
|
||||||
|
"activeTimeMax": 240,
|
||||||
|
"quietTimeMin": 120,
|
||||||
|
"quietTimeMax": 180,
|
||||||
|
"checkToSpawnTimer": 15,
|
||||||
|
"pendingBotsToTrigger": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossDifficulty": {
|
||||||
|
"easy": 0,
|
||||||
|
"normal": 60,
|
||||||
|
"hard": 30,
|
||||||
|
"impossible": 10
|
||||||
|
},
|
||||||
|
"bossConfig": {
|
||||||
|
"bossKnight": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 30,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 30,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 30,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 30
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "ZoneScavBase",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "Zone_TreatmentContainers,Zone_Chalet",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "ZoneMeteoStation",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": "ZoneScavBase2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossBully": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 30,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "ZoneDormitory,ZoneGasStation,ZoneScavBase",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossTagilla": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 30,
|
||||||
|
"factory4_night": 30,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "BotZone",
|
||||||
|
"factory4_night": "BotZone",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossKilla": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 30,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "ZoneCenterBot,ZoneCenter,ZoneOLI,ZoneIDEA,ZoneGoshan",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossZryachiy": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 100,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "Zone_Island",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossGluhar": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 30,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "ZoneRailStrorage,ZonePTOR2,ZoneBarrack,ZoneSubStorage",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossSanitar": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 30,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "ZoneGreenHouses,ZoneSanatorium1,ZoneSanatorium2,ZonePort",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossKolontay": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 30,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 30,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "ZoneSandbox",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "ZoneClimova,ZoneMvd",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossBoar": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 30,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "ZoneCarShowroom",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossKojaniy": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 30
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": "ZoneWoodCutter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bossPartisan": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 30,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 30,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 30,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 30
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sectantPriest": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 15,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 20,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 44,
|
||||||
|
"shoreline": 15,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 15
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "ZoneScavBase",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "BotZone",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "ZoneSandbox",
|
||||||
|
"shoreline": "ZoneSanatorium1,ZoneSanatorium2,ZoneForestSpawn",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": "ZoneMiniHouse,ZoneBrokenVill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"arenaFighterEvent": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 5,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 5
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "ZoneFactoryCenter,ZoneScavBase",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": "ZoneMiniHouse,ZoneClearVill,ZoneRoad,ZoneBrokenVill,ZoneScavBase2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pmcBot": {
|
||||||
|
"enable": true,
|
||||||
|
"addExtraSpawns": true,
|
||||||
|
"disableVanillaSpawns": false,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 75,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "BotZoneBasement,BotZoneFloor1,BotZoneFloor2",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exUsec": {
|
||||||
|
"enable": true,
|
||||||
|
"addExtraSpawns": false,
|
||||||
|
"disableVanillaSpawns": false,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gifter": {
|
||||||
|
"enable": true,
|
||||||
|
"time": -1,
|
||||||
|
"spawnChance": {
|
||||||
|
"bigmap": 0,
|
||||||
|
"factory4_day": 0,
|
||||||
|
"factory4_night": 0,
|
||||||
|
"interchange": 0,
|
||||||
|
"laboratory": 0,
|
||||||
|
"lighthouse": 0,
|
||||||
|
"rezervbase": 0,
|
||||||
|
"sandbox": 0,
|
||||||
|
"sandbox_high": 0,
|
||||||
|
"shoreline": 0,
|
||||||
|
"tarkovstreets": 0,
|
||||||
|
"woods": 0
|
||||||
|
},
|
||||||
|
"bossZone": {
|
||||||
|
"bigmap": "",
|
||||||
|
"factory4_day": "",
|
||||||
|
"factory4_night": "",
|
||||||
|
"interchange": "",
|
||||||
|
"laboratory": "",
|
||||||
|
"lighthouse": "",
|
||||||
|
"rezervbase": "",
|
||||||
|
"sandbox": "",
|
||||||
|
"sandbox_high": "",
|
||||||
|
"shoreline": "",
|
||||||
|
"tarkovstreets": "",
|
||||||
|
"woods": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configAppSettings": {
|
||||||
|
"showUndo": true,
|
||||||
|
"showDefault": false,
|
||||||
|
"disableAnimations": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "Acids Bot Placement System",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"sptVersion": "~3.11",
|
||||||
|
"loadBefore": [
|
||||||
|
"RaidOverhaul",
|
||||||
|
"WTT-RogueJustice"
|
||||||
|
],
|
||||||
|
"loadAfter": [],
|
||||||
|
"incompatibilities": [],
|
||||||
|
"isBundleMod": false,
|
||||||
|
"main": "src/mod.js",
|
||||||
|
"scripts": {
|
||||||
|
"setup": "npm i",
|
||||||
|
"build": "node ./build.mjs",
|
||||||
|
"buildinfo": "node ./build.mjs --verbose"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "20.11",
|
||||||
|
"@typescript-eslint/eslint-plugin": "7.2",
|
||||||
|
"@typescript-eslint/parser": "7.2",
|
||||||
|
"archiver": "^6.0",
|
||||||
|
"eslint": "8.57",
|
||||||
|
"fs-extra": "11.2",
|
||||||
|
"ignore": "^5.2",
|
||||||
|
"tsyringe": "4.8.0",
|
||||||
|
"typescript": "5.8",
|
||||||
|
"winston": "3.12"
|
||||||
|
},
|
||||||
|
"author": "acidphantasm",
|
||||||
|
"contributors": [],
|
||||||
|
"license": "BY-NC-ND 4.0"
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
import { injectable, inject } from "tsyringe";
|
||||||
|
import { IBossLocationSpawn } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper";
|
||||||
|
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||||
|
import { ModConfig } from "../Globals/ModConfig";
|
||||||
|
|
||||||
|
// Default Boss Data
|
||||||
|
import {
|
||||||
|
arenaFighterEventData,
|
||||||
|
bossBoarData,
|
||||||
|
bossBullyData,
|
||||||
|
bossGluharData,
|
||||||
|
bossKillaData,
|
||||||
|
bossKnightData,
|
||||||
|
bossKojaniyData,
|
||||||
|
bossKolontayData,
|
||||||
|
bossPartisanData,
|
||||||
|
bossSanitarData,
|
||||||
|
bossTagillaData,
|
||||||
|
bossZryachiyData,
|
||||||
|
exUsecData,
|
||||||
|
gifterData,
|
||||||
|
pmcBotData,
|
||||||
|
pmcBotReserveData,
|
||||||
|
pmcBotLaboratoryData,
|
||||||
|
sectantPriestData
|
||||||
|
} from "../Defaults/Bosses"
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class BossSpawnControl
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public getCustomMapData(location: string, escapeTimeLimit: number): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
return this.getConfigValueForLocation(location, escapeTimeLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConfigValueForLocation(location: string, escapeTimeLimit: number): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
const bossesForMap: IBossLocationSpawn[] = [];
|
||||||
|
for (const boss in ModConfig.config.bossConfig)
|
||||||
|
{
|
||||||
|
const bossDefaultData = this.cloner.clone(this.getDefaultValuesForBoss(boss, location));
|
||||||
|
const bossConfigData = ModConfig.config.bossConfig[boss];
|
||||||
|
const difficultyWeights = ModConfig.config.bossDifficulty;
|
||||||
|
|
||||||
|
if (!bossConfigData.enable) continue;
|
||||||
|
|
||||||
|
if (boss == "exUsec" && !bossConfigData.disableVanillaSpawns && location == "lighthouse" || boss == "pmcBot" && !bossConfigData.disableVanillaSpawns && (location == "laboratory" || location == "rezervbase"))
|
||||||
|
{
|
||||||
|
for (const bossSpawn in bossDefaultData)
|
||||||
|
{
|
||||||
|
// Create the vanilla spawns
|
||||||
|
//bossDefaultData[bossSpawn].BossChance = bossConfigData.spawnChance[location];
|
||||||
|
bossDefaultData[0].BossDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossesForMap.push(bossDefaultData[bossSpawn]);
|
||||||
|
}
|
||||||
|
if (!bossConfigData.addExtraSpawns) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bossConfigData.spawnChance[location]) continue;
|
||||||
|
|
||||||
|
if (location.includes("factory")) bossConfigData.bossZone[location] = "BotZone"
|
||||||
|
if ((boss == "pmcBot") && bossConfigData.addExtraSpawns)
|
||||||
|
{
|
||||||
|
bossesForMap.push(...this.generateBossWaves(location, escapeTimeLimit));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bossDefaultData[0].BossChance = bossConfigData.spawnChance[location];
|
||||||
|
bossDefaultData[0].BossZone = bossConfigData.bossZone[location];
|
||||||
|
bossDefaultData[0].BossDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossEscortDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].Time = bossConfigData.time;
|
||||||
|
bossesForMap.push(bossDefaultData[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bossesForMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateBossWaves(location: string, escapeTimeLimit: number): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
const pmcWaveSpawnInfo: IBossLocationSpawn[] = [];
|
||||||
|
|
||||||
|
const difficultyWeights = ModConfig.config.bossDifficulty;
|
||||||
|
const waveMaxPMCCount = location != "laboratory" ? 4 : 10;
|
||||||
|
const waveGroupLimit = 4;
|
||||||
|
const waveGroupSize = 2;
|
||||||
|
const waveGroupChance = 100;
|
||||||
|
const waveTimer = 450;
|
||||||
|
const endWavesAtRemainingTime = 300;
|
||||||
|
const waveCount = Math.floor((((escapeTimeLimit * 60) - endWavesAtRemainingTime)) / waveTimer);
|
||||||
|
let currentWaveTime = waveTimer;
|
||||||
|
const bossConfigData = ModConfig.config.bossConfig["pmcBot"];
|
||||||
|
|
||||||
|
//this.logger.warning(`[Boss Waves] Generating ${waveCount} waves for Raiders`)
|
||||||
|
for (let i = 1; i <= waveCount; i++)
|
||||||
|
{
|
||||||
|
if (i == 1) currentWaveTime = -1;
|
||||||
|
|
||||||
|
let currentPMCCount = 0;
|
||||||
|
let groupCount = 0;
|
||||||
|
while (currentPMCCount < waveMaxPMCCount)
|
||||||
|
{
|
||||||
|
if (groupCount >= waveGroupLimit) break;
|
||||||
|
let groupSize = 0;
|
||||||
|
const remainingSpots = waveMaxPMCCount - currentPMCCount;
|
||||||
|
const isAGroup = remainingSpots > 1 ? this.randomUtil.getChance100(waveGroupChance) : false;
|
||||||
|
if (isAGroup)
|
||||||
|
{
|
||||||
|
groupSize = Math.min(remainingSpots - 1, this.randomUtil.getInt(1, waveGroupSize));
|
||||||
|
}
|
||||||
|
const bossDefaultData = this.cloner.clone(this.getDefaultValuesForBoss("pmcBot", ""));
|
||||||
|
|
||||||
|
bossDefaultData[0].BossChance = bossConfigData.spawnChance[location];
|
||||||
|
bossDefaultData[0].BossZone = bossConfigData.bossZone[location];
|
||||||
|
bossDefaultData[0].BossEscortAmount = groupSize.toString();
|
||||||
|
bossDefaultData[0].BossDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossEscortDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].IgnoreMaxBots = false;
|
||||||
|
bossDefaultData[0].Time = currentWaveTime;
|
||||||
|
currentPMCCount += groupSize + 1;
|
||||||
|
groupCount++
|
||||||
|
pmcWaveSpawnInfo.push(bossDefaultData[0]);
|
||||||
|
|
||||||
|
//this.logger.warning(`[Boss Waves] Adding 1 spawn for Raiders to ${location} | GroupSize: ${groupSize + 1}`);
|
||||||
|
}
|
||||||
|
//this.logger.warning(`[Boss Waves] Wave: ${i} | Time: ${currentWaveTime} | Groups: ${groupCount} | TotalRaiderss: ${currentPMCCount}/${waveMaxPMCCount}`);
|
||||||
|
currentWaveTime += waveTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pmcWaveSpawnInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDefaultValuesForBoss(boss: string, location: string): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
switch (boss)
|
||||||
|
{
|
||||||
|
case "bossKnight":
|
||||||
|
return bossKnightData;
|
||||||
|
case "bossBully":
|
||||||
|
return bossBullyData;
|
||||||
|
case "bossTagilla":
|
||||||
|
return bossTagillaData;
|
||||||
|
case "bossKilla":
|
||||||
|
return bossKillaData;
|
||||||
|
case "bossZryachiy":
|
||||||
|
return bossZryachiyData;
|
||||||
|
case "bossGluhar":
|
||||||
|
return bossGluharData;
|
||||||
|
case "bossSanitar":
|
||||||
|
return bossSanitarData;
|
||||||
|
case "bossKolontay":
|
||||||
|
return bossKolontayData;
|
||||||
|
case "bossBoar":
|
||||||
|
return bossBoarData;
|
||||||
|
case "bossKojaniy":
|
||||||
|
return bossKojaniyData;
|
||||||
|
case "bossPartisan":
|
||||||
|
return bossPartisanData;
|
||||||
|
case "sectantPriest":
|
||||||
|
return sectantPriestData;
|
||||||
|
case "arenaFighterEvent":
|
||||||
|
return arenaFighterEventData;
|
||||||
|
case "pmcBot": // Requires Triggers + Has Multiple Zones
|
||||||
|
if (location == "rezervbase") return pmcBotReserveData;
|
||||||
|
if (location == "laboratory") return pmcBotLaboratoryData;
|
||||||
|
else return pmcBotData;
|
||||||
|
case "exUsec": // Has Multiple Zones
|
||||||
|
return exUsecData;
|
||||||
|
case "gifter":
|
||||||
|
return gifterData;
|
||||||
|
default:
|
||||||
|
this.logger.error(`[ABPS] Boss not found in config ${boss}`)
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,221 @@
|
||||||
|
import { injectable, inject } from "tsyringe";
|
||||||
|
import { ILocationBase, IBossLocationSpawn, IWave } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
|
||||||
|
import { BossSpawnControl } from "./BossSpawnControl";
|
||||||
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
|
import { ILocations } from "@spt/models/spt/server/ILocations";
|
||||||
|
import { VanillaAdjustmentControl } from "./VanillaAdjustmentControl";
|
||||||
|
import { PMCSpawnControl } from "./PMCSpawnControl";
|
||||||
|
import { ScavSpawnControl } from "./ScavSpawnControl";
|
||||||
|
import { IRaidChanges } from "@spt/models/spt/location/IRaidChanges";
|
||||||
|
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { ModConfig } from "../Globals/ModConfig";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MapSpawnControl
|
||||||
|
{
|
||||||
|
public validMaps: string[] = [
|
||||||
|
"bigmap",
|
||||||
|
"factory4_day",
|
||||||
|
"factory4_night",
|
||||||
|
"interchange",
|
||||||
|
"laboratory",
|
||||||
|
"lighthouse",
|
||||||
|
"rezervbase",
|
||||||
|
"sandbox",
|
||||||
|
"sandbox_high",
|
||||||
|
"shoreline",
|
||||||
|
"tarkovstreets",
|
||||||
|
"woods"
|
||||||
|
];
|
||||||
|
|
||||||
|
public botMapCache: Record<string, IBossLocationSpawn[]> = {};
|
||||||
|
public scavMapCache: Record<string, IWave[]> = {};
|
||||||
|
public locationData: ILocations = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
|
@inject("BossSpawnControl") protected bossSpawnControl: BossSpawnControl,
|
||||||
|
@inject("ScavSpawnControl") protected scavSpawnControl: ScavSpawnControl,
|
||||||
|
@inject("PMCSpawnControl") protected pmcSpawnControl: PMCSpawnControl,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
@inject("VanillaAdjustmentControl") protected vanillaAdjustmentControl: VanillaAdjustmentControl
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public configureInitialData(): void
|
||||||
|
{
|
||||||
|
this.locationData = this.databaseService.getTables().locations;
|
||||||
|
for (const map in this.validMaps)
|
||||||
|
{
|
||||||
|
const mapName = this.validMaps[map];
|
||||||
|
this.locationData[mapName].base.BossLocationSpawn = [];
|
||||||
|
this.botMapCache[mapName] = [];
|
||||||
|
this.scavMapCache[mapName] = [];
|
||||||
|
if (ModConfig.config.scavConfig.waves.enable) this.vanillaAdjustmentControl.enableAllSpawnSystem(this.locationData[mapName].base);
|
||||||
|
this.vanillaAdjustmentControl.removeExistingWaves(this.locationData[mapName].base);
|
||||||
|
this.vanillaAdjustmentControl.fixPMCHostility(this.locationData[mapName].base);
|
||||||
|
this.vanillaAdjustmentControl.adjustNewWaveSettings(this.locationData[mapName].base);
|
||||||
|
/*
|
||||||
|
This is how you make a spawn point properly
|
||||||
|
if (this.validMaps[map] == "bigmap") {
|
||||||
|
const test = {
|
||||||
|
"BotZoneName": "",
|
||||||
|
"Categories": [
|
||||||
|
"Player"
|
||||||
|
],
|
||||||
|
"ColliderParams": {
|
||||||
|
"_parent": "SpawnSphereParams",
|
||||||
|
"_props": {
|
||||||
|
"Center": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"Radius": 75
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CorePointId": 0,
|
||||||
|
"DelayToCanSpawnSec": 4,
|
||||||
|
"Id": crypto.randomUUID(),
|
||||||
|
"Infiltration": "Boiler Tanks",
|
||||||
|
"Position": {
|
||||||
|
"x": 288.068,
|
||||||
|
"y": 1.718,
|
||||||
|
"z": -200.166
|
||||||
|
},
|
||||||
|
"Rotation": 17.73762,
|
||||||
|
"Sides": [
|
||||||
|
"Pmc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.locationData[mapName].base.SpawnPointParams.push(test);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vanillaAdjustmentControl.disableVanillaSettings();
|
||||||
|
this.vanillaAdjustmentControl.removeCustomPMCWaves();
|
||||||
|
this.buildInitialCache();
|
||||||
|
}
|
||||||
|
public buildInitialCache(): void
|
||||||
|
{
|
||||||
|
this.buildBossWaves();
|
||||||
|
this.buildPMCWaves();
|
||||||
|
this.buildStartingScavs();
|
||||||
|
this.replaceOriginalLocations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildBossWaves(): void
|
||||||
|
{
|
||||||
|
for (const map in this.validMaps)
|
||||||
|
{
|
||||||
|
const mapName = this.validMaps[map];
|
||||||
|
const mapData = this.bossSpawnControl.getCustomMapData(this.validMaps[map], this.locationData[mapName].base.EscapeTimeLimit);
|
||||||
|
if (mapData.length) mapData.forEach((index) => (this.botMapCache[mapName].push(index)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildPMCWaves(): void
|
||||||
|
{
|
||||||
|
for (const map in this.validMaps)
|
||||||
|
{
|
||||||
|
const mapName = this.validMaps[map];
|
||||||
|
const mapData = this.pmcSpawnControl.getCustomMapData(this.validMaps[map], this.locationData[mapName].base.EscapeTimeLimit);
|
||||||
|
if (mapData.length) mapData.forEach((index) => (this.botMapCache[mapName].push(index)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildStartingScavs(): void
|
||||||
|
{
|
||||||
|
for (const map in this.validMaps)
|
||||||
|
{
|
||||||
|
const mapName = this.validMaps[map];
|
||||||
|
if (mapName == "laboratory") continue;
|
||||||
|
const mapData = this.scavSpawnControl.getCustomMapData(this.validMaps[map]);
|
||||||
|
if (mapData.length) mapData.forEach((index) => (this.scavMapCache[mapName].push(index)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private replaceOriginalLocations(): void
|
||||||
|
{
|
||||||
|
for (const map in this.validMaps)
|
||||||
|
{
|
||||||
|
const mapName = this.validMaps[map];
|
||||||
|
this.locationData[mapName].base.BossLocationSpawn = this.cloner.clone(this.botMapCache[mapName]);
|
||||||
|
this.locationData[mapName].base.waves = this.cloner.clone(this.scavMapCache[mapName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public rebuildCache(location: string): void
|
||||||
|
{
|
||||||
|
location = location.toLowerCase();
|
||||||
|
this.locationData = this.databaseService.getTables().locations;
|
||||||
|
this.botMapCache[location] = [];
|
||||||
|
this.scavMapCache[location] = [];
|
||||||
|
this.rebuildBossWave(location);
|
||||||
|
this.rebuildPMCWave(location);
|
||||||
|
this.rebuildStartingScavs(location)
|
||||||
|
this.rebuildLocation(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private rebuildBossWave(location: string): void
|
||||||
|
{
|
||||||
|
const mapName = location.toLowerCase();
|
||||||
|
this.logger.warning(`[ABPS] Recreating bosses for ${mapName}`);
|
||||||
|
|
||||||
|
const mapData = this.bossSpawnControl.getCustomMapData(mapName, this.locationData[mapName].base.EscapeTimeLimit);
|
||||||
|
if (mapData.length) mapData.forEach((index) => (this.botMapCache[mapName].push(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private rebuildPMCWave(location: string): void
|
||||||
|
{
|
||||||
|
const mapName = location.toLowerCase();
|
||||||
|
this.logger.warning(`[ABPS] Recreating PMCs for ${mapName}`);
|
||||||
|
|
||||||
|
const mapData = this.pmcSpawnControl.getCustomMapData(mapName, this.locationData[mapName].base.EscapeTimeLimit);
|
||||||
|
if (mapData.length) mapData.forEach((index) => (this.botMapCache[mapName].push(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private rebuildStartingScavs(location: string): void
|
||||||
|
{
|
||||||
|
const mapName = location.toLowerCase();
|
||||||
|
if (mapName == "laboratory") return;
|
||||||
|
this.logger.warning(`[ABPS] Recreating scavs for ${mapName}`);
|
||||||
|
|
||||||
|
const mapData = this.scavSpawnControl.getCustomMapData(mapName);
|
||||||
|
if (mapData.length) mapData.forEach((index) => (this.scavMapCache[mapName].push(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private rebuildLocation(location: string): void
|
||||||
|
{
|
||||||
|
const mapName = location.toLowerCase();
|
||||||
|
this.locationData[mapName].base.BossLocationSpawn = this.cloner.clone(this.botMapCache[mapName]);
|
||||||
|
this.locationData[mapName].base.waves = this.cloner.clone(this.scavMapCache[mapName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public adjustWaves(mapBase: ILocationBase, raidAdjustments: IRaidChanges): void
|
||||||
|
{
|
||||||
|
const locationName = mapBase.Id.toLowerCase();
|
||||||
|
if (raidAdjustments.simulatedRaidStartSeconds > 60)
|
||||||
|
{
|
||||||
|
const mapBosses = mapBase.BossLocationSpawn.filter((x) => x.Time == -1 && x.BossName != "pmcUSEC" && x.BossName != "pmcBEAR");
|
||||||
|
mapBase.BossLocationSpawn = mapBase.BossLocationSpawn.filter((x) => x.Time > raidAdjustments.simulatedRaidStartSeconds && (x.BossName == "pmcUSEC" || x.BossName == "pmcBEAR"));
|
||||||
|
|
||||||
|
for (const bossWave of mapBase.BossLocationSpawn)
|
||||||
|
{
|
||||||
|
bossWave.Time -= Math.max(raidAdjustments.simulatedRaidStartSeconds, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalRemainingTime = raidAdjustments.raidTimeMinutes * 60;
|
||||||
|
const newStartingPMCs = this.pmcSpawnControl.generateScavRaidRemainingPMCs(locationName, totalRemainingTime);
|
||||||
|
newStartingPMCs.forEach((index) => (mapBase.BossLocationSpawn.push(index)));
|
||||||
|
mapBosses.forEach((index) => (mapBase.BossLocationSpawn.push(index)));
|
||||||
|
|
||||||
|
const newStartingScavs = this.scavSpawnControl.generateStartingScavs(locationName, "assault", true);
|
||||||
|
newStartingScavs.forEach((index) => (mapBase.waves.push(index)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
import { injectable, inject } from "tsyringe";
|
||||||
|
import { IBossLocationSpawn } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||||
|
import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper";
|
||||||
|
|
||||||
|
|
||||||
|
// Default PMC Data
|
||||||
|
import {
|
||||||
|
pmcBEARData,
|
||||||
|
pmcUSECData
|
||||||
|
} from "../Defaults/PMCs";
|
||||||
|
|
||||||
|
import { ModConfig } from "../Globals/ModConfig";
|
||||||
|
import { Labs_NonGateSpawnZones } from "../Defaults/MapSpawnZones";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class PMCSpawnControl
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public getCustomMapData(location: string, escapeTimeLimit: number): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
return this.getConfigValueForLocation(location, escapeTimeLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConfigValueForLocation(location: string, escapeTimeLimit: number): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
let pmcSpawnInfo: IBossLocationSpawn[] = [];
|
||||||
|
if (ModConfig.config.pmcConfig.startingPMCs.enable)
|
||||||
|
{
|
||||||
|
pmcSpawnInfo = pmcSpawnInfo.concat(this.generateStartingPMCWaves(location));
|
||||||
|
}
|
||||||
|
if (ModConfig.config.pmcConfig.waves.enable)
|
||||||
|
{
|
||||||
|
pmcSpawnInfo = pmcSpawnInfo.concat(this.generatePMCWaves(location, escapeTimeLimit));
|
||||||
|
}
|
||||||
|
return pmcSpawnInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateStartingPMCWaves(location: string): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
const startingPMCWaveInfo: IBossLocationSpawn[] = [];
|
||||||
|
const ignoreMaxBotCaps = ModConfig.config.pmcConfig.startingPMCs.ignoreMaxBotCaps;
|
||||||
|
const minPMCCount = ModConfig.config.pmcConfig.startingPMCs.mapLimits[location].min;
|
||||||
|
const maxPMCCount = ModConfig.config.pmcConfig.startingPMCs.mapLimits[location].max;
|
||||||
|
const generatedPMCCount = this.randomUtil.getInt(minPMCCount, maxPMCCount);
|
||||||
|
const groupChance = ModConfig.config.pmcConfig.startingPMCs.groupChance;
|
||||||
|
const groupLimit = ModConfig.config.pmcConfig.startingPMCs.maxGroupCount;
|
||||||
|
const groupMaxSize = ModConfig.config.pmcConfig.startingPMCs.maxGroupSize;
|
||||||
|
const difficultyWeights = ModConfig.config.pmcDifficulty;
|
||||||
|
|
||||||
|
let currentPMCCount = 0;
|
||||||
|
let groupCount = 0;
|
||||||
|
|
||||||
|
while (currentPMCCount < generatedPMCCount)
|
||||||
|
{
|
||||||
|
const canBeAGroup = groupCount >= groupLimit ? false : true;
|
||||||
|
let groupSize = 0;
|
||||||
|
const remainingSpots = generatedPMCCount - currentPMCCount;
|
||||||
|
|
||||||
|
const isAGroup = remainingSpots > 1 ? this.randomUtil.getChance100(groupChance) : false;
|
||||||
|
if (isAGroup && canBeAGroup)
|
||||||
|
{
|
||||||
|
groupSize = Math.min(remainingSpots - 1, this.randomUtil.getInt(1, groupMaxSize));
|
||||||
|
groupCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
const pmcType = this.randomUtil.getChance100(50) ? "pmcUSEC" : "pmcBEAR";
|
||||||
|
const bossDefaultData = this.cloner.clone(this.getDefaultValuesForBoss(pmcType));
|
||||||
|
|
||||||
|
bossDefaultData[0].BossEscortAmount = groupSize.toString();
|
||||||
|
bossDefaultData[0].BossDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossEscortDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossZone = "";
|
||||||
|
bossDefaultData[0].IgnoreMaxBots = ignoreMaxBotCaps;
|
||||||
|
currentPMCCount += groupSize + 1;
|
||||||
|
startingPMCWaveInfo.push(bossDefaultData[0]);
|
||||||
|
|
||||||
|
//this.logger.warning(`[Starting PMC] Adding 1 spawn for ${pmcType} to ${location} | GroupSize: ${groupSize + 1}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this.logger.warning(`[Starting PMCs] Map: ${location} (Time Limit: ${escapeTimeLimit}m) | Limits: ${minPMCCount}-${maxPMCCount} | Groups: ${groupCount} | TotalPMCs: ${currentPMCCount}/${generatedPMCCount}`);
|
||||||
|
return startingPMCWaveInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private generatePMCWaves(location: string, escapeTimeLimit: number): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
const pmcWaveSpawnInfo: IBossLocationSpawn[] = [];
|
||||||
|
|
||||||
|
const ignoreMaxBotCaps = ModConfig.config.pmcConfig.waves.ignoreMaxBotCaps;
|
||||||
|
const difficultyWeights = ModConfig.config.pmcDifficulty;
|
||||||
|
const waveMaxPMCCount = location.includes("factory") ? Math.min(2, ModConfig.config.pmcConfig.waves.maxBotsPerWave - 2) : ModConfig.config.pmcConfig.waves.maxBotsPerWave;
|
||||||
|
const waveGroupLimit = ModConfig.config.pmcConfig.waves.maxGroupCount;
|
||||||
|
const waveGroupSize = ModConfig.config.pmcConfig.waves.maxGroupSize;
|
||||||
|
const waveGroupChance = ModConfig.config.pmcConfig.waves.groupChance;
|
||||||
|
const firstWaveTimer = ModConfig.config.pmcConfig.waves.delayBeforeFirstWave;
|
||||||
|
const waveTimer = ModConfig.config.pmcConfig.waves.secondsBetweenWaves;
|
||||||
|
const endWavesAtRemainingTime = ModConfig.config.pmcConfig.waves.stopWavesBeforeEndOfRaidLimit;
|
||||||
|
const waveCount = Math.floor((((escapeTimeLimit * 60) - endWavesAtRemainingTime) - firstWaveTimer) / waveTimer);
|
||||||
|
let currentWaveTime = firstWaveTimer;
|
||||||
|
|
||||||
|
//this.logger.warning(`[PMC Waves] Generating ${waveCount} waves for PMCs`)
|
||||||
|
for (let i = 1; i <= waveCount; i++)
|
||||||
|
{
|
||||||
|
let currentPMCCount = 0;
|
||||||
|
let groupCount = 0;
|
||||||
|
while (currentPMCCount < waveMaxPMCCount)
|
||||||
|
{
|
||||||
|
const canBeAGroup = groupCount >= waveGroupLimit ? false : true;
|
||||||
|
let groupSize = 0;
|
||||||
|
const remainingSpots = waveMaxPMCCount - currentPMCCount;
|
||||||
|
const isAGroup = remainingSpots > 1 ? this.randomUtil.getChance100(waveGroupChance) : false;
|
||||||
|
if (isAGroup && canBeAGroup)
|
||||||
|
{
|
||||||
|
groupSize = Math.min(remainingSpots - 1, this.randomUtil.getInt(1, waveGroupSize));
|
||||||
|
groupCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
const pmcType = this.randomUtil.getChance100(50) ? "pmcUSEC" : "pmcBEAR";
|
||||||
|
const bossDefaultData = this.cloner.clone(this.getDefaultValuesForBoss(pmcType));
|
||||||
|
|
||||||
|
bossDefaultData[0].BossEscortAmount = groupSize.toString();
|
||||||
|
bossDefaultData[0].Time = currentWaveTime;
|
||||||
|
bossDefaultData[0].BossDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossEscortDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossZone = "";
|
||||||
|
bossDefaultData[0].IgnoreMaxBots = ignoreMaxBotCaps;
|
||||||
|
currentPMCCount += groupSize + 1;
|
||||||
|
pmcWaveSpawnInfo.push(bossDefaultData[0]);
|
||||||
|
|
||||||
|
//this.logger.warning(`[PMC Waves] Adding 1 spawn for ${pmcType} to ${location} | GroupSize: ${groupSize + 1}`);
|
||||||
|
}
|
||||||
|
//this.logger.warning(`[PMC Waves] Wave: ${i} | Time: ${currentWaveTime} | Groups: ${groupCount} | TotalPMCs: ${currentPMCCount}/${waveMaxPMCCount}`);
|
||||||
|
currentWaveTime += waveTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pmcWaveSpawnInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDefaultValuesForBoss(boss: string): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
switch (boss)
|
||||||
|
{
|
||||||
|
case "pmcUSEC":
|
||||||
|
return pmcUSECData;
|
||||||
|
case "pmcBEAR":
|
||||||
|
return pmcBEARData;
|
||||||
|
default:
|
||||||
|
this.logger.error(`[ABPS] PMC not found in config ${boss}`)
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateScavRaidRemainingPMCs(location: string, remainingRaidTime: number): IBossLocationSpawn[]
|
||||||
|
{
|
||||||
|
const startingPMCWaveInfo: IBossLocationSpawn[] = [];
|
||||||
|
const ignoreMaxBotCaps = ModConfig.config.pmcConfig.startingPMCs.ignoreMaxBotCaps;
|
||||||
|
const minPMCCount = ModConfig.config.pmcConfig.startingPMCs.mapLimits[location].min;
|
||||||
|
const maxPMCCount = ModConfig.config.pmcConfig.startingPMCs.mapLimits[location].max;
|
||||||
|
let generatedPMCCount = this.randomUtil.getInt(minPMCCount, maxPMCCount);
|
||||||
|
const groupChance = ModConfig.config.pmcConfig.startingPMCs.groupChance;
|
||||||
|
const groupLimit = ModConfig.config.pmcConfig.startingPMCs.maxGroupCount;
|
||||||
|
const groupMaxSize = ModConfig.config.pmcConfig.startingPMCs.maxGroupSize;
|
||||||
|
const difficultyWeights = ModConfig.config.pmcDifficulty;
|
||||||
|
|
||||||
|
let currentPMCCount = 0;
|
||||||
|
let groupCount = 0;
|
||||||
|
|
||||||
|
if (remainingRaidTime < 600) generatedPMCCount = this.randomUtil.getInt(1, 3);
|
||||||
|
if (remainingRaidTime < 1200) generatedPMCCount = this.randomUtil.getInt(1, 6);
|
||||||
|
if (remainingRaidTime < 1800) generatedPMCCount = this.randomUtil.getInt(4, 9);
|
||||||
|
|
||||||
|
if (location.includes("factory") && generatedPMCCount > 5) generatedPMCCount -= 2;
|
||||||
|
|
||||||
|
while (currentPMCCount < generatedPMCCount)
|
||||||
|
{
|
||||||
|
const canBeAGroup = groupCount >= groupLimit ? false : true;
|
||||||
|
let groupSize = 0;
|
||||||
|
const remainingSpots = generatedPMCCount - currentPMCCount;
|
||||||
|
const isAGroup = remainingSpots > 1 ? this.randomUtil.getChance100(groupChance) : false;
|
||||||
|
if (isAGroup && canBeAGroup)
|
||||||
|
{
|
||||||
|
groupSize = Math.min(remainingSpots - 1, this.randomUtil.getInt(1, groupMaxSize));
|
||||||
|
groupCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
const pmcType = this.randomUtil.getChance100(50) ? "pmcUSEC" : "pmcBEAR";
|
||||||
|
const bossDefaultData = this.cloner.clone(this.getDefaultValuesForBoss(pmcType));
|
||||||
|
|
||||||
|
bossDefaultData[0].BossEscortAmount = groupSize.toString();
|
||||||
|
bossDefaultData[0].BossDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossEscortDifficult = this.weightedRandomHelper.getWeightedValue(difficultyWeights);
|
||||||
|
bossDefaultData[0].BossZone = "";
|
||||||
|
bossDefaultData[0].IgnoreMaxBots = ignoreMaxBotCaps;
|
||||||
|
currentPMCCount += groupSize + 1;
|
||||||
|
startingPMCWaveInfo.push(bossDefaultData[0]);
|
||||||
|
|
||||||
|
//this.logger.warning(`[Starting PMC] Adding 1 spawn for ${pmcType} to ${location} | GroupSize: ${groupSize + 1}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this.logger.warning(`[Starting PMCs] Map: ${location} (Time Limit: ${escapeTimeLimit}m) | Limits: ${minPMCCount}-${maxPMCCount} | Groups: ${groupCount} | TotalPMCs: ${currentPMCCount}/${generatedPMCCount}`);
|
||||||
|
return startingPMCWaveInfo;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
import { injectable, inject } from "tsyringe";
|
||||||
|
import { IWave, WildSpawnType } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||||
|
import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper";
|
||||||
|
|
||||||
|
|
||||||
|
// Default Scav Data
|
||||||
|
|
||||||
|
|
||||||
|
import { ModConfig } from "../Globals/ModConfig";
|
||||||
|
import { scavData } from "../Defaults/Scavs";
|
||||||
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
|
import {
|
||||||
|
Customs_SpawnZones,
|
||||||
|
Customs_SnipeSpawnZones,
|
||||||
|
Factory_SpawnZones,
|
||||||
|
GroundZero_SpawnZones,
|
||||||
|
Interchange_SpawnZones,
|
||||||
|
Labs_NonGateSpawnZones,
|
||||||
|
Lighthouse_NonWaterTreatmentSpawnZones,
|
||||||
|
Lighthouse_SnipeSpawnZones,
|
||||||
|
Reserve_SpawnZones,
|
||||||
|
Shoreline_SpawnZones,
|
||||||
|
Shoreline_SnipeSpawnZones,
|
||||||
|
Streets_SpawnZones,
|
||||||
|
Streets_SnipeSpawnZones,
|
||||||
|
Woods_SpawnZones,
|
||||||
|
Woods_SnipeSpawnZones,
|
||||||
|
GroundZero_SnipeSpawnZones
|
||||||
|
} from "../Defaults/MapSpawnZones";
|
||||||
|
import { createExhaustableArray } from "../Utils/GlobalUtils";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ScavSpawnControl
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
|
@inject("RandomUtil") protected randomUtil: RandomUtil,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
@inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public getCustomMapData(location: string): IWave[]
|
||||||
|
{
|
||||||
|
return this.getConfigValueForLocation(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConfigValueForLocation(location: string): IWave[]
|
||||||
|
{
|
||||||
|
const scavSpawnInfo: IWave[] = [];
|
||||||
|
if (ModConfig.config.scavConfig.startingScavs.enable) scavSpawnInfo.push(...this.generateStartingScavs(location));
|
||||||
|
if (ModConfig.config.scavConfig.startingScavs.startingMarksman)
|
||||||
|
{
|
||||||
|
const marksmanSpawn = this.generateStartingScavs(location, "marksman");
|
||||||
|
if (marksmanSpawn.length)
|
||||||
|
{
|
||||||
|
scavSpawnInfo.push(...marksmanSpawn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scavSpawnInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateStartingScavs(location: string, botRole = "assault", lateStart = false): IWave[]
|
||||||
|
{
|
||||||
|
const scavWaveSpawnInfo: IWave[] = [];
|
||||||
|
|
||||||
|
const waveLength = this.databaseService.getTables().locations[location].base.waves.length;
|
||||||
|
|
||||||
|
const maxStartingSpawns: number = ModConfig.config.scavConfig.startingScavs.maxBotSpawns[location];
|
||||||
|
const scavCap = lateStart ? maxStartingSpawns * 0.75 : maxStartingSpawns;
|
||||||
|
const playerScavChance = lateStart ? 60 : 10;
|
||||||
|
|
||||||
|
const maxBotsPerZone = location.includes("factory") || location.includes("sandbox") ? maxStartingSpawns : ModConfig.config.scavConfig.startingScavs.maxBotsPerZone;
|
||||||
|
const availableSpawnZones = botRole == "assault" ? createExhaustableArray(this.getNonMarksmanSpawnZones(location), this.randomUtil, this.cloner) : createExhaustableArray(this.getMarksmanSpawnZones(location), this.randomUtil, this.cloner);
|
||||||
|
let spawnsAdded = botRole == "assault" ? 0 : waveLength;
|
||||||
|
let marksmanCount = 0;
|
||||||
|
|
||||||
|
while (spawnsAdded < scavCap)
|
||||||
|
{
|
||||||
|
if (spawnsAdded >= maxStartingSpawns) break;
|
||||||
|
const scavDefaultData = this.cloner.clone(this.getDefaultValues());
|
||||||
|
let selectedSpawnZone = location.includes("factory") || location.includes("sandbox") || !availableSpawnZones.hasValues() ? "" : availableSpawnZones.getRandomValue();
|
||||||
|
if (botRole != "assault")
|
||||||
|
{
|
||||||
|
if (!availableSpawnZones.hasValues()) break;
|
||||||
|
if (selectedSpawnZone == undefined) break;
|
||||||
|
if (marksmanCount >= 2) break;
|
||||||
|
selectedSpawnZone = availableSpawnZones.getRandomValue();
|
||||||
|
marksmanCount++;
|
||||||
|
}
|
||||||
|
const remainingSpots = maxStartingSpawns - spawnsAdded;
|
||||||
|
const groupSize = botRole == "assault" ? Math.min(remainingSpots - 1, this.randomUtil.getInt(1, maxBotsPerZone)) : 1;
|
||||||
|
|
||||||
|
scavDefaultData.slots_min = groupSize > 1 ? 1 : 0;
|
||||||
|
scavDefaultData.slots_max = groupSize > 1 ? groupSize : 1;
|
||||||
|
scavDefaultData.time_min = 1;
|
||||||
|
scavDefaultData.time_max = 3;
|
||||||
|
scavDefaultData.number = spawnsAdded;
|
||||||
|
scavDefaultData.WildSpawnType = botRole == "assault" ? WildSpawnType.ASSAULT : WildSpawnType.MARKSMAN;
|
||||||
|
scavDefaultData.isPlayers = botRole == "assault" ? this.randomUtil.getChance100(playerScavChance) ? true : false : false;
|
||||||
|
scavDefaultData.SpawnPoints = selectedSpawnZone;
|
||||||
|
|
||||||
|
spawnsAdded++;
|
||||||
|
scavWaveSpawnInfo.push(scavDefaultData);
|
||||||
|
//this.logger.warning(`[Scav Waves] ${scavDefaultData.number} - Adding 1 spawn for ${botRole} to ${location} | Zone: ${selectedSpawnZone} Min: ${scavDefaultData.slots_min} | Max: ${scavDefaultData.slots_max}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scavWaveSpawnInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDefaultValues(): IWave
|
||||||
|
{
|
||||||
|
return scavData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNonMarksmanSpawnZones(location: string): string[]
|
||||||
|
{
|
||||||
|
switch (location)
|
||||||
|
{
|
||||||
|
case "bigmap":
|
||||||
|
return Customs_SpawnZones;
|
||||||
|
case "factory4_day":
|
||||||
|
case "factory4_night":
|
||||||
|
return Factory_SpawnZones;
|
||||||
|
case "interchange":
|
||||||
|
return Interchange_SpawnZones;
|
||||||
|
case "laboratory":
|
||||||
|
return Labs_NonGateSpawnZones;
|
||||||
|
case "lighthouse":
|
||||||
|
return Lighthouse_NonWaterTreatmentSpawnZones;
|
||||||
|
case "rezervbase":
|
||||||
|
return Reserve_SpawnZones;
|
||||||
|
case "sandbox":
|
||||||
|
case "sandbox_high":
|
||||||
|
return GroundZero_SpawnZones;
|
||||||
|
case "shoreline":
|
||||||
|
return Shoreline_SpawnZones;
|
||||||
|
case "tarkovstreets":
|
||||||
|
return Streets_SpawnZones;
|
||||||
|
case "woods":
|
||||||
|
return Woods_SpawnZones;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMarksmanSpawnZones(location: string): string[]
|
||||||
|
{
|
||||||
|
switch (location)
|
||||||
|
{
|
||||||
|
case "bigmap":
|
||||||
|
return Customs_SnipeSpawnZones;
|
||||||
|
case "factory4_day":
|
||||||
|
case "factory4_night":
|
||||||
|
return undefined;
|
||||||
|
case "interchange":
|
||||||
|
return undefined;
|
||||||
|
case "laboratory":
|
||||||
|
return undefined;
|
||||||
|
case "lighthouse":
|
||||||
|
return Lighthouse_SnipeSpawnZones;
|
||||||
|
case "rezervbase":
|
||||||
|
return undefined;
|
||||||
|
case "sandbox":
|
||||||
|
case "sandbox_high":
|
||||||
|
return GroundZero_SnipeSpawnZones;
|
||||||
|
case "shoreline":
|
||||||
|
return Shoreline_SnipeSpawnZones;
|
||||||
|
case "tarkovstreets":
|
||||||
|
return Streets_SnipeSpawnZones;
|
||||||
|
case "woods":
|
||||||
|
return Woods_SnipeSpawnZones;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateInitialScavsForRemainingRaidTime(location: string, botRole: string): IWave[]
|
||||||
|
{
|
||||||
|
const scavWaveSpawnInfo: IWave[] = [];
|
||||||
|
|
||||||
|
const waveLength = this.databaseService.getTables().locations[location].base.waves.length;
|
||||||
|
|
||||||
|
const maxStartingSpawns: number = ModConfig.config.scavConfig.startingScavs.maxBotSpawns[location] * 0.75;
|
||||||
|
const maxBotsPerZone = location.includes("factory") || location.includes("sandbox") ? maxStartingSpawns : ModConfig.config.scavConfig.startingScavs.maxBotsPerZone;
|
||||||
|
const checkMarksman = ModConfig.config.scavConfig.startingScavs.startingMarksman;
|
||||||
|
|
||||||
|
const availableSpawnZones = botRole == "assault" ? createExhaustableArray(this.getNonMarksmanSpawnZones(location), this.randomUtil, this.cloner) : createExhaustableArray(this.getMarksmanSpawnZones(location), this.randomUtil, this.cloner);
|
||||||
|
let spawnsAdded = botRole == "assault" ? 0 : waveLength;
|
||||||
|
|
||||||
|
while (spawnsAdded < maxStartingSpawns)
|
||||||
|
{
|
||||||
|
if (spawnsAdded >= maxStartingSpawns) break;
|
||||||
|
const scavDefaultData = this.cloner.clone(this.getDefaultValues());
|
||||||
|
const selectedSpawnZone = location.includes("factory") || location.includes("sandbox") || !availableSpawnZones.hasValues() ? "" : availableSpawnZones.getRandomValue();
|
||||||
|
const remainingSpots = maxStartingSpawns - spawnsAdded;
|
||||||
|
const groupSize = Math.min(remainingSpots - 1, this.randomUtil.getInt(1, maxBotsPerZone));
|
||||||
|
|
||||||
|
scavDefaultData.slots_min = groupSize > 1 ? 1 : 1;
|
||||||
|
scavDefaultData.slots_max = groupSize > 1 ? groupSize : 1;
|
||||||
|
scavDefaultData.time_min = 1;
|
||||||
|
scavDefaultData.time_max = 3;
|
||||||
|
scavDefaultData.number = spawnsAdded;
|
||||||
|
scavDefaultData.isPlayers = botRole == "assault" ? this.randomUtil.getChance100(60) ? true : false : false;
|
||||||
|
scavDefaultData.SpawnPoints = selectedSpawnZone;
|
||||||
|
|
||||||
|
spawnsAdded++;
|
||||||
|
scavWaveSpawnInfo.push(scavDefaultData);
|
||||||
|
//this.logger.warning(`[Scav Waves] ${scavDefaultData.number} - Adding 1 spawn for assault to ${location} | Zone: ${selectedSpawnZone} Min: ${scavDefaultData.slots_min} | Max: ${scavDefaultData.slots_max}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scavWaveSpawnInfo;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
import { injectable, inject } from "tsyringe";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
|
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig";
|
||||||
|
import { ILocationBase } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
import { newPMCHostilitySettings } from "../Defaults/Hostility";
|
||||||
|
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig";
|
||||||
|
import { IBotConfig } from "@spt/models/spt/config/IBotConfig";
|
||||||
|
import { ModConfig } from "../Globals/ModConfig";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class VanillaAdjustmentControl
|
||||||
|
{
|
||||||
|
public locationConfig: ILocationConfig;
|
||||||
|
public pmcConfig: IPmcConfig;
|
||||||
|
public botConfig: IBotConfig;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.locationConfig = this.configServer.getConfig<ILocationConfig>(ConfigTypes.LOCATION);
|
||||||
|
this.pmcConfig = this.configServer.getConfig<IPmcConfig>(ConfigTypes.PMC);
|
||||||
|
this.botConfig = this.configServer.getConfig<IBotConfig>(ConfigTypes.BOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disableVanillaSettings(): void
|
||||||
|
{
|
||||||
|
this.locationConfig.splitWaveIntoSingleSpawnsSettings.enabled = false;
|
||||||
|
this.locationConfig.rogueLighthouseSpawnTimeSettings.enabled = false;
|
||||||
|
this.locationConfig.addOpenZonesToAllMaps = false;
|
||||||
|
this.locationConfig.addCustomBotWavesToMaps = false;
|
||||||
|
this.locationConfig.enableBotTypeLimits = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public disableNewSpawnSystem(base: any): void
|
||||||
|
{
|
||||||
|
if (base.Id == "laboratory") return;
|
||||||
|
|
||||||
|
base.NewSpawn = false;
|
||||||
|
base.OfflineNewSpawn = false;
|
||||||
|
base.OldSpawn = true;
|
||||||
|
base.OfflineOldSpawn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enableAllSpawnSystem(base: any): void
|
||||||
|
{
|
||||||
|
if (base.Id == "laboratory") return;
|
||||||
|
|
||||||
|
base.NewSpawn = true;
|
||||||
|
base.OfflineNewSpawn = true;
|
||||||
|
base.OldSpawn = true;
|
||||||
|
base.OfflineOldSpawn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public adjustNewWaveSettings(base: any): void
|
||||||
|
{
|
||||||
|
if (base.Id == "laboratory") return;
|
||||||
|
|
||||||
|
if (ModConfig.config.scavConfig.waves.enableCustomFactory && base.Id.includes("factory"))
|
||||||
|
{
|
||||||
|
// Start-Stop Time for spawns
|
||||||
|
base.BotStart = ModConfig.config.scavConfig.waves.startSpawns;
|
||||||
|
base.BotStop = (base.EscapeTimeLimit * 60) - ModConfig.config.scavConfig.waves.stopSpawns;
|
||||||
|
|
||||||
|
// Start-Stop wave times for active spawning
|
||||||
|
base.BotSpawnTimeOnMin = 10;
|
||||||
|
base.BotSpawnTimeOnMax = 30;
|
||||||
|
|
||||||
|
// Start-Stop wave wait times between active spawning
|
||||||
|
base.BotSpawnTimeOffMin = 240;
|
||||||
|
base.BotSpawnTimeOffMax = 300;
|
||||||
|
|
||||||
|
// Probably how often it checks to spawn while active spawning
|
||||||
|
base.BotSpawnPeriodCheck = 15;
|
||||||
|
|
||||||
|
// Bot count required to trigger a spawn
|
||||||
|
base.BotSpawnCountStep = 3;
|
||||||
|
|
||||||
|
base.BotLocationModifier.NonWaveSpawnBotsLimitPerPlayer = 20;
|
||||||
|
base.BotLocationModifier.NonWaveSpawnBotsLimitPerPlayerPvE = 20;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Start-Stop Time for spawns
|
||||||
|
base.BotStart = ModConfig.config.scavConfig.waves.startSpawns;
|
||||||
|
base.BotStop = (base.EscapeTimeLimit * 60) - ModConfig.config.scavConfig.waves.stopSpawns;
|
||||||
|
|
||||||
|
// Start-Stop wave times for active spawning
|
||||||
|
base.BotSpawnTimeOnMin = ModConfig.config.scavConfig.waves.activeTimeMin;
|
||||||
|
base.BotSpawnTimeOnMax = ModConfig.config.scavConfig.waves.activeTimeMax;
|
||||||
|
|
||||||
|
// Start-Stop wave wait times between active spawning
|
||||||
|
base.BotSpawnTimeOffMin = ModConfig.config.scavConfig.waves.quietTimeMin;
|
||||||
|
base.BotSpawnTimeOffMax = ModConfig.config.scavConfig.waves.quietTimeMax;
|
||||||
|
|
||||||
|
// Probably how often it checks to spawn while active spawning
|
||||||
|
base.BotSpawnPeriodCheck = ModConfig.config.scavConfig.waves.checkToSpawnTimer;
|
||||||
|
|
||||||
|
// Bot count required to trigger a spawn
|
||||||
|
base.BotSpawnCountStep = ModConfig.config.scavConfig.waves.pendingBotsToTrigger;
|
||||||
|
|
||||||
|
base.BotLocationModifier.NonWaveSpawnBotsLimitPerPlayer = 20;
|
||||||
|
base.BotLocationModifier.NonWaveSpawnBotsLimitPerPlayerPvE = 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeExistingWaves(base: any): void
|
||||||
|
{
|
||||||
|
base.waves = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public fixPMCHostility(base: ILocationBase): void
|
||||||
|
{
|
||||||
|
const hostility = base.BotLocationModifier?.AdditionalHostilitySettings;
|
||||||
|
if (hostility)
|
||||||
|
{
|
||||||
|
for (const bot in hostility)
|
||||||
|
{
|
||||||
|
if (hostility[bot].BotRole == "pmcUSEC" || hostility[bot].BotRole == "pmcBEAR")
|
||||||
|
{
|
||||||
|
const newHostilitySettings = this.cloner.clone(newPMCHostilitySettings);
|
||||||
|
newHostilitySettings.BotRole = hostility[bot].BotRole;
|
||||||
|
hostility[bot] = newHostilitySettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeCustomPMCWaves(): void
|
||||||
|
{
|
||||||
|
this.pmcConfig.removeExistingPmcWaves = false;
|
||||||
|
this.pmcConfig.customPmcWaves = {};
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,64 @@
|
||||||
|
export const newPMCHostilitySettings =
|
||||||
|
{
|
||||||
|
AlwaysEnemies: [
|
||||||
|
"arenaFighter",
|
||||||
|
"arenaFighterEvent",
|
||||||
|
"assault",
|
||||||
|
"assaultGroup",
|
||||||
|
"bossBoar",
|
||||||
|
"bossBoarSniper",
|
||||||
|
"bossBully",
|
||||||
|
"bossGluhar",
|
||||||
|
"bossKilla",
|
||||||
|
"bossKnight",
|
||||||
|
"bossKojaniy",
|
||||||
|
"bossKolontay",
|
||||||
|
"bossPartisan",
|
||||||
|
"bossSanitar",
|
||||||
|
"bossTagilla",
|
||||||
|
"crazyAssaultEvent",
|
||||||
|
"cursedAssault",
|
||||||
|
"exUsec",
|
||||||
|
"followerBigPipe",
|
||||||
|
"followerBirdEye",
|
||||||
|
"followerBoar",
|
||||||
|
"followerBoarClose1",
|
||||||
|
"followerBoarClose2",
|
||||||
|
"followerGluharAssault",
|
||||||
|
"followerGluharScout",
|
||||||
|
"followerGluharSecurity",
|
||||||
|
"followerGluharSnipe",
|
||||||
|
"followerKojaniy",
|
||||||
|
"followerKolontayAssault",
|
||||||
|
"followerKolontaySecurity",
|
||||||
|
"followerSanitar",
|
||||||
|
"followerTagilla",
|
||||||
|
"marksman",
|
||||||
|
"peacemaker",
|
||||||
|
"pmcBEAR",
|
||||||
|
"pmcBot",
|
||||||
|
"pmcUSEC",
|
||||||
|
"sectactPriestEvent",
|
||||||
|
"sectantPriest",
|
||||||
|
"sectantWarrior",
|
||||||
|
"skier",
|
||||||
|
"spiritSpring",
|
||||||
|
"spiritWinter"
|
||||||
|
],
|
||||||
|
AlwaysFriends: [
|
||||||
|
"bossZryachiy",
|
||||||
|
"followerZryachiy",
|
||||||
|
"gifter",
|
||||||
|
"peacefullZryachiyEvent",
|
||||||
|
"ravangeZryachiyEvent"
|
||||||
|
],
|
||||||
|
BearEnemyChance: 100,
|
||||||
|
BearPlayerBehaviour: "AlwaysEnemies",
|
||||||
|
BotRole: "",
|
||||||
|
ChancedEnemies: [],
|
||||||
|
Neutral: [],
|
||||||
|
SavagePlayerBehaviour: "AlwaysEnemies",
|
||||||
|
UsecEnemyChance: 100,
|
||||||
|
UsecPlayerBehaviour: "AlwaysEnemies",
|
||||||
|
Warn: []
|
||||||
|
};
|
|
@ -0,0 +1,189 @@
|
||||||
|
export const Customs_SpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneBrige",
|
||||||
|
"ZoneCrossRoad",
|
||||||
|
"ZoneDormitory",
|
||||||
|
"ZoneGasStation",
|
||||||
|
"ZoneFactoryCenter",
|
||||||
|
"ZoneFactorySide",
|
||||||
|
"ZoneOldAZS",
|
||||||
|
"ZoneBlockPost",
|
||||||
|
"ZoneTankSquare",
|
||||||
|
"ZoneWade",
|
||||||
|
"ZoneCustoms",
|
||||||
|
"ZoneScavBase"
|
||||||
|
]
|
||||||
|
|
||||||
|
export const Customs_SnipeSpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneSnipeBrige",
|
||||||
|
"ZoneSnipeTower",
|
||||||
|
"ZoneSnipeFactory",
|
||||||
|
"ZoneBlockPostSniper"
|
||||||
|
]
|
||||||
|
export const Factory_SpawnZones =
|
||||||
|
[
|
||||||
|
"BotZone"
|
||||||
|
]
|
||||||
|
export const Interchange_SpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneCenterBot",
|
||||||
|
"ZoneIDEA",
|
||||||
|
"ZoneCenter",
|
||||||
|
"ZoneIDEAPark",
|
||||||
|
"ZoneTrucks",
|
||||||
|
"ZoneRoad",
|
||||||
|
"ZoneOLI",
|
||||||
|
"ZoneGoshan",
|
||||||
|
"ZoneOLIPark",
|
||||||
|
"ZonePowerStation"
|
||||||
|
]
|
||||||
|
export const Labs_GateSpawnZones =
|
||||||
|
[
|
||||||
|
"BotZoneGate1",
|
||||||
|
"BotZoneGate2"
|
||||||
|
]
|
||||||
|
export const Labs_NonGateSpawnZones =
|
||||||
|
[
|
||||||
|
"BotZoneBasement",
|
||||||
|
"BotZoneFloor1",
|
||||||
|
"BotZoneFloor2"
|
||||||
|
]
|
||||||
|
export const Lighthouse_NonWaterTreatmentSpawnZones =
|
||||||
|
[
|
||||||
|
"Zone_Containers",
|
||||||
|
"Zone_Rocks",
|
||||||
|
"Zone_Chalet",
|
||||||
|
"Zone_Village",
|
||||||
|
"Zone_Bridge",
|
||||||
|
"Zone_OldHouse",
|
||||||
|
"Zone_LongRoad",
|
||||||
|
"Zone_RoofBeach",
|
||||||
|
"Zone_DestroyedHouse",
|
||||||
|
"Zone_RoofContainers",
|
||||||
|
"Zone_Blockpost",
|
||||||
|
"Zone_RoofRocks",
|
||||||
|
"Zone_TreatmentRocks",
|
||||||
|
"Zone_TreatmentContainers",
|
||||||
|
"Zone_TreatmentBeach",
|
||||||
|
"Zone_Hellicopter",
|
||||||
|
"Zone_SniperPeak",
|
||||||
|
"Zone_Island"
|
||||||
|
]
|
||||||
|
export const Lighthouse_WaterTreatmentSpawnZones =
|
||||||
|
[
|
||||||
|
"Zone_Containers",
|
||||||
|
"Zone_Rocks",
|
||||||
|
"Zone_Chalet",
|
||||||
|
"Zone_Village",
|
||||||
|
"Zone_Bridge",
|
||||||
|
"Zone_OldHouse",
|
||||||
|
"Zone_LongRoad",
|
||||||
|
"Zone_RoofBeach",
|
||||||
|
"Zone_DestroyedHouse",
|
||||||
|
"Zone_RoofContainers",
|
||||||
|
"Zone_Blockpost",
|
||||||
|
"Zone_RoofRocks",
|
||||||
|
"Zone_TreatmentRocks",
|
||||||
|
"Zone_TreatmentContainers",
|
||||||
|
"Zone_TreatmentBeach",
|
||||||
|
"Zone_Hellicopter",
|
||||||
|
"Zone_Island"
|
||||||
|
]
|
||||||
|
export const Lighthouse_SnipeSpawnZones =
|
||||||
|
[
|
||||||
|
"Zone_SniperPeak"
|
||||||
|
]
|
||||||
|
|
||||||
|
export const Reserve_SpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneRailStrorage",
|
||||||
|
"ZonePTOR1",
|
||||||
|
"ZonePTOR2",
|
||||||
|
"ZoneBarrack",
|
||||||
|
"ZoneBunkerStorage",
|
||||||
|
"ZoneSubStorage",
|
||||||
|
"ZoneSubCommand"
|
||||||
|
]
|
||||||
|
export const GroundZero_SpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneSandbox"
|
||||||
|
]
|
||||||
|
export const GroundZero_SnipeSpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneSandSnipeCenter",
|
||||||
|
"ZoneSandSnipeCenter2"
|
||||||
|
]
|
||||||
|
export const Shoreline_SpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneGreenHouses",
|
||||||
|
"ZoneIsland",
|
||||||
|
"ZoneForestGasStation",
|
||||||
|
"ZoneGasStation",
|
||||||
|
"ZonePowerStation",
|
||||||
|
"ZoneBunker",
|
||||||
|
"ZoneBusStation",
|
||||||
|
"ZonePort",
|
||||||
|
"ZoneForestTruck",
|
||||||
|
"ZoneForestSpawn",
|
||||||
|
"ZoneSanatorium1",
|
||||||
|
"ZoneSanatorium2",
|
||||||
|
"ZoneStartVillage",
|
||||||
|
"ZoneMeteoStation",
|
||||||
|
"ZoneRailWays",
|
||||||
|
"ZoneSmuglers",
|
||||||
|
"ZonePassClose",
|
||||||
|
"ZoneTunnel"
|
||||||
|
]
|
||||||
|
export const Shoreline_SnipeSpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneBunkeSniper",
|
||||||
|
"ZonePowerStationSniper"
|
||||||
|
]
|
||||||
|
export const Streets_SpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneSW01",
|
||||||
|
"ZoneConstruction",
|
||||||
|
"ZoneCarShowroom",
|
||||||
|
"ZoneCinema",
|
||||||
|
"ZoneFactory",
|
||||||
|
"ZoneHotel_1",
|
||||||
|
"ZoneHotel_2",
|
||||||
|
"ZoneConcordia_1",
|
||||||
|
"ZoneConcordiaParking",
|
||||||
|
"ZoneColumn",
|
||||||
|
"ZoneSW00",
|
||||||
|
"ZoneStilo",
|
||||||
|
"ZoneCard1",
|
||||||
|
"ZoneMvd",
|
||||||
|
"ZoneClimova"
|
||||||
|
]
|
||||||
|
export const Streets_SnipeSpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneSnipeCinema",
|
||||||
|
"ZoneSnipeBuilding",
|
||||||
|
"ZoneSnipeSW01",
|
||||||
|
"ZoneSnipeStilo",
|
||||||
|
"ZoneSnipeCard",
|
||||||
|
"ZoneSnipeCarShowroom"
|
||||||
|
]
|
||||||
|
export const Woods_SpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneWoodCutter",
|
||||||
|
"ZoneHouse",
|
||||||
|
"ZoneBigRocks",
|
||||||
|
"ZoneRoad",
|
||||||
|
"ZoneHighRocks",
|
||||||
|
"ZoneMiniHouse",
|
||||||
|
"ZoneRedHouse",
|
||||||
|
"ZoneScavBase2",
|
||||||
|
"ZoneClearVill",
|
||||||
|
"ZoneBrokenVill",
|
||||||
|
"ZoneUsecBase",
|
||||||
|
"ZoneStoneBunker",
|
||||||
|
"ZoneDepo"
|
||||||
|
]
|
||||||
|
export const Woods_SnipeSpawnZones =
|
||||||
|
[
|
||||||
|
"ZoneHighRocks"
|
||||||
|
]
|
|
@ -0,0 +1,53 @@
|
||||||
|
export const pmcUSECData =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BossChance": 100,
|
||||||
|
"BossDifficult": "normal",
|
||||||
|
"BossEscortAmount": "0",
|
||||||
|
"BossEscortDifficult": "normal",
|
||||||
|
"BossEscortType": "pmcUSEC",
|
||||||
|
"BossName": "pmcUSEC",
|
||||||
|
"BossPlayer": false,
|
||||||
|
"BossZone": "",
|
||||||
|
"Delay": 0,
|
||||||
|
"DependKarma": false,
|
||||||
|
"DependKarmaPVE": false,
|
||||||
|
"ForceSpawn": false,
|
||||||
|
"IgnoreMaxBots": true,
|
||||||
|
"RandomTimeSpawn": false,
|
||||||
|
"SpawnMode": [
|
||||||
|
"pve"
|
||||||
|
],
|
||||||
|
"Supports": null,
|
||||||
|
"Time": -1,
|
||||||
|
"TriggerId": "",
|
||||||
|
"TriggerName": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const pmcBEARData =
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BossChance": 100,
|
||||||
|
"BossDifficult": "normal",
|
||||||
|
"BossEscortAmount": "0",
|
||||||
|
"BossEscortDifficult": "normal",
|
||||||
|
"BossEscortType": "pmcBEAR",
|
||||||
|
"BossName": "pmcBEAR",
|
||||||
|
"BossPlayer": false,
|
||||||
|
"BossZone": "",
|
||||||
|
"Delay": 0,
|
||||||
|
"DependKarma": false,
|
||||||
|
"DependKarmaPVE": false,
|
||||||
|
"ForceSpawn": false,
|
||||||
|
"IgnoreMaxBots": true,
|
||||||
|
"RandomTimeSpawn": false,
|
||||||
|
"SpawnMode": [
|
||||||
|
"pve"
|
||||||
|
],
|
||||||
|
"Supports": null,
|
||||||
|
"Time": -1,
|
||||||
|
"TriggerId": "",
|
||||||
|
"TriggerName": ""
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { WildSpawnType } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
|
||||||
|
export const scavData =
|
||||||
|
{
|
||||||
|
"BotPreset": "easy",
|
||||||
|
"BotSide": "Savage",
|
||||||
|
"SpawnMode": [
|
||||||
|
"pve"
|
||||||
|
],
|
||||||
|
"SpawnPoints": "ZoneBrige",
|
||||||
|
"WildSpawnType": WildSpawnType.ASSAULT,
|
||||||
|
"isPlayers": false,
|
||||||
|
"number": 0,
|
||||||
|
"slots_max": 0,
|
||||||
|
"slots_min": 0,
|
||||||
|
"time_max": -1,
|
||||||
|
"time_min": -1
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { inject, injectable } from "tsyringe";
|
||||||
|
import type { FileSystemSync } from "@spt/utils/FileSystemSync";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import path from "node:path";
|
||||||
|
import { MinMax } from "@spt/models/common/MinMax";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ModConfig
|
||||||
|
{
|
||||||
|
public static config: Config;
|
||||||
|
private lol: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("PrimaryLogger") private logger: ILogger,
|
||||||
|
@inject("FileSystemSync") private fileSystemSync: FileSystemSync
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ModConfig.config = this.fileSystemSync.readJson(path.resolve(__dirname, "../../config/config.json"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export interface Config
|
||||||
|
{
|
||||||
|
pmcDifficulty: Record<DifficultyConfig, number>,
|
||||||
|
pmcConfig: PMCConfig,
|
||||||
|
scavConfig: ScavConfig,
|
||||||
|
bossDifficulty: Record<DifficultyConfig, number>,
|
||||||
|
bossConfig: BossConfig,
|
||||||
|
configAppSettings: ConfigAppSettings,
|
||||||
|
}
|
||||||
|
export interface PMCConfig
|
||||||
|
{
|
||||||
|
startingPMCs: PMCStartingConfig,
|
||||||
|
waves: WaveConfig,
|
||||||
|
}
|
||||||
|
export interface ScavConfig
|
||||||
|
{
|
||||||
|
startingScavs: ScavStartingConfig,
|
||||||
|
waves: ScavWaveConfig,
|
||||||
|
}
|
||||||
|
export interface ScavStartingConfig
|
||||||
|
{
|
||||||
|
enable: boolean,
|
||||||
|
maxBotSpawns: ValidLocations,
|
||||||
|
maxBotsPerZone: number,
|
||||||
|
startingMarksman: boolean,
|
||||||
|
}
|
||||||
|
export interface ScavWaveConfig
|
||||||
|
{
|
||||||
|
enable: boolean,
|
||||||
|
enableCustomFactory: boolean,
|
||||||
|
startSpawns: number,
|
||||||
|
stopSpawns: number,
|
||||||
|
activeTimeMin: number,
|
||||||
|
activeTimeMax: number,
|
||||||
|
quietTimeMin: number,
|
||||||
|
quietTimeMax: number,
|
||||||
|
checkToSpawnTimer: number,
|
||||||
|
pendingBotsToTrigger: number,
|
||||||
|
}
|
||||||
|
export type DifficultyConfig = "easy" | "normal" | "hard" | "impossible";
|
||||||
|
export interface PMCStartingConfig
|
||||||
|
{
|
||||||
|
enable: boolean,
|
||||||
|
ignoreMaxBotCaps: boolean,
|
||||||
|
groupChance: number,
|
||||||
|
maxGroupSize: number,
|
||||||
|
maxGroupCount: number,
|
||||||
|
mapLimits: MinMaxLocations
|
||||||
|
}
|
||||||
|
export interface WaveConfig
|
||||||
|
{
|
||||||
|
enable: boolean,
|
||||||
|
ignoreMaxBotCaps: boolean,
|
||||||
|
groupChance: number,
|
||||||
|
maxGroupSize: number,
|
||||||
|
maxGroupCount: number,
|
||||||
|
maxBotsPerWave: number,
|
||||||
|
delayBeforeFirstWave: number,
|
||||||
|
secondsBetweenWaves: number,
|
||||||
|
stopWavesBeforeEndOfRaidLimit: number
|
||||||
|
}
|
||||||
|
export interface BossConfig
|
||||||
|
{
|
||||||
|
bossKnight: BossLocationInfo,
|
||||||
|
bossBully: BossLocationInfo,
|
||||||
|
bossTagilla: BossLocationInfo,
|
||||||
|
bossKilla: BossLocationInfo,
|
||||||
|
bossZryachiy: BossLocationInfo,
|
||||||
|
bossGluhar: BossLocationInfo,
|
||||||
|
bossSanitar: BossLocationInfo,
|
||||||
|
bossKolontay: BossLocationInfo,
|
||||||
|
bossBoar: BossLocationInfo,
|
||||||
|
bossKojaniy: BossLocationInfo,
|
||||||
|
bossPartisan: BossLocationInfo,
|
||||||
|
sectantPriest: BossLocationInfo,
|
||||||
|
arenaFighterEvent: BossLocationInfo,
|
||||||
|
pmcBot: SpecialLocationInfo,
|
||||||
|
exUsec: SpecialLocationInfo,
|
||||||
|
gifter: BossLocationInfo,
|
||||||
|
}
|
||||||
|
export interface BossLocationInfo
|
||||||
|
{
|
||||||
|
enable: boolean,
|
||||||
|
time: number,
|
||||||
|
spawnChance: ValidLocations,
|
||||||
|
bossZone: ValidLocations;
|
||||||
|
}
|
||||||
|
export interface SpecialLocationInfo
|
||||||
|
{
|
||||||
|
enable: boolean,
|
||||||
|
addExtraSpawns: boolean,
|
||||||
|
disableVanillaSpawns: boolean,
|
||||||
|
time: number,
|
||||||
|
spawnChance: ValidLocations,
|
||||||
|
bossZone: ValidLocations;
|
||||||
|
}
|
||||||
|
export interface ValidLocations
|
||||||
|
{
|
||||||
|
bigmap: number | string,
|
||||||
|
factory4_day: number | string,
|
||||||
|
factory4_night: number | string,
|
||||||
|
interchange: number | string,
|
||||||
|
laboratory: number | string,
|
||||||
|
lighthouse: number | string,
|
||||||
|
rezervbase: number | string,
|
||||||
|
sandbox: number | string,
|
||||||
|
sandbox_high: number | string,
|
||||||
|
shoreline: number | string,
|
||||||
|
tarkovstreets: number | string,
|
||||||
|
woods: number | string,
|
||||||
|
}
|
||||||
|
export interface MinMaxLocations
|
||||||
|
{
|
||||||
|
bigmap: MinMax,
|
||||||
|
factory4_day: MinMax,
|
||||||
|
factory4_night: MinMax,
|
||||||
|
interchange: MinMax,
|
||||||
|
laboratory: MinMax,
|
||||||
|
lighthouse: MinMax,
|
||||||
|
rezervbase: MinMax,
|
||||||
|
sandbox: MinMax,
|
||||||
|
sandbox_high: MinMax,
|
||||||
|
shoreline: MinMax,
|
||||||
|
tarkovstreets: MinMax,
|
||||||
|
woods: MinMax,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigAppSettings
|
||||||
|
{
|
||||||
|
showUndo: boolean,
|
||||||
|
showDefault: boolean,
|
||||||
|
disableAnimations: boolean
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
// SPT
|
||||||
|
import { DependencyContainer, Lifecycle } from "tsyringe";
|
||||||
|
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
|
||||||
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||||
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
|
import { FileSystemSync } from "@spt/utils/FileSystemSync";
|
||||||
|
|
||||||
|
// Custom
|
||||||
|
import { ModConfig } from "./Globals/ModConfig";
|
||||||
|
import { MapSpawnControl } from "./Controls/MapSpawnControl";
|
||||||
|
import { BossSpawnControl } from "./Controls/BossSpawnControl";
|
||||||
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { VanillaAdjustmentControl } from "./Controls/VanillaAdjustmentControl";
|
||||||
|
import { PMCSpawnControl } from "./Controls/PMCSpawnControl";
|
||||||
|
import { StaticRouterHooks } from "./Routers/StaticRouterHooks";
|
||||||
|
import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper";
|
||||||
|
import { ScavSpawnControl } from "./Controls/ScavSpawnControl";
|
||||||
|
|
||||||
|
export class InstanceManager
|
||||||
|
{
|
||||||
|
//#region accessible in or after preSptLoad
|
||||||
|
public modName: string;
|
||||||
|
public container: DependencyContainer;
|
||||||
|
public preSptModLoader: PreSptModLoader;
|
||||||
|
public logger: ILogger;
|
||||||
|
public fileSystemSync: FileSystemSync;
|
||||||
|
public cloner: ICloner;
|
||||||
|
public staticRouterModService: StaticRouterModService;
|
||||||
|
|
||||||
|
public staticRouterHooks: StaticRouterHooks;
|
||||||
|
public modConfig: ModConfig;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region accessible in or after postDBLoad
|
||||||
|
public databaseService: DatabaseService;
|
||||||
|
public weightedRandomHelper: WeightedRandomHelper;
|
||||||
|
public vanillaAdjustmentControl: VanillaAdjustmentControl;
|
||||||
|
public bossSpawnControl: BossSpawnControl;
|
||||||
|
public pmcSpawnControl: PMCSpawnControl;
|
||||||
|
public scavSpawnControl: ScavSpawnControl;
|
||||||
|
public mapSpawnControl: MapSpawnControl;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region accessible in or after PostSptLoad
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
// Call at the start of the mods postDBLoad method
|
||||||
|
public preSptLoad(container: DependencyContainer, mod: string): void
|
||||||
|
{
|
||||||
|
this.modName = mod;
|
||||||
|
this.container = container;
|
||||||
|
|
||||||
|
// SPT Classes
|
||||||
|
this.preSptModLoader = container.resolve<PreSptModLoader>("PreSptModLoader");
|
||||||
|
this.logger = container.resolve<ILogger>("WinstonLogger");
|
||||||
|
this.fileSystemSync = container.resolve<FileSystemSync>("FileSystemSync");
|
||||||
|
this.cloner = container.resolve<ICloner>("PrimaryCloner");
|
||||||
|
this.staticRouterModService = container.resolve<StaticRouterModService>("StaticRouterModService");
|
||||||
|
|
||||||
|
// Custom Classes
|
||||||
|
this.container.register<VanillaAdjustmentControl>("VanillaAdjustmentControl", VanillaAdjustmentControl, { lifecycle: Lifecycle.Singleton })
|
||||||
|
this.container.register<BossSpawnControl>("BossSpawnControl", BossSpawnControl, { lifecycle: Lifecycle.Singleton })
|
||||||
|
this.container.register<ScavSpawnControl>("ScavSpawnControl", ScavSpawnControl, { lifecycle: Lifecycle.Singleton })
|
||||||
|
this.container.register<PMCSpawnControl>("PMCSpawnControl", PMCSpawnControl, { lifecycle: Lifecycle.Singleton })
|
||||||
|
this.container.register<MapSpawnControl>("MapSpawnControl", MapSpawnControl, { lifecycle: Lifecycle.Singleton })
|
||||||
|
|
||||||
|
this.container.register<StaticRouterHooks>("StaticRouterHooks", StaticRouterHooks, { lifecycle: Lifecycle.Singleton })
|
||||||
|
this.staticRouterHooks = container.resolve<StaticRouterHooks>("StaticRouterHooks");
|
||||||
|
|
||||||
|
// Custom Special
|
||||||
|
|
||||||
|
// Resolve this last to set mod configs
|
||||||
|
this.container.register<ModConfig>("ModConfig", ModConfig, { lifecycle: Lifecycle.Singleton })
|
||||||
|
this.modConfig = container.resolve<ModConfig>("ModConfig");
|
||||||
|
}
|
||||||
|
|
||||||
|
public postDBLoad(container: DependencyContainer): void
|
||||||
|
{
|
||||||
|
// SPT Classes
|
||||||
|
this.databaseService = container.resolve<DatabaseService>("DatabaseService");
|
||||||
|
this.weightedRandomHelper = container.resolve<WeightedRandomHelper>("WeightedRandomHelper");
|
||||||
|
this.vanillaAdjustmentControl = container.resolve<VanillaAdjustmentControl>("VanillaAdjustmentControl");
|
||||||
|
this.bossSpawnControl = container.resolve<BossSpawnControl>("BossSpawnControl");
|
||||||
|
this.scavSpawnControl = container.resolve<ScavSpawnControl>("ScavSpawnControl");
|
||||||
|
this.pmcSpawnControl = container.resolve<PMCSpawnControl>("PMCSpawnControl");
|
||||||
|
this.mapSpawnControl = container.resolve<MapSpawnControl>("MapSpawnControl");
|
||||||
|
}
|
||||||
|
|
||||||
|
public postSptLoad(container: DependencyContainer): void
|
||||||
|
{
|
||||||
|
// SPT Classes
|
||||||
|
this.databaseService = container.resolve<DatabaseService>("DatabaseService");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { inject, injectable } from "tsyringe";
|
||||||
|
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||||
|
import { MapSpawnControl } from "../Controls/MapSpawnControl";
|
||||||
|
import path from "node:path";
|
||||||
|
import { FileSystemSync } from "@spt/utils/FileSystemSync";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class StaticRouterHooks
|
||||||
|
{
|
||||||
|
public cacheRebuilt: boolean = false;
|
||||||
|
public mapToRebuild: string = "";
|
||||||
|
public bossTrackingData: BossTrackingData = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("StaticRouterModService") protected staticRouterService: StaticRouterModService,
|
||||||
|
@inject("MapSpawnControl") protected mapSpawnControl: MapSpawnControl,
|
||||||
|
@inject("FileSystemSync") protected fileSystem: FileSystemSync
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public registerRouterHooks(): void
|
||||||
|
{
|
||||||
|
|
||||||
|
this.staticRouterService.registerStaticRouter(
|
||||||
|
"ABPS-StartMatchRouter",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
url: "/client/match/local/start",
|
||||||
|
action: async (url, info, sessionId, output) =>
|
||||||
|
{
|
||||||
|
this.mapToRebuild = info.location;
|
||||||
|
if (this.cacheRebuilt)
|
||||||
|
{
|
||||||
|
this.cacheRebuilt = false;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ABPS"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.staticRouterService.registerStaticRouter(
|
||||||
|
"ABPS-EndMatchRouter",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
url: "/client/match/local/end",
|
||||||
|
action: async (url, info, sessionId, output) =>
|
||||||
|
{
|
||||||
|
if (!this.cacheRebuilt)
|
||||||
|
{
|
||||||
|
this.mapSpawnControl.rebuildCache(this.mapToRebuild);
|
||||||
|
this.cacheRebuilt = true;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ABPS"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.staticRouterService.registerStaticRouter(
|
||||||
|
"ABPS-BossTrackingRoutes",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
url: "/abps/save",
|
||||||
|
action: async (url, info: BossTrackingData, sessionId, output) => this.saveBossTrackingData(info)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "/abps/load",
|
||||||
|
action: async (url, info, sessionId, output) => JSON.stringify(this.bossTrackingData)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ABPS"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveBossTrackingData(payload: BossTrackingData): Promise<string>
|
||||||
|
{
|
||||||
|
if (!payload)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.bossTrackingData = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.save();
|
||||||
|
return JSON.stringify({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async save(): Promise<void>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const filename = path.join(__dirname, "../../bossTrackingData.json");
|
||||||
|
await this.fileSystem.writeJson(filename, this.bossTrackingData, 2);
|
||||||
|
}
|
||||||
|
catch (error)
|
||||||
|
{
|
||||||
|
console.error("[ABPS] Failed to save boss tracking data! " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async load(): Promise<void>
|
||||||
|
{
|
||||||
|
const filename = path.join(__dirname, "../../bossTrackingData.json");
|
||||||
|
if (this.fileSystem.exists(filename))
|
||||||
|
{
|
||||||
|
const jsonData = this.fileSystem.readJson(filename);
|
||||||
|
this.bossTrackingData = jsonData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.bossTrackingData = {};
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BossTrackingInfo = {
|
||||||
|
spawnedLastRaid: boolean,
|
||||||
|
chance: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type BossTrackingData = Record<string, Record<string, BossTrackingInfo>>;
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { ExhaustableArray } from "@spt/models/spt/server/ExhaustableArray";
|
||||||
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { RandomUtil } from "@spt/utils/RandomUtil";
|
||||||
|
|
||||||
|
export function createExhaustableArray<T>(itemsToAddToArray: T[], randomUtil: RandomUtil, cloner: ICloner): ExhaustableArray<T>
|
||||||
|
{
|
||||||
|
return new ExhaustableArray<T>(itemsToAddToArray, randomUtil, cloner);
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { DependencyContainer } from "tsyringe";
|
||||||
|
import { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
|
||||||
|
import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
|
||||||
|
import { InstanceManager } from "./InstanceManager";
|
||||||
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
|
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
|
||||||
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
|
import { FileSystemSync } from "@spt/utils/FileSystemSync";
|
||||||
|
|
||||||
|
import { minVersion, satisfies, SemVer } from "semver";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import { ILocationBase } from "@spt/models/eft/common/ILocationBase";
|
||||||
|
import { IRaidChanges } from "@spt/models/spt/location/IRaidChanges";
|
||||||
|
|
||||||
|
|
||||||
|
class ABPS implements IPreSptLoadMod, IPostDBLoadMod
|
||||||
|
{
|
||||||
|
// Create InstanceManager - Thank you Cj as per usual
|
||||||
|
private instance: InstanceManager = new InstanceManager();
|
||||||
|
|
||||||
|
// PreSPTLoad
|
||||||
|
public preSptLoad(container: DependencyContainer): void
|
||||||
|
{
|
||||||
|
const logger = container.resolve<ILogger>("WinstonLogger");
|
||||||
|
if (!this.validSptVersion(container))
|
||||||
|
{
|
||||||
|
logger.error(`[ABPS] This version of ABPS was not made for your version of SPT. Disabling. Requires ${this.validMinimumSptVersion(container)} or higher.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.instance.preSptLoad(container, "ABPS");
|
||||||
|
this.instance.staticRouterHooks.registerRouterHooks();
|
||||||
|
|
||||||
|
container.afterResolution("RaidTimeAdjustmentService", (_t, result: any) =>
|
||||||
|
{
|
||||||
|
result.adjustWaves = (mapBase: ILocationBase, raidAdjustments: IRaidChanges) =>
|
||||||
|
{
|
||||||
|
this.instance.mapSpawnControl.adjustWaves(mapBase, raidAdjustments);
|
||||||
|
}
|
||||||
|
}, {frequency: "Always"});
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostDBLoad
|
||||||
|
public postDBLoad(container: DependencyContainer): void
|
||||||
|
{
|
||||||
|
this.instance.postDBLoad(container);
|
||||||
|
this.instance.mapSpawnControl.configureInitialData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version Validation
|
||||||
|
public validSptVersion(container: DependencyContainer): boolean
|
||||||
|
{
|
||||||
|
const fileSysem = container.resolve<FileSystemSync>("FileSystemSync");
|
||||||
|
const configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||||
|
const sptConfig = configServer.getConfig<ICoreConfig>(ConfigTypes.CORE);
|
||||||
|
|
||||||
|
const sptVersion = globalThis.G_SPTVERSION || sptConfig.sptVersion;
|
||||||
|
const packageJsonPath: string = path.join(__dirname, "../package.json");
|
||||||
|
const modSptVersion = fileSysem.readJson(packageJsonPath).sptVersion;
|
||||||
|
|
||||||
|
return satisfies(sptVersion, modSptVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public validMinimumSptVersion(container: DependencyContainer): SemVer
|
||||||
|
{
|
||||||
|
const fileSysem = container.resolve<FileSystemSync>("FileSystemSync");
|
||||||
|
const packageJsonPath: string = path.join(__dirname, "../package.json");
|
||||||
|
const modSptVersion = fileSysem.readJson(packageJsonPath).sptVersion;
|
||||||
|
|
||||||
|
return minVersion(modSptVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mod = new ABPS();
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue