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] | ||||
| gameName=spt | ||||
| modid=0 | ||||
| version=d2025.1.17.0 | ||||
| version=d2025.5.13.0 | ||||
| newestVersion= | ||||
| category="1,2" | ||||
| category="-1," | ||||
| nexusFileStatus=1 | ||||
| installationFile=DrakiaXYZ-Waypoints-1.6.1.7z | ||||
| installationFile=acidphantasm-botplacementsystem.zip | ||||
| repository=Nexus | ||||
| ignoredVersion= | ||||
| comments= | ||||
|  | @ -15,7 +15,7 @@ url= | |||
| hasCustomURL=false | ||||
| lastNexusQuery= | ||||
| lastNexusUpdate= | ||||
| nexusLastModified=2024-12-16T06:41:17Z | ||||
| nexusLastModified=2025-05-13T08:46:56Z | ||||
| nexusCategory=0 | ||||
| converted=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