Mod init
This commit is contained in:
@@ -0,0 +1,389 @@
|
||||
You're no longer the only PMC running around placing markers and collecting quest items. The bots have transcended and are coming for you...
|
||||
|
||||
**This mod may have a performance impact**, but it should be minimal starting with the 0.5.0 release. If you notice performance problems, please try using the built-in AI limiter.
|
||||
|
||||
**---------- Mod Compatibility ----------**
|
||||
|
||||
**REQUIRES:**
|
||||
* [BigBrain](https://hub.sp-tarkov.com/files/file/1219-bigbrain/)
|
||||
* [Waypoints](https://hub.sp-tarkov.com/files/file/1119-waypoints-expanded-bot-patrols-and-navmesh/)
|
||||
|
||||
**Highly Recommended:**
|
||||
* [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/) (2.1.12 or later recommended)
|
||||
* [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/) (1.3.0 or later recommended)
|
||||
|
||||
**NOT compatible with:**
|
||||
* [AI Limit](https://hub.sp-tarkov.com/files/file/793-ai-limit/) or any other mods that disable AI in a similar manner. This mod relies on the AI being active throughout the entire map. **Starting with 0.2.10, Questing Bots has its own AI Limiter feature.** Please see the tab below for more information.
|
||||
* [Traveler](https://hub.sp-tarkov.com/files/file/1212-traveler/) (You MUST use another mod like [SWAG + DONUTS](https://hub.sp-tarkov.com/files/file/878-swag-donuts-dynamic-spawn-waves-and-custom-spawn-points/) to manage bot spawning when using this mod. Otherwise, bots will spawn right in front of you.)
|
||||
|
||||
**Compatible with:**
|
||||
* [SWAG + DONUTS](https://hub.sp-tarkov.com/files/file/878-swag-donuts-dynamic-spawn-waves-and-custom-spawn-points/)
|
||||
* [Late to the Party](https://hub.sp-tarkov.com/files/file/1099-late-to-the-party/) (if **bot_spawns.enabled=true** in this mod, ensure **adjust_bot_spawn_chances.adjust_pmc_conversion_chances=false** in LTTP)
|
||||
|
||||
**NOTE: Please disable the bot-spawning system in this mod if you're using other mods that manage spawning! Otherwise, there will be too many bots on the map. The bot-spawning system in this mod will be automatically disabled** if any of the following mods are detected:
|
||||
* [SWAG + DONUTS](https://hub.sp-tarkov.com/files/file/878-swag-donuts-dynamic-spawn-waves-and-custom-spawn-points/)
|
||||
* [MOAR](https://hub.sp-tarkov.com/files/file/1059-moar-bots-spawning-difficulty/)
|
||||
|
||||
**---------- Overview ----------**
|
||||
|
||||
There are two main components of this mod: adding an objective system to the AI and directly controlling PMC and player-Scav spawning to mimic live Tarkov.
|
||||
|
||||
**Objective System:**
|
||||
Instead of simply patrolling their spawn areas, bots will now move around the map to perform randomly-selected quest objectives. By default this system is only active for PMC's and player Scavs, but it can be enabled for normal Scavs and bosses if you want an extra challenge.
|
||||
|
||||
After spawning (regardless of when this occurs during the raid), bots will immediately begin questing, and there are only a few conditions that will cause them to stop questing:
|
||||
* They got stuck too many times
|
||||
* Their health is too low and they're unable to heal
|
||||
* They're over-encumbered
|
||||
* They're trying to extract (using [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/))
|
||||
|
||||
Otherwise, they will only temporarily stop questing for the following reasons:
|
||||
* They're currently or were just recently in combat
|
||||
* They heard a suspicious noise
|
||||
* They recently completed an objective
|
||||
* They're checking for or have found loot
|
||||
* Their health is too low or they have blacked limbs (besides arms)
|
||||
* Their energy or hydration is too low
|
||||
* They have followers that are too far from them
|
||||
|
||||
There are several types of quests available to each bot:
|
||||
* **EFT Quests:** Bots will go to locations specified in EFT quests for placing markers, collecting/placing items, killing other bots, etc. Bots can also use quests added by other mods.
|
||||
* **Spawn Rush:** At the beginning of the raid, bots that are within a certain distance of you will run to your spawn point. Only a certain number of bots are allowed to perform this quest, and they won't always select it. This makes PVP-focused maps like Factory even more challenging.
|
||||
* **Boss Hunter:** Bots will search zones in which bosses are known to spawn. They will only be allowed to select this quest at the beginning of the raid (within the first 5 minutes by default) and if they're a high enough level.
|
||||
* **Airdrop Chaser:** Bots will run to the most recent airdrop if it's close to them (within 500m by default). They will be allowed to select this quest within **questing.bot_quests.airdrop_bot_interest_time** seconds (420s by default) of the airdrop crate landing.
|
||||
* **Spawn Point Wandering:** Bots will wander to different spawn points around the map. This is used as a last resort in case the bot doesn't select any other quests. This quest is currently disabled by default because it should no longer be needed with the quest variety offered in the 0.4.0 and later releases.
|
||||
* **"Standard" Quests:** Bots will go to specified locations around the map. They will prioritize more desirable locations for loot and locations that are closer to them. These also include some sniping and camping quests on all maps, so be careful!
|
||||
* **"Custom" Quests:** You can create your own quests for bots using the templates for "standard" quests. None are provided by default.
|
||||
|
||||
**PMC and Player-Scav Spawning Systems:**
|
||||
At the beginning of the raid, PMC's will spawn around the map at actual EFT PMC spawn points. The spawning system will try to separate spawn points as much as possible, but spawn killing is still entirely possible just like it is in live Tarkov. The total number of PMC's that can spawn is a random number between the minimum and maximum player count for the map (other mods can change these values). However, you count as one of those PMC's for PMC raids. That number will be reduced if you spawn into the map late for a Scav run. The PMC difficulty is set by your raid settings in EFT.
|
||||
|
||||
Starting with the 0.4.0 release, player Scavs will also spawn throughout the raid. Each group of player Scavs will be assigned a minimum spawn time that is generated using SPT's raid-time-reduction settings for Scav raids. This mod will use SPT's weighting settings for choosing when player Scavs will spawn into each location, it will add some randomness, and then it will generate a spawn schedule for all player-Scav spawns. Effectively, this means that player Scavs are most likely to spawn toward the middle and last third of raids. They're unlikely to spawn toward the very beginning or very end of them. Player Scavs can spawn at any EFT PMC or player-Scav spawn point on the map, and player-Scav bot difficulty is set by your raid settings in EFT.
|
||||
|
||||
Only a certain (configurable) number of initial PMC's will spawn at the beginning of the raid, and the rest will spawn as the existing ones die. PMC's that spawn after the initial wave can spawn anywhere that is far enough from you and other bots (at any EFT spawn point for PMC's or player Scavs). After all PMC's have spawned, player Scavs will be allowed to spawn. The maximum total number of PMC's and player Scavs on the map cannot exceed the number of initial PMC's (determined by **bot_spawns.max_alive_bots**). For example, Customs allows 10-12 players, but Questing Bots only allows 7 to be on the map at the same time by default. That means 7 PMC's will spawn immediately as the raid starts, and as some of them die others will spawn to replace them. After all PMC's have spawned and less than 7 are remaining, player Scavs will be allowed to spawn. If there are 5 PMC's left on the map, 2 player Scavs will be allowed to spawn. If there are 3 PMC's left on the map, 4 player Scavs will be allowed to spawn, and so on. Even if most total PMC's have died, player Scavs will not be allowed to spawn earlier than their scheduled spawn times.
|
||||
|
||||
A new feature of the 0.4.0 and later releases is an advanced spawning system that tricks EFT into thinking that PMC's and player Scavs are human players. This makes PMC's and player Scavs not count toward the bot cap for the map, so they shouldn't impede normal EFT bot spawns for normal Scavs and bosses. It also prevents PMC's and player Scavs from counting toward the maximum bot counts for each zone on the map, and this allows normal Scavs to spawn like they would in live EFT. Without this system, all initial bosses must be configured to spawn first (which is a config option in this mod) or EFT may suppress them due to the high number of bots on the map. To accomodate the large initial PMC wave and still allow Scavs and bosses to spawn, the bot cap can be optionally increased (which is also a config option in this mod) if you're not using the advanced spawning system.
|
||||
|
||||
**---------- Bot Quest-Selection Algorithm Overview ----------**
|
||||
|
||||
When each bot spawns, this mod finds the furthest extract from them and references it when selecting new quests for the bot. If the bot ever comes close enough to that extract while traversing the map, this happens again; a new extract is selected for it that is the furthest one from its current location. This continues until the bot extracts or dies. This extract is NOT used when bots extract via [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/); it is only used when this mod selects new quests for the bot.
|
||||
|
||||
Before selecting a quest for a bot, all quests are first filtered to ensure they have at least one valid location on the map and the bot is able to accept the quest (it's not blocked by player level, etc.). Then, the following metrics are generated for every valid quest:
|
||||
1) The distance between the bot and each objective for the quest with some randomness applied (by **questing.bot_quests.distance_randomness**). This value is then normalized based on the furthest objective from the bot (for any valid quest), and finally it's multiplied by a weighting factor defined by **questing.bot_quests.distance_weighting** (1 by default).
|
||||
2) A "desirability" rating for each quest, which is the desirability rating assigned to the quest but with some randomness applied (by **questing.bot_quests.desirability_randomness**). This value is divided by 100 and then multiplied by a weighting factor defined by **questing.bot_quests.desirability_weighting** (1 by default).
|
||||
3) The angle between two vectors: the vector between the bot and its selected extract (described above), and the vector between the bot and each objective for the quest. If the quest objective is in the same direction as the bot's selected extract, this angle will be small. If the bot has to move further from its selected extract, this angle will be large. Angles that are below a certain threshold (90 deg by default) are reduced down to 0 deg. This value is divided by 180 deg minus the threshold just described (90 deg by default), and finally it's multiplied by a weighting factor defined by **questing.bot_quests.exfil_direction_weighting**, which is different for every map.
|
||||
|
||||
These three metrics are then added together, and the result is the overall score for the corresponding quest. The quest with the highest score is assigned to the bot. If for some reason the bot is unable to perform that quest, it selects the one with the next-highest score, and so on. If no quests are available for the bot to select, this mod will first try allowing the bot to perform repeatable quests early (before the **questing.bot_questing_requirements.repeat_quest_delay** delay expires). If there are no available repeatable quests, this mod will then attempt to make the bot extract via [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/). Finally, this mod will stop assigning new quests to the bot.
|
||||
|
||||
**---------- How to Add Custom Quests ----------**
|
||||
|
||||
To add custom quests to a map, first create a *user\mods\DanW-SPTQuestingBots-#.#.#\quests\custom* directory if it doesn't already exist. Then, create a file for each map for which you want to add custom quests. The file name should exactly match the corresponding file in the *user\mods\DanW-SPTQuestingBots-#.#.#\quests\standard* directory (case sensitive).
|
||||
|
||||
The three major data structures are:
|
||||
* **Quests**: A quest is a collection of at least one quest objective, and objectives can be placed anywhere on the map. Objectives can be completed in any order.
|
||||
|
||||
Quests have the following properties:
|
||||
* **repeatable**: Boolean value indicating if the bot can repeat the quest later in the raid. This is typically used for quests that are PvP or PvE focused, where a bot might want to check an area again later in the raid for more enemies.
|
||||
* **isCamping**: If the quest should be considered to be a camping quest
|
||||
* **isSniping**: If the quest should be considered to be a sniping quest
|
||||
* **pmcsOnly**: Only PMC's will be allowed to select the quest
|
||||
* **minLevel**: Only bots that are at least this player level will be allowed to select the quest
|
||||
* **maxLevel**: Only bots that are at most this player level will be allowed to select the quest
|
||||
* **maxBots**: The maximum number of bots that can be performing the quest at the same time.
|
||||
* **maxBotsInGroup**: If the number of bots in a group exceeds this value, the boss of the group will not be allowed to select this quest.
|
||||
* **desirability**: A rating roughly equivalent to a percentage that indicates "how much" bots want to select this quest. Quests with high desirability ratings (50+) are very likely to be selected, and quests with low desirability ratings (<20) are unlikely to be selected unless the bot is close to them.
|
||||
* **minRaidET**: The quest can only be selected if at least this many seconds have elapsed in the raid. This is based on the overall raid time, not the time after you spawn. For example, if you set **maxRaidET=60** for a quest and you spawn into a Factory raid with 15 minutes remaining, this quest will never be used because 300 seconds has already elapsed in the overall raid. This property is typically used to make bots rush to locations like Dorms when the raid begins.
|
||||
* **maxRaidET**: The quest can only be selected if more more than this many seconds have elapsed in the raid. See **minRaidET** for more information.
|
||||
* **maxTimeOnQuest**: The maximum time (in seconds) that a bot is allowed to continue doing the quest after it completes at least one of its objectives. This is intended to add more variety to bot questing instead of having them stay in one area for a long period of time. By default, this is 300 seconds.
|
||||
* **canRunBetweenObjectives**: Boolean indicating if bots are allowed to sprint to the next objective in the quest after it completes at least one objective. This is intended to be used in areas where stealth is more important (typically in buildings). This is **true** by default.
|
||||
* **requiredSwitches**: A dictionary of the switches that must be in a specific position bot bots to perform the quest. The dictionary key is the ID of the switch, and the value is a boolean indicating if the switch must be open (actuated). If the dictionary is empty, no switches will be checked.
|
||||
* **name**: The name of the quest. This doesn't have to be unique, but it's best if it is to avoid confusion when troubleshooting.
|
||||
* **waypoints**: An array of waypoints that can be used to assist bots with finding paths to the quest's objectives. Each waypoint is an (x, y, z) coordinate.
|
||||
* **objectives**: An array of the objectives in the quest. Bots can complete objectives in any order.
|
||||
|
||||
* **Objectives**: An objective is a collection of at least one step. An objective represents a list of actions that the bot must complete in the order you specify.
|
||||
|
||||
Quest objectives have the following properties:
|
||||
* **repeatable**: Boolean value indicating if the bot can repeat the quest objective later in the raid. This is typically used for quests are are PvP or PvE focused, where a bot might want to check an area again later in the raid for more enemies.
|
||||
* **minDistanceFromBot**: The objective will only be selected if the bot is at least this many meters away from it.
|
||||
* **maxDistanceFromBot**: The objective will only be selected if the bot is no more than this many meters away from it.
|
||||
* **maxRunDistance**: If bots get within this radius (in meters) of the position for the first step in the objective, they will no longer be allowed to sprint. This is intended to be used in areas where stealth is more important (typically in buildings). This is **0** by default.
|
||||
* **lootAfterCompleting**: The only valid options for this are "Default", "Force", and "Inhibit" (case-sensitive). If "Force" is used, Questing Bots will try invoking [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/) to make the bot scan for loot immediately after completing each step in the objective. However, bots will not be able to loot if they're in combat or have no available space. If "Inhibit" is used, this mod will try invoking [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/) to prevent the bot from looting until after it selects another quest objective. [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/) version 1.2.1 or later is required for either option to work. [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/) 2.1.9 or later is required for Questing Bots to properly force bots to loot.
|
||||
* **steps**: An array of the steps in the objective. Bots will complete the steps exactly in the order you specify.
|
||||
|
||||
* **Steps**: A step is an individual component of an objective.
|
||||
|
||||
Quest objective steps have the following properties:
|
||||
* **position**: The position on the map that the bot will try to reach
|
||||
* **lookToPosition**: The position on the map that bots will look toward after they reach **position**. This is only used for the **Ambush** and **Snipe** step types described below.
|
||||
* **waitTimeAfterCompleting**: The time the bot must wait after completing the step before it will be allowed to quest again. The default value for this field is defined by **questing.default_wait_time_after_objective_completion**.
|
||||
* **stepType**: The only valid options for this are (case-sensitive):
|
||||
* **MoveToPosition**: Bots are instructed to go to **position**. If possible, they're allowed to unlock doors that block their path.
|
||||
* **HoldAtPosition**: Bots are instructed to remain within **maxDistance** meters of **position** and stay alert for a random time between the min and max values of **minElapsedTime**.
|
||||
* **Ambush**: Bots are instructed to go to **position**, stand still, and look at **lookToPosition** for a random time between the min and max values of **minElapsedTime**. This is used to simulate camping quests.
|
||||
* **Snipe**: The same as **Ambush**, but bots can be interrupted if they hear suspicious noises
|
||||
* **ToggleSwitch**: Bots are instructed to go to **position** and toggle the switch defined by **switchID**.
|
||||
* **RequestExtract**: This mod will try to instruct bots to extract via [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/).
|
||||
* **CloseNearbyDoors**: Bots are instructed to close all doors within **maxDistance** meters of **position**
|
||||
|
||||
If **stepType** is omitted, **MoveToPosition** is used by default.
|
||||
* **minElapsedTime**: The range of minimum and maximum time that a bot will perform certain types of objective steps (namely **HoldAtPosition**, **Ambush**, and **Snipe**).
|
||||
* **switchID**: If **stepType="ToggleSwitch"**, this is the ID of the switch the bot should open. It needs to exactly match one of the results in the "Found switches" debug message shown in the bepinex console when loading into the map.
|
||||
* **maxDistance**: If **stepType="HoldAtPosition"**, this is the maximum distance (in meters) bots will be allowed to wander from **position** for the objective step. If **stepType="CloseNearbyDoors"**, bots will close all doors within this radius of **position** (in meters).
|
||||
* **chanceOfHavingKey**: The chance that bots will have keys to unlock any doors that are blocking their paths to this objective step. This overrides the default chance of having keys defined by **questing.unlocking_doors.default_chance_of_bots_having_keys** or **questing.bot_quests.eft_quests.chance_of_having_keys**.
|
||||
|
||||
**Tips and Tricks**
|
||||
* Objectives should be sparsely placed on the map. Since bots take a break from questing after each objective is completed, they will wander around the area (for an unknown distance) before continuing the quest. If you place objective positions too close to each other, the bot will unnecessarily run back and forth around the area. As a rule of thumb, place objectives at least 20m from each other.
|
||||
* If you want a bot to go to several specific positions that are close to each other (i.e. small, adjacent rooms), use multiple steps in a single objective instead of using multiple objectives each with a single step.
|
||||
* Bots will use the NavMesh to calculate the more efficient path to their objective (using an internal Unity algorithm). They cannot perform complex actions to reach objective locations, so avoid placing objective steps on top of objects (i.e. inside truck beds) or in areas that are difficult to reach. Bots will not know to crouch or jump to get around obstacles.
|
||||
* Quest waypoints can help bots find paths to objectives in labyrinthic areas, but adding too many to a quest may impact performance.
|
||||
|
||||
**---------- Bot Group Spawning System ----------**
|
||||
|
||||
* Spawn chances for various group sizes are configurable. By default, solo spawns are most likely, but 2-man and 3-man groups will be commonly seen. 4-man and 5-man groups are rare but possible.
|
||||
* EFT will assign one bot in the group to be a "boss", and the boss will select the quest to perform. All other bots in the group will follow the boss.
|
||||
* If any group members stray too far from the boss, the boss will stop questing and attempt to regroup
|
||||
* If any member of the group engages in combat, all other members will stop questing (or following) and engage in combat too.
|
||||
* If the boss is allowed to sprint, so are its followers and vice versa.
|
||||
* If the boss of a bot group dies, EFT will automatically assign a new one from the remaining members
|
||||
* Followers are only allowed to loot if they remain within a certain distance from the boss
|
||||
|
||||
**---------- AI Limiter System ----------**
|
||||
|
||||
Since normal AI Limit mods will disable bots that are questing (which will prevent them from exploring the map), this mod has its own AI Limiter with the following features:
|
||||
* AI Limiting must be explicitly enabled in the F12 menu.
|
||||
* AI Limiting must be explicitly enabled for bots that are questing for each map. By default, questing bots will only be disabled on Streets.
|
||||
* Bots will only be allowed to be disabled if they are beyond a certain distance (200m by default) from you
|
||||
* Bots will only be allowed to be disabled if they are beyond a certain distance (75m by default) from other bots that are questing (and not disabled)
|
||||
|
||||
**---------- Configuration Options in *config.json* ----------**
|
||||
|
||||
**Main Options:**
|
||||
* **enabled**: Completely enable or disable all featues of this mod.
|
||||
* **debug.enabled**: Enable debug mode.
|
||||
* **debug.scav_cooldown_time**: Cooldown timer (in seconds) after a Scav raid ends before you're allowed to start another one. This is **1500** by default, which is the same as the base game.
|
||||
* **debug.full_length_scav_raids**: If **true**, Scav raids will always be full-length.
|
||||
* **debug.free_labs_access**: If **true**, Labs cards are no longer required to enter Labs, and you're also allowed to do Scav runs in Labs.
|
||||
* **debug.always_spawn_pmcs**: If **true**, PMC's will spawn even when you select "None" for the amount of bots when starting a raid.
|
||||
* **debug.always_spawn_pscavs**: If **true**, player Scavs will spawn even when you select "None" for the amount of bots when starting a raid.
|
||||
* **debug.always_have_airdrops**: If **true**, an airdrop will appear at the beginning of all raids for applicable maps
|
||||
* **debug.show_zone_outlines**: If **true**, EFT quest zones will be outlined in light blue. Target locations for each zone will have light-blue spherical outlines.
|
||||
* **debug.show_failed_paths**: If **true**, whenever a bot gets stuck its target path will be drawn in red.
|
||||
* **debug.show_door_interaction_test_points**: If **true**, the positions tested when determining where bots should travel to unlock doors will have spherical outlines. If the a valid NavMesh position cannot be found for the test point, the outline color will be white. If a valid NavMesh position is found but the bot cannot access that point, the outline color will be yellow. If a valid NavMesh position is found and the bot can access that point, the outline color will be magenta. The position selected for the bot will be shown with a green outline.
|
||||
* **max_calc_time_per_frame_ms**: The maximum amount of time (in milliseconds) the mod is allowed to run quest-generation and PMC-spawning procedures per frame. By default this is set to **5** ms, and delays of <15 ms are basically imperceptible.
|
||||
* **chance_of_being_hostile_toward_bosses.scav**: The chance that Scavs will be hostile toward all bosses on the map. This is **0%** by default.
|
||||
* **chance_of_being_hostile_toward_bosses.pscav**: The chance that player Scavs will be hostile toward all bosses on the map even if the bosses aren't hostile toward them (i.e. Rogues are not initially hostile toward player Scavs). This is **20%** by default.
|
||||
* **chance_of_being_hostile_toward_bosses.pmc**: The chance that PMC's will be hostile toward all bosses on the map even if the bosses aren't hostile toward them (i.e. Rogues are not initially hostile toward USEC PMC's). This is **80%** by default.
|
||||
* **chance_of_being_hostile_toward_bosses.boss**: The chance that bosses will be hostile toward all other bosses on the map. This is **0%** by default.
|
||||
|
||||
**Questing Options:**
|
||||
* **questing.enabled**: Completely enable or disable questing.
|
||||
* **questing.bot_pathing_update_interval_ms**: The interval (in milliseconds) at which each bot will recalculate its path to its current objective. If this value is very low, performance will be impacted. If this value is very high, the bot will not react to obstacles changing as quickly (i.e. doors being unlocked). By default, this is **100** ms.
|
||||
* **questing.brain_layer_priority**: The priority number assigned to the questing "brain" layer. **Do not change this unless you know what you're doing!** By default, this is set to **26** which is higher than most EFT brain layers and higher than [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/)'s brain layers. If this is set much lower than 26, bots will prioritize other actions. If you're using [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/) and you reduce this to be less than 23, bots will never quest.
|
||||
* **questing.quest_selection_timeout**: If a quest cannot be selected for a bot after trying for this amount of time (in seconds), the mod will give up and write an error message.
|
||||
* **questing.allowed_bot_types_for_questing.scav**: If Scavs are allowed to quest. This is **false** by default.
|
||||
* **questing.allowed_bot_types_for_questing.pscav**: If player Scavs are allowed to quest. This is **true** by default.
|
||||
* **questing.allowed_bot_types_for_questing.pmc**: If PMC's are allowed to quest. This is **true** by default.
|
||||
* **questing.allowed_bot_types_for_questing.boss**: If bosses are allowed to quest. This is **false** by default. Boss followers will never be allowed to quest.
|
||||
* **questing.allowed_bot_types_for_questing.min/max**: The minimum and maximum time (in seconds) that a bot will wait after ending combat before it's allowed to quest again. After the bot is no longer actively engaged in combat, it will continue its quest following a random delay between these two values. This is to allow the bot to search for threats before blindly running toward its objective.
|
||||
* **questing.stuck_bot_detection.distance**: The minimum distance (in meters) the bot must travel over a period of **questing.stuck_bot_detection.time** seconds while questing or the mod will assume it's stuck. This is **2** m by default.
|
||||
* **questing.stuck_bot_detection.time**: The maximum time (in seconds) the bot is allowed to move less than **questing.stuck_bot_detection.distance** meters while questing or the mod will assume it's stuck. This is **20** s by default.
|
||||
* **questing.stuck_bot_detection.max_count**: The maximum number of times the bot can be stuck before questing is completely disabled for it. This counter is reset whenever the bot completes an objective. Whenever the bot is assumed to be stuck, a new objective will be selected for it to force it to generate a different path. This is **8** by default.
|
||||
* **questing.stuck_bot_detection.follower_break_time**: If a boss follower is stuck while trying to follow it, it will take a break for this many seconds (**10** by default).
|
||||
* **questing.stuck_bot_detection.max_not_able_bodied_time**: If a bot is continuously not able-bodied (typically due to injuries) for this amount of time (in seconds), it will be separated from its group. If it's the boss of its group, a new boss will be selected for that group. This timer is paused when a bot is either in combat or hears suspicious noises. This is **120** s by default.
|
||||
* **questing.unlocking_doors.enabled.scav**: If questing Scavs are allowed to open locked doors. This is **false** by default.
|
||||
* **questing.unlocking_doors.enabled.pscav**: If questing player Scavs are allowed to open locked doors. This is **false** by default.
|
||||
* **questing.unlocking_doors.enabled.pmc**: If questing PMC's are allowed to open locked doors. This is **true** by default.
|
||||
* **questing.unlocking_doors.enabled.boss**: If questing bosses are allowed to open locked doors. This is **false** by default.
|
||||
* **questing.unlocking_doors.search_radius**: The distance (in meters) to search around the bot for locked doors. This is **25**m by default.
|
||||
* **questing.unlocking_doors.max_distance_to_unlock**: The maximum distance (in meters) that a bot is allowed to be from a door in order to unlock it. This is **0.5**m by default. **Do not change this unless you know what you're doing!**
|
||||
* **questing.unlocking_doors.door_approach_position_search_radius**: The distance (in meters) to search around doors for positions that are on the NavMesh and have complete paths to the bot's current location. This is **0.75**m by default. **Do not change this unless you know what you're doing!**
|
||||
* **questing.unlocking_doors.door_approach_position_search_offset**: The distance (in meters) to offset the search positions around doors determined by **questing.unlocking_doors.door_approach_position_search_radius**. This is **-0.75**m by default. **Do not change this unless you know what you're doing!**
|
||||
* **questing.unlocking_doors.pause_time_after_unlocking**: The time (in seconds) bots must wait after unlocking doors before they're allowed to continue with their quests. If this is too low, their pathing will not be updated and they may fail the quest they're currently doing. **Do not change this unless you know what you're doing!**
|
||||
* **questing.unlocking_doors.debounce_time**: The time (in seconds) bots must wait after selecting a door to unlock before they're allowed to select another one to unlock. This is to prevent bots from rapidly selecting doors instead of allowing them to change objectives. This is **1**s by default.
|
||||
* **questing.unlocking_doors.default_chance_of_bots_having_keys**: The default chance (in percentage) that bots will have keys for quest locations.
|
||||
* **questing.min_time_between_switching_objectives**: The minimum amount of time (in seconds) the bot must wait after completing an objective before a new objective is selected for it. This is to allow it to check its surroundings, search for loot, etc. This is **5** s by default.
|
||||
* **questing.default_wait_time_after_objective_completion**: The default time (in seconds) a bot will wait after completing a quest objective step before it will select a new one. This is **5** s by default.
|
||||
* **questing.wait_time_before_planting**: If the bot needs to plant an item at a quest location, this is the time (in seconds) it will wait between reaching its target location and beginning to "plant" the required item. This is **1** s by default. If this is much lower than **1** s, there may be strange behavior when the bot transitions into planting its item.
|
||||
* **questing.quest_generation.navmesh_search_distance_item**: The radius (in meters) around quest items (i.e. the bronze pocket watch) to seach for a valid NavMesh position to use for a target location for creating a quest objective for it. If this value is too low, bots may not be able to generate a complete path to the item. If this value is too high, bots may generate paths into adjacent rooms or to vertical positions on different floors. This is **1.5** m by default.
|
||||
* **questing.quest_generation.navmesh_search_distance_zone**: The radius (in meters) around target positions in zones (i.e. trigger areas for placing markers) to seach for a valid NavMesh position to use for a target location for creating a quest objective for it. If this value is too low, bots may not be able to generate a complete path to the zone. If this value is too high, bots may generate paths into adjacent rooms or to vertical positions on different floors. This is **1.5** m by default. The target position for a zone is the center-most valid NavMesh position in it. If the zone surrounds multiple floors in a building, the lowest floor is typically used.
|
||||
* **questing.quest_generation.navmesh_search_distance_spawn**: The radius (in meters) around spawn points to seach for a valid NavMesh position to use for a target location for creating a quest objective for it. If this value is too low, bots may not be able to generate a complete path to the spawn point. If this value is too high, bots may generate paths into adjacent rooms or to vertical positions on different floors. This is **2** m by default.
|
||||
* **questing.quest_generation.navmesh_search_distance_doors**: The radius (in meters) to search for a valid NavMesh position around the test points used for determining if a bot can unlock a door. If this value is too low, bots may not be able to unlock the door. If this value is too high, bots may generate paths into adjacent rooms or to vertical positions on different floors. This is **0.75** m by default.
|
||||
* **questing.bot_search_distances.objective_reached_ideal**: Bots must travel within this distance (in meters) of their target objective positions for the objective to be considered successfully completed. This is **0.25** m by default.
|
||||
* **questing.bot_search_distances.objective_reached_navmesh_path_error**: The maximum distance (in meters) that the end of a bot's calculated path can be from its target objective position before the objective is considerd unreachable. This is **20** m by default.
|
||||
* **questing.bot_search_distances.max_navmesh_path_error**: If a complete path cannot be generated to a bot's target objective position, it will try to get within this radius (in meters) of it anyway. This is to simulate situations like bots checking if a door is unlocked when it doesn't have the key. This is **10** m by default.
|
||||
* **questing.bot_pathing.max_start_position_discrepancy**: The minimum distance (in meters) between the bot's position and the start of its path above which its path will be recalculated if there is a difference between its current target position and the target position for its quest. **Do not change this unless you know what you're doing!**
|
||||
* **questing.bot_pathing.incomplete_path_retry_interval**: If a bot's path to its objective is incomplete, the path will be recalculated at this interval (in seconds) until a complete path is found. This is **5** s by default.
|
||||
* **questing.bot_questing_requirements.exclude_bots_by_level**: Each quest has a minimum and maximum player level assigned to it. If this option is **true** (which is the default setting), bots will only be allowed to select a quest if its player level is within this range. This prevents low-level bots from selecting end-game quests and vice versa.
|
||||
* **questing.bot_questing_requirements.repeat_quest_delay**: The minimum delay (in seconds) after a bot stops performing objectives for a repeatable quest before it's allowed to repeat the quest. This is **360** s by default.
|
||||
* **questing.bot_questing_requirements.max_time_per_quest**: The maximum amount of time (in seconds) that bots are allowed to perform objectives for the same quest. This is to encourage questing diversity for bots and to deter them from remaining in the same area for a long time. This is **300** s by default.
|
||||
* **questing.bot_questing_requirements.min_hydration**: The minimum hydration level permitted for bots or they will not be allowed to quest. This is **20** by default.
|
||||
* **questing.bot_questing_requirements.min_energy**: The minimum energy level permitted for bots or they will not be allowed to quest. This is **20** by default.
|
||||
* **questing.bot_questing_requirements.min_health_head**: The minimum permitted health percentage of a bot's head or it will not be allowed to quest. This is **50%** by default.
|
||||
* **questing.bot_questing_requirements.min_health_chest**: The minimum permitted health percentage of a bot's chest or it will not be allowed to quest. This is **50%** by default.
|
||||
* **questing.bot_questing_requirements.min_health_stomach**: The minimum permitted health percentage of a bot's stomach or it will not be allowed to quest. This is **50%** by default.
|
||||
* **questing.bot_questing_requirements.min_health_legs**: The minimum permitted health percentage of either of a bot's legs or it will not be allowed to quest. This is **50%** by default.
|
||||
* **questing.bot_questing_requirements.max_overweight_percentage**: The maximum total weight permitted for bots (as a percentage of their overweight threshold) or they will not be allowed to quest. This is **100%** by default.
|
||||
* **questing.bot_questing_requirements.search_time_after_combat.min/max:** Bots will not be allowed to quest until a random amount of time (in seconds) in this range has passed after combat most recently ended for them.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.enabled**: If bots are allowed to stop questing due to suspicious noises. This is **true** by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.min_corrected_sound_power**: If the "loudness" of a sound is less than this value, bots will ignore it. Currently, this results in all bots (even those wearing a headset) ignoring you if you crouch-walk at the slowest speed. This is **17** by default, and the units are unknown.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.max_distance_footsteps**: If bots hear footsteps within this distance (in meters), they will become suspicious and stop questing. This is **20** m by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.max_distance_gunfire**: If bots hear gunfire within this distance (in meters), they will become suspicious and stop questing. This is **50** m by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.max_distance_gunfire_suppressed**: If bots hear suppressed gunfire within this distance (in meters), they will become suspicious and stop questing. This is **50** m by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.loudness_multiplier_footsteps**: A scaling factor to adjust the bot's hearing sensitivity to footsteps. This is **1** by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.loudness_multiplier_headset**: A scaling factor to apply to the "loudness" of sounds if a bot is wearing any type of headset. This is **1.3** by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.loudness_multiplier_helmet_low_deaf**: A scaling factor to apply to the "loudness" of sounds if a bot is wearing a helmet that has a "low deafness" rating for reducing the volume of sounds it perceives. This is **0.8** by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.loudness_multiplier_helmet_high_deaf**: A scaling factor to apply to the "loudness" of sounds if a bot is wearing a helmet that has a "high deafness" rating for reducing the volume of sounds it perceives. This is **0.6** by default.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.suspicious_time.min/max**: If a bot becomes suspicious of a noise, they will stop questing until a random amount of time (in seconds) in this range has passed after the last suspicious noise they heard.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.max_suspicious_time**: The maximum time (in seconds) that a bot can remain suspicious of noises it hears before it will be forced to ignore them for at least **questing.bot_questing_requirements.hearing_sensor.suspicion_cooldown_time** seconds. This is to prevent bots for remaining in one spot for a long time, especially for PVP-focused maps like Factory. The value of this is map-specific.
|
||||
* **questing.bot_questing_requirements.hearing_sensor.suspicion_cooldown_time**: If a bot is suspicious for at least **questing.bot_questing_requirements.hearing_sensor.max_suspicious_time** seconds, it will be forced to ignore suspicious noises for this amount of time (in seconds) before it will be allowed to be suspicious of noises again. This is **7** s by default.
|
||||
* **questing.bot_questing_requirements.break_for_looting.enabled**: If **true** (the default setting), bots will temporarily stop questing at certain intervals to check for loot (or whatever).
|
||||
* **questing.bot_questing_requirements.break_for_looting.min_time_between_looting_checks**: The minimum delay (in seconds) after a bot takes a break to check for loot before it will be allowed to take a break again. If this value is very low, bots may frequently back-track and may never reach their objectives. If this value is high, bots will rarely loot. This is **50** s by default.
|
||||
* **questing.bot_questing_requirements.break_for_looting.min_time_between_follower_looting_checks**: The minimum delay (in seconds) after any of a bot's followers take a break to check for loot before they will be allowed to take a break again. If this value is very low, bot groups may frequently back-track and may never reach their objectives. If this value is high, followers will rarely loot. This is **30** s by default.
|
||||
* **questing.bot_questing_requirements.break_for_looting.min_time_between_looting_events**: The minimum delay (in seconds) after a bot successfully finds loot before it will be allowed to take a break again. If this value is very low, bots may frequently back-track and may never reach their objectives. If this value is high, bots will rarely loot. This supersedes **bot_questing_requirements.break_for_looting.min_time_between_looting_checks**, and it requires [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/) (or it will be ignored). This is **80** s by default.
|
||||
* **questing.bot_questing_requirements.break_for_looting.max_time_to_start_looting**: The duration of each break (in seconds). If one of the [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/) brain layers is not active after this time, the bot will resume questing. This is **2** s by default.
|
||||
* **questing.bot_questing_requirements.break_for_looting.max_loot_scan_time**: The maximum time that bots will be allowed to search for loot via [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/). If the bot hasn't found any loot within this time, it will continue questing. If it has found loot, it will not continue questing until it's completely finished with looting. This is **4** s by default.
|
||||
* **questing.bot_questing_requirements.break_for_looting.max_distance_from_boss**: The maximum distance (in meters) that a follower will be allowed to travel from its boss while looting. If the follower exceeds this distance, it will be forced to stop looting and regroup. This is **75** m by default.
|
||||
* **questing.bot_questing_requirements.max_follower_distance.max_wait_time**: The maximum time (in seconds) that a bot's followers are allowed to be too far from it before it will stop questing and regroup. This is **5** s by default.
|
||||
* **questing.bot_questing_requirements.max_follower_distance.min_regroup_time**: The minimum time (in seconds) that a bot will be forced to regroup with its followers if it's too far from them. After this time, the bot will be allowed to patrol its area instead. This is **1** s by default.
|
||||
* **questing.bot_questing_requirements.max_follower_distance.regroup_pause_time**: When a boss reaches its nearest follower while regrouping, it will stop regrouping for this amount of time (in seconds). After that delay, it will continue regrouping if required, or it will continue questing. This delay is to prevent bosses from standing completely still while waiting for the rest of their followers to regroup. This is **2** s by default.
|
||||
* **questing.bot_questing_requirements.max_follower_distance.target_range.min/max**: The allowed range of distances (in meters) that followers will try to be from their boss while questing. If a follower needs to get closer to its boss, it will try to get within the **min** distance (**10** m by default) of it. After that, it will be allowed to wander up to the **max** distance (**20** m by default) from it.
|
||||
* **questing.bot_questing_requirements.max_follower_distance.nearest**: If the bot has any followers, it will not be allowed to quest if its nearest follower is more than this distance (in meters) from it. This is **25** m by default.
|
||||
* **questing.bot_questing_requirements.max_follower_distance.furthest**: If the bot has any followers, it will not be allowed to quest if its furthest follower is more than this distance (in meters) from it. This is **40** m by default.
|
||||
* **questing.extraction_requirements.min_alive_time**: The minimum time (in seconds) a bot must wait after spawning before it will be allowed to extract. This is **60** s by default.
|
||||
* **questing.extraction_requirements.must_extract_time_remaining**: The time remaining in the raid (in seconds) after which bots will be unable to select new quest objectives and must extract instead. Requires [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/) 2.1.7 or later. By default, this is **300** s.
|
||||
* **questing.extraction_requirements.total_quests.min/max**: The minimum and maximum quests that a bot must complete before being instructed to extract. The actual number is randomly selected between this range. Requires [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/) 2.1.7 or later. Bots can still be instructed to extract if they satisfy their **questing.extraction_requirements.EFT_quests.min/max** requirement.
|
||||
* **questing.extraction_requirements.EFT_quests.min/max**: The minimum and maximum EFT quests that a bot must complete before being instructed to extract. The actual number is randomly selected between this range. Requires [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/) 2.1.7 or later. Bots can still be instructed to extract if they satisfy their **questing.extraction_requirements.total_quests.min/max** requirement.
|
||||
* **questing.sprinting_limitations.stamina.min**: The lower stamina threshold (as a fraction of max stamina) below which bots will not be allowed to sprint. This is **0.1** by default.
|
||||
* **questing.sprinting_limitations.stamina.max**: The upper stamina threshold (as a fraction of max stamina) above which bots will always be allowed to sprint. If a bot's stamina drops below **questing.sprinting_limitations.stamina.min**, it won't be allowed to sprint again until its stamina raises back to this level or higher. This is **0.5** by default.
|
||||
* **questing.sprinting_limitations.sharp_path_corners.distance**: If a bot is within this distance (in meters) of a sharp corner in its path, it will not be allowed to sprint. This was mainly implemented to prevent bots from shuffle-running around stairwells. This is **2** m by default.
|
||||
* **questing.sprinting_limitations.sharp_path_corners.angle**: The angle (in degrees) between segments in a bot's path above which the corner is considered a sharp corner. This was mainly implemented to prevent bots from shuffle-running around stairwells. This is **45** deg by default.
|
||||
* **questing.sprinting_limitations.approaching_closed_doors.distance**: If a bot is within this distance (in meters) of a closed door and is heading toward it, it will not be allowed to sprint. This was implemented to prevent bots from sliding into closed doors before opening them. this is **3** m by default.
|
||||
* **questing.sprinting_limitations.approaching_closed_doors.angle**: If a bot is within **questing.sprinting_limitations.approaching_closed_doors.distance** meters of a closed door, it will not be allowed to sprint if the angle between the bot's heading and the vector from the bot to the door is less than this value (in degrees). This was implemented to prevent bots from sliding into closed doors before opening them. This is **60** deg by default.
|
||||
* **questing.bot_quests.distance_randomness**: One of the sources of "randomness" to apply when selecting a new quest for a bot. This is defined as a percentage of the total range of distances between the bot and every quest objective available to it. By default, this is **30%**.
|
||||
* **questing.bot_quests.desirability_randomness**: The maximum amount that desirability ratings of quests can be randomly changed when bots select new quests. By default, this is **20%**.
|
||||
* **questing.bot_quests.distance_weighting**: A factor to change how much the distances between bots and possible quest objectives for them are weighted when selecting new quests. Higher numbers mean that bots will tend to select quests that are closer, but not necessarily more desirable. This is **1** by default.
|
||||
* **questing.bot_quests.desirability_weighting**: A factor to change how much the desirability of quests are weighted when selecting new quests for bots. Higher numbers mean that bots will tend to select quests that are more desirable even if they're further away. This is **1** by default.
|
||||
* **questing.bot_quests.desirability_camping_multiplier**: The desirability of all camping quests (determined by **isCamping=true** in their settings) will be multiplied by this factor. This is **1** by default.
|
||||
* **questing.bot_quests.desirability_sniping_multiplier**: The desirability of all sniping quests (determined by **isSniping=true** in their settings) will be multiplied by this factor. This is **1** by default.
|
||||
* **questing.bot_quests.exfil_direction_weighting.xxx**: A factor to change how likely bots are to select new quests that are in the direction of their selected exfil point. Higher numbers mean that bots will tend to select quests that are on the way to their selected exfil even if they're undesirable. This factor is different for every map.
|
||||
* **questing.bot_quests.exfil_direction_max_angle**: If the angle between the vector from a bot to its selected exfil and the vector from the bot to a quest objective is below this value (in degrees), the angle will be ignored (treated as 0 deg) for that objective when selecting new quests for bots. This is to allow bots to meander toward their selected exfil instead of having them tend to follow a straight path toward it. This is **90** deg by default.
|
||||
* **questing.bot_quests.exfil_reached_min_fraction**: This value is multiplied by the maximum distance between all exfils on the map to determine the distance threshold below which bots will change their selected exfils. If a bot travels within that threshold of its selected exfil, it will choose a new exfil. This is to allow bots to travel around the map instead of gravitating toward their initially selected exfils even after they reach them. This is **0.2** by default.
|
||||
* **questing.bot_quests.blacklisted_boss_hunter_bosses**: An array containing the names of bosses that bots doing the "Boss Hunter" quest will not be allowed to hunt.
|
||||
* **questing.bot_quests.airdrop_bot_interest_time**: The time (in seconds) after an airdop lands during which bots can go to it via an "Airdrop Chaser" quest. This is **420** s by default.
|
||||
* **questing.bot_quests.eft_quests.xxx**: The settings to apply to all quests based on EFT's quests.
|
||||
* **questing.bot_quests.spawn_rush.xxx**: The settings to apply to the "Spawn Rush" quest.
|
||||
* **questing.bot_quests.spawn_point_wander.xxx**: The settings to apply to the "Spawn Point Wandering" quest.
|
||||
* **questing.bot_quests.boss_hunter.xxx**: The settings to apply to the "Boss Hunter" quest.
|
||||
* **questing.bot_quests.airdrop_chaser.xxx**: The settings to apply to the "Airdrop Chaser" quest.
|
||||
|
||||
**Options for Each Section in *bot_quests*:**
|
||||
* **desirability**: The desirability rating (in percent) of the quest. Bots will be more likely to select quests with higher desirability ratings.
|
||||
* **max_bots_per_quest**: The maximum number of bots that can actively be performing each quest of that type.
|
||||
* **min_distance**: Each objective in the quest will only be selected if the bot is at least this many meters away from it.
|
||||
* **max_distance**: Each objective in the quest will only be selected if the bot is at most this many meters away from it.
|
||||
* **max_raid_ET**: The quest can only be selected if this many seconds (or less) have elapsed in the raid. This is based on the overall raid time, not the time after you spawn. For example, if you set **maxRaidET=60** for a quest and you spawn into a Factory raid with 15 minutes remaining, this quest will never be used because 300 seconds has already elapsed in the overall raid.
|
||||
* **chance_of_having_keys**: The chance that bots will have keys for the locations specified in the quests.
|
||||
* **match_looting_behavior_distance**: If there are any EFT quest objectives within this distance of non-EFT quest objectives, the EFT quest-objective looting behavior ("Force" or "Inhibit") will be changed to match the nearby non-EFT quest-objective looting behavior
|
||||
* **min_level**: The absolute minimum player level allowed for bots to select the quest.
|
||||
* **max_level**: The absolute maximum player level allowed for bots to select the quest.
|
||||
* **level_range**: An array of [minimum player level for the quest, level range] pairs to determine the maximum player level for each quest of that type. This value is added to the minimum player level for the quest. For example, if a quest is only available at level 15, the level range for it will be 20 (as determined via interpolation of this array using its default values). As a result, only bots between levels 15 and 35 will be allowed select that quest.
|
||||
|
||||
**PMC and Player-Scav Spawning Options:**
|
||||
* **bot_spawns.enabled**: Allow this mod to spawn PMC's and player Scavs (**true** by default).
|
||||
* **bot_spawns.blacklisted_pmc_bot_brains**: An array of the bot "brain" types that SPT will not be able to use when generating initial PMC's. These "brain" types have behaviors that inhibit their ability to quest, and this causes them to get stuck in areas for a long time (including their spawn locations). **Do not change this unless you know what you're doing!**
|
||||
* **bot_spawns.spawn_retry_time**: If any bots fail to spawn, no other attempts will be made to spawn more of them for this amount of time (in seconds). By default, this is **10** s.
|
||||
* **bot_spawns.delay_game_start_until_bot_gen_finishes**: After the final loading screen shows "0:00.000" for a few seconds, the game will be further delayed from starting if not all bots have been generated. Without doing this, PMC's may not spawn immediately when the raid starts, and the remaining bots will take much longer to generate. This is **true** by default.
|
||||
* **bot_spawns.spawn_initial_bosses_first**: If initial bosses must spawn before PMC's are allowed to spawn. This does not apply to Factory (Day or Night). If this is **false** and **bot_spawns.advanced_eft_bot_count_management=false**, initial PMC spawns may prevent some bosses (i.e. Rogues on Lighthouse) from spawning at the beginning of the raid. This is **false** by default and assumes **bot_spawns.advanced_eft_bot_count_management=true**.
|
||||
* **bot_spawns.advanced_eft_bot_count_management**: If **true**, this enables code that tricks EFT into thinking that bots generated by this mod are human players. This makes EFT ignore bot caps (both total and zone-specific) for PMC's and player Scavs generated by this mod. This is **true** by default.
|
||||
* **bot_spawns.bot_cap_adjustments.enabled**: If bot caps should be modified (**false** by default assuming **bot_spawns.advanced_eft_bot_count_management=true**).
|
||||
* **bot_spawns.bot_cap_adjustments.min_other_bots_allowed_to_spawn**: PMC's and player Scavs will not be allowed to spawn unless there are fewer than this value below the bot count for the map. For example, if this value is 4 and the maximum bot cap is 20, PMC's and player Scavs will not be allowed to spawn if there are 17 or more alive bots in the map. This is to retain a "buffer" below the maximum bot cap so that Scavs are able to continue spawning throughout the raid. This is **2** by default.
|
||||
* **bot_spawns.bot_cap_adjustments.add_max_players_to_bot_cap**: If this is **true** (which is the default setting), the bot cap for the map will be increased by the maximum number of players for the map. This is to better emulate live Tarkov where there can still be many Scavs around the map even with a full lobby.
|
||||
* **bot_spawns.bot_cap_adjustments.max_additional_bots**: The bot cap for the map will not be allowed to be increased by more than this value. If this value is too high, performance may be impacted. This is **5** by default.
|
||||
* **bot_spawns.bot_cap_adjustments.max_total_bots**: The highest allowed bot cap for any map. If this value is too high, performance may be impacted. This is **26** by default.
|
||||
* **bot_spawns.limit_initial_boss_spawns.enabled**: If initial boss spawns should be limited (**true** by default).
|
||||
* **bot_spawns.limit_initial_boss_spawns.disable_rogue_delay**: If the 180-second delay SPT adds to Rogue spawns on Lighthouse should be removed. This is **true** by default.
|
||||
* **bot_spawns.limit_initial_boss_spawns.max_initial_bosses**: The maximum number of bosses that are allowed to spawn at the beginning of the raid (including Raiders and Rogues). After this number is reached, all remaining initial boss spawns will be canceled. If this number is too high, few Scavs will be able to spawn after the initial PMC spawns. This is **14** by default.
|
||||
* **bot_spawns.limit_initial_boss_spawns.max_initial_rogues**: The maximum number of Rogues that are allowed to spawn at the beginning of the raid. After this number is reached, all remaining initial Rogue spawns will be canceled. If this number is too high, few Scavs will be able to spawn after the initial PMC spawns. This is **10** by default.
|
||||
* **bot_spawns.max_alive_bots**: The maximum number of PMC's and player Scavs (combined) that can be alive at the same time on each map. This only applies to PMC's and player Scavs generated by this mod; it doesn't apply to bots spawned by other mods or for Scavs converted to PMC's or player Scavs automatically by SPT.
|
||||
* **bot_spawns.pmcs.xxx**: The settings to apply to PMC spawns (see below for details).
|
||||
* **bot_spawns.player_scavs.xxx**: The settings to apply to player Scav spawns (see below for details).
|
||||
* **adjust_pscav_chance.enabled**: If the chances that Scavs are converted to player Scavs should be adjusted throughout the raid. This is only used if **bot_spawns.enabled=false** or **bot_spawns.player_scavs.enabled=false**, and it is **true** by default.
|
||||
* **adjust_pscav_chance.chance_vs_time_remaining_fraction**: An array describing how likely Scavs are to be converted to player Scavs as a function of the fraction of time remaining in the raid. This is based on the overall raid time, not the time after you spawn. The array contains [fraction of raid time remaining, conversion chance] pairs, and there is no limit to the number of pairs.
|
||||
|
||||
**Options for *bot_spawns.pmcs* and *bot_spawns.player_scavs*:**
|
||||
* **enabled**: If the corresponding bot type will be allowed to spawn. This is **true** by default for both bot types.
|
||||
* **min_distance_from_players_initial**: The minimum distance (in meters) that a bot must be from you and other bots when selecting its spawn point. This is used during the first wave of spawns and is **25** m by default.
|
||||
* **min_distance_from_players_during_raid**: The minimum distance (in meters) that a bot must be from you and other bots when selecting its spawn point. This is used after the first wave of spawns.
|
||||
* **min_distance_from_players_during_raid_factory**: The minimum distance (in meters) that a bot must be from you and other bots when selecting its spawn point. This is used after the first wave of spawns. However, this is only used for Factory raids instead of **min_distance_from_players_during_raid**.
|
||||
* **fraction_of_max_players**: When determining how many total bots of this type will spawn throughout the raid, the maximum player count for the map is multiplied by this value. This is **1** by default for PMC's and **1.5** by default for player Scavs.
|
||||
* **fraction_of_max_players_vs_raidET**: If you spawn late into the raid as a Scav, the minimum and maximum initial PMC's will be reduced by a factor determined by this array. The array contains [fraction of raid time remaining, fraction of max players] pairs, and there is no limit to the number of pairs.
|
||||
* **time_randomness**: The maximum percentage of total raid time (before reducing it for Scav raids) that player-Scav spawns can be randomly adjusted when generating a spawn schedule for them. However, player Scavs will never be allowed to spawn earlier than the minimum reduced raid time in the SPT configuration for the map, and they will never be allowed to spawn later than the maximum reduced raid time for the map. This is **10%** by default.
|
||||
* **bots_per_group_distribution**: An array describing how likely bot groups of various sizes are allowed to spawn. When generating bot groups, this mod will select a random number for each group between 0 and 1. It will then use interpolation to determine how many bots to add to the group using this array. The first column is the look-up value for the random number selected for the group, and the second column is the number of bots to add to the group. The interpolated value for number of bots is rounded to the nearest integer.
|
||||
|
||||
**---------- Known Issues ----------**
|
||||
|
||||
**Objective System:**
|
||||
* Mods that add a lot of new quests may cause latency issues that may result in game stability problems and stuttering
|
||||
* Bots tend to get trapped in certain areas. Known areas:
|
||||
* Customs between Warehouse 4 and New Gas
|
||||
* Customs in some Dorms rooms (i.e. 214 and 220 in 3 story)
|
||||
* Lighthouse in the mountains near the Resort spawn
|
||||
* Lighthouse on the rocks near the helicopter crash
|
||||
* Bots blindly run to their objective (unless they're in combat) even if it's certain death (i.e. running into the Sawmill when Shturman is there).
|
||||
* Bots take the most direct path to their objectives, which may involve running in the middle of an open area without any cover.
|
||||
* Certain bot "brains" stay in a combat state for a long time, during which they're unable to continue their quests.
|
||||
* Certain bot "brains" are blacklisted because they cause the bot to always be in a combat state and therefore never quest (i.e. exUSEC's when they're near a stationary weapon)
|
||||
* Some quest items or locations can't be resolved:
|
||||
* Fortress for Capturing Outposts in Customs
|
||||
* Scav Base for Capturing Outposts in Woods
|
||||
* Health Resort for Capturing Outposts in Shoreline
|
||||
* Bronze pocket watch for Checking in Customs
|
||||
* Flash drive with fake info for Bullshit in Customs
|
||||
* Mountain Area for Return the Favor in Woods
|
||||
* The second and third bunkers for Assessment Part 2 in Woods
|
||||
* The satellite antenna in the USEC camp for Return the Favor in Woods
|
||||
* The cottage area for Overpopulation in Lighthouse
|
||||
* The main area for Assessment - Part 1 in Lighthouse
|
||||
* The bridge for Knock-Knock in Lighthouse
|
||||
* All locations for Long Line in Interchange
|
||||
* The 21WS Container for Provocation in Interchange
|
||||
* The underground depot for Safe Corridor in Reserve
|
||||
* One of the locations for Bunker Part 2 in Reserve (not sure which)
|
||||
* Bots sometimes unlock doors for no reason if they can't properly resolve their quest locations. Examples include marking the tanker at New Gas in Customs; bots will fail to find a position to mark the tanker and then nearby unlock rooms in New Gas for no reason.
|
||||
* A *"Destroying GameObjects immediately is not permitted during physics trigger/contact, animation event callbacks or OnValidate. You must use Destroy instead."* error will sometimes appear in the game console after a bot unlocks a door. This can be ignored.
|
||||
* Player-level ranges for some quests are not reasonable, so bots may do late-game quests at low player levels and vice versa. This is because EFT has no minimum level defined for several quest lines.
|
||||
|
||||
**PMC and Player-Scav Spawning System:**
|
||||
* If there is a lot of PMC action at the beginning of the raid, the rest of the raid will feel dead. However, this isn't so different from live Tarkov.
|
||||
* If **advanced_eft_bot_count_management=false**, not all PMC's or player Scavs spawn into Streets because too many Scavs spawn into the map first
|
||||
* In maps with a high number of max players, Scavs don't always spawn when the game generates them if your **max_alive_bots** setting is high and **advanced_eft_bot_count_management=false**
|
||||
* In maps with a high number of max players, performance can be pretty bad if your **max_alive_bots** setting is high
|
||||
* Noticeable stuttering for (possibly) several seconds when the initial PMC wave spawns if your **max_alive_bots** setting is high
|
||||
* Performance may be worse if **advanced_eft_bot_count_management=true** because EFT will be allowed to spawn more Scavs than with previous versions of this mod.
|
||||
* When using "As Online" raid difficulty, all PMC's spawn with "Normal" difficulty at the beginning of the raid.
|
||||
|
||||
**---------- Roadmap (Expect Similar Accuracy to EFT's) ----------**
|
||||
|
||||
* **0.5.1** (ETA: Late May)
|
||||
* Set PMC and PScav difficulties independently of the difficulty set for the raid in EFT
|
||||
* Add new quest type: hidden-stash running
|
||||
* Add new quest type: blood-thirsty cheater (will be disabled by default)
|
||||
* Move initial quest-data generation to the server to protect for mods that add lots of quests (like QuestManiac)
|
||||
* **0.5.2** (ETA: Early June)
|
||||
* Add optional quest prerequisite to have at least one item in a list (i.e. a sniper rifle for sniping areas or an encoded DSP for Lighthouse)
|
||||
* Add configuration options to overwrite default settings for EFT-based quests and their objectives
|
||||
* **0.6.0** (ETA: Late July)
|
||||
* Separate spawning system into a separate mod
|
||||
* **Not Planned**
|
||||
* Add waypoints to have PMC's path around dangerous spots in the map or in very open areas
|
||||
* Improve PMC senses to dissuade them from going to areas where many bots have died. Might require interaction with SAIN; TBD.
|
||||
|
||||
**---------- Credits ----------**
|
||||
|
||||
* Thanks to [Props](https://hub.sp-tarkov.com/user/18746-props/) for sharing the code [DONUTS](https://hub.sp-tarkov.com/files/file/878-swag-donuts-dynamic-spawn-waves-and-custom-spawn-points/) uses to spawn bots. This was the inspiration to create this mod.
|
||||
* Thanks to [DrakiaXYZ](https://hub.sp-tarkov.com/user/30839-drakiaxyz/) for creating [BigBrain](https://hub.sp-tarkov.com/files/file/1219-bigbrain/) and [Waypoints](https://hub.sp-tarkov.com/files/file/1119-waypoints-expanded-bot-patrols-and-navmesh/) and for all of your help with developing this mod. Also, thanks for your help with adding interop capability to [SAIN](https://hub.sp-tarkov.com/files/file/1062-sain-2-0-solarint-s-ai-modifications-full-ai-combat-system-replacement/).
|
||||
* Thanks to [nooky](https://hub.sp-tarkov.com/user/29062-nooky/) for lots of help with testing and ensuring this mod remains compatible with [SWAG + DONUTS](https://hub.sp-tarkov.com/files/file/878-swag-donuts-dynamic-spawn-waves-and-custom-spawn-points/).
|
||||
* Thanks to [Skwizzy](https://hub.sp-tarkov.com/user/31303-skwizzy/) for help with adding interop capability to [Looting Bots](https://hub.sp-tarkov.com/files/file/1096-looting-bots/).
|
||||
* Thanks to everyone else on Discord who helped to test the many alpha releases of this mod and provided feedback to make it better. There are too many people to name, but you're all awesome.
|
||||
* Of course, thanks to the SPT development team who made this possible in the first place.
|
||||
@@ -0,0 +1,312 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"debug": {
|
||||
"enabled": true,
|
||||
"scav_cooldown_time": 1500,
|
||||
"full_length_scav_raids": false,
|
||||
"free_labs_access": false,
|
||||
"always_spawn_pmcs": false,
|
||||
"always_spawn_pscavs": false,
|
||||
"always_have_airdrops": false,
|
||||
"show_zone_outlines": false,
|
||||
"show_failed_paths": false,
|
||||
"show_door_interaction_test_points": false
|
||||
},
|
||||
"max_calc_time_per_frame_ms": 5,
|
||||
"chance_of_being_hostile_toward_bosses": {
|
||||
"scav": 0,
|
||||
"pscav": 20,
|
||||
"pmc": 80,
|
||||
"boss": 0
|
||||
},
|
||||
"questing": {
|
||||
"enabled": true,
|
||||
"bot_pathing_update_interval_ms": 100,
|
||||
"brain_layer_priority": 26,
|
||||
"quest_selection_timeout": 250,
|
||||
"allowed_bot_types_for_questing": {
|
||||
"scav": false,
|
||||
"pscav": true,
|
||||
"pmc": true,
|
||||
"boss": false
|
||||
},
|
||||
"stuck_bot_detection": {
|
||||
"distance": 2,
|
||||
"time": 20,
|
||||
"max_count": 8,
|
||||
"follower_break_time": 10,
|
||||
"max_not_able_bodied_time": 120
|
||||
},
|
||||
"unlocking_doors" : {
|
||||
"enabled": {
|
||||
"scav": false,
|
||||
"pscav": false,
|
||||
"pmc": true,
|
||||
"boss": false
|
||||
},
|
||||
"search_radius": 25,
|
||||
"max_distance_to_unlock": 0.5,
|
||||
"door_approach_position_search_radius": 0.75,
|
||||
"door_approach_position_search_offset": -0.75,
|
||||
"pause_time_after_unlocking": 5,
|
||||
"debounce_time": 1,
|
||||
"default_chance_of_bots_having_keys": 25
|
||||
},
|
||||
"min_time_between_switching_objectives": 5,
|
||||
"default_wait_time_after_objective_completion": 5,
|
||||
"wait_time_before_planting": 1,
|
||||
"quest_generation": {
|
||||
"navmesh_search_distance_item": 1.5,
|
||||
"navmesh_search_distance_zone": 1.5,
|
||||
"navmesh_search_distance_spawn": 2,
|
||||
"navmesh_search_distance_doors": 0.75
|
||||
},
|
||||
"bot_search_distances": {
|
||||
"objective_reached_ideal": 0.5,
|
||||
"objective_reached_navmesh_path_error": 20,
|
||||
"max_navmesh_path_error": 10
|
||||
},
|
||||
"bot_pathing": {
|
||||
"max_start_position_discrepancy": 0.5,
|
||||
"incomplete_path_retry_interval": 5
|
||||
},
|
||||
"bot_questing_requirements": {
|
||||
"exclude_bots_by_level": true,
|
||||
"repeat_quest_delay": 360,
|
||||
"max_time_per_quest": 300,
|
||||
"min_hydration": 20,
|
||||
"min_energy": 20,
|
||||
"min_health_head": 50,
|
||||
"min_health_chest": 50,
|
||||
"min_health_stomach": 50,
|
||||
"min_health_legs": 50,
|
||||
"max_overweight_percentage": 100,
|
||||
"search_time_after_combat": {
|
||||
"min": 10,
|
||||
"max": 30
|
||||
},
|
||||
"hearing_sensor": {
|
||||
"enabled": true,
|
||||
"min_corrected_sound_power": 17,
|
||||
"max_distance_footsteps": 20,
|
||||
"max_distance_gunfire": 50,
|
||||
"max_distance_gunfire_suppressed": 50,
|
||||
"loudness_multiplier_footsteps": 1,
|
||||
"loudness_multiplier_headset": 1.3,
|
||||
"loudness_multiplier_helmet_low_deaf": 0.8,
|
||||
"loudness_multiplier_helmet_high_deaf": 0.6,
|
||||
"suspicious_time": {
|
||||
"min": 5,
|
||||
"max": 20
|
||||
},
|
||||
"max_suspicious_time": {
|
||||
"default": 60,
|
||||
"factory4_day": 30,
|
||||
"factory4_night": 45,
|
||||
"bigmap": 120,
|
||||
"woods": 120,
|
||||
"shoreline": 120,
|
||||
"lighthouse": 120,
|
||||
"rezervbase": 120,
|
||||
"interchange": 120,
|
||||
"laboratory": 60,
|
||||
"tarkovstreets": 120,
|
||||
"sandbox": 120
|
||||
},
|
||||
"suspicion_cooldown_time": 7
|
||||
},
|
||||
"break_for_looting": {
|
||||
"enabled": true,
|
||||
"min_time_between_looting_checks": 50,
|
||||
"min_time_between_follower_looting_checks": 30,
|
||||
"min_time_between_looting_events": 80,
|
||||
"max_time_to_start_looting": 2,
|
||||
"max_loot_scan_time": 4,
|
||||
"max_distance_from_boss": 50
|
||||
},
|
||||
"max_follower_distance": {
|
||||
"max_wait_time": 5,
|
||||
"min_regroup_time": 1,
|
||||
"regroup_pause_time": 2,
|
||||
"target_range": {
|
||||
"min": 7,
|
||||
"max": 12
|
||||
},
|
||||
"nearest": 15,
|
||||
"furthest": 25
|
||||
}
|
||||
},
|
||||
"extraction_requirements": {
|
||||
"min_alive_time": 60,
|
||||
"must_extract_time_remaining": 300,
|
||||
"total_quests": {
|
||||
"min": 6,
|
||||
"max": 12
|
||||
},
|
||||
"EFT_quests": {
|
||||
"min": 2,
|
||||
"max": 5
|
||||
}
|
||||
},
|
||||
"sprinting_limitations": {
|
||||
"stamina": {
|
||||
"min": 0.1,
|
||||
"max": 0.5
|
||||
},
|
||||
"sharp_path_corners" : {
|
||||
"distance": 2,
|
||||
"angle": 45
|
||||
},
|
||||
"approaching_closed_doors" : {
|
||||
"distance": 3,
|
||||
"angle": 60
|
||||
}
|
||||
},
|
||||
"bot_quests": {
|
||||
"distance_randomness": 30,
|
||||
"desirability_randomness": 20,
|
||||
"distance_weighting": 1,
|
||||
"desirability_weighting": 1,
|
||||
"desirability_camping_multiplier": 1,
|
||||
"desirability_sniping_multiplier": 1,
|
||||
"exfil_direction_weighting": {
|
||||
"default": 0,
|
||||
"factory4_day": 0.2,
|
||||
"factory4_night": 0.2,
|
||||
"bigmap": 0.7,
|
||||
"woods": 0.7,
|
||||
"shoreline": 0.7,
|
||||
"lighthouse": 0.5,
|
||||
"rezervbase": 0.4,
|
||||
"interchange": 0.7,
|
||||
"laboratory": 0.3,
|
||||
"tarkovstreets": 0.7,
|
||||
"sandbox": 0.5
|
||||
},
|
||||
"exfil_direction_max_angle": 90,
|
||||
"exfil_reached_min_fraction": 0.2,
|
||||
"blacklisted_boss_hunter_bosses": [ "gifter", "arenaFighterEvent", "shooterBTR" ],
|
||||
"airdrop_bot_interest_time": 420,
|
||||
"eft_quests": {
|
||||
"desirability": 60,
|
||||
"max_bots_per_quest": 3,
|
||||
"chance_of_having_keys": 50,
|
||||
"match_looting_behavior_distance": 5,
|
||||
"level_range": [
|
||||
[0, 99],
|
||||
[1, 8],
|
||||
[10, 15],
|
||||
[20, 25],
|
||||
[30, 30],
|
||||
[40, 40]
|
||||
]
|
||||
},
|
||||
"spawn_rush": {
|
||||
"desirability": 100,
|
||||
"max_bots_per_quest": 1,
|
||||
"max_distance": 75,
|
||||
"max_raid_ET": 30
|
||||
},
|
||||
"spawn_point_wander": {
|
||||
"desirability": 0,
|
||||
"min_distance": 75,
|
||||
"max_bots_per_quest": 30
|
||||
},
|
||||
"boss_hunter": {
|
||||
"desirability": 40,
|
||||
"min_level": 15,
|
||||
"max_raid_ET": 300,
|
||||
"min_distance": 50,
|
||||
"max_bots_per_quest": 2
|
||||
},
|
||||
"airdrop_chaser": {
|
||||
"desirability": 70,
|
||||
"max_bots_per_quest": 3,
|
||||
"max_distance": 400
|
||||
}
|
||||
}
|
||||
},
|
||||
"bot_spawns": {
|
||||
"enabled": true,
|
||||
"blacklisted_pmc_bot_brains": [ "bossKilla", "bossTagilla", "exUsec", "followerGluharAssault", "followerGluharProtect", "crazyAssaultEvent", "bossKnight" ],
|
||||
"spawn_retry_time": 10,
|
||||
"delay_game_start_until_bot_gen_finishes": true,
|
||||
"spawn_initial_bosses_first": false,
|
||||
"advanced_eft_bot_count_management": true,
|
||||
"bot_cap_adjustments" : {
|
||||
"enabled": false,
|
||||
"min_other_bots_allowed_to_spawn": 2,
|
||||
"add_max_players_to_bot_cap": true,
|
||||
"max_additional_bots": 5,
|
||||
"max_total_bots": 26
|
||||
},
|
||||
"limit_initial_boss_spawns" : {
|
||||
"enabled": true,
|
||||
"disable_rogue_delay": true,
|
||||
"max_initial_bosses": 14,
|
||||
"max_initial_rogues": 10
|
||||
},
|
||||
"max_alive_bots": {
|
||||
"default": 6,
|
||||
"factory4_day": 5,
|
||||
"factory4_night": 5,
|
||||
"bigmap": 7,
|
||||
"woods": 8,
|
||||
"shoreline": 7,
|
||||
"lighthouse": 7,
|
||||
"rezervbase": 7,
|
||||
"interchange": 8,
|
||||
"laboratory": 9,
|
||||
"tarkovstreets": 8,
|
||||
"sandbox": 7
|
||||
},
|
||||
"pmcs" : {
|
||||
"enabled": true,
|
||||
"min_distance_from_players_initial": 25,
|
||||
"min_distance_from_players_during_raid": 75,
|
||||
"min_distance_from_players_during_raid_factory": 50,
|
||||
"fraction_of_max_players_vs_raidET": [
|
||||
[0, 0.2],
|
||||
[0.2, 0.2],
|
||||
[0.6, 0.5],
|
||||
[0.8, 0.7],
|
||||
[0.9, 0.9],
|
||||
[0.95, 1],
|
||||
[1, 1]
|
||||
],
|
||||
"bots_per_group_distribution" : [
|
||||
[0.40, 1],
|
||||
[0.80, 2],
|
||||
[0.92, 3],
|
||||
[0.97, 4],
|
||||
[1.00, 5]
|
||||
]
|
||||
},
|
||||
"player_scavs": {
|
||||
"enabled": true,
|
||||
"min_distance_from_players_initial": 25,
|
||||
"min_distance_from_players_during_raid": 75,
|
||||
"min_distance_from_players_during_raid_factory": 35,
|
||||
"fraction_of_max_players": 1.5,
|
||||
"time_randomness": 10,
|
||||
"bots_per_group_distribution" : [
|
||||
[0.80, 1],
|
||||
[0.90, 2],
|
||||
[0.95, 3],
|
||||
[0.98, 4],
|
||||
[1.00, 5]
|
||||
]
|
||||
}
|
||||
},
|
||||
"adjust_pscav_chance" : {
|
||||
"enabled": true,
|
||||
"chance_vs_time_remaining_fraction" : [
|
||||
[0, 50],
|
||||
[0.3, 50],
|
||||
[0.5, 20],
|
||||
[0.8, 10],
|
||||
[0.9, 0],
|
||||
[1, 0]
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "SPTQuestingBots",
|
||||
"version": "0.5.0",
|
||||
"main": "src/mod.js",
|
||||
"license": "MIT",
|
||||
"author": "DanW",
|
||||
"akiVersion": ">=3.8.0 <=3.9.0",
|
||||
"scripts": {
|
||||
"setup": "npm i",
|
||||
"build": "node ./packageBuild.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.4.5",
|
||||
"@typescript-eslint/eslint-plugin": "6.2.0",
|
||||
"@typescript-eslint/parser": "6.2.0",
|
||||
"bestzip": "2.2.1",
|
||||
"eslint": "8.46.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"glob": "10.3.3",
|
||||
"tsyringe": "4.8.0",
|
||||
"typescript": "5.1.6"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CommonUtils = void 0;
|
||||
const config_json_1 = __importDefault(require("../config/config.json"));
|
||||
class CommonUtils {
|
||||
logger;
|
||||
databaseTables;
|
||||
localeService;
|
||||
debugMessagePrefix = "[Questing Bots] ";
|
||||
translations;
|
||||
constructor(logger, databaseTables, localeService) {
|
||||
this.logger = logger;
|
||||
this.databaseTables = databaseTables;
|
||||
this.localeService = localeService;
|
||||
// Get all translations for the current locale
|
||||
this.translations = this.localeService.getLocaleDb();
|
||||
}
|
||||
logInfo(message, alwaysShow = false) {
|
||||
if (config_json_1.default.enabled || alwaysShow)
|
||||
this.logger.info(this.debugMessagePrefix + message);
|
||||
}
|
||||
logWarning(message) {
|
||||
this.logger.warning(this.debugMessagePrefix + message);
|
||||
}
|
||||
logError(message) {
|
||||
this.logger.error(this.debugMessagePrefix + message);
|
||||
}
|
||||
getItemName(itemID) {
|
||||
const translationKey = `${itemID} Name`;
|
||||
if (translationKey in this.translations)
|
||||
return this.translations[translationKey];
|
||||
// If a key can't be found in the translations dictionary, fall back to the template data if possible
|
||||
if (!(itemID in this.databaseTables.templates.items)) {
|
||||
return undefined;
|
||||
}
|
||||
const item = this.databaseTables.templates.items[itemID];
|
||||
return item._name;
|
||||
}
|
||||
}
|
||||
exports.CommonUtils = CommonUtils;
|
||||
//# sourceMappingURL=CommonUtils.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CommonUtils.js","sourceRoot":"","sources":["CommonUtils.ts"],"names":[],"mappings":";;;;;;AAAA,wEAA8C;AAM9C,MAAa,WAAW;IAKC;IAAyB;IAAyC;IAH/E,kBAAkB,GAAG,kBAAkB,CAAC;IACxC,YAAY,CAAyB;IAE7C,YAAqB,MAAe,EAAU,cAA+B,EAAU,aAA4B;QAA9F,WAAM,GAAN,MAAM,CAAS;QAAU,mBAAc,GAAd,cAAc,CAAiB;QAAU,kBAAa,GAAb,aAAa,CAAe;QAE/G,8CAA8C;QAC9C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IACzD,CAAC;IAEM,OAAO,CAAC,OAAe,EAAE,UAAU,GAAG,KAAK;QAE9C,IAAI,qBAAS,CAAC,OAAO,IAAI,UAAU;YAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,CAAC;IAC5D,CAAC;IAEM,UAAU,CAAC,OAAe;QAE7B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,CAAC;IAC3D,CAAC;IAEM,QAAQ,CAAC,OAAe;QAE3B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,CAAC;IACzD,CAAC;IAEM,WAAW,CAAC,MAAc;QAE7B,MAAM,cAAc,GAAG,GAAG,MAAM,OAAO,CAAC;QACxC,IAAI,cAAc,IAAI,IAAI,CAAC,YAAY;YACnC,OAAO,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;QAE7C,qGAAqG;QACrG,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,EACpD,CAAC;YACG,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;CACJ;AA1CD,kCA0CC"}
|
||||
@@ -0,0 +1,49 @@
|
||||
import modConfig from "../config/config.json";
|
||||
|
||||
import type { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import type { IDatabaseTables } from "@spt-aki/models/spt/server/IDatabaseTables";
|
||||
import type { LocaleService } from "@spt-aki/services/LocaleService";
|
||||
|
||||
export class CommonUtils
|
||||
{
|
||||
private debugMessagePrefix = "[Questing Bots] ";
|
||||
private translations: Record<string, string>;
|
||||
|
||||
constructor (private logger: ILogger, private databaseTables: IDatabaseTables, private localeService: LocaleService)
|
||||
{
|
||||
// Get all translations for the current locale
|
||||
this.translations = this.localeService.getLocaleDb();
|
||||
}
|
||||
|
||||
public logInfo(message: string, alwaysShow = false): void
|
||||
{
|
||||
if (modConfig.enabled || alwaysShow)
|
||||
this.logger.info(this.debugMessagePrefix + message);
|
||||
}
|
||||
|
||||
public logWarning(message: string): void
|
||||
{
|
||||
this.logger.warning(this.debugMessagePrefix + message);
|
||||
}
|
||||
|
||||
public logError(message: string): void
|
||||
{
|
||||
this.logger.error(this.debugMessagePrefix + message);
|
||||
}
|
||||
|
||||
public getItemName(itemID: string): string
|
||||
{
|
||||
const translationKey = `${itemID} Name`;
|
||||
if (translationKey in this.translations)
|
||||
return this.translations[translationKey];
|
||||
|
||||
// If a key can't be found in the translations dictionary, fall back to the template data if possible
|
||||
if (!(itemID in this.databaseTables.templates.items))
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const item = this.databaseTables.templates.items[itemID];
|
||||
return item._name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.QuestManager = void 0;
|
||||
class QuestManager {
|
||||
commonUtils;
|
||||
vfs;
|
||||
constructor(commonUtils, vfs) {
|
||||
this.commonUtils = commonUtils;
|
||||
this.vfs = vfs;
|
||||
}
|
||||
validateCustomQuests() {
|
||||
const path = `${__dirname}/../quests`;
|
||||
let totalStandardQuestsAdded = 0;
|
||||
let totalCustomQuestsAdded = 0;
|
||||
// Ensure the directory for standard quests exists
|
||||
if (this.vfs.exists(`${path}/standard/`)) {
|
||||
const standardQuests = this.vfs.getFiles(`${path}/standard/`);
|
||||
for (const i in standardQuests) {
|
||||
const questFileText = this.vfs.readFile(`${path}/standard/${standardQuests[i]}`);
|
||||
// If the JSON file can be parsed into a Quest array, assume it's fine
|
||||
const quests = JSON.parse(questFileText);
|
||||
totalStandardQuestsAdded += quests.length;
|
||||
this.commonUtils.logInfo(`Found ${quests.length} standard quest(s) in "/standard/${standardQuests[i]}"`);
|
||||
}
|
||||
if (totalStandardQuestsAdded === 0) {
|
||||
this.commonUtils.logError("No standard quests have been added. Mod files may have been corrupted. Please try reinstalling.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.commonUtils.logError("The \"user\\mods\\DanW-SPTQuestingBots\\quests\\standard\" directory is missing. Mod files may have been corrupted. Please try reinstalling.");
|
||||
}
|
||||
// Check if the directory for custom quests exists
|
||||
if (this.vfs.exists(`${path}/custom/`)) {
|
||||
const customQuests = this.vfs.getFiles(`${path}/custom/`);
|
||||
for (const i in customQuests) {
|
||||
const questFileText = this.vfs.readFile(`${path}/custom/${customQuests[i]}`);
|
||||
// If the JSON file can be parsed into a Quest array, assume it's fine
|
||||
const quests = JSON.parse(questFileText);
|
||||
totalCustomQuestsAdded += quests.length;
|
||||
this.commonUtils.logInfo(`Found ${quests.length} custom quest(s) in "/custom/${customQuests[i]}"`);
|
||||
}
|
||||
if (totalCustomQuestsAdded === 0) {
|
||||
this.commonUtils.logWarning("No custom quests found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.QuestManager = QuestManager;
|
||||
//# sourceMappingURL=QuestManager.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"QuestManager.js","sourceRoot":"","sources":["QuestManager.ts"],"names":[],"mappings":";;;AAsCA,MAAa,YAAY;IAEA;IAAkC;IAAvD,YAAqB,WAAwB,EAAU,GAAQ;QAA1C,gBAAW,GAAX,WAAW,CAAa;QAAU,QAAG,GAAH,GAAG,CAAK;IAG/D,CAAC;IAEM,oBAAoB;QAEvB,MAAM,IAAI,GAAG,GAAG,SAAS,YAAY,CAAC;QACtC,IAAI,wBAAwB,GAAG,CAAC,CAAC;QACjC,IAAI,sBAAsB,GAAG,CAAC,CAAC;QAE/B,kDAAkD;QAClD,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,EACxC,CAAC;YACG,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC;YAC9D,KAAK,MAAM,CAAC,IAAI,cAAc,EAC9B,CAAC;gBACG,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAI,aAAa,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAEjF,sEAAsE;gBACtE,MAAM,MAAM,GAAa,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;gBACnD,wBAAwB,IAAI,MAAM,CAAC,MAAM,CAAC;gBAE1C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,MAAM,CAAC,MAAM,oCAAoC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7G,CAAC;YAED,IAAI,wBAAwB,KAAK,CAAC,EAClC,CAAC;gBACG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,iGAAiG,CAAC,CAAC;YACjI,CAAC;QACL,CAAC;aAED,CAAC;YACG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,8IAA8I,CAAC,CAAC;QAC9K,CAAC;QAED,kDAAkD;QAClD,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,EACtC,CAAC;YACG,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAI,UAAU,CAAC,CAAC;YAC1D,KAAK,MAAM,CAAC,IAAI,YAAY,EAC5B,CAAC;gBACG,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAI,WAAW,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAE7E,sEAAsE;gBACtE,MAAM,MAAM,GAAa,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;gBACnD,sBAAsB,IAAI,MAAM,CAAC,MAAM,CAAC;gBAExC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,MAAM,CAAC,MAAM,gCAAgC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACvG,CAAC;YAED,IAAI,sBAAsB,KAAK,CAAC,EAChC,CAAC;gBACG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,yBAAyB,CAAC,CAAC;YAC3D,CAAC;QACL,CAAC;IACL,CAAC;CACJ;AA3DD,oCA2DC"}
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { CommonUtils } from "./CommonUtils";
|
||||
import type { VFS } from "@spt-aki/utils/VFS";
|
||||
|
||||
export interface Quest
|
||||
{
|
||||
repeatable: boolean,
|
||||
minLevel: number,
|
||||
maxLevel: number,
|
||||
chanceForSelecting: number,
|
||||
priority: number,
|
||||
maxRaidET: number,
|
||||
canRunBetweenObjectives: boolean,
|
||||
name: string,
|
||||
objectives: QuestObjective[]
|
||||
}
|
||||
|
||||
export interface QuestObjective
|
||||
{
|
||||
repeatable: boolean,
|
||||
maxBots: number,
|
||||
minDistanceFromBot: number,
|
||||
maxDistanceFromBot: number,
|
||||
maxRunDistance: number,
|
||||
steps: QuestObjectiveStep[]
|
||||
}
|
||||
|
||||
export interface QuestObjectiveStep
|
||||
{
|
||||
position : Vector3
|
||||
}
|
||||
|
||||
export interface Vector3
|
||||
{
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
}
|
||||
|
||||
export class QuestManager
|
||||
{
|
||||
constructor (private commonUtils: CommonUtils, private vfs: VFS)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public validateCustomQuests() : void
|
||||
{
|
||||
const path = `${__dirname}/../quests`;
|
||||
let totalStandardQuestsAdded = 0;
|
||||
let totalCustomQuestsAdded = 0;
|
||||
|
||||
// Ensure the directory for standard quests exists
|
||||
if (this.vfs.exists(`${path}/standard/`))
|
||||
{
|
||||
const standardQuests = this.vfs.getFiles(`${path}/standard/`);
|
||||
for (const i in standardQuests)
|
||||
{
|
||||
const questFileText = this.vfs.readFile(`${path}/standard/${standardQuests[i]}`);
|
||||
|
||||
// If the JSON file can be parsed into a Quest array, assume it's fine
|
||||
const quests : Quest[] = JSON.parse(questFileText);
|
||||
totalStandardQuestsAdded += quests.length;
|
||||
|
||||
this.commonUtils.logInfo(`Found ${quests.length} standard quest(s) in "/standard/${standardQuests[i]}"`);
|
||||
}
|
||||
|
||||
if (totalStandardQuestsAdded === 0)
|
||||
{
|
||||
this.commonUtils.logError("No standard quests have been added. Mod files may have been corrupted. Please try reinstalling.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.commonUtils.logError("The \"user\\mods\\DanW-SPTQuestingBots\\quests\\standard\" directory is missing. Mod files may have been corrupted. Please try reinstalling.");
|
||||
}
|
||||
|
||||
// Check if the directory for custom quests exists
|
||||
if (this.vfs.exists(`${path}/custom/`))
|
||||
{
|
||||
const customQuests = this.vfs.getFiles(`${path}/custom/`);
|
||||
for (const i in customQuests)
|
||||
{
|
||||
const questFileText = this.vfs.readFile(`${path}/custom/${customQuests[i]}`);
|
||||
|
||||
// If the JSON file can be parsed into a Quest array, assume it's fine
|
||||
const quests : Quest[] = JSON.parse(questFileText);
|
||||
totalCustomQuestsAdded += quests.length;
|
||||
|
||||
this.commonUtils.logInfo(`Found ${quests.length} custom quest(s) in "/custom/${customQuests[i]}"`);
|
||||
}
|
||||
|
||||
if (totalCustomQuestsAdded === 0)
|
||||
{
|
||||
this.commonUtils.logWarning("No custom quests found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const config_json_1 = __importDefault(require("../config/config.json"));
|
||||
const CommonUtils_1 = require("./CommonUtils");
|
||||
const QuestManager_1 = require("./QuestManager");
|
||||
const ConfigTypes_1 = require("/snapshot/project/obj/models/enums/ConfigTypes");
|
||||
const modName = "SPTQuestingBots";
|
||||
class QuestingBots {
|
||||
commonUtils;
|
||||
questManager;
|
||||
logger;
|
||||
configServer;
|
||||
databaseServer;
|
||||
databaseTables;
|
||||
localeService;
|
||||
questHelper;
|
||||
profileHelper;
|
||||
vfs;
|
||||
httpResponseUtil;
|
||||
randomUtil;
|
||||
botController;
|
||||
botGenerationCacheService;
|
||||
iBotConfig;
|
||||
iPmcConfig;
|
||||
iLocationConfig;
|
||||
iAirdropConfig;
|
||||
convertIntoPmcChanceOrig = {};
|
||||
basePScavConversionChance;
|
||||
preAkiLoad(container) {
|
||||
this.logger = container.resolve("WinstonLogger");
|
||||
const staticRouterModService = container.resolve("StaticRouterModService");
|
||||
const dynamicRouterModService = container.resolve("DynamicRouterModService");
|
||||
// Get config.json settings for the bepinex plugin
|
||||
staticRouterModService.registerStaticRouter(`StaticGetConfig${modName}`, [{
|
||||
url: "/QuestingBots/GetConfig",
|
||||
action: () => {
|
||||
return JSON.stringify(config_json_1.default);
|
||||
}
|
||||
}], "GetConfig");
|
||||
// Report error messages to the SPT-AKI server console in case the user hasn't enabled the bepinex console
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicReportError${modName}`, [{
|
||||
url: "/QuestingBots/ReportError/",
|
||||
action: (url) => {
|
||||
const urlParts = url.split("/");
|
||||
const errorMessage = urlParts[urlParts.length - 1];
|
||||
const regex = /%20/g;
|
||||
this.commonUtils.logError(errorMessage.replace(regex, " "));
|
||||
return JSON.stringify({ resp: "OK" });
|
||||
}
|
||||
}], "ReportError");
|
||||
// Get the logging directory for saving quest information after raids
|
||||
staticRouterModService.registerStaticRouter(`StaticGetLoggingPath${modName}`, [{
|
||||
url: "/QuestingBots/GetLoggingPath",
|
||||
action: () => {
|
||||
const loggingPath = `${__dirname}\\..\\log\\`;
|
||||
this.commonUtils.logInfo(`Logging path: ${loggingPath}`);
|
||||
return JSON.stringify({ path: loggingPath });
|
||||
}
|
||||
}], "GetLoggingPath");
|
||||
if (!config_json_1.default.enabled) {
|
||||
return;
|
||||
}
|
||||
// Game start
|
||||
// Needed to update Scav timer
|
||||
staticRouterModService.registerStaticRouter(`StaticAkiGameStart${modName}`, [{
|
||||
url: "/client/game/start",
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
action: (url, info, sessionId, output) => {
|
||||
if (config_json_1.default.debug.enabled) {
|
||||
this.updateScavTimer(sessionId);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}], "aki");
|
||||
// Apply a scalar factor to the SPT-AKI PMC conversion chances
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicAdjustPMCConversionChances${modName}`, [{
|
||||
url: "/QuestingBots/AdjustPMCConversionChances/",
|
||||
action: (url) => {
|
||||
const urlParts = url.split("/");
|
||||
const factor = Number(urlParts[urlParts.length - 2]);
|
||||
const verify = JSON.parse(urlParts[urlParts.length - 1].toLowerCase());
|
||||
this.adjustPmcConversionChance(factor, verify);
|
||||
return JSON.stringify({ resp: "OK" });
|
||||
}
|
||||
}], "AdjustPMCConversionChances");
|
||||
// Apply a scalar factor to the SPT-AKI PScav conversion chance
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicAdjustPScavChance${modName}`, [{
|
||||
url: "/QuestingBots/AdjustPScavChance/",
|
||||
action: (url) => {
|
||||
const urlParts = url.split("/");
|
||||
const factor = Number(urlParts[urlParts.length - 1]);
|
||||
this.iBotConfig.chanceAssaultScavHasPlayerScavName = Math.round(this.basePScavConversionChance * factor);
|
||||
this.commonUtils.logInfo(`Adjusted PScav spawn chance to ${this.iBotConfig.chanceAssaultScavHasPlayerScavName}%`);
|
||||
return JSON.stringify({ resp: "OK" });
|
||||
}
|
||||
}], "AdjustPScavChance");
|
||||
// Get all EFT quest templates
|
||||
// NOTE: This includes custom quests added by mods
|
||||
staticRouterModService.registerStaticRouter(`GetAllQuestTemplates${modName}`, [{
|
||||
url: "/QuestingBots/GetAllQuestTemplates",
|
||||
action: () => {
|
||||
return JSON.stringify({ templates: this.questHelper.getQuestsFromDb() });
|
||||
}
|
||||
}], "GetAllQuestTemplates");
|
||||
// Override bot generation to include PScav conversion chance
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicGenerateBot${modName}`, [{
|
||||
url: "/QuestingBots/GenerateBot",
|
||||
action: (url, info, sessionID) => {
|
||||
const urlParts = url.split("/");
|
||||
const pScavChance = Number(urlParts[urlParts.length - 1]);
|
||||
const bots = this.generateBots(info, sessionID, this.randomUtil.getChance100(pScavChance));
|
||||
return this.httpResponseUtil.getBody(bots);
|
||||
}
|
||||
}], "GenerateBot");
|
||||
// Get Scav-raid settings to determine PScav conversion chances
|
||||
staticRouterModService.registerStaticRouter(`GetScavRaidSettings${modName}`, [{
|
||||
url: "/QuestingBots/GetScavRaidSettings",
|
||||
action: () => {
|
||||
return JSON.stringify({ maps: this.iLocationConfig.scavRaidTimeSettings.maps });
|
||||
}
|
||||
}], "GetScavRaidSettings");
|
||||
}
|
||||
postDBLoad(container) {
|
||||
this.configServer = container.resolve("ConfigServer");
|
||||
this.databaseServer = container.resolve("DatabaseServer");
|
||||
this.localeService = container.resolve("LocaleService");
|
||||
this.questHelper = container.resolve("QuestHelper");
|
||||
this.profileHelper = container.resolve("ProfileHelper");
|
||||
this.vfs = container.resolve("VFS");
|
||||
this.httpResponseUtil = container.resolve("HttpResponseUtil");
|
||||
this.randomUtil = container.resolve("RandomUtil");
|
||||
this.botController = container.resolve("BotController");
|
||||
this.botGenerationCacheService = container.resolve("BotGenerationCacheService");
|
||||
this.iBotConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.BOT);
|
||||
this.iPmcConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.PMC);
|
||||
this.iLocationConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.LOCATION);
|
||||
this.iAirdropConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.AIRDROP);
|
||||
this.databaseTables = this.databaseServer.getTables();
|
||||
this.basePScavConversionChance = this.iBotConfig.chanceAssaultScavHasPlayerScavName;
|
||||
this.commonUtils = new CommonUtils_1.CommonUtils(this.logger, this.databaseTables, this.localeService);
|
||||
this.questManager = new QuestManager_1.QuestManager(this.commonUtils, this.vfs);
|
||||
if (!config_json_1.default.enabled) {
|
||||
return;
|
||||
}
|
||||
// Ensure all of the custom quests are valid JSON files
|
||||
this.questManager.validateCustomQuests();
|
||||
if (config_json_1.default.debug.always_have_airdrops) {
|
||||
this.commonUtils.logInfo("Forcing airdrops to occur at the beginning of every raid...");
|
||||
this.iAirdropConfig.airdropChancePercent.bigmap = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.woods = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.lighthouse = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.shoreline = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.interchange = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.reserve = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.tarkovStreets = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.sandbox = 100;
|
||||
this.iAirdropConfig.airdropMinStartTimeSeconds = 5;
|
||||
this.iAirdropConfig.airdropMaxStartTimeSeconds = 10;
|
||||
}
|
||||
// Adjust parameters to make debugging easier
|
||||
if (config_json_1.default.debug.enabled) {
|
||||
this.commonUtils.logInfo("Applying debug options...");
|
||||
if (config_json_1.default.debug.scav_cooldown_time < this.databaseTables.globals.config.SavagePlayCooldown) {
|
||||
this.databaseTables.globals.config.SavagePlayCooldown = config_json_1.default.debug.scav_cooldown_time;
|
||||
}
|
||||
if (config_json_1.default.debug.free_labs_access) {
|
||||
this.databaseTables.locations.laboratory.base.AccessKeys = [];
|
||||
this.databaseTables.locations.laboratory.base.DisabledForScav = false;
|
||||
}
|
||||
if (config_json_1.default.debug.full_length_scav_raids) {
|
||||
this.forceFullLengthScavRaids();
|
||||
}
|
||||
}
|
||||
}
|
||||
postAkiLoad(container) {
|
||||
if (!config_json_1.default.enabled) {
|
||||
this.commonUtils.logInfo("Mod disabled in config.json", true);
|
||||
return;
|
||||
}
|
||||
this.removeBlacklistedBrainTypes();
|
||||
// If we find SWAG or MOAR, disable initial spawns
|
||||
const preAkiModLoader = container.resolve("PreAkiModLoader");
|
||||
if (config_json_1.default.bot_spawns.enabled && preAkiModLoader.getImportedModsNames().includes("SWAG")) {
|
||||
this.commonUtils.logWarning("SWAG Detected. Disabling bot spawning.");
|
||||
config_json_1.default.bot_spawns.enabled = false;
|
||||
}
|
||||
if (config_json_1.default.bot_spawns.enabled && preAkiModLoader.getImportedModsNames().includes("DewardianDev-MOAR")) {
|
||||
this.commonUtils.logWarning("MOAR Detected. Disabling bot spawning.");
|
||||
config_json_1.default.bot_spawns.enabled = false;
|
||||
}
|
||||
if (preAkiModLoader.getImportedModsNames().includes("Andrudis-QuestManiac")) {
|
||||
this.commonUtils.logWarning("QuestManiac Detected. This mod is known to cause performance issues when used with QuestingBots. No support will be provided.");
|
||||
}
|
||||
// Make Questing Bots control PScav spawning
|
||||
if (config_json_1.default.adjust_pscav_chance.enabled || (config_json_1.default.bot_spawns.enabled && config_json_1.default.bot_spawns.player_scavs.enabled)) {
|
||||
this.iBotConfig.chanceAssaultScavHasPlayerScavName = 0;
|
||||
}
|
||||
if (!config_json_1.default.bot_spawns.enabled) {
|
||||
return;
|
||||
}
|
||||
this.commonUtils.logInfo("Configuring game for bot spawning...");
|
||||
// Store the current PMC-conversion chances in case they need to be restored later
|
||||
this.setOriginalPMCConversionChances();
|
||||
// Currently these are all PMC waves, which are unnecessary with PMC spawns in this mod
|
||||
this.disableCustomBossWaves();
|
||||
// Disable all of the extra Scavs that spawn into Factory
|
||||
this.disableCustomScavWaves();
|
||||
// If Rogues don't spawn immediately, PMC spawns will be significantly delayed
|
||||
if (config_json_1.default.bot_spawns.limit_initial_boss_spawns.disable_rogue_delay) {
|
||||
this.commonUtils.logInfo("Removing SPT Rogue spawn delay...");
|
||||
this.iLocationConfig.rogueLighthouseSpawnTimeSettings.waitTimeSeconds = -1;
|
||||
}
|
||||
if (config_json_1.default.bot_spawns.advanced_eft_bot_count_management) {
|
||||
this.commonUtils.logWarning("Enabling advanced_eft_bot_count_management will instruct EFT to ignore this mod's PMC's and PScavs when spawning more bots.");
|
||||
}
|
||||
if (config_json_1.default.bot_spawns.bot_cap_adjustments.enabled) {
|
||||
this.increaseBotCaps();
|
||||
}
|
||||
this.commonUtils.logInfo("Configuring game for bot spawning...done.");
|
||||
}
|
||||
updateScavTimer(sessionId) {
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionId);
|
||||
const scavData = this.profileHelper.getScavProfile(sessionId);
|
||||
if ((scavData.Info === null) || (scavData.Info === undefined)) {
|
||||
this.commonUtils.logInfo("Scav profile hasn't been created yet.");
|
||||
return;
|
||||
}
|
||||
// In case somebody disables scav runs and later wants to enable them, we need to reset their Scav timer unless it's plausible
|
||||
const worstCooldownFactor = this.getWorstSavageCooldownModifier();
|
||||
if (scavData.Info.SavageLockTime - pmcData.Info.LastTimePlayedAsSavage > this.databaseTables.globals.config.SavagePlayCooldown * worstCooldownFactor * 1.1) {
|
||||
this.commonUtils.logInfo(`Resetting scav timer for sessionId=${sessionId}...`);
|
||||
scavData.Info.SavageLockTime = 0;
|
||||
}
|
||||
}
|
||||
// Return the highest Scav cooldown factor from Fence's rep levels
|
||||
getWorstSavageCooldownModifier() {
|
||||
// Initialize the return value at something very low
|
||||
let worstCooldownFactor = 0.01;
|
||||
for (const level in this.databaseTables.globals.config.FenceSettings.Levels) {
|
||||
if (this.databaseTables.globals.config.FenceSettings.Levels[level].SavageCooldownModifier > worstCooldownFactor)
|
||||
worstCooldownFactor = this.databaseTables.globals.config.FenceSettings.Levels[level].SavageCooldownModifier;
|
||||
}
|
||||
return worstCooldownFactor;
|
||||
}
|
||||
setOriginalPMCConversionChances() {
|
||||
// Store the default PMC-conversion chances for each bot type defined in SPT's configuration file
|
||||
let logMessage = "";
|
||||
for (const pmcType in this.iPmcConfig.convertIntoPmcChance) {
|
||||
if (this.convertIntoPmcChanceOrig[pmcType] !== undefined) {
|
||||
logMessage += `${pmcType}: already buffered, `;
|
||||
continue;
|
||||
}
|
||||
const chances = {
|
||||
min: this.iPmcConfig.convertIntoPmcChance[pmcType].min,
|
||||
max: this.iPmcConfig.convertIntoPmcChance[pmcType].max
|
||||
};
|
||||
this.convertIntoPmcChanceOrig[pmcType] = chances;
|
||||
logMessage += `${pmcType}: ${chances.min}-${chances.max}%, `;
|
||||
}
|
||||
this.commonUtils.logInfo(`Reading default PMC spawn chances: ${logMessage}`);
|
||||
}
|
||||
adjustPmcConversionChance(scalingFactor, verify) {
|
||||
// Adjust the chances for each applicable bot type
|
||||
let logMessage = "";
|
||||
let verified = true;
|
||||
for (const pmcType in this.iPmcConfig.convertIntoPmcChance) {
|
||||
// Do not allow the chances to exceed 100%. Who knows what might happen...
|
||||
const min = Math.round(Math.min(100, this.convertIntoPmcChanceOrig[pmcType].min * scalingFactor));
|
||||
const max = Math.round(Math.min(100, this.convertIntoPmcChanceOrig[pmcType].max * scalingFactor));
|
||||
if (verify) {
|
||||
if (this.iPmcConfig.convertIntoPmcChance[pmcType].min !== min) {
|
||||
verified = false;
|
||||
break;
|
||||
}
|
||||
if (this.iPmcConfig.convertIntoPmcChance[pmcType].max !== max) {
|
||||
verified = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.iPmcConfig.convertIntoPmcChance[pmcType].min = min;
|
||||
this.iPmcConfig.convertIntoPmcChance[pmcType].max = max;
|
||||
logMessage += `${pmcType}: ${min}-${max}%, `;
|
||||
}
|
||||
}
|
||||
if (!verify) {
|
||||
this.commonUtils.logInfo(`Adjusting PMC spawn chances (${scalingFactor}): ${logMessage}`);
|
||||
}
|
||||
if (!verified) {
|
||||
this.commonUtils.logError("Another mod has changed the PMC conversion chances. This mod may not work properly!");
|
||||
}
|
||||
}
|
||||
disableCustomBossWaves() {
|
||||
this.commonUtils.logInfo("Disabling custom boss waves...");
|
||||
this.iLocationConfig.customWaves.boss = {};
|
||||
}
|
||||
disableCustomScavWaves() {
|
||||
this.commonUtils.logInfo("Disabling custom Scav waves...");
|
||||
this.iLocationConfig.customWaves.normal = {};
|
||||
}
|
||||
increaseBotCaps() {
|
||||
if (!config_json_1.default.bot_spawns.bot_cap_adjustments.add_max_players_to_bot_cap) {
|
||||
return;
|
||||
}
|
||||
const maxAddtlBots = config_json_1.default.bot_spawns.bot_cap_adjustments.max_additional_bots;
|
||||
const maxTotalBots = config_json_1.default.bot_spawns.bot_cap_adjustments.max_total_bots;
|
||||
this.iBotConfig.maxBotCap.factory4_day = Math.min(this.iBotConfig.maxBotCap.factory4_day + Math.min(this.databaseTables.locations.factory4_day.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.factory4_night = Math.min(this.iBotConfig.maxBotCap.factory4_night + Math.min(this.databaseTables.locations.factory4_night.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.bigmap = Math.min(this.iBotConfig.maxBotCap.bigmap + Math.min(this.databaseTables.locations.bigmap.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.woods = Math.min(this.iBotConfig.maxBotCap.woods + Math.min(this.databaseTables.locations.woods.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.shoreline = Math.min(this.iBotConfig.maxBotCap.shoreline + Math.min(this.databaseTables.locations.shoreline.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.lighthouse = Math.min(this.iBotConfig.maxBotCap.lighthouse + Math.min(this.databaseTables.locations.lighthouse.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.rezervbase = Math.min(this.iBotConfig.maxBotCap.rezervbase + Math.min(this.databaseTables.locations.rezervbase.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.interchange = Math.min(this.iBotConfig.maxBotCap.interchange + Math.min(this.databaseTables.locations.interchange.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.laboratory = Math.min(this.iBotConfig.maxBotCap.laboratory + Math.min(this.databaseTables.locations.laboratory.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.tarkovstreets = Math.min(this.iBotConfig.maxBotCap.tarkovstreets + Math.min(this.databaseTables.locations.tarkovstreets.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.sandbox = Math.min(this.iBotConfig.maxBotCap.sandbox + Math.min(this.databaseTables.locations.sandbox.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.default = Math.min(this.iBotConfig.maxBotCap.default + maxAddtlBots, maxTotalBots);
|
||||
for (const location in this.iBotConfig.maxBotCap) {
|
||||
this.commonUtils.logInfo(`Changed bot cap for ${location} to: ${this.iBotConfig.maxBotCap[location]}`);
|
||||
}
|
||||
}
|
||||
removeBlacklistedBrainTypes() {
|
||||
const badBrains = config_json_1.default.bot_spawns.blacklisted_pmc_bot_brains;
|
||||
this.commonUtils.logInfo("Removing blacklisted brain types from being used for PMC's...");
|
||||
let removedBrains = 0;
|
||||
for (const pmcType in this.iPmcConfig.pmcType) {
|
||||
for (const map in this.iPmcConfig.pmcType[pmcType]) {
|
||||
const mapBrains = this.iPmcConfig.pmcType[pmcType][map];
|
||||
for (const i in badBrains) {
|
||||
if (mapBrains[badBrains[i]] === undefined) {
|
||||
continue;
|
||||
}
|
||||
//this.commonUtils.logInfo(`Removing ${badBrains[i]} from ${pmcType} in ${map}...`);
|
||||
delete mapBrains[badBrains[i]];
|
||||
removedBrains++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.commonUtils.logInfo(`Removing blacklisted brain types from being used for PMC's...done. Removed entries: ${removedBrains}`);
|
||||
}
|
||||
forceFullLengthScavRaids() {
|
||||
this.commonUtils.logInfo("Forcing full-length Scav raids...");
|
||||
for (const map in this.iLocationConfig.scavRaidTimeSettings.maps) {
|
||||
this.iLocationConfig.scavRaidTimeSettings.maps[map].reducedChancePercent = 0;
|
||||
}
|
||||
}
|
||||
generateBots(info, sessionID, shouldBePScavGroup) {
|
||||
const bots = this.botController.generate(sessionID, info);
|
||||
if (!shouldBePScavGroup) {
|
||||
return bots;
|
||||
}
|
||||
const pmcNames = [
|
||||
...this.databaseTables.bots.types.usec.firstName,
|
||||
...this.databaseTables.bots.types.bear.firstName
|
||||
];
|
||||
for (const bot in bots) {
|
||||
if (info.conditions[0].Role !== "assault") {
|
||||
continue;
|
||||
}
|
||||
bots[bot].Info.Nickname = `${bots[bot].Info.Nickname} (${this.randomUtil.getArrayValue(pmcNames)})`;
|
||||
}
|
||||
return bots;
|
||||
}
|
||||
}
|
||||
module.exports = { mod: new QuestingBots() };
|
||||
//# sourceMappingURL=mod.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,558 @@
|
||||
import modConfig from "../config/config.json";
|
||||
import { CommonUtils } from "./CommonUtils";
|
||||
import { QuestManager } from "./QuestManager";
|
||||
|
||||
import type { DependencyContainer } from "tsyringe";
|
||||
import type { IPreAkiLoadMod } from "@spt-aki/models/external/IPreAkiLoadMod";
|
||||
import type { IPostDBLoadMod } from "@spt-aki/models/external/IPostDBLoadMod";
|
||||
import type { IPostAkiLoadMod } from "@spt-aki/models/external/IPostAkiLoadMod";
|
||||
import type { StaticRouterModService } from "@spt-aki/services/mod/staticRouter/StaticRouterModService";
|
||||
import type { DynamicRouterModService } from "@spt-aki/services/mod/dynamicRouter/DynamicRouterModService";
|
||||
import type { PreAkiModLoader } from "@spt-aki/loaders/PreAkiModLoader";
|
||||
|
||||
import type { MinMax } from "@spt-aki/models/common/MinMax";
|
||||
import type { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import type { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import type { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import type { IDatabaseTables } from "@spt-aki/models/spt/server/IDatabaseTables";
|
||||
import type { LocaleService } from "@spt-aki/services/LocaleService";
|
||||
import type { QuestHelper } from "@spt-aki/helpers/QuestHelper";
|
||||
import type { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||
import type { VFS } from "@spt-aki/utils/VFS";
|
||||
import type { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
|
||||
import type { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
import type { BotController } from "@spt-aki/controllers/BotController";
|
||||
import type { BotGenerationCacheService } from "@spt-aki/services/BotGenerationCacheService";
|
||||
import type { IGenerateBotsRequestData } from "@spt-aki/models/eft/bot/IGenerateBotsRequestData";
|
||||
import type { IBotBase } from "@spt-aki/models/eft/common/tables/IBotBase";
|
||||
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import type { IBotConfig } from "@spt-aki/models/spt/config/IBotConfig";
|
||||
import type { IPmcConfig } from "@spt-aki/models/spt/config/IPmcConfig";
|
||||
import type { ILocationConfig } from "@spt-aki/models/spt/config/ILocationConfig";
|
||||
import type { IAirdropConfig } from "@spt-aki/models/spt/config/IAirdropConfig";
|
||||
|
||||
const modName = "SPTQuestingBots";
|
||||
|
||||
class QuestingBots implements IPreAkiLoadMod, IPostAkiLoadMod, IPostDBLoadMod
|
||||
{
|
||||
private commonUtils: CommonUtils
|
||||
private questManager: QuestManager
|
||||
|
||||
private logger: ILogger;
|
||||
private configServer: ConfigServer;
|
||||
private databaseServer: DatabaseServer;
|
||||
private databaseTables: IDatabaseTables;
|
||||
private localeService: LocaleService;
|
||||
private questHelper: QuestHelper;
|
||||
private profileHelper: ProfileHelper;
|
||||
private vfs: VFS;
|
||||
private httpResponseUtil: HttpResponseUtil;
|
||||
private randomUtil: RandomUtil;
|
||||
private botController: BotController;
|
||||
private botGenerationCacheService: BotGenerationCacheService;
|
||||
private iBotConfig: IBotConfig;
|
||||
private iPmcConfig: IPmcConfig;
|
||||
private iLocationConfig: ILocationConfig;
|
||||
private iAirdropConfig: IAirdropConfig;
|
||||
|
||||
private convertIntoPmcChanceOrig: Record<string, MinMax> = {};
|
||||
private basePScavConversionChance: number;
|
||||
|
||||
public preAkiLoad(container: DependencyContainer): void
|
||||
{
|
||||
this.logger = container.resolve<ILogger>("WinstonLogger");
|
||||
const staticRouterModService = container.resolve<StaticRouterModService>("StaticRouterModService");
|
||||
const dynamicRouterModService = container.resolve<DynamicRouterModService>("DynamicRouterModService");
|
||||
|
||||
// Get config.json settings for the bepinex plugin
|
||||
staticRouterModService.registerStaticRouter(`StaticGetConfig${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/GetConfig",
|
||||
action: () =>
|
||||
{
|
||||
return JSON.stringify(modConfig);
|
||||
}
|
||||
}], "GetConfig"
|
||||
);
|
||||
|
||||
// Report error messages to the SPT-AKI server console in case the user hasn't enabled the bepinex console
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicReportError${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/ReportError/",
|
||||
action: (url: string) =>
|
||||
{
|
||||
const urlParts = url.split("/");
|
||||
const errorMessage = urlParts[urlParts.length - 1];
|
||||
|
||||
const regex = /%20/g;
|
||||
this.commonUtils.logError(errorMessage.replace(regex, " "));
|
||||
|
||||
return JSON.stringify({ resp: "OK" });
|
||||
}
|
||||
}], "ReportError"
|
||||
);
|
||||
|
||||
// Get the logging directory for saving quest information after raids
|
||||
staticRouterModService.registerStaticRouter(`StaticGetLoggingPath${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/GetLoggingPath",
|
||||
action: () =>
|
||||
{
|
||||
const loggingPath = `${__dirname}\\..\\log\\`;
|
||||
this.commonUtils.logInfo(`Logging path: ${loggingPath}`);
|
||||
|
||||
return JSON.stringify({ path: loggingPath });
|
||||
}
|
||||
}], "GetLoggingPath"
|
||||
);
|
||||
|
||||
if (!modConfig.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Game start
|
||||
// Needed to update Scav timer
|
||||
staticRouterModService.registerStaticRouter(`StaticAkiGameStart${modName}`,
|
||||
[{
|
||||
url: "/client/game/start",
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
action: (url: string, info: any, sessionId: string, output: string) =>
|
||||
{
|
||||
if (modConfig.debug.enabled)
|
||||
{
|
||||
this.updateScavTimer(sessionId);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}], "aki"
|
||||
);
|
||||
|
||||
// Apply a scalar factor to the SPT-AKI PMC conversion chances
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicAdjustPMCConversionChances${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/AdjustPMCConversionChances/",
|
||||
action: (url: string) =>
|
||||
{
|
||||
const urlParts = url.split("/");
|
||||
const factor: number = Number(urlParts[urlParts.length - 2]);
|
||||
const verify: boolean = JSON.parse(urlParts[urlParts.length - 1].toLowerCase());
|
||||
|
||||
this.adjustPmcConversionChance(factor, verify);
|
||||
return JSON.stringify({ resp: "OK" });
|
||||
}
|
||||
}], "AdjustPMCConversionChances"
|
||||
);
|
||||
|
||||
// Apply a scalar factor to the SPT-AKI PScav conversion chance
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicAdjustPScavChance${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/AdjustPScavChance/",
|
||||
action: (url: string) =>
|
||||
{
|
||||
const urlParts = url.split("/");
|
||||
const factor: number = Number(urlParts[urlParts.length - 1]);
|
||||
|
||||
this.iBotConfig.chanceAssaultScavHasPlayerScavName = Math.round(this.basePScavConversionChance * factor);
|
||||
this.commonUtils.logInfo(`Adjusted PScav spawn chance to ${this.iBotConfig.chanceAssaultScavHasPlayerScavName}%`);
|
||||
|
||||
return JSON.stringify({ resp: "OK" });
|
||||
}
|
||||
}], "AdjustPScavChance"
|
||||
);
|
||||
|
||||
// Get all EFT quest templates
|
||||
// NOTE: This includes custom quests added by mods
|
||||
staticRouterModService.registerStaticRouter(`GetAllQuestTemplates${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/GetAllQuestTemplates",
|
||||
action: () =>
|
||||
{
|
||||
return JSON.stringify({ templates: this.questHelper.getQuestsFromDb() });
|
||||
}
|
||||
}], "GetAllQuestTemplates"
|
||||
);
|
||||
|
||||
// Override bot generation to include PScav conversion chance
|
||||
dynamicRouterModService.registerDynamicRouter(`DynamicGenerateBot${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/GenerateBot",
|
||||
action: (url: string, info: IGenerateBotsRequestData, sessionID: string) =>
|
||||
{
|
||||
const urlParts = url.split("/");
|
||||
const pScavChance: number = Number(urlParts[urlParts.length - 1]);
|
||||
|
||||
const bots = this.generateBots(info, sessionID, this.randomUtil.getChance100(pScavChance));
|
||||
|
||||
return this.httpResponseUtil.getBody(bots);
|
||||
}
|
||||
}], "GenerateBot"
|
||||
);
|
||||
|
||||
// Get Scav-raid settings to determine PScav conversion chances
|
||||
staticRouterModService.registerStaticRouter(`GetScavRaidSettings${modName}`,
|
||||
[{
|
||||
url: "/QuestingBots/GetScavRaidSettings",
|
||||
action: () =>
|
||||
{
|
||||
return JSON.stringify({ maps: this.iLocationConfig.scavRaidTimeSettings.maps });
|
||||
}
|
||||
}], "GetScavRaidSettings"
|
||||
);
|
||||
}
|
||||
|
||||
public postDBLoad(container: DependencyContainer): void
|
||||
{
|
||||
this.configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
this.databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
|
||||
this.localeService = container.resolve<LocaleService>("LocaleService");
|
||||
this.questHelper = container.resolve<QuestHelper>("QuestHelper");
|
||||
this.profileHelper = container.resolve<ProfileHelper>("ProfileHelper");
|
||||
this.vfs = container.resolve<VFS>("VFS");
|
||||
this.httpResponseUtil = container.resolve<HttpResponseUtil>("HttpResponseUtil");
|
||||
this.randomUtil = container.resolve<RandomUtil>("RandomUtil");
|
||||
this.botController = container.resolve<BotController>("BotController");
|
||||
this.botGenerationCacheService = container.resolve<BotGenerationCacheService>("BotGenerationCacheService");
|
||||
|
||||
this.iBotConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||
this.iPmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||
this.iLocationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||
this.iAirdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP);
|
||||
|
||||
this.databaseTables = this.databaseServer.getTables();
|
||||
this.basePScavConversionChance = this.iBotConfig.chanceAssaultScavHasPlayerScavName;
|
||||
this.commonUtils = new CommonUtils(this.logger, this.databaseTables, this.localeService);
|
||||
this.questManager = new QuestManager(this.commonUtils, this.vfs);
|
||||
|
||||
if (!modConfig.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure all of the custom quests are valid JSON files
|
||||
this.questManager.validateCustomQuests();
|
||||
|
||||
if (modConfig.debug.always_have_airdrops)
|
||||
{
|
||||
this.commonUtils.logInfo("Forcing airdrops to occur at the beginning of every raid...");
|
||||
|
||||
this.iAirdropConfig.airdropChancePercent.bigmap = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.woods = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.lighthouse = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.shoreline = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.interchange = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.reserve = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.tarkovStreets = 100;
|
||||
this.iAirdropConfig.airdropChancePercent.sandbox = 100;
|
||||
|
||||
this.iAirdropConfig.airdropMinStartTimeSeconds = 5;
|
||||
this.iAirdropConfig.airdropMaxStartTimeSeconds = 10;
|
||||
}
|
||||
|
||||
// Adjust parameters to make debugging easier
|
||||
if (modConfig.debug.enabled)
|
||||
{
|
||||
this.commonUtils.logInfo("Applying debug options...");
|
||||
|
||||
if (modConfig.debug.scav_cooldown_time < this.databaseTables.globals.config.SavagePlayCooldown)
|
||||
{
|
||||
this.databaseTables.globals.config.SavagePlayCooldown = modConfig.debug.scav_cooldown_time;
|
||||
}
|
||||
|
||||
if (modConfig.debug.free_labs_access)
|
||||
{
|
||||
this.databaseTables.locations.laboratory.base.AccessKeys = [];
|
||||
this.databaseTables.locations.laboratory.base.DisabledForScav = false;
|
||||
}
|
||||
|
||||
if (modConfig.debug.full_length_scav_raids)
|
||||
{
|
||||
this.forceFullLengthScavRaids();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public postAkiLoad(container: DependencyContainer): void
|
||||
{
|
||||
if (!modConfig.enabled)
|
||||
{
|
||||
this.commonUtils.logInfo("Mod disabled in config.json", true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeBlacklistedBrainTypes();
|
||||
|
||||
// If we find SWAG or MOAR, disable initial spawns
|
||||
const preAkiModLoader = container.resolve<PreAkiModLoader>("PreAkiModLoader");
|
||||
if (modConfig.bot_spawns.enabled && preAkiModLoader.getImportedModsNames().includes("SWAG"))
|
||||
{
|
||||
this.commonUtils.logWarning("SWAG Detected. Disabling bot spawning.");
|
||||
modConfig.bot_spawns.enabled = false;
|
||||
}
|
||||
if (modConfig.bot_spawns.enabled && preAkiModLoader.getImportedModsNames().includes("DewardianDev-MOAR"))
|
||||
{
|
||||
this.commonUtils.logWarning("MOAR Detected. Disabling bot spawning.");
|
||||
modConfig.bot_spawns.enabled = false;
|
||||
}
|
||||
|
||||
if (preAkiModLoader.getImportedModsNames().includes("Andrudis-QuestManiac"))
|
||||
{
|
||||
this.commonUtils.logWarning("QuestManiac Detected. This mod is known to cause performance issues when used with QuestingBots. No support will be provided.");
|
||||
}
|
||||
|
||||
// Make Questing Bots control PScav spawning
|
||||
if (modConfig.adjust_pscav_chance.enabled || (modConfig.bot_spawns.enabled && modConfig.bot_spawns.player_scavs.enabled))
|
||||
{
|
||||
this.iBotConfig.chanceAssaultScavHasPlayerScavName = 0;
|
||||
}
|
||||
|
||||
if (!modConfig.bot_spawns.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.commonUtils.logInfo("Configuring game for bot spawning...");
|
||||
|
||||
// Store the current PMC-conversion chances in case they need to be restored later
|
||||
this.setOriginalPMCConversionChances();
|
||||
|
||||
// Currently these are all PMC waves, which are unnecessary with PMC spawns in this mod
|
||||
this.disableCustomBossWaves();
|
||||
|
||||
// Disable all of the extra Scavs that spawn into Factory
|
||||
this.disableCustomScavWaves();
|
||||
|
||||
// If Rogues don't spawn immediately, PMC spawns will be significantly delayed
|
||||
if (modConfig.bot_spawns.limit_initial_boss_spawns.disable_rogue_delay)
|
||||
{
|
||||
this.commonUtils.logInfo("Removing SPT Rogue spawn delay...");
|
||||
this.iLocationConfig.rogueLighthouseSpawnTimeSettings.waitTimeSeconds = -1;
|
||||
}
|
||||
|
||||
if (modConfig.bot_spawns.advanced_eft_bot_count_management)
|
||||
{
|
||||
this.commonUtils.logWarning("Enabling advanced_eft_bot_count_management will instruct EFT to ignore this mod's PMC's and PScavs when spawning more bots.");
|
||||
}
|
||||
|
||||
if (modConfig.bot_spawns.bot_cap_adjustments.enabled)
|
||||
{
|
||||
this.increaseBotCaps();
|
||||
}
|
||||
|
||||
this.commonUtils.logInfo("Configuring game for bot spawning...done.");
|
||||
}
|
||||
|
||||
private updateScavTimer(sessionId: string): void
|
||||
{
|
||||
const pmcData = this.profileHelper.getPmcProfile(sessionId);
|
||||
const scavData = this.profileHelper.getScavProfile(sessionId);
|
||||
|
||||
if ((scavData.Info === null) || (scavData.Info === undefined))
|
||||
{
|
||||
this.commonUtils.logInfo("Scav profile hasn't been created yet.");
|
||||
return;
|
||||
}
|
||||
|
||||
// In case somebody disables scav runs and later wants to enable them, we need to reset their Scav timer unless it's plausible
|
||||
const worstCooldownFactor = this.getWorstSavageCooldownModifier();
|
||||
if (scavData.Info.SavageLockTime - pmcData.Info.LastTimePlayedAsSavage > this.databaseTables.globals.config.SavagePlayCooldown * worstCooldownFactor * 1.1)
|
||||
{
|
||||
this.commonUtils.logInfo(`Resetting scav timer for sessionId=${sessionId}...`);
|
||||
scavData.Info.SavageLockTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the highest Scav cooldown factor from Fence's rep levels
|
||||
private getWorstSavageCooldownModifier(): number
|
||||
{
|
||||
// Initialize the return value at something very low
|
||||
let worstCooldownFactor = 0.01;
|
||||
|
||||
for (const level in this.databaseTables.globals.config.FenceSettings.Levels)
|
||||
{
|
||||
if (this.databaseTables.globals.config.FenceSettings.Levels[level].SavageCooldownModifier > worstCooldownFactor)
|
||||
worstCooldownFactor = this.databaseTables.globals.config.FenceSettings.Levels[level].SavageCooldownModifier;
|
||||
}
|
||||
return worstCooldownFactor;
|
||||
}
|
||||
|
||||
private setOriginalPMCConversionChances(): void
|
||||
{
|
||||
// Store the default PMC-conversion chances for each bot type defined in SPT's configuration file
|
||||
let logMessage = "";
|
||||
for (const pmcType in this.iPmcConfig.convertIntoPmcChance)
|
||||
{
|
||||
if (this.convertIntoPmcChanceOrig[pmcType] !== undefined)
|
||||
{
|
||||
logMessage += `${pmcType}: already buffered, `;
|
||||
continue;
|
||||
}
|
||||
|
||||
const chances: MinMax = {
|
||||
min: this.iPmcConfig.convertIntoPmcChance[pmcType].min,
|
||||
max: this.iPmcConfig.convertIntoPmcChance[pmcType].max
|
||||
}
|
||||
this.convertIntoPmcChanceOrig[pmcType] = chances;
|
||||
|
||||
logMessage += `${pmcType}: ${chances.min}-${chances.max}%, `;
|
||||
}
|
||||
|
||||
this.commonUtils.logInfo(`Reading default PMC spawn chances: ${logMessage}`);
|
||||
}
|
||||
|
||||
private adjustPmcConversionChance(scalingFactor: number, verify: boolean): void
|
||||
{
|
||||
// Adjust the chances for each applicable bot type
|
||||
let logMessage = "";
|
||||
let verified = true;
|
||||
for (const pmcType in this.iPmcConfig.convertIntoPmcChance)
|
||||
{
|
||||
// Do not allow the chances to exceed 100%. Who knows what might happen...
|
||||
const min = Math.round(Math.min(100, this.convertIntoPmcChanceOrig[pmcType].min * scalingFactor));
|
||||
const max = Math.round(Math.min(100, this.convertIntoPmcChanceOrig[pmcType].max * scalingFactor));
|
||||
|
||||
if (verify)
|
||||
{
|
||||
if (this.iPmcConfig.convertIntoPmcChance[pmcType].min !== min)
|
||||
{
|
||||
verified = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.iPmcConfig.convertIntoPmcChance[pmcType].max !== max)
|
||||
{
|
||||
verified = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.iPmcConfig.convertIntoPmcChance[pmcType].min = min;
|
||||
this.iPmcConfig.convertIntoPmcChance[pmcType].max = max;
|
||||
|
||||
logMessage += `${pmcType}: ${min}-${max}%, `;
|
||||
}
|
||||
}
|
||||
|
||||
if (!verify)
|
||||
{
|
||||
this.commonUtils.logInfo(`Adjusting PMC spawn chances (${scalingFactor}): ${logMessage}`);
|
||||
}
|
||||
|
||||
if (!verified)
|
||||
{
|
||||
this.commonUtils.logError("Another mod has changed the PMC conversion chances. This mod may not work properly!");
|
||||
}
|
||||
}
|
||||
|
||||
private disableCustomBossWaves(): void
|
||||
{
|
||||
this.commonUtils.logInfo("Disabling custom boss waves...");
|
||||
this.iLocationConfig.customWaves.boss = {};
|
||||
}
|
||||
|
||||
private disableCustomScavWaves(): void
|
||||
{
|
||||
this.commonUtils.logInfo("Disabling custom Scav waves...");
|
||||
this.iLocationConfig.customWaves.normal = {};
|
||||
}
|
||||
|
||||
private increaseBotCaps(): void
|
||||
{
|
||||
if (!modConfig.bot_spawns.bot_cap_adjustments.add_max_players_to_bot_cap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const maxAddtlBots = modConfig.bot_spawns.bot_cap_adjustments.max_additional_bots;
|
||||
const maxTotalBots = modConfig.bot_spawns.bot_cap_adjustments.max_total_bots;
|
||||
|
||||
this.iBotConfig.maxBotCap.factory4_day = Math.min(this.iBotConfig.maxBotCap.factory4_day + Math.min(this.databaseTables.locations.factory4_day.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.factory4_night = Math.min(this.iBotConfig.maxBotCap.factory4_night + Math.min(this.databaseTables.locations.factory4_night.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.bigmap = Math.min(this.iBotConfig.maxBotCap.bigmap + Math.min(this.databaseTables.locations.bigmap.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.woods = Math.min(this.iBotConfig.maxBotCap.woods + Math.min(this.databaseTables.locations.woods.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.shoreline = Math.min(this.iBotConfig.maxBotCap.shoreline + Math.min(this.databaseTables.locations.shoreline.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.lighthouse = Math.min(this.iBotConfig.maxBotCap.lighthouse + Math.min(this.databaseTables.locations.lighthouse.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.rezervbase = Math.min(this.iBotConfig.maxBotCap.rezervbase + Math.min(this.databaseTables.locations.rezervbase.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.interchange = Math.min(this.iBotConfig.maxBotCap.interchange + Math.min(this.databaseTables.locations.interchange.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.laboratory = Math.min(this.iBotConfig.maxBotCap.laboratory + Math.min(this.databaseTables.locations.laboratory.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.tarkovstreets = Math.min(this.iBotConfig.maxBotCap.tarkovstreets + Math.min(this.databaseTables.locations.tarkovstreets.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.sandbox = Math.min(this.iBotConfig.maxBotCap.sandbox + Math.min(this.databaseTables.locations.sandbox.base.MaxPlayers, maxAddtlBots), maxTotalBots);
|
||||
this.iBotConfig.maxBotCap.default = Math.min(this.iBotConfig.maxBotCap.default + maxAddtlBots, maxTotalBots);
|
||||
|
||||
for (const location in this.iBotConfig.maxBotCap)
|
||||
{
|
||||
this.commonUtils.logInfo(`Changed bot cap for ${location} to: ${this.iBotConfig.maxBotCap[location]}`);
|
||||
}
|
||||
}
|
||||
|
||||
private removeBlacklistedBrainTypes(): void
|
||||
{
|
||||
const badBrains = modConfig.bot_spawns.blacklisted_pmc_bot_brains;
|
||||
this.commonUtils.logInfo("Removing blacklisted brain types from being used for PMC's...");
|
||||
|
||||
let removedBrains = 0;
|
||||
for (const pmcType in this.iPmcConfig.pmcType)
|
||||
{
|
||||
for (const map in this.iPmcConfig.pmcType[pmcType])
|
||||
{
|
||||
const mapBrains = this.iPmcConfig.pmcType[pmcType][map];
|
||||
|
||||
for (const i in badBrains)
|
||||
{
|
||||
if (mapBrains[badBrains[i]] === undefined)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//this.commonUtils.logInfo(`Removing ${badBrains[i]} from ${pmcType} in ${map}...`);
|
||||
delete mapBrains[badBrains[i]];
|
||||
removedBrains++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.commonUtils.logInfo(`Removing blacklisted brain types from being used for PMC's...done. Removed entries: ${removedBrains}`);
|
||||
}
|
||||
|
||||
private forceFullLengthScavRaids(): void
|
||||
{
|
||||
this.commonUtils.logInfo("Forcing full-length Scav raids...");
|
||||
|
||||
for (const map in this.iLocationConfig.scavRaidTimeSettings.maps)
|
||||
{
|
||||
this.iLocationConfig.scavRaidTimeSettings.maps[map].reducedChancePercent = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private generateBots(info: IGenerateBotsRequestData, sessionID: string, shouldBePScavGroup: boolean) : IBotBase[]
|
||||
{
|
||||
const bots = this.botController.generate(sessionID, info);
|
||||
|
||||
if (!shouldBePScavGroup)
|
||||
{
|
||||
return bots;
|
||||
}
|
||||
|
||||
const pmcNames = [
|
||||
...this.databaseTables.bots.types.usec.firstName,
|
||||
...this.databaseTables.bots.types.bear.firstName
|
||||
];
|
||||
|
||||
for (const bot in bots)
|
||||
{
|
||||
if (info.conditions[0].Role !== "assault")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bots[bot].Info.Nickname = `${bots[bot].Info.Nickname} (${this.randomUtil.getArrayValue(pmcNames)})`
|
||||
}
|
||||
|
||||
return bots;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { mod: new QuestingBots() }
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 DrakiaXYZ
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"nextUpdate": 1717917796,
|
||||
"maxIncreaseMult": 10
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "LiveFleaPrices",
|
||||
"version": "1.1.1",
|
||||
"main": "src/mod.js",
|
||||
"license": "MIT",
|
||||
"author": "DrakiaXYZ",
|
||||
"akiVersion": "~3.8",
|
||||
"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",
|
||||
"os": "^0.1",
|
||||
"tsyringe": "4.8.0",
|
||||
"typescript": "5.4",
|
||||
"winston": "3.12"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = __importStar(require("node:fs"));
|
||||
const path = __importStar(require("node:path"));
|
||||
class Mod {
|
||||
static container;
|
||||
static updateTimer;
|
||||
static config;
|
||||
static configPath = path.resolve(__dirname, "../config/config.json");
|
||||
static pricesPath = path.resolve(__dirname, "../config/prices.json");
|
||||
static originalPrices;
|
||||
async postDBLoadAsync(container) {
|
||||
Mod.container = container;
|
||||
Mod.config = JSON.parse(fs.readFileSync(Mod.configPath, "utf-8"));
|
||||
// Store a clone of the original prices table, so we can make sure things don't go too crazy
|
||||
const databaseServer = Mod.container.resolve("DatabaseServer");
|
||||
const priceTable = databaseServer.getTables().templates.prices;
|
||||
Mod.originalPrices = structuredClone(priceTable);
|
||||
// Update prices on startup
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
let fetchPrices = false;
|
||||
if (currentTime > Mod.config.nextUpdate) {
|
||||
fetchPrices = true;
|
||||
}
|
||||
if (!await Mod.updatePrices(fetchPrices)) {
|
||||
return;
|
||||
}
|
||||
// Setup a refresh interval to update once every hour
|
||||
Mod.updateTimer = setInterval(Mod.updatePrices, (60 * 60 * 1000));
|
||||
}
|
||||
static async updatePrices(fetchPrices = true) {
|
||||
const logger = Mod.container.resolve("WinstonLogger");
|
||||
const databaseServer = Mod.container.resolve("DatabaseServer");
|
||||
const ragfairPriceService = Mod.container.resolve("RagfairPriceService");
|
||||
const priceTable = databaseServer.getTables().templates.prices;
|
||||
const itemTable = databaseServer.getTables().templates.items;
|
||||
const handbookTable = databaseServer.getTables().templates.handbook;
|
||||
let prices;
|
||||
// Fetch the latest prices.json if we're triggered with fetch enabled, or the prices file doesn't exist
|
||||
if (fetchPrices || !fs.existsSync(Mod.pricesPath)) {
|
||||
logger.info("Fetching Flea Prices...");
|
||||
const response = await fetch("https://raw.githubusercontent.com/DrakiaXYZ/SPT-LiveFleaPriceDB/main/prices.json");
|
||||
// If the request failed, disable future updating
|
||||
if (!response?.ok) {
|
||||
logger.error(`Error fetching flea prices: ${response.status} (${response.statusText})`);
|
||||
clearInterval(Mod.updateTimer);
|
||||
return false;
|
||||
}
|
||||
prices = await response.json();
|
||||
// Store the prices to disk for next time
|
||||
fs.writeFileSync(Mod.pricesPath, JSON.stringify(prices));
|
||||
// Update config file with the next update time
|
||||
Mod.config.nextUpdate = Math.floor(Date.now() / 1000) + 3600;
|
||||
fs.writeFileSync(Mod.configPath, JSON.stringify(Mod.config, null, 4));
|
||||
}
|
||||
// Otherwise, read the file from disk
|
||||
else {
|
||||
prices = JSON.parse(fs.readFileSync(Mod.pricesPath, "utf-8"));
|
||||
}
|
||||
// Loop through the new prices file, updating all prices present
|
||||
for (const itemId in prices) {
|
||||
if (!itemTable[itemId]) {
|
||||
continue;
|
||||
}
|
||||
let basePrice = Mod.originalPrices[itemId];
|
||||
if (!basePrice) {
|
||||
basePrice = handbookTable.Items.find(x => x.Id === itemId)?.Price ?? 0;
|
||||
}
|
||||
const maxPrice = basePrice * Mod.config.maxIncreaseMult;
|
||||
if (maxPrice !== 0 && prices[itemId] <= maxPrice) {
|
||||
priceTable[itemId] = prices[itemId];
|
||||
}
|
||||
else {
|
||||
logger.debug(`Setting ${itemId} to ${maxPrice} instead of ${prices[itemId]} due to over inflation`);
|
||||
priceTable[itemId] = maxPrice;
|
||||
}
|
||||
}
|
||||
// Update dynamic price cache.
|
||||
// Note: We currently cast to `any` to bypass the protected state of the generateDynamicPrices method
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
ragfairPriceService.generateDynamicPrices();
|
||||
logger.info("Flea Prices Updated!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { mod: new Mod() };
|
||||
//# sourceMappingURL=mod.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"mod.js","sourceRoot":"","sources":["mod.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { DependencyContainer } from "tsyringe";
|
||||
|
||||
import type { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import type { IPostDBLoadModAsync } from "@spt-aki/models/external/IPostDBLoadModAsync";
|
||||
import type { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import type { RagfairPriceService } from "@spt-aki/services/RagfairPriceService";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
class Mod implements IPostDBLoadModAsync
|
||||
{
|
||||
private static container: DependencyContainer;
|
||||
private static updateTimer: NodeJS.Timeout;
|
||||
private static config: Config;
|
||||
private static configPath = path.resolve(__dirname, "../config/config.json");
|
||||
private static pricesPath = path.resolve(__dirname, "../config/prices.json");
|
||||
private static originalPrices;
|
||||
|
||||
public async postDBLoadAsync(container: DependencyContainer): Promise<void>
|
||||
{
|
||||
Mod.container = container;
|
||||
Mod.config = JSON.parse(fs.readFileSync(Mod.configPath, "utf-8"));
|
||||
|
||||
// Store a clone of the original prices table, so we can make sure things don't go too crazy
|
||||
const databaseServer = Mod.container.resolve<DatabaseServer>("DatabaseServer");
|
||||
const priceTable = databaseServer.getTables().templates.prices;
|
||||
Mod.originalPrices = structuredClone(priceTable);
|
||||
|
||||
// Update prices on startup
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
let fetchPrices = false;
|
||||
if (currentTime > Mod.config.nextUpdate)
|
||||
{
|
||||
fetchPrices = true;
|
||||
}
|
||||
|
||||
if (!await Mod.updatePrices(fetchPrices))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup a refresh interval to update once every hour
|
||||
Mod.updateTimer = setInterval(Mod.updatePrices, (60 * 60 * 1000));
|
||||
}
|
||||
|
||||
static async updatePrices(fetchPrices = true): Promise<boolean>
|
||||
{
|
||||
const logger = Mod.container.resolve<ILogger>("WinstonLogger");
|
||||
const databaseServer = Mod.container.resolve<DatabaseServer>("DatabaseServer");
|
||||
const ragfairPriceService = Mod.container.resolve<RagfairPriceService>("RagfairPriceService");
|
||||
const priceTable = databaseServer.getTables().templates.prices;
|
||||
const itemTable = databaseServer.getTables().templates.items;
|
||||
const handbookTable = databaseServer.getTables().templates.handbook;
|
||||
let prices: Record<string, number>;
|
||||
|
||||
// Fetch the latest prices.json if we're triggered with fetch enabled, or the prices file doesn't exist
|
||||
if (fetchPrices || !fs.existsSync(Mod.pricesPath))
|
||||
{
|
||||
logger.info("Fetching Flea Prices...");
|
||||
const response = await fetch("https://raw.githubusercontent.com/DrakiaXYZ/SPT-LiveFleaPriceDB/main/prices.json");
|
||||
|
||||
// If the request failed, disable future updating
|
||||
if (!response?.ok)
|
||||
{
|
||||
logger.error(`Error fetching flea prices: ${response.status} (${response.statusText})`);
|
||||
clearInterval(Mod.updateTimer);
|
||||
return false;
|
||||
}
|
||||
|
||||
prices = await response.json();
|
||||
|
||||
// Store the prices to disk for next time
|
||||
fs.writeFileSync(Mod.pricesPath, JSON.stringify(prices));
|
||||
|
||||
// Update config file with the next update time
|
||||
Mod.config.nextUpdate = Math.floor(Date.now() / 1000) + 3600;
|
||||
fs.writeFileSync(Mod.configPath, JSON.stringify(Mod.config, null, 4));
|
||||
}
|
||||
// Otherwise, read the file from disk
|
||||
else
|
||||
{
|
||||
prices = JSON.parse(fs.readFileSync(Mod.pricesPath, "utf-8"));
|
||||
}
|
||||
|
||||
// Loop through the new prices file, updating all prices present
|
||||
for (const itemId in prices)
|
||||
{
|
||||
if (!itemTable[itemId])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let basePrice = Mod.originalPrices[itemId];
|
||||
if (!basePrice)
|
||||
{
|
||||
basePrice = handbookTable.Items.find(x => x.Id === itemId)?.Price ?? 0;
|
||||
}
|
||||
|
||||
const maxPrice = basePrice * Mod.config.maxIncreaseMult;
|
||||
if (maxPrice !== 0 && prices[itemId] <= maxPrice)
|
||||
{
|
||||
priceTable[itemId] = prices[itemId];
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(`Setting ${itemId} to ${maxPrice} instead of ${prices[itemId]} due to over inflation`);
|
||||
priceTable[itemId] = maxPrice;
|
||||
}
|
||||
}
|
||||
|
||||
// Update dynamic price cache.
|
||||
// Note: We currently cast to `any` to bypass the protected state of the generateDynamicPrices method
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
(ragfairPriceService as any).generateDynamicPrices();
|
||||
|
||||
logger.info("Flea Prices Updated!");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
interface Config
|
||||
{
|
||||
nextUpdate: number,
|
||||
maxIncreaseMult: number,
|
||||
}
|
||||
|
||||
module.exports = { mod: new Mod() }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"_id": "gunsmith",
|
||||
"working": true,
|
||||
"availableInRaid": false,
|
||||
"items_buy": {
|
||||
"id_list": [],
|
||||
"category": ["54009119af1c881c07000029"]
|
||||
},
|
||||
"items_buy_prohibited": {
|
||||
"id_list": [],
|
||||
"category": ["5795f317245977243854e041", "543be5dd4bdc2deb348b4569"]
|
||||
},
|
||||
"customization_seller": false,
|
||||
"name": "gunsmith",
|
||||
"surname": " ",
|
||||
"nickname": "gunsmith",
|
||||
"location": " ",
|
||||
"avatar": "/files/trader/avatar/gunsmith.jpg",
|
||||
"balance_rub": 50000000,
|
||||
"balance_dol": 0,
|
||||
"balance_eur": 0,
|
||||
"unlockedByDefault": true,
|
||||
"discount": 0,
|
||||
"discount_end": 0,
|
||||
"buyer_up": true,
|
||||
"currency": "RUB",
|
||||
"nextResupply": 1615141448,
|
||||
"repair": {
|
||||
"availability": false,
|
||||
"quality": "0",
|
||||
"excluded_id_list": [],
|
||||
"excluded_category": [],
|
||||
"currency": "5449016a4bdc2d6f028b456f",
|
||||
"currency_coefficient": 1,
|
||||
"price_rate": 1
|
||||
},
|
||||
"insurance": {
|
||||
"availability": false,
|
||||
"min_payment": 0,
|
||||
"min_return_hour": 0,
|
||||
"max_return_hour": 0,
|
||||
"max_storage_time": 99,
|
||||
"excluded_category": []
|
||||
},
|
||||
"gridHeight": 300,
|
||||
"loyaltyLevels": [
|
||||
{
|
||||
"minLevel": 1,
|
||||
"minSalesSum": 0,
|
||||
"minStanding": 0,
|
||||
"buy_price_coef": 50,
|
||||
"repair_price_coef": 150,
|
||||
"insurance_price_coef": 10,
|
||||
"exchange_price_coef": 0,
|
||||
"heal_price_coef": 0
|
||||
}
|
||||
],
|
||||
"sell_category": []
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "gunsmith",
|
||||
"version": "2.0.7",
|
||||
"main": "src/mod.js",
|
||||
"license": "MIT",
|
||||
"author": "Alex",
|
||||
"akiVersion": "3.8.x",
|
||||
"isBundleMod": false,
|
||||
"loadBefore": [],
|
||||
"loadAfter": [],
|
||||
"incompatibilities": [],
|
||||
"contributors": [
|
||||
"CilginDalgic"
|
||||
],
|
||||
"scripts": {},
|
||||
"devDependencies": {}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
@@ -0,0 +1,149 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ConfigTypes_1 = require("/snapshot/project/obj/models/enums/ConfigTypes");
|
||||
// New trader settings aamfac
|
||||
const baseJson = __importStar(require("../db/base.json"));
|
||||
const Traders_1 = require("/snapshot/project/obj/models/enums/Traders");
|
||||
const assortJson = __importStar(require("../db/assort.json"));
|
||||
class gunsmith {
|
||||
mod;
|
||||
logger;
|
||||
configServer;
|
||||
ragfairConfig;
|
||||
constructor() {
|
||||
this.mod = "gunsmith"; // Set name of mod so we can log it to console later
|
||||
}
|
||||
/**
|
||||
* Some work needs to be done prior to SPT code being loaded, registering the profile image + setting trader update time inside the trader config json
|
||||
* @param container Dependency container
|
||||
*/
|
||||
preAkiLoad(container) {
|
||||
this.logger = container.resolve("WinstonLogger");
|
||||
this.logger.debug(`[${this.mod}] preAki Loading... `);
|
||||
const preAkiModLoader = container.resolve("PreAkiModLoader");
|
||||
const imageRouter = container.resolve("ImageRouter");
|
||||
const configServer = container.resolve("ConfigServer");
|
||||
const traderConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.TRADER);
|
||||
this.registerProfileImage(preAkiModLoader, imageRouter);
|
||||
this.setupTraderUpdateTime(traderConfig);
|
||||
// Add trader to trader enum
|
||||
Traders_1.Traders["gunsmith"] = "gunsmith";
|
||||
this.logger.debug(`[${this.mod}] preAki Loaded`);
|
||||
}
|
||||
/**
|
||||
* Majority of trader-related work occurs after the aki database has been loaded but prior to SPT code being run
|
||||
* @param container Dependency container
|
||||
*/
|
||||
postDBLoad(container) {
|
||||
this.logger.debug(`[${this.mod}] postDb Loading... `);
|
||||
this.configServer = container.resolve("ConfigServer");
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes_1.ConfigTypes.RAGFAIR);
|
||||
// Resolve SPT classes we'll use
|
||||
const databaseServer = container.resolve("DatabaseServer");
|
||||
const configServer = container.resolve("ConfigServer");
|
||||
const traderConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.TRADER);
|
||||
const jsonUtil = container.resolve("JsonUtil");
|
||||
// Get a reference to the database tables
|
||||
const tables = databaseServer.getTables();
|
||||
// Add new trader to the trader dictionary in DatabaseServer
|
||||
this.addTraderToDb(baseJson, tables, jsonUtil);
|
||||
this.addTraderToLocales(tables, baseJson.name, "gunsmith", baseJson.nickname, baseJson.location, "A distinguished gunsmith who smuggles some of the best weapons and equipment into Tarkov. Also he is excellent in repairing weapons and armor.");
|
||||
// Add item purchase threshold value (what % durability does trader stop buying items at)
|
||||
// traderConfig.durabilityPurchaseThreshhold[baseJson._id] = 60;
|
||||
this.ragfairConfig.traders[baseJson._id] = true;
|
||||
this.logger.debug(`[${this.mod}] postDb Loaded`);
|
||||
}
|
||||
/**
|
||||
* Add profile picture to our trader
|
||||
* @param preAkiModLoader mod loader class - used to get the mods file path
|
||||
* @param imageRouter image router class - used to register the trader image path so we see their image on trader page
|
||||
*/
|
||||
registerProfileImage(preAkiModLoader, imageRouter) {
|
||||
// Reference the mod "res" folder
|
||||
const imageFilepath = `./${preAkiModLoader.getModPath(this.mod)}res`;
|
||||
// Register a route to point to the profile picture
|
||||
imageRouter.addRoute(baseJson.avatar.replace(".jpg", ""), `${imageFilepath}/gunsmith.jpg`);
|
||||
}
|
||||
/**
|
||||
* Add record to trader config to set the refresh time of trader in seconds (default is 60 minutes)
|
||||
* @param traderConfig trader config to add our trader to
|
||||
*/
|
||||
setupTraderUpdateTime(traderConfig) {
|
||||
// Add refresh time in seconds to config
|
||||
const traderRefreshRecord = { traderId: baseJson._id, seconds: { min: 1000, max: 6000 } };
|
||||
traderConfig.updateTime.push(traderRefreshRecord);
|
||||
}
|
||||
/**
|
||||
* Add our new trader to the database
|
||||
* @param traderDetailsToAdd trader details
|
||||
* @param tables database
|
||||
* @param jsonUtil json utility class
|
||||
*/
|
||||
// rome-ignore lint/suspicious/noExplicitAny: traderDetailsToAdd comes from base.json, so no type
|
||||
addTraderToDb(traderDetailsToAdd, tables, jsonUtil) {
|
||||
// Add trader to trader table, key is the traders id
|
||||
tables.traders[traderDetailsToAdd._id] = {
|
||||
assort: jsonUtil.deserialize(jsonUtil.serialize(assortJson)), // assorts are the 'offers' trader sells, can be a single item (e.g. carton of milk) or multiple items as a collection (e.g. a gun)
|
||||
base: jsonUtil.deserialize(jsonUtil.serialize(traderDetailsToAdd)),
|
||||
questassort: {
|
||||
started: {},
|
||||
success: {},
|
||||
fail: {}
|
||||
} // Empty object as trader has no assorts unlocked by quests
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Add traders name/location/description to the locale table
|
||||
* @param tables database tables
|
||||
* @param fullName fullname of trader
|
||||
* @param firstName first name of trader
|
||||
* @param nickName nickname of trader
|
||||
* @param location location of trader
|
||||
* @param description description of trader
|
||||
*/
|
||||
addTraderToLocales(tables, fullName, firstName, nickName, location, description) {
|
||||
// For each language, add locale for the new trader
|
||||
const locales = Object.values(tables.locales.global);
|
||||
for (const locale of locales) {
|
||||
locale[`${baseJson._id} FullName`] = fullName;
|
||||
locale[`${baseJson._id} FirstName`] = firstName;
|
||||
locale[`${baseJson._id} Nickname`] = nickName;
|
||||
locale[`${baseJson._id} Location`] = location;
|
||||
locale[`${baseJson._id} Description`] = description;
|
||||
}
|
||||
}
|
||||
addItemToLocales(tables, itemTpl, name, shortName, Description) {
|
||||
// For each language, add locale for the new trader
|
||||
const locales = Object.values(tables.locales.global);
|
||||
for (const locale of locales) {
|
||||
locale[`${itemTpl} Name`] = name;
|
||||
locale[`${itemTpl} ShortName`] = shortName;
|
||||
locale[`${itemTpl} Description`] = Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = { mod: new gunsmith() };
|
||||
//# sourceMappingURL=mod.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"mod.js","sourceRoot":"","sources":["mod.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,168 @@
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
|
||||
// SPT types
|
||||
import { IPreAkiLoadMod } from "@spt-aki/models/external/IPreAkiLoadMod";
|
||||
import { IPostDBLoadMod } from "@spt-aki/models/external/IPostDBLoadMod";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { PreAkiModLoader } from "@spt-aki/loaders/PreAkiModLoader";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { ImageRouter } from "@spt-aki/routers/ImageRouter";
|
||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { ITraderAssort, ITraderBase } from "@spt-aki/models/eft/common/tables/ITrader";
|
||||
import { ITraderConfig, UpdateTime } from "@spt-aki/models/spt/config/ITraderConfig";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
import { IDatabaseTables } from "@spt-aki/models/spt/server/IDatabaseTables";
|
||||
|
||||
|
||||
// New trader settings aamfac
|
||||
import * as baseJson from "../db/base.json";
|
||||
import { Traders } from "@spt-aki/models/enums/Traders";
|
||||
import * as assortJson from "../db/assort.json";
|
||||
|
||||
class gunsmith implements IPreAkiLoadMod, IPostDBLoadMod {
|
||||
mod: string
|
||||
logger: ILogger
|
||||
private configServer: ConfigServer;
|
||||
private ragfairConfig: IRagfairConfig;
|
||||
|
||||
constructor() {
|
||||
this.mod = "gunsmith"; // Set name of mod so we can log it to console later
|
||||
}
|
||||
|
||||
/**
|
||||
* Some work needs to be done prior to SPT code being loaded, registering the profile image + setting trader update time inside the trader config json
|
||||
* @param container Dependency container
|
||||
*/
|
||||
public preAkiLoad(container: DependencyContainer): void {
|
||||
this.logger = container.resolve<ILogger>("WinstonLogger");
|
||||
this.logger.debug(`[${this.mod}] preAki Loading... `);
|
||||
|
||||
const preAkiModLoader: PreAkiModLoader = container.resolve<PreAkiModLoader>("PreAkiModLoader");
|
||||
const imageRouter: ImageRouter = container.resolve<ImageRouter>("ImageRouter");
|
||||
const configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
const traderConfig: ITraderConfig = configServer.getConfig<ITraderConfig>(ConfigTypes.TRADER);
|
||||
|
||||
this.registerProfileImage(preAkiModLoader, imageRouter);
|
||||
|
||||
this.setupTraderUpdateTime(traderConfig);
|
||||
|
||||
|
||||
// Add trader to trader enum
|
||||
Traders["gunsmith"] = "gunsmith";
|
||||
this.logger.debug(`[${this.mod}] preAki Loaded`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Majority of trader-related work occurs after the aki database has been loaded but prior to SPT code being run
|
||||
* @param container Dependency container
|
||||
*/
|
||||
public postDBLoad(container: DependencyContainer): void {
|
||||
this.logger.debug(`[${this.mod}] postDb Loading... `);
|
||||
|
||||
this.configServer = container.resolve("ConfigServer");
|
||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||
|
||||
// Resolve SPT classes we'll use
|
||||
const databaseServer: DatabaseServer = container.resolve<DatabaseServer>("DatabaseServer");
|
||||
const configServer: ConfigServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
const traderConfig: ITraderConfig = configServer.getConfig(ConfigTypes.TRADER);
|
||||
const jsonUtil: JsonUtil = container.resolve<JsonUtil>("JsonUtil");
|
||||
|
||||
// Get a reference to the database tables
|
||||
const tables = databaseServer.getTables();
|
||||
|
||||
// Add new trader to the trader dictionary in DatabaseServer
|
||||
this.addTraderToDb(baseJson, tables, jsonUtil);
|
||||
|
||||
this.addTraderToLocales(tables, baseJson.name, "gunsmith", baseJson.nickname, baseJson.location, "A distinguished gunsmith who smuggles some of the best weapons and equipment into Tarkov. Also he is excellent in repairing weapons and armor.");
|
||||
|
||||
// Add item purchase threshold value (what % durability does trader stop buying items at)
|
||||
// traderConfig.durabilityPurchaseThreshhold[baseJson._id] = 60;
|
||||
this.ragfairConfig.traders[baseJson._id] = true;
|
||||
|
||||
this.logger.debug(`[${this.mod}] postDb Loaded`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add profile picture to our trader
|
||||
* @param preAkiModLoader mod loader class - used to get the mods file path
|
||||
* @param imageRouter image router class - used to register the trader image path so we see their image on trader page
|
||||
*/
|
||||
private registerProfileImage(preAkiModLoader: PreAkiModLoader, imageRouter: ImageRouter): void
|
||||
{
|
||||
// Reference the mod "res" folder
|
||||
const imageFilepath = `./${preAkiModLoader.getModPath(this.mod)}res`;
|
||||
|
||||
// Register a route to point to the profile picture
|
||||
imageRouter.addRoute(baseJson.avatar.replace(".jpg", ""), `${imageFilepath}/gunsmith.jpg`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add record to trader config to set the refresh time of trader in seconds (default is 60 minutes)
|
||||
* @param traderConfig trader config to add our trader to
|
||||
*/
|
||||
private setupTraderUpdateTime(traderConfig: ITraderConfig): void
|
||||
{
|
||||
// Add refresh time in seconds to config
|
||||
const traderRefreshRecord: UpdateTime = { traderId: baseJson._id, seconds: { min: 1000, max: 6000 } }
|
||||
traderConfig.updateTime.push(traderRefreshRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add our new trader to the database
|
||||
* @param traderDetailsToAdd trader details
|
||||
* @param tables database
|
||||
* @param jsonUtil json utility class
|
||||
*/
|
||||
|
||||
// rome-ignore lint/suspicious/noExplicitAny: traderDetailsToAdd comes from base.json, so no type
|
||||
private addTraderToDb(traderDetailsToAdd: any, tables: IDatabaseTables, jsonUtil: JsonUtil): void
|
||||
{
|
||||
// Add trader to trader table, key is the traders id
|
||||
tables.traders[traderDetailsToAdd._id] = {
|
||||
assort: jsonUtil.deserialize(jsonUtil.serialize(assortJson)) as ITraderAssort, // assorts are the 'offers' trader sells, can be a single item (e.g. carton of milk) or multiple items as a collection (e.g. a gun)
|
||||
base: jsonUtil.deserialize(jsonUtil.serialize(traderDetailsToAdd)) as ITraderBase,
|
||||
questassort: {
|
||||
started: {},
|
||||
success: {},
|
||||
fail: {}
|
||||
} // Empty object as trader has no assorts unlocked by quests
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add traders name/location/description to the locale table
|
||||
* @param tables database tables
|
||||
* @param fullName fullname of trader
|
||||
* @param firstName first name of trader
|
||||
* @param nickName nickname of trader
|
||||
* @param location location of trader
|
||||
* @param description description of trader
|
||||
*/
|
||||
private addTraderToLocales(tables: IDatabaseTables, fullName: string, firstName: string, nickName: string, location: string, description: string)
|
||||
{
|
||||
// For each language, add locale for the new trader
|
||||
const locales = Object.values(tables.locales.global) as Record<string, string>[];
|
||||
for (const locale of locales) {
|
||||
locale[`${baseJson._id} FullName`] = fullName;
|
||||
locale[`${baseJson._id} FirstName`] = firstName;
|
||||
locale[`${baseJson._id} Nickname`] = nickName;
|
||||
locale[`${baseJson._id} Location`] = location;
|
||||
locale[`${baseJson._id} Description`] = description;
|
||||
}
|
||||
}
|
||||
|
||||
private addItemToLocales(tables: IDatabaseTables, itemTpl: string, name: string, shortName: string, Description: string)
|
||||
{
|
||||
// For each language, add locale for the new trader
|
||||
const locales = Object.values(tables.locales.global) as Record<string, string>[];
|
||||
for (const locale of locales) {
|
||||
locale[`${itemTpl} Name`] = name;
|
||||
locale[`${itemTpl} ShortName`] = shortName;
|
||||
locale[`${itemTpl} Description`] = Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { mod: new gunsmith() }
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "LootValueBackend",
|
||||
"version": "2.0.0",
|
||||
"main": "src/LootValueStaticRouter.js",
|
||||
"license": "MIT",
|
||||
"author": "IhanaMies",
|
||||
"akiVersion": "~3.8",
|
||||
"scripts": {
|
||||
"setup": "npm i",
|
||||
"build": "node ./packageBuild.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"bestzip": "2.2.1",
|
||||
"eslint": "8.30.0",
|
||||
"fs-extra": "11.1.0",
|
||||
"glob": "8.0.3",
|
||||
"tsyringe": "4.7.0",
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class Mod {
|
||||
itemHelper;
|
||||
offerService;
|
||||
tradeHelper;
|
||||
profileHelper;
|
||||
saveServer;
|
||||
logger;
|
||||
preAkiLoad(container) {
|
||||
const logger = container.resolve("WinstonLogger");
|
||||
this.logger = logger;
|
||||
const staticRouterModService = container.resolve("StaticRouterModService");
|
||||
//HELPERS
|
||||
this.itemHelper = container.resolve("ItemHelper");
|
||||
this.offerService = container.resolve("RagfairOfferService");
|
||||
this.tradeHelper = container.resolve("TradeHelper");
|
||||
this.profileHelper = container.resolve("ProfileHelper");
|
||||
this.saveServer = container.resolve("SaveServer");
|
||||
// Hook up a new static route
|
||||
staticRouterModService.registerStaticRouter("LootValueRoutes", [
|
||||
{
|
||||
url: "/LootValue/GetItemLowestFleaPrice",
|
||||
//info is the payload from client in json
|
||||
//output is the response back to client
|
||||
action: (url, info, sessionID, output) => {
|
||||
return (JSON.stringify(this.getItemLowestFleaPrice(info.templateId)));
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/LootValue/SellItemToTrader",
|
||||
//info is the payload from client in json
|
||||
//output is the response back to client
|
||||
action: (url, info, sessionID, output) => {
|
||||
let response = this.sellItemToTrader(sessionID, info.ItemId, info.TraderId, info.Price);
|
||||
return (JSON.stringify(response));
|
||||
}
|
||||
}
|
||||
], "custom-static-LootValueRoutes");
|
||||
}
|
||||
getItemLowestFleaPrice(templateId) {
|
||||
let offers = this.offerService.getOffersOfType(templateId);
|
||||
if (offers && offers.length > 0) {
|
||||
offers = offers.filter(a => a.user.memberType != 4 //exclude traders
|
||||
&& a.requirements[0]._tpl == '5449016a4bdc2d6f028b456f' //consider only ruble trades
|
||||
&& this.itemHelper.getItemQualityModifier(a.items[0]) == 1 //and items with full durability
|
||||
);
|
||||
if (offers.length > 0)
|
||||
return (offers.sort((a, b) => a.summaryCost - b.summaryCost)[0]).summaryCost;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
sellItemToTrader(sessionId, itemId, traderId, price) {
|
||||
let pmcData = this.profileHelper.getPmcProfile(sessionId);
|
||||
if (!pmcData) {
|
||||
this.logger.error("pmcData was null");
|
||||
return false;
|
||||
}
|
||||
let item = pmcData.Inventory.items.find(x => x._id === itemId);
|
||||
if (!item) {
|
||||
this.logger.error("item was null");
|
||||
return false;
|
||||
}
|
||||
let sellRequest = {
|
||||
Action: "sell_to_trader",
|
||||
type: "sell_to_trader",
|
||||
tid: traderId,
|
||||
price: price,
|
||||
items: [{
|
||||
id: itemId,
|
||||
count: item.upd ? item.upd.StackObjectsCount ? item.upd.StackObjectsCount : 1 : 1,
|
||||
scheme_id: 0
|
||||
}]
|
||||
};
|
||||
let response = this.tradeHelper.sellItem(pmcData, pmcData, sellRequest, sessionId);
|
||||
this.saveServer.saveProfile(sessionId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
module.exports = { mod: new Mod() };
|
||||
//# sourceMappingURL=LootValueStaticRouter.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"LootValueStaticRouter.js","sourceRoot":"","sources":["LootValueStaticRouter.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService";
|
||||
import { ItemHelper } from "@spt-aki/helpers/ItemHelper";
|
||||
import { IRagfairOffer } from "@spt-aki/models/eft/ragfair/IRagfairOffer";
|
||||
import { TradeHelper } from "@spt-aki/helpers/TradeHelper";
|
||||
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||
import { IProcessSellTradeRequestData } from "@spt-aki/models/eft/trade/IProcessSellTradeRequestData";
|
||||
import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse";
|
||||
import { SaveServer } from '@spt-aki/servers/SaveServer';
|
||||
|
||||
import type { IPreAkiLoadMod } from "@spt-aki/models/external/IPreAkiLoadMod";
|
||||
import type { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import type { StaticRouterModService} from "@spt-aki/services/mod/staticRouter/StaticRouterModService";
|
||||
|
||||
class Mod implements IPreAkiLoadMod
|
||||
{
|
||||
private itemHelper: ItemHelper;
|
||||
private offerService: RagfairOfferService;
|
||||
private tradeHelper: TradeHelper;
|
||||
private profileHelper: ProfileHelper;
|
||||
private saveServer: SaveServer;
|
||||
|
||||
private logger: ILogger;
|
||||
|
||||
public preAkiLoad(container: DependencyContainer): void {
|
||||
const logger = container.resolve<ILogger>("WinstonLogger");
|
||||
this.logger = logger;
|
||||
|
||||
const staticRouterModService = container.resolve<StaticRouterModService>("StaticRouterModService");
|
||||
|
||||
//HELPERS
|
||||
this.itemHelper = container.resolve<ItemHelper>("ItemHelper");
|
||||
this.offerService = container.resolve<RagfairOfferService>("RagfairOfferService");
|
||||
this.tradeHelper = container.resolve<TradeHelper>("TradeHelper");
|
||||
this.profileHelper = container.resolve<ProfileHelper>("ProfileHelper");
|
||||
this.saveServer = container.resolve<SaveServer>("SaveServer");
|
||||
|
||||
// Hook up a new static route
|
||||
staticRouterModService.registerStaticRouter(
|
||||
"LootValueRoutes",
|
||||
[
|
||||
{
|
||||
url: "/LootValue/GetItemLowestFleaPrice",
|
||||
//info is the payload from client in json
|
||||
//output is the response back to client
|
||||
action: (url, info, sessionID, output) => {
|
||||
return(JSON.stringify(this.getItemLowestFleaPrice(info.templateId)));
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/LootValue/SellItemToTrader",
|
||||
//info is the payload from client in json
|
||||
//output is the response back to client
|
||||
action: (url, info, sessionID, output) => {
|
||||
let response = this.sellItemToTrader(sessionID, info.ItemId, info.TraderId, info.Price);
|
||||
return(JSON.stringify(response));
|
||||
}
|
||||
}
|
||||
],
|
||||
"custom-static-LootValueRoutes"
|
||||
);
|
||||
}
|
||||
|
||||
private getItemLowestFleaPrice(templateId: string): number {
|
||||
let offers: IRagfairOffer[] = this.offerService.getOffersOfType(templateId);
|
||||
|
||||
if (offers && offers.length > 0) {
|
||||
offers = offers.filter(a => a.user.memberType != 4 //exclude traders
|
||||
&& a.requirements[0]._tpl == '5449016a4bdc2d6f028b456f' //consider only ruble trades
|
||||
&& this.itemHelper.getItemQualityModifier(a.items[0]) == 1 //and items with full durability
|
||||
);
|
||||
|
||||
if (offers.length > 0)
|
||||
return(offers.sort((a,b) => a.summaryCost - b.summaryCost)[0]).summaryCost;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private sellItemToTrader(sessionId: string, itemId: string, traderId: string, price: number): boolean {
|
||||
let pmcData = this.profileHelper.getPmcProfile(sessionId)
|
||||
if (!pmcData) {
|
||||
this.logger.error("pmcData was null");
|
||||
return false;
|
||||
}
|
||||
|
||||
let item = pmcData.Inventory.items.find(x => x._id === itemId)
|
||||
if (!item) {
|
||||
this.logger.error("item was null");
|
||||
return false;
|
||||
}
|
||||
|
||||
let sellRequest: IProcessSellTradeRequestData = {
|
||||
Action: "sell_to_trader",
|
||||
type: "sell_to_trader",
|
||||
tid: traderId,
|
||||
price: price,
|
||||
items: [{
|
||||
id: itemId,
|
||||
count: item.upd ? item.upd.StackObjectsCount ? item.upd.StackObjectsCount : 1 : 1,
|
||||
scheme_id: 0
|
||||
}]
|
||||
};
|
||||
|
||||
let response: IItemEventRouterResponse = this.tradeHelper.sellItem(pmcData, pmcData, sellRequest, sessionId);
|
||||
this.saveServer.saveProfile(sessionId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {mod: new Mod()}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "MoreCheckmarksBackend",
|
||||
"version": "1.5.13",
|
||||
"main": "src/MoreCheckmarksStaticRouter.js",
|
||||
"license": "MIT",
|
||||
"author": "VIP",
|
||||
"akiVersion": "~3.8",
|
||||
"scripts": {
|
||||
"setup": "npm i",
|
||||
"build": "node ./packageBuild.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"bestzip": "2.2.1",
|
||||
"eslint": "8.30.0",
|
||||
"fs-extra": "11.1.0",
|
||||
"glob": "8.0.3",
|
||||
"tsyringe": "4.7.0",
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ConfigTypes_1 = require("/snapshot/project/obj/models/enums/ConfigTypes");
|
||||
const Traders_1 = require("/snapshot/project/obj/models/enums/Traders");
|
||||
class Mod {
|
||||
questConfig;
|
||||
preAkiLoad(container) {
|
||||
const logger = container.resolve("WinstonLogger");
|
||||
const dynamicRouterModService = container.resolve("DynamicRouterModService");
|
||||
const staticRouterModService = container.resolve("StaticRouterModService");
|
||||
const profileHelper = container.resolve("ProfileHelper");
|
||||
const questHelper = container.resolve("QuestHelper");
|
||||
const configServer = container.resolve("ConfigServer");
|
||||
this.questConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.QUEST);
|
||||
//const questConditionHelper = container.resolve<QuestConditionHelper>("QuestConditionHelper");
|
||||
const traderHelper = container.resolve("TraderHelper");
|
||||
const databaseServer = container.resolve("DatabaseServer");
|
||||
const fenceService = container.resolve("FenceService");
|
||||
// Hook up a new static route
|
||||
staticRouterModService.registerStaticRouter("MoreCheckmarksRoutes", [
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/quests",
|
||||
action: (url, info, sessionID, output) => {
|
||||
logger.info("MoreCheckmarks making quest data request");
|
||||
const quests = [];
|
||||
const allQuests = questHelper.getQuestsFromDb();
|
||||
//const allQuests = databaseServer.getTables().templates.quests;
|
||||
const profile = profileHelper.getPmcProfile(sessionID);
|
||||
if (profile && profile.Quests) {
|
||||
for (const quest of allQuests) {
|
||||
// Skip if not a quest we can have
|
||||
if (profile.Info && this.questIsForOtherSide(profile.Info.Side, quest._id)) {
|
||||
continue;
|
||||
}
|
||||
// Skip if already complete or can't complete
|
||||
const questStatus = questHelper.getQuestStatus(profile, quest._id);
|
||||
/*
|
||||
Locked = 0,
|
||||
AvailableForStart = 1,
|
||||
Started = 2,
|
||||
AvailableForFinish = 3,
|
||||
Success = 4,
|
||||
Fail = 5,
|
||||
FailRestartable = 6,
|
||||
MarkedAsFailed = 7,
|
||||
Expired = 8,
|
||||
AvailableAfter = 9
|
||||
*/
|
||||
if (questStatus >= 3 && questStatus <= 8) {
|
||||
continue;
|
||||
}
|
||||
quests.push(quest);
|
||||
}
|
||||
logger.info("Got quests");
|
||||
}
|
||||
else {
|
||||
logger.info("Unable to fetch quests for MoreCheckmarks");
|
||||
}
|
||||
return JSON.stringify(quests);
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/assorts",
|
||||
action: (url, info, sessionID, output) => {
|
||||
logger.info("MoreCheckmarks making trader assort data request");
|
||||
const assorts = [];
|
||||
if (databaseServer && databaseServer.getTables()) {
|
||||
if (Traders_1.Traders && traderHelper) {
|
||||
for (const value of Object.values(Traders_1.Traders)) {
|
||||
if (value == "579dc571d53a0658a154fbec" && fenceService.getRawFenceAssorts()) {
|
||||
assorts.push(fenceService.getRawFenceAssorts());
|
||||
}
|
||||
else if (databaseServer.getTables().traders[value] && databaseServer.getTables().traders[value].assort) {
|
||||
assorts.push(databaseServer.getTables().traders[value].assort);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.info("Unable to fetch assorts for MoreCheckmarks");
|
||||
}
|
||||
}
|
||||
return JSON.stringify(assorts);
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/items",
|
||||
action: (url, info, sessionID, output) => {
|
||||
logger.info("MoreCheckmarks making item data request");
|
||||
const items = {};
|
||||
if (databaseServer && databaseServer.getTables() && databaseServer.getTables().templates && databaseServer.getTables().templates.items) {
|
||||
return JSON.stringify(databaseServer.getTables().templates.items);
|
||||
}
|
||||
else {
|
||||
return JSON.stringify(items);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/locales",
|
||||
action: (url, info, sessionID, output) => {
|
||||
logger.info("MoreCheckmarks making locale request");
|
||||
const locales = {};
|
||||
if (databaseServer && databaseServer.getTables() && databaseServer.getTables().locales) {
|
||||
return JSON.stringify(databaseServer.getTables().locales);
|
||||
}
|
||||
else {
|
||||
return JSON.stringify(locales);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/productions",
|
||||
action: (url, info, sessionID, output) => {
|
||||
logger.info("MoreCheckmarks making productions request");
|
||||
const production = {};
|
||||
if (databaseServer && databaseServer.getTables() && databaseServer.getTables().hideout && databaseServer.getTables().hideout.production) {
|
||||
return JSON.stringify(databaseServer.getTables().hideout.production);
|
||||
}
|
||||
else {
|
||||
return JSON.stringify(production);
|
||||
}
|
||||
}
|
||||
}
|
||||
], "custom-static-MoreCheckmarksRoutes");
|
||||
}
|
||||
questIsForOtherSide(playerSide, questId) {
|
||||
const isUsec = playerSide.toLowerCase() === "usec";
|
||||
if (isUsec && this.questConfig.bearOnlyQuests.includes(questId)) {
|
||||
// player is usec and quest is bear only, skip
|
||||
return true;
|
||||
}
|
||||
if (!isUsec && this.questConfig.usecOnlyQuests.includes(questId)) {
|
||||
// player is bear and quest is usec only, skip
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
module.exports = { mod: new Mod() };
|
||||
//# sourceMappingURL=MoreCheckmarksStaticRouter.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MoreCheckmarksStaticRouter.js","sourceRoot":"","sources":["MoreCheckmarksStaticRouter.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,199 @@
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
import type { IPreAkiLoadMod } from "@spt-aki/models/external/IPreAkiLoadMod";
|
||||
import type { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import type {DynamicRouterModService} from "@spt-aki/services/mod/dynamicRouter/DynamicRouterModService";
|
||||
import type {StaticRouterModService} from "@spt-aki/services/mod/staticRouter/StaticRouterModService";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||
//import { QuestConditionHelper } from "@spt-aki/helpers/QuestConditionHelper";
|
||||
import { QuestHelper } from "@spt-aki/helpers/QuestHelper";
|
||||
import { IQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { Traders } from "@spt-aki/models/enums/Traders";
|
||||
import { TraderHelper } from "@spt-aki/helpers/TraderHelper";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { FenceService } from "@spt-aki/services/FenceService";
|
||||
import { ITemplateItem } from "@spt-aki/models/eft/common/tables/ITemplateItem";
|
||||
import { ILocaleBase } from "@spt-aki/models/spt/server/ILocaleBase";
|
||||
|
||||
class Mod implements IPreAkiLoadMod
|
||||
{
|
||||
protected questConfig: IQuestConfig;
|
||||
|
||||
public preAkiLoad(container: DependencyContainer): void {
|
||||
const logger = container.resolve<ILogger>("WinstonLogger");
|
||||
const dynamicRouterModService = container.resolve<DynamicRouterModService>("DynamicRouterModService");
|
||||
const staticRouterModService = container.resolve<StaticRouterModService>("StaticRouterModService");
|
||||
const profileHelper = container.resolve<ProfileHelper>("ProfileHelper");
|
||||
const questHelper = container.resolve<QuestHelper>("QuestHelper");
|
||||
const configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
this.questConfig = configServer.getConfig(ConfigTypes.QUEST);
|
||||
//const questConditionHelper = container.resolve<QuestConditionHelper>("QuestConditionHelper");
|
||||
const traderHelper = container.resolve<TraderHelper>("TraderHelper");
|
||||
const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
|
||||
const fenceService = container.resolve<FenceService>("FenceService");
|
||||
|
||||
// Hook up a new static route
|
||||
staticRouterModService.registerStaticRouter(
|
||||
"MoreCheckmarksRoutes",
|
||||
[
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/quests",
|
||||
action: (url, info, sessionID, output) =>
|
||||
{
|
||||
logger.info("MoreCheckmarks making quest data request");
|
||||
const quests: IQuest[] = [];
|
||||
const allQuests = questHelper.getQuestsFromDb();
|
||||
//const allQuests = databaseServer.getTables().templates.quests;
|
||||
const profile: IPmcData = profileHelper.getPmcProfile(sessionID);
|
||||
|
||||
if(profile && profile.Quests)
|
||||
{
|
||||
for (const quest of allQuests)
|
||||
{
|
||||
// Skip if not a quest we can have
|
||||
if (profile.Info && this.questIsForOtherSide(profile.Info.Side, quest._id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if already complete or can't complete
|
||||
const questStatus = questHelper.getQuestStatus(profile, quest._id);
|
||||
/*
|
||||
Locked = 0,
|
||||
AvailableForStart = 1,
|
||||
Started = 2,
|
||||
AvailableForFinish = 3,
|
||||
Success = 4,
|
||||
Fail = 5,
|
||||
FailRestartable = 6,
|
||||
MarkedAsFailed = 7,
|
||||
Expired = 8,
|
||||
AvailableAfter = 9
|
||||
*/
|
||||
if (questStatus >= 3 && questStatus <= 8)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
quests.push(quest);
|
||||
}
|
||||
logger.info("Got quests");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("Unable to fetch quests for MoreCheckmarks");
|
||||
}
|
||||
|
||||
return JSON.stringify(quests);
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/assorts",
|
||||
action: (url, info, sessionID, output) =>
|
||||
{
|
||||
logger.info("MoreCheckmarks making trader assort data request");
|
||||
const assorts: ITraderAssort[] = [];
|
||||
|
||||
if(databaseServer && databaseServer.getTables())
|
||||
{
|
||||
if(Traders && traderHelper)
|
||||
{
|
||||
for (const value of Object.values(Traders))
|
||||
{
|
||||
if(value == "579dc571d53a0658a154fbec" && fenceService.getRawFenceAssorts())
|
||||
{
|
||||
assorts.push(fenceService.getRawFenceAssorts());
|
||||
}
|
||||
else if(databaseServer.getTables().traders[value] && databaseServer.getTables().traders[value].assort)
|
||||
{
|
||||
assorts.push(databaseServer.getTables().traders[value].assort);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("Unable to fetch assorts for MoreCheckmarks");
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(assorts);
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/items",
|
||||
action: (url, info, sessionID, output) =>
|
||||
{
|
||||
logger.info("MoreCheckmarks making item data request");
|
||||
|
||||
const items: Record<string, ITemplateItem> = {};
|
||||
if(databaseServer && databaseServer.getTables() && databaseServer.getTables().templates && databaseServer.getTables().templates.items)
|
||||
{
|
||||
return JSON.stringify(databaseServer.getTables().templates.items);
|
||||
}
|
||||
else
|
||||
{
|
||||
return JSON.stringify(items);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/locales",
|
||||
action: (url, info, sessionID, output) =>
|
||||
{
|
||||
logger.info("MoreCheckmarks making locale request");
|
||||
|
||||
const locales: ILocaleBase = {};
|
||||
if(databaseServer && databaseServer.getTables() && databaseServer.getTables().locales)
|
||||
{
|
||||
return JSON.stringify(databaseServer.getTables().locales);
|
||||
}
|
||||
else
|
||||
{
|
||||
return JSON.stringify(locales);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: "/MoreCheckmarksRoutes/productions",
|
||||
action: (url, info, sessionID, output) =>
|
||||
{
|
||||
logger.info("MoreCheckmarks making productions request");
|
||||
|
||||
const production: IHideoutProduction = {};
|
||||
if(databaseServer && databaseServer.getTables() && databaseServer.getTables().hideout && databaseServer.getTables().hideout.production)
|
||||
{
|
||||
return JSON.stringify(databaseServer.getTables().hideout.production);
|
||||
}
|
||||
else
|
||||
{
|
||||
return JSON.stringify(production);
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"custom-static-MoreCheckmarksRoutes"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
protected questIsForOtherSide(playerSide: string, questId: string): boolean
|
||||
{
|
||||
const isUsec = playerSide.toLowerCase() === "usec";
|
||||
if (isUsec && this.questConfig.bearOnlyQuests.includes(questId))
|
||||
{
|
||||
// player is usec and quest is bear only, skip
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isUsec && this.questConfig.usecOnlyQuests.includes(questId))
|
||||
{
|
||||
// player is bear and quest is usec only, skip
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
module.exports = {mod: new Mod()}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Kevin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,14 @@
|
||||
# IMPORTANT
|
||||
# PLEASE READ THE MOD PAGE FOR THE MOST UP TO DATE INFORMATION!
|
||||
# tbh I hardly update this so please check the mod page for info/faq/questions!
|
||||
#
|
||||
#
|
||||
|
||||
# SWAG + Donuts
|
||||

|
||||
|
||||
**All credit goes to Props, creator of SWAG and DONUTS**
|
||||
|
||||
---
|
||||
|
||||
All mod info can be found on the mod page: https://hub.sp-tarkov.com/files/file/878-swag-donuts-dynamic-spawn-waves-and-custom-spawn-points/
|
||||
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"TotalBossesPerMap": {
|
||||
"factory": 1,
|
||||
"factory_night": 1,
|
||||
"customs": 1,
|
||||
"woods": 1,
|
||||
"shoreline": 1,
|
||||
"lighthouse": 1,
|
||||
"reserve": 1,
|
||||
"interchange": 1,
|
||||
"laboratory": 1,
|
||||
"streets": 1
|
||||
},
|
||||
"Bosses": {
|
||||
"useGlobalBossSpawnChance": true,
|
||||
"gluhar": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 35,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"goons": {
|
||||
"customs": 35,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 30,
|
||||
"reserve": 0,
|
||||
"shoreline": 35,
|
||||
"streets": 0,
|
||||
"woods": 35
|
||||
},
|
||||
"kaban": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 35,
|
||||
"woods": 0
|
||||
},
|
||||
"killa": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 35,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"kolontay": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 35,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 35,
|
||||
"woods": 0
|
||||
},
|
||||
"reshala": {
|
||||
"customs": 35,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"sanitar": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 25,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"shturman": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 15
|
||||
},
|
||||
"tagilla": {
|
||||
"customs": 0,
|
||||
"factory": 35,
|
||||
"factory_night": 35,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"zryachiy": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 100,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
}
|
||||
},
|
||||
"CustomBosses": {
|
||||
"santa": {
|
||||
"enabled": true,
|
||||
"forceSpawnOutsideEvent": false,
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"punisher": {
|
||||
"enabled": false,
|
||||
"useProgressSpawnChance": true,
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"legion": {
|
||||
"enabled": false,
|
||||
"useProgressSpawnChance": true,
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": [
|
||||
"ZoneRailStrorage",
|
||||
"ZonePTOR2",
|
||||
"ZoneBarrack"
|
||||
],
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
],
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharassault",
|
||||
"BossName": "bossgluhar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharsecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": [
|
||||
"ZoneScavBase"
|
||||
]
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": [
|
||||
"Zone_Chalet",
|
||||
"Zone_TreatmentContainers"
|
||||
]
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": [
|
||||
"ZoneMeteoStation",
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
]
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossName": "bossknight",
|
||||
"BossChance": 20,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "exusec",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbigpipe"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerbirdeye"
|
||||
}
|
||||
],
|
||||
"BossZone": [
|
||||
"ZoneScavBase2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
],
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": [
|
||||
"ZoneCarShowroom"
|
||||
],
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
},
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "bossboarsniper",
|
||||
"BossName": "bossboarsniper",
|
||||
"BossZone": [
|
||||
"ZoneSnipeCarShowroom"
|
||||
],
|
||||
"Time": 99999,
|
||||
"TriggerId": "BossBoarBorn",
|
||||
"TriggerName": "botEvent"
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 11,
|
||||
"BossEscortAmount": "6",
|
||||
"BossEscortType": "followerboar",
|
||||
"BossName": "bossboar",
|
||||
"BossZone": null,
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerboar"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose1"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "followerboarclose2"
|
||||
}
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": [
|
||||
"ZoneCenterBot",
|
||||
"ZoneCenter",
|
||||
"ZoneOLI",
|
||||
"ZoneIDEA",
|
||||
"ZoneGoshan"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followertagilla",
|
||||
"BossName": "bosskilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
]
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": [
|
||||
"ZoneClimova",
|
||||
"ZoneMvd"
|
||||
]
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossName": "bosskolontay",
|
||||
"BossChance": 35,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity",
|
||||
"Supports": [
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontayassault"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkolontaysecurity"
|
||||
},
|
||||
{
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followergluharscout"
|
||||
}
|
||||
],
|
||||
"BossZone": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": [
|
||||
"ZoneDormitory",
|
||||
"ZoneGasStation"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bossbully",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2",
|
||||
"ZonePort"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 5,
|
||||
"BossEscortAmount": "2,3",
|
||||
"BossEscortType": "followersanitar",
|
||||
"BossName": "bosssanitar",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerkojaniy",
|
||||
"BossName": "bosskojaniy",
|
||||
"BossZone": [
|
||||
"ZoneWoodCutter"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": [
|
||||
"BotZone"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": [
|
||||
"BotZone"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": [
|
||||
"ZoneSanatorium1",
|
||||
"ZoneSanatorium2"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 30,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "followerbully",
|
||||
"BossName": "bosstagilla",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": [
|
||||
"Zone_Island"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 0,
|
||||
"BossEscortAmount": "2",
|
||||
"BossEscortType": "followerzryachiy",
|
||||
"BossName": "bosszryachiy",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"BossDifficulty": "normal",
|
||||
"BossEscortDifficulty": "normal",
|
||||
"disableAllSpawns": {
|
||||
"bosses": false,
|
||||
"rogues": false,
|
||||
"raiders": false,
|
||||
"cultists": false,
|
||||
"scav_snipers": false,
|
||||
"bloodhounds": false
|
||||
},
|
||||
"Spawns": {
|
||||
"useGlobalSpawnChance": true,
|
||||
"bloodhounds": {
|
||||
"customs": 5,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 5,
|
||||
"streets": 0,
|
||||
"woods": 5
|
||||
},
|
||||
"cultists": {
|
||||
"customs": 10,
|
||||
"factory": 0,
|
||||
"factory_night": 2,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 0,
|
||||
"reserve": 0,
|
||||
"shoreline": 10,
|
||||
"streets": 0,
|
||||
"woods": 12
|
||||
},
|
||||
"raiders": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 65,
|
||||
"lighthouse": 0,
|
||||
"reserve": 65,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"rogues": {
|
||||
"customs": 0,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 75,
|
||||
"reserve": 0,
|
||||
"shoreline": 0,
|
||||
"streets": 0,
|
||||
"woods": 0
|
||||
},
|
||||
"scav_snipers": {
|
||||
"customs": 65,
|
||||
"factory": 0,
|
||||
"factory_night": 0,
|
||||
"groundzero": 0,
|
||||
"interchange": 0,
|
||||
"laboratory": 0,
|
||||
"lighthouse": 65,
|
||||
"reserve": 0,
|
||||
"shoreline": 65,
|
||||
"streets": 65,
|
||||
"woods": 65
|
||||
}
|
||||
},
|
||||
"MaxBotCap": {
|
||||
"factory": 16,
|
||||
"customs": 24,
|
||||
"woods": 26,
|
||||
"shoreline": 24,
|
||||
"lighthouse": 26,
|
||||
"reserve": 24,
|
||||
"interchange": 24,
|
||||
"laboratory": 18,
|
||||
"streets": 26,
|
||||
"groundzero": 22
|
||||
},
|
||||
"NightMaxBotCap": {
|
||||
"factory_night": 16,
|
||||
"customs": 24,
|
||||
"woods": 26,
|
||||
"shoreline": 24,
|
||||
"lighthouse": 26,
|
||||
"reserve": 24,
|
||||
"interchange": 24,
|
||||
"laboratory": 18,
|
||||
"streets": 26,
|
||||
"groundzero": 22
|
||||
},
|
||||
"UseDefaultSpawns": {
|
||||
"Waves": false,
|
||||
"Bosses": false,
|
||||
"TriggeredWaves": false
|
||||
},
|
||||
"DebugOutput": false
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "bosslegion",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 15,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "exusec",
|
||||
"BossDifficult": "impossible",
|
||||
"BossEscortDifficult": "impossible",
|
||||
"BossName": "bosspunisher",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossName": "gifter",
|
||||
"BossEscortType": "gifter",
|
||||
"BossEscortAmount": "0",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": [
|
||||
"ZoneBrige",
|
||||
"ZoneBlockPost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossEscortAmount": "3,4",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZoneStartVillage",
|
||||
"ZoneGasStation",
|
||||
"ZonePowerStation"
|
||||
]
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "3,4",
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossName": "arenafighterevent",
|
||||
"BossChance": 5,
|
||||
"BossEscortType": "arenafighterevent",
|
||||
"BossEscortAmount": "3,4",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZoneClearVill",
|
||||
"ZoneRoad",
|
||||
"ZoneHouse",
|
||||
"ZoneBigRocks"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": [
|
||||
"ZoneCustoms",
|
||||
"ZoneWade",
|
||||
"ZoneCrossRoad",
|
||||
"ZoneOldAZS"
|
||||
]
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 55,
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossEscortAmount": "2",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"BotZone"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 55,
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossEscortAmount": "2",
|
||||
"Time": 300,
|
||||
"BossZone": [
|
||||
"BotZone"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 55,
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossEscortAmount": "2",
|
||||
"Time": 600,
|
||||
"BossZone": [
|
||||
"BotZone"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 55,
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossEscortAmount": "2",
|
||||
"Time": 900,
|
||||
"BossZone": [
|
||||
"BotZone"
|
||||
]
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossEscortAmount": "3",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZoneForestGasStation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossEscortAmount": "3",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZoneForestSpawn"
|
||||
]
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "4",
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossName": "sectantpriest",
|
||||
"BossChance": 15,
|
||||
"BossEscortType": "sectantwarrior",
|
||||
"BossEscortAmount": "4",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZoneMiniHouse",
|
||||
"ZoneBrokenVill"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 55,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor1"
|
||||
],
|
||||
"Time": 900
|
||||
},
|
||||
{
|
||||
"BossChance": 55,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor2"
|
||||
],
|
||||
"Time": 300
|
||||
},
|
||||
{
|
||||
"BossChance": 45,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneBasement"
|
||||
],
|
||||
"Time": 450,
|
||||
"TriggerId": "autoId_00008_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 45,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneBasement"
|
||||
],
|
||||
"Time": 800,
|
||||
"TriggerId": "autoId_00010_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 40,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneBasement"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00007_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 45,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor2"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00007_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor1"
|
||||
],
|
||||
"Time": 600,
|
||||
"TriggerId": "autoId_00632_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor2"
|
||||
],
|
||||
"Time": 600,
|
||||
"TriggerId": "autoId_00632_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor1"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00012_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor2"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00012_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor1"
|
||||
],
|
||||
"Time": 1200,
|
||||
"TriggerId": "autoId_00014_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,2,2,1,1,2,2,2,2,1,1,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor2"
|
||||
],
|
||||
"Time": 1200,
|
||||
"TriggerId": "autoId_00014_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,2,2,1,1,2,2,2,2,1,1,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor1"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00009_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneFloor2"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00009_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 40,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneGate2"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00014_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 45,
|
||||
"BossEscortAmount": "1,1,1,1,2,2,2,1,1,1,1,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"BotZoneGate1"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00632_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"ZoneRailStrorage"
|
||||
],
|
||||
"Time": 1470
|
||||
},
|
||||
{
|
||||
"BossChance": 40,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"ZoneRailStrorage"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00632_EXFIL",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 40,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"ZoneSubCommand"
|
||||
],
|
||||
"Time": -1,
|
||||
"TriggerId": "autoId_00000_D2_LEVER",
|
||||
"TriggerName": "interactObject"
|
||||
},
|
||||
{
|
||||
"BossChance": 40,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": [
|
||||
"ZoneSubCommand"
|
||||
],
|
||||
"Time": 3,
|
||||
"TriggerId": "raider_simple_patroling",
|
||||
"TriggerName": "interactObject"
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 35,
|
||||
"BossEscortAmount": "2,2,2,2,3",
|
||||
"BossEscortType": "pmcbot",
|
||||
"BossName": "pmcbot",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": [
|
||||
"Zone_Blockpost"
|
||||
],
|
||||
"Time": 1
|
||||
},
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": [
|
||||
"Zone_RoofContainers"
|
||||
],
|
||||
"Time": 1
|
||||
},
|
||||
{
|
||||
"BossChance": 40,
|
||||
"BossEscortAmount": "1,1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": [
|
||||
"Zone_TreatmentRocks"
|
||||
],
|
||||
"Time": 1
|
||||
},
|
||||
{
|
||||
"BossChance": 40,
|
||||
"BossEscortAmount": "1,1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": [
|
||||
"Zone_TreatmentBeach"
|
||||
],
|
||||
"Time": 1
|
||||
},
|
||||
{
|
||||
"BossChance": 80,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": [
|
||||
"Zone_RoofRocks"
|
||||
],
|
||||
"Time": 1
|
||||
},
|
||||
{
|
||||
"BossChance": 80,
|
||||
"BossEscortAmount": "1",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": [
|
||||
"Zone_RoofBeach"
|
||||
],
|
||||
"Time": 1
|
||||
},
|
||||
{
|
||||
"BossChance": 10,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": [
|
||||
"Zone_Hellicopter"
|
||||
],
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossChance": 100,
|
||||
"BossEscortAmount": "1,2",
|
||||
"BossEscortType": "exusec",
|
||||
"BossName": "exusec",
|
||||
"BossZone": null,
|
||||
"Time": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"customs": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSnipeTower"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSnipeFactory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneBlockPostSniper"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSnipeBrige"
|
||||
]
|
||||
}
|
||||
],
|
||||
"factory": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"factory_night": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"groundzero": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSandSnipeCenter"
|
||||
]
|
||||
}
|
||||
],
|
||||
"interchange": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"laboratory": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"lighthouse": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"Zone_SniperPeak"
|
||||
]
|
||||
}
|
||||
],
|
||||
"reserve": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": null
|
||||
}
|
||||
],
|
||||
"shoreline": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"BossEscortType": "marksman",
|
||||
"BossEscortAmount": "0",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZonePowerStationSniper"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"BossEscortType": "marksman",
|
||||
"BossEscortAmount": "0",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZoneBunkeSniper"
|
||||
]
|
||||
}
|
||||
],
|
||||
"streets": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSnipeCinema"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSnipeCarShowroom"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSnipeBuilding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"Time": 1,
|
||||
"BossEscortAmount": "0",
|
||||
"BossEscortType": "marksman",
|
||||
"BossZone": [
|
||||
"ZoneSnipeSW01"
|
||||
]
|
||||
}
|
||||
],
|
||||
"woods": [
|
||||
{
|
||||
"BossName": "marksman",
|
||||
"BossChance": 65,
|
||||
"BossEscortType": "marksman",
|
||||
"BossEscortAmount": "0",
|
||||
"Time": 1,
|
||||
"BossZone": [
|
||||
"ZoneHighRocks"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "SWAG + DONUTS",
|
||||
"version": "3.3.6",
|
||||
"main": "src/SWAG.js",
|
||||
"license": "MIT",
|
||||
"author": "nooky and props",
|
||||
"akiVersion": "~3.8.0",
|
||||
"scripts": {
|
||||
"setup": "npm i",
|
||||
"build": "node ./packageBuild.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"bestzip": "2.2.1",
|
||||
"eslint": "8.30.0",
|
||||
"fs-extra": "11.1.0",
|
||||
"glob": "8.0.3",
|
||||
"tsyringe": "4.7.0",
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.aiAmountProper = exports.validMaps = exports.pmcType = exports.diffProper = exports.reverseMapNames = exports.reverseBossNames = exports.roleCase = exports.Props = exports.Center = exports.ColliderParams = exports.Position = exports.SpawnPointParam = exports.MapWrapper = exports.GroupPattern = exports.Bot = void 0;
|
||||
class Bot {
|
||||
BotType;
|
||||
MaxBotCount;
|
||||
}
|
||||
exports.Bot = Bot;
|
||||
class GroupPattern {
|
||||
Name;
|
||||
Bots;
|
||||
Time_min;
|
||||
Time_max;
|
||||
BotZone;
|
||||
RandomTimeSpawn;
|
||||
OnlySpawnOnce;
|
||||
}
|
||||
exports.GroupPattern = GroupPattern;
|
||||
class MapWrapper {
|
||||
MapName;
|
||||
MapGroups;
|
||||
MapBosses;
|
||||
}
|
||||
exports.MapWrapper = MapWrapper;
|
||||
class SpawnPointParam {
|
||||
Id;
|
||||
Position;
|
||||
Rotation;
|
||||
Sides;
|
||||
Categories;
|
||||
Infiltration;
|
||||
DelayToCanSpawnSec;
|
||||
ColliderParams;
|
||||
BotZoneName;
|
||||
}
|
||||
exports.SpawnPointParam = SpawnPointParam;
|
||||
class Position {
|
||||
x;
|
||||
y;
|
||||
z;
|
||||
}
|
||||
exports.Position = Position;
|
||||
class ColliderParams {
|
||||
_parent;
|
||||
_props;
|
||||
}
|
||||
exports.ColliderParams = ColliderParams;
|
||||
class Center {
|
||||
x;
|
||||
y;
|
||||
z;
|
||||
}
|
||||
exports.Center = Center;
|
||||
class Props {
|
||||
Center;
|
||||
Radius;
|
||||
}
|
||||
exports.Props = Props;
|
||||
exports.roleCase = {
|
||||
assault: "assault",
|
||||
exusec: "exUsec",
|
||||
marksman: "marksman",
|
||||
pmcbot: "pmcBot",
|
||||
sectantpriest: "sectantPriest",
|
||||
sectantwarrior: "sectantWarrior",
|
||||
assaultgroup: "assaultGroup",
|
||||
bossbully: "bossBully",
|
||||
bosstagilla: "bossTagilla",
|
||||
bossgluhar: "bossGluhar",
|
||||
bosskilla: "bossKilla",
|
||||
bosskojaniy: "bossKojaniy",
|
||||
bosssanitar: "bossSanitar",
|
||||
bossboar: "bossBoar",
|
||||
bossboarsniper: "bossBoarSniper",
|
||||
bosskolontay: "bossKolontay",
|
||||
bosspunisher: "bosspunisher",
|
||||
bosslegion: "bosslegion",
|
||||
followerboar: "followerBoar",
|
||||
followerboarclose1: "followerBoarClose1",
|
||||
followerboarclose2: "followerBoarClose2",
|
||||
followerbully: "followerBully",
|
||||
followergluharassault: "followerGluharAssault",
|
||||
followergluharscout: "followerGluharScout",
|
||||
followergluharsecurity: "followerGluharSecurity",
|
||||
followergluharsnipe: "followerGluharSnipe",
|
||||
followerkojaniy: "followerKojaniy",
|
||||
followersanitar: "followerSanitar",
|
||||
followertagilla: "followerTagilla",
|
||||
followerkolontayassault: "followerKolontayAssault",
|
||||
followerkolontaysecurity: "followerKolontaySecurity",
|
||||
cursedassault: "cursedAssault",
|
||||
pmc: "pmc",
|
||||
usec: "usec",
|
||||
bear: "bear",
|
||||
sptbear: "sptBear",
|
||||
sptusec: "sptUsec",
|
||||
bosstest: "bossTest",
|
||||
followertest: "followerTest",
|
||||
gifter: "gifter",
|
||||
bossknight: "bossKnight",
|
||||
followerbigpipe: "followerBigPipe",
|
||||
followerbirdeye: "followerBirdEye",
|
||||
bosszryachiy: "bossZryachiy",
|
||||
followerzryachiy: "followerZryachiy",
|
||||
arenafighterevent: "arenaFighterEvent",
|
||||
crazyassaultevent: "crazyAssaultEvent"
|
||||
};
|
||||
exports.reverseBossNames = {
|
||||
bossboar: "kaban",
|
||||
bossbully: "reshala",
|
||||
bosstagilla: "tagilla",
|
||||
bossgluhar: "gluhar",
|
||||
bosskilla: "killa",
|
||||
bosskojaniy: "shturman",
|
||||
bosssanitar: "sanitar",
|
||||
bossknight: "goons",
|
||||
bosszryachiy: "zryachiy",
|
||||
bosskolontay: "kolontay",
|
||||
marksman: "scav_snipers",
|
||||
sectantpriest: "cultists",
|
||||
exusec: "rogues",
|
||||
pmcbot: "raiders",
|
||||
crazyassaultevent: "crazyscavs",
|
||||
arenafighterevent: "bloodhounds",
|
||||
bosspunisher: "punisher",
|
||||
bosslegion: "legion",
|
||||
gifter: "santa"
|
||||
};
|
||||
exports.reverseMapNames = {
|
||||
factory4_day: "factory",
|
||||
factory4_night: "factory_night",
|
||||
bigmap: "customs",
|
||||
woods: "woods",
|
||||
shoreline: "shoreline",
|
||||
lighthouse: "lighthouse",
|
||||
rezervbase: "reserve",
|
||||
interchange: "interchange",
|
||||
laboratory: "laboratory",
|
||||
tarkovstreets: "streets",
|
||||
sandbox: "groundzero"
|
||||
};
|
||||
exports.diffProper = {
|
||||
easy: "easy",
|
||||
asonline: "random",
|
||||
normal: "normal",
|
||||
hard: "hard",
|
||||
impossible: "impossible"
|
||||
};
|
||||
exports.pmcType = ["sptbear", "sptusec"];
|
||||
exports.validMaps = [
|
||||
"bigmap",
|
||||
"factory4_day",
|
||||
"factory4_night",
|
||||
"interchange",
|
||||
"laboratory",
|
||||
"lighthouse",
|
||||
"rezervbase",
|
||||
"shoreline",
|
||||
"tarkovstreets",
|
||||
"woods",
|
||||
"sandbox"
|
||||
];
|
||||
exports.aiAmountProper = {
|
||||
low: 0.5,
|
||||
asonline: 1,
|
||||
medium: 1,
|
||||
high: 2,
|
||||
horde: 4,
|
||||
};
|
||||
//# sourceMappingURL=ClassDef.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ClassDef.js","sourceRoot":"","sources":["ClassDef.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
BossLocationSpawn,
|
||||
} from "@spt-aki/models/eft/common/ILocationBase";
|
||||
|
||||
export interface BossPattern extends BossLocationSpawn {
|
||||
OnlySpawnOnce?: boolean;
|
||||
}
|
||||
|
||||
export class Bot {
|
||||
BotType: string;
|
||||
MaxBotCount: number;
|
||||
}
|
||||
|
||||
export class GroupPattern {
|
||||
Name: string;
|
||||
Bots: Bot[];
|
||||
Time_min: number;
|
||||
Time_max: number;
|
||||
BotZone: string;
|
||||
RandomTimeSpawn?: boolean;
|
||||
OnlySpawnOnce?: boolean;
|
||||
}
|
||||
|
||||
export class MapWrapper {
|
||||
MapName: string;
|
||||
MapGroups: GroupPattern[];
|
||||
MapBosses: BossPattern[];
|
||||
}
|
||||
|
||||
export class SpawnPointParam {
|
||||
Id: string;
|
||||
Position: Position;
|
||||
Rotation: number;
|
||||
Sides: string[];
|
||||
Categories: string[];
|
||||
Infiltration: string;
|
||||
DelayToCanSpawnSec: number;
|
||||
ColliderParams: ColliderParams;
|
||||
BotZoneName: string;
|
||||
}
|
||||
|
||||
export class Position {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export class ColliderParams {
|
||||
_parent: string;
|
||||
_props: Props;
|
||||
}
|
||||
|
||||
export class Center {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export class Props {
|
||||
Center: Center;
|
||||
Radius: number;
|
||||
}
|
||||
|
||||
export const roleCase: object = {
|
||||
assault: "assault",
|
||||
exusec: "exUsec",
|
||||
marksman: "marksman",
|
||||
pmcbot: "pmcBot",
|
||||
sectantpriest: "sectantPriest",
|
||||
sectantwarrior: "sectantWarrior",
|
||||
assaultgroup: "assaultGroup",
|
||||
bossbully: "bossBully",
|
||||
bosstagilla: "bossTagilla",
|
||||
bossgluhar: "bossGluhar",
|
||||
bosskilla: "bossKilla",
|
||||
bosskojaniy: "bossKojaniy",
|
||||
bosssanitar: "bossSanitar",
|
||||
bossboar: "bossBoar",
|
||||
bossboarsniper: "bossBoarSniper",
|
||||
bosskolontay: "bossKolontay",
|
||||
bosspunisher: "bosspunisher",
|
||||
bosslegion: "bosslegion",
|
||||
followerboar: "followerBoar",
|
||||
followerboarclose1: "followerBoarClose1",
|
||||
followerboarclose2: "followerBoarClose2",
|
||||
followerbully: "followerBully",
|
||||
followergluharassault: "followerGluharAssault",
|
||||
followergluharscout: "followerGluharScout",
|
||||
followergluharsecurity: "followerGluharSecurity",
|
||||
followergluharsnipe: "followerGluharSnipe",
|
||||
followerkojaniy: "followerKojaniy",
|
||||
followersanitar: "followerSanitar",
|
||||
followertagilla: "followerTagilla",
|
||||
followerkolontayassault: "followerKolontayAssault",
|
||||
followerkolontaysecurity: "followerKolontaySecurity",
|
||||
cursedassault: "cursedAssault",
|
||||
pmc: "pmc",
|
||||
usec: "usec",
|
||||
bear: "bear",
|
||||
sptbear: "sptBear",
|
||||
sptusec: "sptUsec",
|
||||
bosstest: "bossTest",
|
||||
followertest: "followerTest",
|
||||
gifter: "gifter",
|
||||
bossknight: "bossKnight",
|
||||
followerbigpipe: "followerBigPipe",
|
||||
followerbirdeye: "followerBirdEye",
|
||||
bosszryachiy: "bossZryachiy",
|
||||
followerzryachiy: "followerZryachiy",
|
||||
arenafighterevent: "arenaFighterEvent",
|
||||
crazyassaultevent: "crazyAssaultEvent"
|
||||
};
|
||||
|
||||
export const reverseBossNames: object = {
|
||||
bossboar: "kaban",
|
||||
bossbully: "reshala",
|
||||
bosstagilla: "tagilla",
|
||||
bossgluhar: "gluhar",
|
||||
bosskilla: "killa",
|
||||
bosskojaniy: "shturman",
|
||||
bosssanitar: "sanitar",
|
||||
bossknight: "goons",
|
||||
bosszryachiy: "zryachiy",
|
||||
bosskolontay: "kolontay",
|
||||
marksman: "scav_snipers",
|
||||
sectantpriest: "cultists",
|
||||
exusec: "rogues",
|
||||
pmcbot: "raiders",
|
||||
crazyassaultevent: "crazyscavs",
|
||||
arenafighterevent: "bloodhounds",
|
||||
bosspunisher: "punisher",
|
||||
bosslegion: "legion",
|
||||
gifter: "santa"
|
||||
};
|
||||
|
||||
export const reverseMapNames: object = {
|
||||
factory4_day: "factory",
|
||||
factory4_night: "factory_night",
|
||||
bigmap: "customs",
|
||||
woods: "woods",
|
||||
shoreline: "shoreline",
|
||||
lighthouse: "lighthouse",
|
||||
rezervbase: "reserve",
|
||||
interchange: "interchange",
|
||||
laboratory: "laboratory",
|
||||
tarkovstreets: "streets",
|
||||
sandbox: "groundzero"
|
||||
};
|
||||
|
||||
export const diffProper = {
|
||||
easy: "easy",
|
||||
asonline: "random",
|
||||
normal: "normal",
|
||||
hard: "hard",
|
||||
impossible: "impossible"
|
||||
};
|
||||
|
||||
export const pmcType: string[] = ["sptbear", "sptusec"];
|
||||
|
||||
export const validMaps: string[] = [
|
||||
"bigmap",
|
||||
"factory4_day",
|
||||
"factory4_night",
|
||||
"interchange",
|
||||
"laboratory",
|
||||
"lighthouse",
|
||||
"rezervbase",
|
||||
"shoreline",
|
||||
"tarkovstreets",
|
||||
"woods",
|
||||
"sandbox"
|
||||
];
|
||||
|
||||
export const aiAmountProper = {
|
||||
low: 0.5,
|
||||
asonline: 1,
|
||||
medium: 1,
|
||||
high: 2,
|
||||
horde: 4,
|
||||
};
|
||||
@@ -0,0 +1,599 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ConfigTypes_1 = require("/snapshot/project/obj/models/enums/ConfigTypes");
|
||||
const ContextVariableType_1 = require("/snapshot/project/obj/context/ContextVariableType");
|
||||
const JsonUtil_1 = require("/snapshot/project/obj/utils/JsonUtil");
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const ClassDef = __importStar(require("./ClassDef"));
|
||||
const ClassDef_1 = require("./ClassDef");
|
||||
// General SWAG Config
|
||||
const config_json_1 = __importDefault(require("../config/config.json"));
|
||||
const bossConfig_json_1 = __importDefault(require("../config/bossConfig.json"));
|
||||
// Bosses
|
||||
const gluhar_json_1 = __importDefault(require("../config/bosses/gluhar.json"));
|
||||
const goons_json_1 = __importDefault(require("../config/bosses/goons.json"));
|
||||
const kaban_json_1 = __importDefault(require("../config/bosses/kaban.json"));
|
||||
const killa_json_1 = __importDefault(require("../config/bosses/killa.json"));
|
||||
const kolontay_json_1 = __importDefault(require("../config/bosses/kolontay.json"));
|
||||
const reshala_json_1 = __importDefault(require("../config/bosses/reshala.json"));
|
||||
const sanitar_json_1 = __importDefault(require("../config/bosses/sanitar.json"));
|
||||
const shturman_json_1 = __importDefault(require("../config/bosses/shturman.json"));
|
||||
const tagilla_json_1 = __importDefault(require("../config/bosses/tagilla.json"));
|
||||
const zryachiy_json_1 = __importDefault(require("../config/bosses/zryachiy.json"));
|
||||
// Spawn Configs
|
||||
const bloodhounds_json_1 = __importDefault(require("../config/other/bloodhounds.json"));
|
||||
const cultists_json_1 = __importDefault(require("../config/other/cultists.json"));
|
||||
const raiders_json_1 = __importDefault(require("../config/other/raiders.json"));
|
||||
const rogues_json_1 = __importDefault(require("../config/other/rogues.json"));
|
||||
const scav_snipers_json_1 = __importDefault(require("../config/other/scav_snipers.json"));
|
||||
// Custom
|
||||
const punisher_json_1 = __importDefault(require("../config/custom/punisher.json"));
|
||||
const legion_json_1 = __importDefault(require("../config/custom/legion.json"));
|
||||
const otherSpawnConfigs = [
|
||||
bloodhounds_json_1.default,
|
||||
cultists_json_1.default,
|
||||
scav_snipers_json_1.default,
|
||||
raiders_json_1.default,
|
||||
rogues_json_1.default
|
||||
];
|
||||
const bossSpawnConfigs = [
|
||||
gluhar_json_1.default,
|
||||
goons_json_1.default,
|
||||
kaban_json_1.default,
|
||||
killa_json_1.default,
|
||||
kolontay_json_1.default,
|
||||
reshala_json_1.default,
|
||||
sanitar_json_1.default,
|
||||
shturman_json_1.default,
|
||||
tagilla_json_1.default,
|
||||
zryachiy_json_1.default
|
||||
];
|
||||
const customSpawnConfigs = [
|
||||
punisher_json_1.default,
|
||||
legion_json_1.default
|
||||
];
|
||||
const modName = "SWAG";
|
||||
let logger;
|
||||
let LocationCallbacks;
|
||||
LocationCallbacks;
|
||||
let jsonUtil;
|
||||
JsonUtil_1.JsonUtil;
|
||||
let botConfig;
|
||||
let pmcConfig;
|
||||
let iGlobals;
|
||||
let configServer;
|
||||
let databaseServer;
|
||||
let locations;
|
||||
let seasonalEvents;
|
||||
let randomUtil;
|
||||
let profileHelper;
|
||||
let sessionId;
|
||||
let BossWaveSpawnedOnceAlready;
|
||||
const customPatterns = {};
|
||||
class SWAG {
|
||||
static savedLocationData = {
|
||||
factory4_day: undefined,
|
||||
factory4_night: undefined,
|
||||
bigmap: undefined,
|
||||
interchange: undefined,
|
||||
laboratory: undefined,
|
||||
lighthouse: undefined,
|
||||
rezervbase: undefined,
|
||||
shoreline: undefined,
|
||||
tarkovstreets: undefined,
|
||||
woods: undefined,
|
||||
sandbox: undefined,
|
||||
// unused
|
||||
develop: undefined,
|
||||
hideout: undefined,
|
||||
privatearea: undefined,
|
||||
suburbs: undefined,
|
||||
terminal: undefined,
|
||||
town: undefined,
|
||||
};
|
||||
static pmcType = ["sptbear", "sptusec"];
|
||||
static randomWaveTimer = {
|
||||
time_min: 0,
|
||||
time_max: 0,
|
||||
};
|
||||
static actual_timers = {
|
||||
time_min: 0,
|
||||
time_max: 0,
|
||||
};
|
||||
static waveCounter = {
|
||||
count: 1,
|
||||
};
|
||||
static raid_time = {
|
||||
time_of_day: "day",
|
||||
};
|
||||
static bossCount = {
|
||||
count: 0,
|
||||
};
|
||||
preAkiLoad(container) {
|
||||
const HttpResponse = container.resolve("HttpResponseUtil");
|
||||
const staticRouterModService = container.resolve("StaticRouterModService");
|
||||
staticRouterModService.registerStaticRouter(`${modName}/client/match/offline/end`, [
|
||||
{
|
||||
url: "/client/match/offline/end",
|
||||
action: (url, info, sessionID, output) => {
|
||||
sessionId = sessionID;
|
||||
SWAG.ClearDefaultSpawns();
|
||||
SWAG.ConfigureMaps();
|
||||
return LocationCallbacks.getLocationData(url, info, sessionID);
|
||||
},
|
||||
},
|
||||
], "SWAG");
|
||||
staticRouterModService.registerStaticRouter(`${modName}/client/locations`, [
|
||||
{
|
||||
url: "/client/locations",
|
||||
action: (url, info, sessionID, output) => {
|
||||
sessionId = sessionID;
|
||||
SWAG.ClearDefaultSpawns();
|
||||
SWAG.ConfigureMaps();
|
||||
return LocationCallbacks.getLocationData(url, info, sessionID);
|
||||
},
|
||||
},
|
||||
], "SWAG");
|
||||
staticRouterModService.registerStaticRouter(`${modName}/client/items`, [
|
||||
{
|
||||
url: "/client/items",
|
||||
action: (url, info, sessionID, output) => {
|
||||
sessionId = sessionID;
|
||||
const locationConfig = container.resolve("ConfigServer").getConfig(ConfigTypes_1.ConfigTypes.LOCATION);
|
||||
// as of SPT 3.6.0 we need to disable the new spawn system so that SWAG can clear spawns properly
|
||||
if (!config_json_1.default?.UseDefaultSpawns?.Waves ||
|
||||
!config_json_1.default?.UseDefaultSpawns?.Bosses ||
|
||||
!config_json_1.default?.UseDefaultSpawns?.TriggeredWaves) {
|
||||
SWAG.disableSpawnSystems();
|
||||
}
|
||||
// disable more vanilla spawn stuff
|
||||
locationConfig.splitWaveIntoSingleSpawnsSettings.enabled = false;
|
||||
locationConfig.rogueLighthouseSpawnTimeSettings.enabled = false;
|
||||
locationConfig.fixEmptyBotWavesSettings.enabled = false;
|
||||
locationConfig.addOpenZonesToAllMaps = false;
|
||||
locationConfig.addCustomBotWavesToMaps = false;
|
||||
locationConfig.enableBotTypeLimits = false;
|
||||
logger.info("SWAG: Vanilla spawn systems disabled");
|
||||
return output;
|
||||
},
|
||||
},
|
||||
], "SWAG");
|
||||
staticRouterModService.registerStaticRouter(`${modName}/client/raid/configuration`, [{
|
||||
url: "/client/raid/configuration",
|
||||
action: (url, info, sessionID, output) => {
|
||||
try {
|
||||
// Retrieve configurations
|
||||
const botConfig = container.resolve("ConfigServer").getConfig(ConfigTypes_1.ConfigTypes.BOT);
|
||||
const pmcConfig = container.resolve("ConfigServer").getConfig(ConfigTypes_1.ConfigTypes.PMC);
|
||||
// Disable PMC conversion
|
||||
const conversionTypes = ["assault", "cursedassault", "pmcbot", "exusec", "arenafighter", "arenafighterevent", "crazyassaultevent"];
|
||||
conversionTypes.forEach(type => {
|
||||
pmcConfig.convertIntoPmcChance[type] = { min: 0, max: 0 };
|
||||
});
|
||||
logger.info("SWAG: PMC conversion is OFF (this is good - be sure this loads AFTER Realism/SVM)");
|
||||
// Adjust time and map caps
|
||||
const appContext = container.resolve("ApplicationContext");
|
||||
const weatherController = container.resolve("WeatherController");
|
||||
const matchInfoStartOff = appContext.getLatestValue(ContextVariableType_1.ContextVariableType.RAID_CONFIGURATION).getValue();
|
||||
const time = weatherController.generate().time;
|
||||
let realTime = time;
|
||||
if (matchInfoStartOff.timeVariant === "PAST") {
|
||||
let [hours, minutes] = time.split(":").map(Number);
|
||||
hours = (hours - 12 + 24) % 24; // Adjust time backwards by 12 hours and ensure it wraps correctly
|
||||
realTime = `${hours}:${minutes}`;
|
||||
}
|
||||
// Determine Time of Day
|
||||
let TOD = "day";
|
||||
let [hours] = realTime.split(":").map(Number);
|
||||
if ((matchInfoStartOff.location !== "factory4_night" && hours >= 5 && hours < 22) ||
|
||||
matchInfoStartOff.location === "factory4_day" ||
|
||||
matchInfoStartOff.location.toLowerCase() === "laboratory") {
|
||||
TOD = "day";
|
||||
}
|
||||
else {
|
||||
TOD = "night";
|
||||
}
|
||||
// Set map caps based on Time of Day
|
||||
if (TOD === "day") {
|
||||
Object.keys(config_json_1.default.MaxBotCap).forEach(key => {
|
||||
botConfig.maxBotCap[key] = config_json_1.default.MaxBotCap[key];
|
||||
});
|
||||
}
|
||||
else { // "night"
|
||||
Object.keys(config_json_1.default.NightMaxBotCap).forEach(key => {
|
||||
botConfig.maxBotCap[key] = config_json_1.default.NightMaxBotCap[key];
|
||||
});
|
||||
}
|
||||
logger.info(`SWAG: ${TOD} Raid Max Bot Caps set`);
|
||||
return HttpResponse.nullResponse();
|
||||
}
|
||||
catch (e) {
|
||||
logger.error(`SWAG: Failed To modify PMC conversion, you may have more PMCs than you're supposed to. Error: ${e}`);
|
||||
return HttpResponse.nullResponse();
|
||||
}
|
||||
},
|
||||
}], "SWAG");
|
||||
}
|
||||
postDBLoad(container) {
|
||||
logger = container.resolve("WinstonLogger");
|
||||
LocationCallbacks =
|
||||
container.resolve("LocationCallbacks");
|
||||
jsonUtil = container.resolve("JsonUtil");
|
||||
configServer = container.resolve("ConfigServer");
|
||||
botConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.BOT);
|
||||
pmcConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.PMC);
|
||||
databaseServer = container.resolve("DatabaseServer");
|
||||
locations = databaseServer.getTables().locations;
|
||||
randomUtil = container.resolve("RandomUtil");
|
||||
seasonalEvents = container.resolve("SeasonalEventService");
|
||||
profileHelper = container.resolve("ProfileHelper");
|
||||
}
|
||||
/**
|
||||
* Returns all available OpenZones specified in location.base.OpenZones as well as any OpenZone found in the SpawnPointParams.
|
||||
* Filters out all sniper zones
|
||||
* @param map
|
||||
* @returns
|
||||
*/
|
||||
static GetOpenZones(map) {
|
||||
const baseobj = locations[map]?.base;
|
||||
// Get all OpenZones defined in the base obj that do not include sniper zones. Need to filter for empty strings as well.
|
||||
const foundOpenZones = baseobj?.OpenZones?.split(",")
|
||||
.filter((name) => !name.includes("Snipe"))
|
||||
.filter((name) => name.trim() !== "") ?? [];
|
||||
// Sometimes there are zones in the SpawnPointParams that arent listed in the OpenZones, parse these and add them to the list of zones
|
||||
baseobj?.SpawnPointParams?.forEach((spawn) => {
|
||||
//check spawn for open zones and if it doesn't exist add to end of array
|
||||
if (spawn?.BotZoneName &&
|
||||
!foundOpenZones.includes(spawn.BotZoneName) &&
|
||||
!spawn.BotZoneName.includes("Snipe")) {
|
||||
foundOpenZones.push(spawn.BotZoneName);
|
||||
}
|
||||
});
|
||||
//logger.info(`SWAG: Open Zones(${map}): ${JSON.stringify(foundOpenZones)}`);
|
||||
return foundOpenZones;
|
||||
}
|
||||
static shuffleArray(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
}
|
||||
static ConfigureMaps() {
|
||||
const bossConfigs = {};
|
||||
const otherConfigs = {};
|
||||
const customConfigs = {};
|
||||
bossSpawnConfigs.forEach(data => {
|
||||
Object.keys(data).forEach(mapKey => {
|
||||
if (bossConfig_json_1.default.TotalBossesPerMap[mapKey] === 0 || config_json_1.default.disableAllSpawns.bosses) {
|
||||
return;
|
||||
}
|
||||
if (!bossConfigs[mapKey]) {
|
||||
bossConfigs[mapKey] = [];
|
||||
}
|
||||
const filteredBosses = data[mapKey].filter(boss => {
|
||||
// ignore boarsniper
|
||||
if (boss.BossName == "bossboarsniper") {
|
||||
return false;
|
||||
}
|
||||
const shouldSkip = boss.BossChance === 0 ||
|
||||
(bossConfig_json_1.default.Bosses.useGlobalBossSpawnChance &&
|
||||
bossConfig_json_1.default.Bosses[ClassDef_1.reverseBossNames[boss.BossName]][mapKey] === 0);
|
||||
return !shouldSkip;
|
||||
});
|
||||
bossConfigs[mapKey].push(...filteredBosses);
|
||||
});
|
||||
});
|
||||
otherSpawnConfigs.forEach(data => {
|
||||
Object.keys(data).forEach(mapKey => {
|
||||
if (!otherConfigs[mapKey]) {
|
||||
otherConfigs[mapKey] = [];
|
||||
}
|
||||
const filteredBosses = data[mapKey].filter(boss => {
|
||||
const bossType = ClassDef_1.reverseBossNames[boss.BossName];
|
||||
if (config_json_1.default.disableAllSpawns[bossType]) {
|
||||
return false;
|
||||
}
|
||||
const shouldSkip = boss.BossChance === 0 ||
|
||||
(config_json_1.default.Spawns.useGlobalSpawnChance && config_json_1.default.Spawns[bossType][mapKey] === 0);
|
||||
return !shouldSkip;
|
||||
});
|
||||
otherConfigs[mapKey].push(...filteredBosses);
|
||||
});
|
||||
});
|
||||
customSpawnConfigs.forEach(data => {
|
||||
Object.keys(data).forEach(mapKey => {
|
||||
if (!customConfigs[mapKey]) {
|
||||
customConfigs[mapKey] = [];
|
||||
}
|
||||
const filteredBosses = data[mapKey].filter(boss => {
|
||||
if (boss.BossName == "gifter") {
|
||||
if (!bossConfig_json_1.default.CustomBosses.santa.enabled ||
|
||||
(!seasonalEvents.christmasEventEnabled() && !bossConfig_json_1.default.CustomBosses.santa.forceSpawnOutsideEvent)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const shouldSkip = boss.BossChance === 0 ||
|
||||
!bossConfig_json_1.default.CustomBosses[ClassDef_1.reverseBossNames[boss.BossName]].enabled ||
|
||||
(bossConfig_json_1.default.CustomBosses[ClassDef_1.reverseBossNames[boss.BossName]].enabled &&
|
||||
bossConfig_json_1.default.CustomBosses[ClassDef_1.reverseBossNames[boss.BossName]][mapKey] === 0);
|
||||
return !shouldSkip;
|
||||
});
|
||||
customConfigs[mapKey].push(...filteredBosses);
|
||||
});
|
||||
});
|
||||
// Shuffle each array within the configuration objects
|
||||
Object.values(bossConfigs).forEach(array => this.shuffleArray(array));
|
||||
Object.values(otherConfigs).forEach(array => this.shuffleArray(array));
|
||||
Object.values(customConfigs).forEach(array => this.shuffleArray(array));
|
||||
ClassDef.validMaps.forEach((globalmap) => {
|
||||
if (bossConfigs[ClassDef_1.reverseMapNames[globalmap]]) {
|
||||
bossConfigs[ClassDef_1.reverseMapNames[globalmap]].forEach(boss => {
|
||||
SWAG.SpawnBosses(boss, globalmap);
|
||||
SWAG.bossCount.count += 1;
|
||||
});
|
||||
}
|
||||
// reset boss count for the next map
|
||||
SWAG.bossCount.count = 0;
|
||||
if (otherConfigs[ClassDef_1.reverseMapNames[globalmap]]) {
|
||||
otherConfigs[ClassDef_1.reverseMapNames[globalmap]].forEach(spawn => {
|
||||
SWAG.SpawnBots(spawn, globalmap);
|
||||
});
|
||||
}
|
||||
if (customConfigs[ClassDef_1.reverseMapNames[globalmap]]) {
|
||||
customConfigs[ClassDef_1.reverseMapNames[globalmap]].forEach(custom => {
|
||||
SWAG.SpawnCustom(custom, globalmap);
|
||||
});
|
||||
}
|
||||
logger.warning(`SWAG: Configured boss spawns for map ${globalmap}`);
|
||||
});
|
||||
}
|
||||
static SpawnBosses(boss, globalmap) {
|
||||
if (bossConfig_json_1.default.TotalBossesPerMap[ClassDef_1.reverseMapNames[globalmap]] == 0) {
|
||||
config_json_1.default.DebugOutput &&
|
||||
logger.info("SWAG: TotalBosses set to 0 for this map, skipping boss spawn");
|
||||
return;
|
||||
}
|
||||
else if (bossConfig_json_1.default.TotalBossesPerMap[ClassDef_1.reverseMapNames[globalmap]] != -1 && (SWAG.bossCount.count >= bossConfig_json_1.default.TotalBossesPerMap[ClassDef_1.reverseMapNames[globalmap]])) {
|
||||
config_json_1.default.DebugOutput &&
|
||||
logger.info("SWAG: Skipping boss spawn as total boss count has been met already");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
let wave = SWAG.ConfigureBossWave(boss, globalmap);
|
||||
locations[globalmap].base.BossLocationSpawn.push(wave);
|
||||
}
|
||||
}
|
||||
static SpawnBots(boss, globalmap) {
|
||||
let wave = SWAG.ConfigureBossWave(boss, globalmap);
|
||||
locations[globalmap].base.BossLocationSpawn.push(wave);
|
||||
}
|
||||
static SpawnCustom(boss, globalmap) {
|
||||
let wave = SWAG.ConfigureBossWave(boss, globalmap);
|
||||
locations[globalmap].base.BossLocationSpawn.push(wave);
|
||||
}
|
||||
static ConfigureBossWave(boss, globalmap) {
|
||||
let spawnChance = 0;
|
||||
let spawnZones = boss.BossZone || null;
|
||||
let bossName = ClassDef_1.roleCase[boss.BossName.toLowerCase()] || boss.BossName;
|
||||
const getRandomDifficulty = () => {
|
||||
const availableDifficulties = ["easy", "normal", "hard", "impossible"];
|
||||
const randomIndex = Math.floor(Math.random() * availableDifficulties.length);
|
||||
return availableDifficulties[randomIndex];
|
||||
};
|
||||
let difficultyKey = boss.BossDifficult || config_json_1.default.BossDifficulty.toLowerCase();
|
||||
let difficulty = difficultyKey === "asonline" ? getRandomDifficulty() : ClassDef_1.diffProper[difficultyKey];
|
||||
let escortDifficultyKey = boss.BossEscortDifficult || config_json_1.default.BossEscortDifficulty.toLowerCase();
|
||||
let escort_difficulty = escortDifficultyKey === "asonline" ? getRandomDifficulty() : ClassDef_1.diffProper[escortDifficultyKey];
|
||||
boss?.Supports?.forEach((escort) => {
|
||||
escort.BossEscortDifficult = [escort_difficulty];
|
||||
escort.BossEscortType = ClassDef_1.roleCase[escort.BossEscortType.toLowerCase()];
|
||||
});
|
||||
// exclusive to bosses only
|
||||
if (boss.BossName.startsWith("boss")) {
|
||||
spawnChance = this.adjustBossSpawnChance(boss, globalmap);
|
||||
}
|
||||
// something other than bosses
|
||||
else if (config_json_1.default.Spawns.useGlobalSpawnChance) {
|
||||
spawnChance = config_json_1.default.Spawns[ClassDef_1.reverseBossNames[boss.BossName]][ClassDef_1.reverseMapNames[globalmap]];
|
||||
}
|
||||
else {
|
||||
spawnChance = boss.BossChance || 0;
|
||||
}
|
||||
// zones
|
||||
if (spawnZones != null) {
|
||||
spawnZones = boss.BossZone || spawnZones;
|
||||
if (spawnZones.length > 1) {
|
||||
// let's just pick one zone, can't trust BSG to do this correctly
|
||||
let random_zone = SWAG.getRandIntInclusive(0, spawnZones.length - 1);
|
||||
spawnZones = spawnZones[random_zone];
|
||||
}
|
||||
// if it's not > 1 and not null, then we'll assume there's a single zone defined instead
|
||||
else {
|
||||
spawnZones = spawnZones[0];
|
||||
}
|
||||
}
|
||||
// Using the SPT class here
|
||||
const wave = {
|
||||
BossName: bossName,
|
||||
BossChance: spawnChance,
|
||||
BossZone: !!spawnZones
|
||||
? spawnZones
|
||||
: SWAG.savedLocationData[globalmap].openZones &&
|
||||
SWAG.savedLocationData[globalmap].openZones.length > 0
|
||||
? randomUtil.getStringArrayValue(SWAG.savedLocationData[globalmap].openZones)
|
||||
: "",
|
||||
BossPlayer: false,
|
||||
BossDifficult: difficulty,
|
||||
BossEscortType: ClassDef_1.roleCase[boss.BossEscortType.toLowerCase()],
|
||||
BossEscortDifficult: escort_difficulty,
|
||||
BossEscortAmount: boss.BossEscortAmount || "0",
|
||||
Time: boss.Time || -1,
|
||||
Supports: boss.Supports || null,
|
||||
RandomTimeSpawn: boss.RandomTimeSpawn || false,
|
||||
TriggerId: boss.TriggerId || "",
|
||||
TriggerName: boss.TriggerName || ""
|
||||
};
|
||||
if (spawnChance != 0) {
|
||||
config_json_1.default.DebugOutput && logger.warning(`Configured Boss Wave: ${JSON.stringify(wave)}`);
|
||||
}
|
||||
return wave;
|
||||
}
|
||||
static adjustBossSpawnChance(boss, globalmap) {
|
||||
// I need to refactor this garbage
|
||||
if (boss.BossName === "bosspunisher") {
|
||||
if (bossConfig_json_1.default.CustomBosses.punisher.enabled) {
|
||||
if (bossConfig_json_1.default.CustomBosses.punisher.useProgressSpawnChance) {
|
||||
const pmcProfile = profileHelper.getPmcProfile(sessionId);
|
||||
const profileId = pmcProfile?._id;
|
||||
const punisherBossProgressFilePath = path.resolve(__dirname, `../../WTT-RogueJustice/profiles/${profileId}/progress.json`);
|
||||
try {
|
||||
const progressData = JSON.parse(fs.readFileSync(punisherBossProgressFilePath, "utf8"));
|
||||
return progressData?.actualPunisherChance ?? 1;
|
||||
}
|
||||
catch (error) {
|
||||
logger.warning("SWAG: Unable to load Punisher Boss progress file, either you don't have the mod installed or you don't have a Punisher progress file yet.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if progress spawn chance is not enabled
|
||||
return bossConfig_json_1.default.CustomBosses["punisher"][ClassDef_1.reverseMapNames[globalmap]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if punisher is not enabled
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (boss.BossName === "bosslegion") {
|
||||
if (bossConfig_json_1.default.CustomBosses.legion.enabled) {
|
||||
if (bossConfig_json_1.default.CustomBosses.legion.useProgressSpawnChance) {
|
||||
const legionBossProgressFilePath = path.resolve(__dirname, "../../RaidOverhaul/config/LegionChance.json");
|
||||
try {
|
||||
const progressData = JSON.parse(fs.readFileSync(legionBossProgressFilePath, "utf8"));
|
||||
return progressData?.legionChance ?? 15;
|
||||
}
|
||||
catch (error) {
|
||||
logger.warning("SWAG: Unable to load Legion Boss progress file, either you don't have the mod installed or you deleted your LegionChance.json.");
|
||||
}
|
||||
}
|
||||
// if progress spawn chance is not enabled
|
||||
return bossConfig_json_1.default.CustomBosses["legion"][ClassDef_1.reverseMapNames[globalmap]];
|
||||
}
|
||||
// if legion is not enabled
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// all other bosses...
|
||||
else if (bossConfig_json_1.default.Bosses.useGlobalBossSpawnChance) {
|
||||
// edge case, only applies to Kaban
|
||||
if (boss.BossName == "bossboarsniper") {
|
||||
return boss.BossChance;
|
||||
}
|
||||
return bossConfig_json_1.default.Bosses[ClassDef_1.reverseBossNames[boss.BossName]][ClassDef_1.reverseMapNames[globalmap]];
|
||||
}
|
||||
// if global boss chance is not enabled
|
||||
else {
|
||||
return boss.BossChance;
|
||||
}
|
||||
}
|
||||
static getRandIntInclusive(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
static disableSpawnSystems() {
|
||||
let map;
|
||||
for (map in locations) {
|
||||
if (map === "base" || map === "hideout") {
|
||||
continue;
|
||||
}
|
||||
locations[map].base.OfflineNewSpawn = false;
|
||||
locations[map].base.OfflineOldSpawn = true;
|
||||
locations[map].base.NewSpawn = false;
|
||||
locations[map].base.OldSpawn = true;
|
||||
}
|
||||
}
|
||||
static ClearDefaultSpawns() {
|
||||
let map;
|
||||
for (map in locations) {
|
||||
if (map === "base" || map === "hideout") {
|
||||
continue;
|
||||
}
|
||||
// Save a backup of the wave data and the BossLocationSpawn to use when restoring defaults on raid end. Store openzones in this data as well
|
||||
if (!SWAG.savedLocationData[map]) {
|
||||
const locationBase = locations[map].base;
|
||||
SWAG.savedLocationData[map] = {
|
||||
waves: locationBase.waves,
|
||||
BossLocationSpawn: locationBase.BossLocationSpawn,
|
||||
openZones: this.GetOpenZones(map),
|
||||
};
|
||||
}
|
||||
// Reset Database, Cringe -- i stole this code from LUA
|
||||
locations[map].base.waves = [...SWAG.savedLocationData[map].waves];
|
||||
locations[map].base.BossLocationSpawn = [
|
||||
...SWAG.savedLocationData[map].BossLocationSpawn,
|
||||
];
|
||||
//Clear bots spawn
|
||||
if (!config_json_1.default?.UseDefaultSpawns?.Waves) {
|
||||
locations[map].base.waves = [];
|
||||
}
|
||||
//Clear boss spawn
|
||||
const bossLocationSpawn = locations[map].base.BossLocationSpawn;
|
||||
if (!config_json_1.default?.UseDefaultSpawns?.Bosses &&
|
||||
!config_json_1.default?.UseDefaultSpawns?.TriggeredWaves) {
|
||||
locations[map].base.BossLocationSpawn = [];
|
||||
}
|
||||
else {
|
||||
// Remove Default Boss Spawns
|
||||
if (!config_json_1.default?.UseDefaultSpawns?.Bosses) {
|
||||
for (let i = 0; i < bossLocationSpawn.length; i++) {
|
||||
// Triggered wave check
|
||||
if (bossLocationSpawn[i]?.TriggerName?.length === 0) {
|
||||
locations[map].base.BossLocationSpawn.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove Default Triggered Waves
|
||||
if (!config_json_1.default?.UseDefaultSpawns?.TriggeredWaves) {
|
||||
for (let i = 0; i < bossLocationSpawn.length; i++) {
|
||||
// Triggered wave check
|
||||
if (bossLocationSpawn[i]?.TriggerName?.length > 0) {
|
||||
locations[map].base.BossLocationSpawn.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = { mod: new SWAG() };
|
||||
//# sourceMappingURL=SWAG.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,789 @@
|
||||
import {
|
||||
BossLocationSpawn,
|
||||
ILocationBase,
|
||||
Wave,
|
||||
} from "@spt-aki/models/eft/common/ILocationBase";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { IPostDBLoadMod } from "@spt-aki/models/external/IPostDBLoadMod";
|
||||
import { IPreAkiLoadMod } from "@spt-aki/models/external/IPreAkiLoadMod";
|
||||
import { IBotConfig } from "@spt-aki/models/spt/config/IBotConfig";
|
||||
import { ILocations } from "@spt-aki/models/spt/server/ILocations";
|
||||
import { ILocationConfig } from "@spt-aki/models/spt/config/ILocationConfig";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { IGlobals } from "@spt-aki/models/eft/common/IGlobals";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { ContextVariableType } from "@spt-aki/context/ContextVariableType";
|
||||
import { ApplicationContext } from "@spt-aki/context/ApplicationContext";
|
||||
import { WeatherController } from "@spt-aki/controllers/WeatherController";
|
||||
import { IGetRaidConfigurationRequestData } from "@spt-aki/models/eft/match/IGetRaidConfigurationRequestData";
|
||||
import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil";
|
||||
import { StaticRouterModService } from "@spt-aki/services/mod/staticRouter/StaticRouterModService";
|
||||
import { JsonUtil } from "@spt-aki/utils/JsonUtil";
|
||||
import { RandomUtil } from "@spt-aki/utils/RandomUtil";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
import { LocationCallbacks } from "@spt-aki/callbacks/LocationCallbacks";
|
||||
import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService";
|
||||
import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper";
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as ClassDef from "./ClassDef";
|
||||
import {
|
||||
BossPattern,
|
||||
GroupPattern,
|
||||
aiAmountProper,
|
||||
diffProper,
|
||||
roleCase,
|
||||
reverseMapNames,
|
||||
reverseBossNames,
|
||||
} from "./ClassDef";
|
||||
|
||||
// General SWAG Config
|
||||
import config from "../config/config.json";
|
||||
import bossConfig from "../config/bossConfig.json";
|
||||
|
||||
// Bosses
|
||||
import gluhar from "../config/bosses/gluhar.json";
|
||||
import goons from "../config/bosses/goons.json";
|
||||
import kaban from "../config/bosses/kaban.json";
|
||||
import killa from "../config/bosses/killa.json";
|
||||
import kolontay from "../config/bosses/kolontay.json";
|
||||
import reshala from "../config/bosses/reshala.json";
|
||||
import sanitar from "../config/bosses/sanitar.json";
|
||||
import shturman from "../config/bosses/shturman.json";
|
||||
import tagilla from "../config/bosses/tagilla.json";
|
||||
import zryachiy from "../config/bosses/zryachiy.json";
|
||||
|
||||
// Spawn Configs
|
||||
import bloodhounds from "../config/other/bloodhounds.json";
|
||||
import cultists from "../config/other/cultists.json";
|
||||
import raiders from "../config/other/raiders.json";
|
||||
import rogues from "../config/other/rogues.json";
|
||||
import scav_snipers from "../config/other/scav_snipers.json";
|
||||
|
||||
// Custom
|
||||
import punisher from "../config/custom/punisher.json"
|
||||
import legion from "../config/custom/legion.json"
|
||||
|
||||
const otherSpawnConfigs = [
|
||||
bloodhounds,
|
||||
cultists,
|
||||
scav_snipers,
|
||||
raiders,
|
||||
rogues
|
||||
];
|
||||
|
||||
const bossSpawnConfigs = [
|
||||
gluhar,
|
||||
goons,
|
||||
kaban,
|
||||
killa,
|
||||
kolontay,
|
||||
reshala,
|
||||
sanitar,
|
||||
shturman,
|
||||
tagilla,
|
||||
zryachiy
|
||||
];
|
||||
|
||||
const customSpawnConfigs = [
|
||||
punisher,
|
||||
legion
|
||||
]
|
||||
|
||||
const modName = "SWAG";
|
||||
let logger: ILogger;
|
||||
let LocationCallbacks;
|
||||
LocationCallbacks;
|
||||
let jsonUtil;
|
||||
JsonUtil;
|
||||
let botConfig: IBotConfig;
|
||||
let pmcConfig: IBotConfig;
|
||||
let iGlobals: IGlobals;
|
||||
let configServer: ConfigServer;
|
||||
let databaseServer: DatabaseServer;
|
||||
let locations: ILocations;
|
||||
let seasonalEvents: SeasonalEventService;
|
||||
let randomUtil: RandomUtil;
|
||||
let profileHelper: ProfileHelper;
|
||||
let sessionId : string;
|
||||
let BossWaveSpawnedOnceAlready: boolean;
|
||||
|
||||
const customPatterns: Record<string, ClassDef.GroupPattern> = {};
|
||||
|
||||
type LocationName = keyof Omit<ILocations, "base">;
|
||||
type LocationBackupData = Record<
|
||||
LocationName,
|
||||
| {
|
||||
waves: Wave[];
|
||||
BossLocationSpawn: BossLocationSpawn[];
|
||||
openZones: string[];
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
|
||||
type GlobalPatterns = Record<string, MapPatterns>;
|
||||
type MapPatterns = {
|
||||
MapGroups: GroupPattern[];
|
||||
MapBosses: BossPattern[];
|
||||
};
|
||||
|
||||
class SWAG implements IPreAkiLoadMod, IPostDBLoadMod {
|
||||
public static savedLocationData: LocationBackupData = {
|
||||
factory4_day: undefined,
|
||||
factory4_night: undefined,
|
||||
bigmap: undefined,
|
||||
interchange: undefined,
|
||||
laboratory: undefined,
|
||||
lighthouse: undefined,
|
||||
rezervbase: undefined,
|
||||
shoreline: undefined,
|
||||
tarkovstreets: undefined,
|
||||
woods: undefined,
|
||||
sandbox: undefined,
|
||||
|
||||
// unused
|
||||
develop: undefined,
|
||||
hideout: undefined,
|
||||
privatearea: undefined,
|
||||
suburbs: undefined,
|
||||
terminal: undefined,
|
||||
town: undefined,
|
||||
};
|
||||
public static pmcType: string[] = ["sptbear", "sptusec"];
|
||||
|
||||
public static randomWaveTimer = {
|
||||
time_min: 0,
|
||||
time_max: 0,
|
||||
};
|
||||
|
||||
public static actual_timers = {
|
||||
time_min: 0,
|
||||
time_max: 0,
|
||||
};
|
||||
|
||||
public static waveCounter = {
|
||||
count: 1,
|
||||
};
|
||||
|
||||
public static raid_time = {
|
||||
time_of_day: "day",
|
||||
};
|
||||
|
||||
public static bossCount = {
|
||||
count: 0,
|
||||
};
|
||||
|
||||
preAkiLoad(container: DependencyContainer): void {
|
||||
const HttpResponse =
|
||||
container.resolve<HttpResponseUtil>("HttpResponseUtil");
|
||||
|
||||
const staticRouterModService = container.resolve<StaticRouterModService>(
|
||||
"StaticRouterModService"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`${modName}/client/match/offline/end`,
|
||||
[
|
||||
{
|
||||
url: "/client/match/offline/end",
|
||||
action: (
|
||||
url: string,
|
||||
info: any,
|
||||
sessionID: string,
|
||||
output: string
|
||||
): any => {
|
||||
sessionId = sessionID;
|
||||
SWAG.ClearDefaultSpawns();
|
||||
SWAG.ConfigureMaps();
|
||||
return LocationCallbacks.getLocationData(url, info, sessionID);
|
||||
},
|
||||
},
|
||||
],
|
||||
"SWAG"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`${modName}/client/locations`,
|
||||
[
|
||||
{
|
||||
url: "/client/locations",
|
||||
action: (
|
||||
url: string,
|
||||
info: any,
|
||||
sessionID: string,
|
||||
output: string
|
||||
): any => {
|
||||
sessionId = sessionID;
|
||||
SWAG.ClearDefaultSpawns();
|
||||
SWAG.ConfigureMaps();
|
||||
return LocationCallbacks.getLocationData(url, info, sessionID);
|
||||
},
|
||||
},
|
||||
],
|
||||
"SWAG"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`${modName}/client/items`,
|
||||
[
|
||||
{
|
||||
url: "/client/items",
|
||||
action: (
|
||||
url: string,
|
||||
info: any,
|
||||
sessionID: string,
|
||||
output: string
|
||||
) => {
|
||||
sessionId = sessionID;
|
||||
const locationConfig = container.resolve<ConfigServer>("ConfigServer").getConfig<ILocationConfig>(ConfigTypes.LOCATION);
|
||||
|
||||
// as of SPT 3.6.0 we need to disable the new spawn system so that SWAG can clear spawns properly
|
||||
if (
|
||||
!config?.UseDefaultSpawns?.Waves ||
|
||||
!config?.UseDefaultSpawns?.Bosses ||
|
||||
!config?.UseDefaultSpawns?.TriggeredWaves
|
||||
) {
|
||||
SWAG.disableSpawnSystems();
|
||||
}
|
||||
|
||||
// disable more vanilla spawn stuff
|
||||
locationConfig.splitWaveIntoSingleSpawnsSettings.enabled = false;
|
||||
locationConfig.rogueLighthouseSpawnTimeSettings.enabled = false;
|
||||
locationConfig.fixEmptyBotWavesSettings.enabled = false;
|
||||
locationConfig.addOpenZonesToAllMaps = false;
|
||||
locationConfig.addCustomBotWavesToMaps = false;
|
||||
locationConfig.enableBotTypeLimits = false;
|
||||
|
||||
logger.info(
|
||||
"SWAG: Vanilla spawn systems disabled"
|
||||
);
|
||||
|
||||
return output;
|
||||
},
|
||||
},
|
||||
],
|
||||
"SWAG"
|
||||
);
|
||||
|
||||
staticRouterModService.registerStaticRouter(
|
||||
`${modName}/client/raid/configuration`,
|
||||
[{
|
||||
url: "/client/raid/configuration",
|
||||
action: (
|
||||
url: string,
|
||||
info: any,
|
||||
sessionID: string,
|
||||
output: string
|
||||
): any => {
|
||||
try {
|
||||
// Retrieve configurations
|
||||
const botConfig = container.resolve<ConfigServer>("ConfigServer").getConfig<IBotConfig>(ConfigTypes.BOT);
|
||||
const pmcConfig = container.resolve<ConfigServer>("ConfigServer").getConfig<IBotConfig>(ConfigTypes.PMC);
|
||||
|
||||
// Disable PMC conversion
|
||||
const conversionTypes = ["assault", "cursedassault", "pmcbot", "exusec", "arenafighter", "arenafighterevent", "crazyassaultevent"];
|
||||
conversionTypes.forEach(type => {
|
||||
pmcConfig.convertIntoPmcChance[type] = { min: 0, max: 0 };
|
||||
});
|
||||
|
||||
logger.info("SWAG: PMC conversion is OFF (this is good - be sure this loads AFTER Realism/SVM)");
|
||||
|
||||
// Adjust time and map caps
|
||||
const appContext = container.resolve<ApplicationContext>("ApplicationContext");
|
||||
const weatherController = container.resolve<WeatherController>("WeatherController");
|
||||
const matchInfoStartOff = appContext.getLatestValue(ContextVariableType.RAID_CONFIGURATION).getValue<IGetRaidConfigurationRequestData>();
|
||||
const time = weatherController.generate().time;
|
||||
|
||||
let realTime = time;
|
||||
if (matchInfoStartOff.timeVariant === "PAST") {
|
||||
let [hours, minutes] = time.split(":").map(Number);
|
||||
hours = (hours - 12 + 24) % 24; // Adjust time backwards by 12 hours and ensure it wraps correctly
|
||||
realTime = `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
// Determine Time of Day
|
||||
let TOD = "day";
|
||||
let [hours] = realTime.split(":").map(Number);
|
||||
if ((matchInfoStartOff.location !== "factory4_night" && hours >= 5 && hours < 22) ||
|
||||
matchInfoStartOff.location === "factory4_day" ||
|
||||
matchInfoStartOff.location.toLowerCase() === "laboratory") {
|
||||
TOD = "day";
|
||||
} else {
|
||||
TOD = "night";
|
||||
}
|
||||
|
||||
// Set map caps based on Time of Day
|
||||
if (TOD === "day") {
|
||||
Object.keys(config.MaxBotCap).forEach(key => {
|
||||
botConfig.maxBotCap[key] = config.MaxBotCap[key];
|
||||
});
|
||||
} else { // "night"
|
||||
Object.keys(config.NightMaxBotCap).forEach(key => {
|
||||
botConfig.maxBotCap[key] = config.NightMaxBotCap[key];
|
||||
});
|
||||
}
|
||||
logger.info(`SWAG: ${TOD} Raid Max Bot Caps set`);
|
||||
|
||||
return HttpResponse.nullResponse();
|
||||
} catch (e) {
|
||||
logger.error(`SWAG: Failed To modify PMC conversion, you may have more PMCs than you're supposed to. Error: ${e}`);
|
||||
return HttpResponse.nullResponse();
|
||||
}
|
||||
},
|
||||
}],
|
||||
"SWAG"
|
||||
);
|
||||
}
|
||||
|
||||
postDBLoad(container: DependencyContainer): void {
|
||||
logger = container.resolve<ILogger>("WinstonLogger");
|
||||
LocationCallbacks =
|
||||
container.resolve<LocationCallbacks>("LocationCallbacks");
|
||||
jsonUtil = container.resolve<JsonUtil>("JsonUtil");
|
||||
configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
botConfig = configServer.getConfig<IBotConfig>(ConfigTypes.BOT);
|
||||
pmcConfig = configServer.getConfig<IBotConfig>(ConfigTypes.PMC);
|
||||
databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
|
||||
locations = databaseServer.getTables().locations;
|
||||
randomUtil = container.resolve<RandomUtil>("RandomUtil");
|
||||
seasonalEvents = container.resolve<SeasonalEventService>("SeasonalEventService");
|
||||
profileHelper = container.resolve<ProfileHelper>("ProfileHelper");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available OpenZones specified in location.base.OpenZones as well as any OpenZone found in the SpawnPointParams.
|
||||
* Filters out all sniper zones
|
||||
* @param map
|
||||
* @returns
|
||||
*/
|
||||
static GetOpenZones(map: LocationName): string[] {
|
||||
const baseobj: ILocationBase = locations[map]?.base;
|
||||
|
||||
// Get all OpenZones defined in the base obj that do not include sniper zones. Need to filter for empty strings as well.
|
||||
const foundOpenZones =
|
||||
baseobj?.OpenZones?.split(",")
|
||||
.filter((name) => !name.includes("Snipe"))
|
||||
.filter((name) => name.trim() !== "") ?? [];
|
||||
|
||||
// Sometimes there are zones in the SpawnPointParams that arent listed in the OpenZones, parse these and add them to the list of zones
|
||||
baseobj?.SpawnPointParams?.forEach((spawn) => {
|
||||
//check spawn for open zones and if it doesn't exist add to end of array
|
||||
if (
|
||||
spawn?.BotZoneName &&
|
||||
!foundOpenZones.includes(spawn.BotZoneName) &&
|
||||
!spawn.BotZoneName.includes("Snipe")
|
||||
) {
|
||||
foundOpenZones.push(spawn.BotZoneName);
|
||||
}
|
||||
});
|
||||
|
||||
//logger.info(`SWAG: Open Zones(${map}): ${JSON.stringify(foundOpenZones)}`);
|
||||
return foundOpenZones;
|
||||
}
|
||||
|
||||
static shuffleArray(array: any[]) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
}
|
||||
|
||||
static ConfigureMaps(): void {
|
||||
|
||||
const bossConfigs: { [key: string]: any[] } = {};
|
||||
const otherConfigs: { [key: string]: any[] } = {};
|
||||
const customConfigs: { [key: string]: any[] } = {};
|
||||
|
||||
bossSpawnConfigs.forEach(data => {
|
||||
Object.keys(data).forEach(mapKey => {
|
||||
if (bossConfig.TotalBossesPerMap[mapKey] === 0 || config.disableAllSpawns.bosses) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bossConfigs[mapKey]) {
|
||||
bossConfigs[mapKey] = [];
|
||||
}
|
||||
|
||||
const filteredBosses = data[mapKey].filter(boss => {
|
||||
|
||||
// ignore boarsniper
|
||||
if (boss.BossName == "bossboarsniper") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shouldSkip = boss.BossChance === 0 ||
|
||||
(bossConfig.Bosses.useGlobalBossSpawnChance &&
|
||||
bossConfig.Bosses[reverseBossNames[boss.BossName]][mapKey] === 0);
|
||||
return !shouldSkip;
|
||||
});
|
||||
|
||||
bossConfigs[mapKey].push(...filteredBosses);
|
||||
});
|
||||
});
|
||||
|
||||
otherSpawnConfigs.forEach(data => {
|
||||
Object.keys(data).forEach(mapKey => {
|
||||
|
||||
if (!otherConfigs[mapKey]) {
|
||||
otherConfigs[mapKey] = [];
|
||||
}
|
||||
|
||||
const filteredBosses = data[mapKey].filter(boss => {
|
||||
const bossType = reverseBossNames[boss.BossName];
|
||||
|
||||
if (config.disableAllSpawns[bossType]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shouldSkip = boss.BossChance === 0 ||
|
||||
(config.Spawns.useGlobalSpawnChance && config.Spawns[bossType][mapKey] === 0);
|
||||
|
||||
return !shouldSkip;
|
||||
});
|
||||
|
||||
otherConfigs[mapKey].push(...filteredBosses);
|
||||
});
|
||||
});
|
||||
|
||||
customSpawnConfigs.forEach(data => {
|
||||
Object.keys(data).forEach(mapKey => {
|
||||
if (!customConfigs[mapKey]) {
|
||||
customConfigs[mapKey] = [];
|
||||
}
|
||||
|
||||
const filteredBosses = data[mapKey].filter(boss => {
|
||||
|
||||
if (boss.BossName == "gifter") {
|
||||
if (!bossConfig.CustomBosses.santa.enabled ||
|
||||
(!seasonalEvents.christmasEventEnabled() && !bossConfig.CustomBosses.santa.forceSpawnOutsideEvent)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const shouldSkip = boss.BossChance === 0 ||
|
||||
!bossConfig.CustomBosses[reverseBossNames[boss.BossName]].enabled ||
|
||||
(bossConfig.CustomBosses[reverseBossNames[boss.BossName]].enabled &&
|
||||
bossConfig.CustomBosses[reverseBossNames[boss.BossName]][mapKey] === 0);
|
||||
return !shouldSkip;
|
||||
});
|
||||
|
||||
customConfigs[mapKey].push(...filteredBosses);
|
||||
});
|
||||
});
|
||||
|
||||
// Shuffle each array within the configuration objects
|
||||
Object.values(bossConfigs).forEach(array => this.shuffleArray(array));
|
||||
Object.values(otherConfigs).forEach(array => this.shuffleArray(array));
|
||||
Object.values(customConfigs).forEach(array => this.shuffleArray(array));
|
||||
|
||||
ClassDef.validMaps.forEach((globalmap: LocationName) => {
|
||||
if (bossConfigs[reverseMapNames[globalmap]]) {
|
||||
bossConfigs[reverseMapNames[globalmap]].forEach(boss => {
|
||||
SWAG.SpawnBosses(boss, globalmap);
|
||||
SWAG.bossCount.count += 1;
|
||||
});
|
||||
}
|
||||
// reset boss count for the next map
|
||||
SWAG.bossCount.count = 0;
|
||||
|
||||
if (otherConfigs[reverseMapNames[globalmap]]) {
|
||||
otherConfigs[reverseMapNames[globalmap]].forEach(spawn => {
|
||||
SWAG.SpawnBots(spawn, globalmap);
|
||||
});
|
||||
}
|
||||
|
||||
if (customConfigs[reverseMapNames[globalmap]]) {
|
||||
customConfigs[reverseMapNames[globalmap]].forEach(custom => {
|
||||
SWAG.SpawnCustom(custom, globalmap);
|
||||
});
|
||||
}
|
||||
|
||||
logger.warning(`SWAG: Configured boss spawns for map ${globalmap}`);
|
||||
});
|
||||
}
|
||||
|
||||
static SpawnBosses(
|
||||
boss: ClassDef.BossPattern,
|
||||
globalmap: LocationName,
|
||||
): void {
|
||||
|
||||
if (bossConfig.TotalBossesPerMap[reverseMapNames[globalmap]] == 0) {
|
||||
config.DebugOutput &&
|
||||
logger.info(
|
||||
"SWAG: TotalBosses set to 0 for this map, skipping boss spawn"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
else if (bossConfig.TotalBossesPerMap[reverseMapNames[globalmap]] != -1 && (SWAG.bossCount.count >= bossConfig.TotalBossesPerMap[reverseMapNames[globalmap]])) {
|
||||
config.DebugOutput &&
|
||||
logger.info(
|
||||
"SWAG: Skipping boss spawn as total boss count has been met already"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
else {
|
||||
let wave: BossLocationSpawn = SWAG.ConfigureBossWave(boss, globalmap);
|
||||
locations[globalmap].base.BossLocationSpawn.push(wave);
|
||||
}
|
||||
}
|
||||
|
||||
static SpawnBots(
|
||||
boss: ClassDef.BossPattern,
|
||||
globalmap: LocationName,
|
||||
): void {
|
||||
|
||||
let wave: BossLocationSpawn = SWAG.ConfigureBossWave(boss, globalmap);
|
||||
locations[globalmap].base.BossLocationSpawn.push(wave);
|
||||
}
|
||||
|
||||
static SpawnCustom(
|
||||
boss: ClassDef.BossPattern,
|
||||
globalmap: LocationName,
|
||||
): void {
|
||||
|
||||
let wave: BossLocationSpawn = SWAG.ConfigureBossWave(boss, globalmap);
|
||||
locations[globalmap].base.BossLocationSpawn.push(wave);
|
||||
}
|
||||
|
||||
static ConfigureBossWave(boss: BossLocationSpawn, globalmap: LocationName): BossLocationSpawn {
|
||||
let spawnChance = 0;
|
||||
let spawnZones = boss.BossZone || null;
|
||||
let bossName = roleCase[boss.BossName.toLowerCase()] || boss.BossName;
|
||||
|
||||
const getRandomDifficulty = () => {
|
||||
const availableDifficulties = ["easy", "normal", "hard", "impossible"];
|
||||
const randomIndex = Math.floor(Math.random() * availableDifficulties.length);
|
||||
return availableDifficulties[randomIndex];
|
||||
};
|
||||
|
||||
let difficultyKey = boss.BossDifficult || config.BossDifficulty.toLowerCase();
|
||||
let difficulty = difficultyKey === "asonline" ? getRandomDifficulty() : diffProper[difficultyKey];
|
||||
|
||||
let escortDifficultyKey = boss.BossEscortDifficult || config.BossEscortDifficulty.toLowerCase();
|
||||
let escort_difficulty = escortDifficultyKey === "asonline" ? getRandomDifficulty() : diffProper[escortDifficultyKey];
|
||||
|
||||
boss?.Supports?.forEach((escort) => {
|
||||
escort.BossEscortDifficult = [escort_difficulty];
|
||||
escort.BossEscortType = roleCase[escort.BossEscortType.toLowerCase()];
|
||||
});
|
||||
|
||||
// exclusive to bosses only
|
||||
if (boss.BossName.startsWith("boss")) {
|
||||
spawnChance = this.adjustBossSpawnChance(boss, globalmap);
|
||||
}
|
||||
// something other than bosses
|
||||
else if (config.Spawns.useGlobalSpawnChance) {
|
||||
spawnChance = config.Spawns[reverseBossNames[boss.BossName]][reverseMapNames[globalmap]];
|
||||
}
|
||||
else {
|
||||
spawnChance = boss.BossChance || 0;
|
||||
}
|
||||
|
||||
// zones
|
||||
if (spawnZones != null) {
|
||||
spawnZones = boss.BossZone || spawnZones;
|
||||
if (spawnZones.length > 1) {
|
||||
// let's just pick one zone, can't trust BSG to do this correctly
|
||||
let random_zone = SWAG.getRandIntInclusive(0, spawnZones.length - 1);
|
||||
spawnZones = spawnZones[random_zone];
|
||||
}
|
||||
// if it's not > 1 and not null, then we'll assume there's a single zone defined instead
|
||||
else {
|
||||
spawnZones = spawnZones[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Using the SPT class here
|
||||
const wave: BossLocationSpawn = {
|
||||
BossName: bossName,
|
||||
BossChance: spawnChance,
|
||||
BossZone: !!spawnZones
|
||||
? spawnZones
|
||||
: SWAG.savedLocationData[globalmap].openZones &&
|
||||
SWAG.savedLocationData[globalmap].openZones.length > 0
|
||||
? randomUtil.getStringArrayValue(
|
||||
SWAG.savedLocationData[globalmap].openZones
|
||||
)
|
||||
: "",
|
||||
BossPlayer: false,
|
||||
BossDifficult: difficulty,
|
||||
BossEscortType: roleCase[boss.BossEscortType.toLowerCase()],
|
||||
BossEscortDifficult: escort_difficulty,
|
||||
BossEscortAmount: boss.BossEscortAmount || "0",
|
||||
Time: boss.Time || -1,
|
||||
Supports: boss.Supports || null,
|
||||
RandomTimeSpawn: boss.RandomTimeSpawn || false,
|
||||
TriggerId: boss.TriggerId || "",
|
||||
TriggerName: boss.TriggerName || ""
|
||||
};
|
||||
|
||||
if (spawnChance != 0) {
|
||||
config.DebugOutput && logger.warning(`Configured Boss Wave: ${JSON.stringify(wave)}`);
|
||||
}
|
||||
|
||||
return wave;
|
||||
}
|
||||
|
||||
static adjustBossSpawnChance(boss: BossLocationSpawn, globalmap: LocationName): number {
|
||||
|
||||
// I need to refactor this garbage
|
||||
if (boss.BossName === "bosspunisher") {
|
||||
if (bossConfig.CustomBosses.punisher.enabled) {
|
||||
if (bossConfig.CustomBosses.punisher.useProgressSpawnChance) {
|
||||
const pmcProfile = profileHelper.getPmcProfile(sessionId);
|
||||
const profileId = pmcProfile?._id;
|
||||
const punisherBossProgressFilePath = path.resolve(
|
||||
__dirname,
|
||||
`../../WTT-RogueJustice/profiles/${profileId}/progress.json`
|
||||
);
|
||||
|
||||
try {
|
||||
const progressData = JSON.parse(
|
||||
fs.readFileSync(punisherBossProgressFilePath, "utf8")
|
||||
);
|
||||
return progressData?.actualPunisherChance ?? 1;
|
||||
} catch (error) {
|
||||
logger.warning(
|
||||
"SWAG: Unable to load Punisher Boss progress file, either you don't have the mod installed or you don't have a Punisher progress file yet.",
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// if progress spawn chance is not enabled
|
||||
return bossConfig.CustomBosses["punisher"][reverseMapNames[globalmap]];
|
||||
}
|
||||
} else {
|
||||
// if punisher is not enabled
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (boss.BossName === "bosslegion") {
|
||||
if (bossConfig.CustomBosses.legion.enabled) {
|
||||
if (bossConfig.CustomBosses.legion.useProgressSpawnChance) {
|
||||
|
||||
const legionBossProgressFilePath = path.resolve(
|
||||
__dirname,
|
||||
"../../RaidOverhaul/config/LegionChance.json"
|
||||
);
|
||||
|
||||
try {
|
||||
const progressData = JSON.parse(
|
||||
fs.readFileSync(legionBossProgressFilePath, "utf8")
|
||||
);
|
||||
return progressData?.legionChance ?? 15;
|
||||
|
||||
} catch (error) {
|
||||
logger.warning(
|
||||
"SWAG: Unable to load Legion Boss progress file, either you don't have the mod installed or you deleted your LegionChance.json."
|
||||
);
|
||||
}
|
||||
}
|
||||
// if progress spawn chance is not enabled
|
||||
return bossConfig.CustomBosses["legion"][reverseMapNames[globalmap]];
|
||||
}
|
||||
// if legion is not enabled
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// all other bosses...
|
||||
else if (bossConfig.Bosses.useGlobalBossSpawnChance) {
|
||||
// edge case, only applies to Kaban
|
||||
if (boss.BossName == "bossboarsniper") {
|
||||
return boss.BossChance;
|
||||
}
|
||||
return bossConfig.Bosses[reverseBossNames[boss.BossName]][reverseMapNames[globalmap]];
|
||||
}
|
||||
|
||||
// if global boss chance is not enabled
|
||||
else {
|
||||
return boss.BossChance;
|
||||
}
|
||||
}
|
||||
|
||||
static getRandIntInclusive(min: number, max: number): number {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
static disableSpawnSystems(): void {
|
||||
let map: keyof ILocations;
|
||||
for (map in locations) {
|
||||
if (map === "base" || map === "hideout") {
|
||||
continue;
|
||||
}
|
||||
locations[map].base.OfflineNewSpawn = false;
|
||||
locations[map].base.OfflineOldSpawn = true;
|
||||
locations[map].base.NewSpawn = false;
|
||||
locations[map].base.OldSpawn = true;
|
||||
}
|
||||
}
|
||||
|
||||
static ClearDefaultSpawns(): void {
|
||||
let map: keyof ILocations;
|
||||
for (map in locations) {
|
||||
if (map === "base" || map === "hideout") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save a backup of the wave data and the BossLocationSpawn to use when restoring defaults on raid end. Store openzones in this data as well
|
||||
if (!SWAG.savedLocationData[map]) {
|
||||
const locationBase = locations[map].base;
|
||||
SWAG.savedLocationData[map] = {
|
||||
waves: locationBase.waves,
|
||||
BossLocationSpawn: locationBase.BossLocationSpawn,
|
||||
openZones: this.GetOpenZones(map),
|
||||
};
|
||||
}
|
||||
|
||||
// Reset Database, Cringe -- i stole this code from LUA
|
||||
locations[map].base.waves = [...SWAG.savedLocationData[map].waves];
|
||||
locations[map].base.BossLocationSpawn = [
|
||||
...SWAG.savedLocationData[map].BossLocationSpawn,
|
||||
];
|
||||
|
||||
//Clear bots spawn
|
||||
if (!config?.UseDefaultSpawns?.Waves) {
|
||||
locations[map].base.waves = [];
|
||||
}
|
||||
|
||||
//Clear boss spawn
|
||||
const bossLocationSpawn = locations[map].base.BossLocationSpawn;
|
||||
if (
|
||||
!config?.UseDefaultSpawns?.Bosses &&
|
||||
!config?.UseDefaultSpawns?.TriggeredWaves
|
||||
) {
|
||||
locations[map].base.BossLocationSpawn = [];
|
||||
} else {
|
||||
// Remove Default Boss Spawns
|
||||
if (!config?.UseDefaultSpawns?.Bosses) {
|
||||
for (let i = 0; i < bossLocationSpawn.length; i++) {
|
||||
// Triggered wave check
|
||||
if (bossLocationSpawn[i]?.TriggerName?.length === 0) {
|
||||
locations[map].base.BossLocationSpawn.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove Default Triggered Waves
|
||||
if (!config?.UseDefaultSpawns?.TriggeredWaves) {
|
||||
for (let i = 0; i < bossLocationSpawn.length; i++) {
|
||||
// Triggered wave check
|
||||
if (bossLocationSpawn[i]?.TriggerName?.length > 0) {
|
||||
locations[map].base.BossLocationSpawn.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { mod: new SWAG() };
|
||||
@@ -0,0 +1,5 @@
|
||||
[{000214A0-0000-0000-C000-000000000046}]
|
||||
Prop3=19,11
|
||||
[InternetShortcut]
|
||||
IDList=
|
||||
URL=https://hub.sp-tarkov.com/advertising-disclaimer/
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"CurrentlySelectedPreset":"xiaoxi654"
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
{
|
||||
"PresetNotes": "Hi, how\u0027s your day so far? :]\r\nNothing much, just bare minimum i\u0027d like to have.",
|
||||
"Items": {
|
||||
"HeatFactor": 1,
|
||||
"ExamineTime": 1,
|
||||
"ExamineKeys": false,
|
||||
"AddSignalPistolToSpec": false,
|
||||
"WeaponHeatOff": false,
|
||||
"SMGToHolster": false,
|
||||
"PistolToMain": false,
|
||||
"AllExaminedItems": true,
|
||||
"MisfireChance": 1,
|
||||
"EquipRigsWithArmors": false,
|
||||
"RemoveSecureContainerFilters": false,
|
||||
"MalfunctChanceMult": 1,
|
||||
"WeightChanger": 0.75,
|
||||
"ItemPriceMult": 1,
|
||||
"BSGCashStack": false,
|
||||
"RubStack": 500000,
|
||||
"DollarStack": 50000,
|
||||
"EuroStack": 50000,
|
||||
"AmmoLoadSpeed": 0.75,
|
||||
"LootExp": 1,
|
||||
"EnableItems": true,
|
||||
"ExamineExp": 1,
|
||||
"AmmoStacks": {
|
||||
"MarksmanRound": 40,
|
||||
"RifleRound": 60,
|
||||
"ShotgunRound": 30,
|
||||
"PistolRound": 90
|
||||
},
|
||||
"RemoveRaidRestr": true,
|
||||
"RemoveBackpacksRestrictions": false,
|
||||
"AvoidSingleKeys": false,
|
||||
"AvoidMarkedKeys": false,
|
||||
"KeyUseMult": 1,
|
||||
"KeycardUseMult": 1,
|
||||
"KeyDurabilityThreshold": 40,
|
||||
"IDChanger": false,
|
||||
"NoGearPenalty": false,
|
||||
"IDBox": "",
|
||||
"RemoveKeysUsageNumber": false,
|
||||
"RaidDrop": false
|
||||
},
|
||||
"Hideout": {
|
||||
"EnableStash": false,
|
||||
"Stash": {
|
||||
"StashLvl4": 68,
|
||||
"StashLvl3": 48,
|
||||
"StashLvl2": 38,
|
||||
"StashLvl1": 28
|
||||
},
|
||||
"Regeneration": {
|
||||
"OfflineRegen": false,
|
||||
"HealthRegen": 1,
|
||||
"HideoutHealth": false,
|
||||
"HideoutEnergy": false,
|
||||
"HideoutHydration": false,
|
||||
"HydrationRegen": 1,
|
||||
"EnergyRegen": 1
|
||||
},
|
||||
"WaterFilterTime": 325,
|
||||
"BitcoinTime": 2416,
|
||||
"MaxBitcoins": 3,
|
||||
"NoFuelMult": 1,
|
||||
"ScavCasePrice": 0.25,
|
||||
"ScavCaseTime": 0.01,
|
||||
"HideoutConstMult": 0.25,
|
||||
"HideoutProdMult": 0.25,
|
||||
"WaterFilterRate": 66,
|
||||
"GPUBoostRate": 1.5,
|
||||
"AirFilterRate": 0.75,
|
||||
"RemoveConstructionsRequirements": false,
|
||||
"RemoveSkillRequirements": false,
|
||||
"RemoveTraderLevelRequirements": false,
|
||||
"EnableHideout": true,
|
||||
"FuelConsumptionRate": 1
|
||||
},
|
||||
"Traders": {
|
||||
"Fence": {
|
||||
"ArmorDurability_Max": 60,
|
||||
"GunDurability_Max": 60,
|
||||
"ArmorDurability_Min": 35,
|
||||
"GunDurability_Min": 35,
|
||||
"PriceMult": 1.2,
|
||||
"PremiumAmountOnSale": 50,
|
||||
"PresetCount": 5,
|
||||
"StockTime_Min": 50,
|
||||
"StockTime_Max": 150,
|
||||
"AmountOnSale": 140,
|
||||
"PresetMult": 2,
|
||||
"Blacklist": "5c164d2286f774194c5e69fa\r\n543be6674bdc2df1348b4569\r\n5448bf274bdc2dfc2f8b456a\r\n5447bedf4bdc2d87278b4568\r\n6275303a9f372d6ea97f9ec7\r\n62e9103049c018f425059f38\r\n59f32bb586f774757e1e8442\r\n59f32c3b86f77472a31742f0\r\n627bce33f21bc425b06ab967\r\n62f109593b54472778797866\r\n5d52cc5ba4b9367408500062\r\n5d52d479a4b936793d58c76b\r\n5e99711486f7744bfc4af328\r\n62f10b79e7ee985f386b2f47\r\n633ffb5d419dbf4bea7004c6\r\n543be5dd4bdc2deb348b4569\r\n65649eb40bf0ed77b8044453\r\n5448e54d4bdc2dcc718b4568\r\n5a341c4086f77401f2541505\r\n5422acb9af1c889c16000029\r\n64d0b40fbe2eed70e254e2d4\r\n5fc22d7c187fea44d52eda44"
|
||||
},
|
||||
"RemoveTradeLimits": true,
|
||||
"QuestRedeemTime": 48,
|
||||
"TraderMarkup": {
|
||||
"Ragman": 62,
|
||||
"Peacekeeper": 45,
|
||||
"Fence": 40,
|
||||
"Prapor": 50,
|
||||
"Jaeger": 60,
|
||||
"Mechanic": 56,
|
||||
"Skier": 49,
|
||||
"Therapist": 63
|
||||
},
|
||||
"TraderSell": {
|
||||
"Ragman": 1,
|
||||
"Peacekeeper": 1,
|
||||
"Prapor": 1,
|
||||
"Jaeger": 1,
|
||||
"Mechanic": 1,
|
||||
"Skier": 1,
|
||||
"Therapist": 1
|
||||
},
|
||||
"MinDurabSell": 60,
|
||||
"RemoveTimeCondition": false,
|
||||
"AllQuestsAvailable": false,
|
||||
"RemoveBarterOffers": false,
|
||||
"RemoveCurrencyOffers": false,
|
||||
"IncreaseAssort": false,
|
||||
"UnlockQuestAssort": false,
|
||||
"EnableTraders": true,
|
||||
"FIRRestrictsQuests": false,
|
||||
"TradersLvl4": false,
|
||||
"FIRTrade": false
|
||||
},
|
||||
"Loot": {
|
||||
"Airdrops": {
|
||||
"Mixed": {
|
||||
"ArmorMin": 1,
|
||||
"ArmorMax": 5,
|
||||
"BarterMin": 15,
|
||||
"BarterMax": 35,
|
||||
"PresetMin": 3,
|
||||
"PresetMax": 5,
|
||||
"CratesMin": 1,
|
||||
"CratesMax": 2
|
||||
},
|
||||
"Medical": {
|
||||
"ArmorMin": 0,
|
||||
"ArmorMax": 0,
|
||||
"BarterMin": 17,
|
||||
"BarterMax": 28,
|
||||
"PresetMin": 0,
|
||||
"PresetMax": 0,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 0
|
||||
},
|
||||
"Barter": {
|
||||
"ArmorMin": 0,
|
||||
"ArmorMax": 0,
|
||||
"BarterMin": 20,
|
||||
"BarterMax": 35,
|
||||
"PresetMin": 0,
|
||||
"PresetMax": 0,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 0
|
||||
},
|
||||
"Weapon": {
|
||||
"ArmorMin": 3,
|
||||
"ArmorMax": 6,
|
||||
"BarterMin": 11,
|
||||
"BarterMax": 22,
|
||||
"PresetMin": 6,
|
||||
"PresetMax": 8,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 2
|
||||
},
|
||||
"Sandbox_air": 20,
|
||||
"Streets_air": 20,
|
||||
"AirtimeMin": 1,
|
||||
"AirtimeMax": 5,
|
||||
"Lighthouse_air": 20,
|
||||
"Bigmap_air": 25,
|
||||
"Interchange_air": 20,
|
||||
"Shoreline_air": 20,
|
||||
"Reserve_air": 20,
|
||||
"Woods_air": 25
|
||||
},
|
||||
"EnableLoot": false,
|
||||
"Locations": {
|
||||
"Streets": {
|
||||
"Loose": 3,
|
||||
"Container": 1
|
||||
},
|
||||
"Sandbox": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Lighthouse": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Bigmap": {
|
||||
"Loose": 2.5,
|
||||
"Container": 1
|
||||
},
|
||||
"Interchange": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"FactoryDay": {
|
||||
"Loose": 3.5,
|
||||
"Container": 1
|
||||
},
|
||||
"Laboratory": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Shoreline": {
|
||||
"Loose": 3.7,
|
||||
"Container": 1
|
||||
},
|
||||
"Reserve": {
|
||||
"Loose": 2.9,
|
||||
"Container": 1
|
||||
},
|
||||
"Woods": {
|
||||
"Loose": 1.9,
|
||||
"Container": 1
|
||||
},
|
||||
"FactoryNight": {
|
||||
"Loose": 3.5,
|
||||
"Container": 1
|
||||
},
|
||||
"AllContainers": false
|
||||
}
|
||||
},
|
||||
"Player": {
|
||||
"CharXP": {
|
||||
"ScavKill": 80,
|
||||
"ScavHMult": 1.1,
|
||||
"PMCKill": 175,
|
||||
"PMCHMult": 1.2
|
||||
},
|
||||
"RaidMult": {
|
||||
"MIA": 1,
|
||||
"Runner": 0.5,
|
||||
"Survived": 1.3,
|
||||
"Killed": 1
|
||||
},
|
||||
"EnableStats": false,
|
||||
"Skills": {
|
||||
"SkillFatigueReset": 200,
|
||||
"SkillFreshEffect": 1.3,
|
||||
"SkillFPoints": 1,
|
||||
"SkillPointsBeforeFatigue": 1,
|
||||
"SkillMinEffect": 0.0001,
|
||||
"SkillFatiguePerPoint": 0.6
|
||||
},
|
||||
"FallDamage": false,
|
||||
"BlackStomach": 5,
|
||||
"HydrationLoss": 1,
|
||||
"EnergyLoss": 1,
|
||||
"EnableHealth": true,
|
||||
"SkillProgMult": 0.4,
|
||||
"Health": {
|
||||
"LeftArm": 75,
|
||||
"RightArm": 75,
|
||||
"Head": 50,
|
||||
"Chest": 100,
|
||||
"Stomach": 85,
|
||||
"LeftLeg": 80,
|
||||
"RightLeg": 80
|
||||
},
|
||||
"WeaponSkillMult": 1,
|
||||
"EnablePlayer": true,
|
||||
"DiedHealth": {
|
||||
"Saveeffects": true,
|
||||
"Savehealth": true,
|
||||
"Health_blacked": 0.1,
|
||||
"Health_death": 0.3
|
||||
},
|
||||
"MaxStamina": 100,
|
||||
"UnlimitedStamina": false,
|
||||
"MaxHydration": 100,
|
||||
"MaxEnergy": 100
|
||||
},
|
||||
"Raids": {
|
||||
"SandboxAddLevel": false,
|
||||
"RaidTime": 0,
|
||||
"SaveQuestItems": false,
|
||||
"Exfils": {
|
||||
"CarSandbox": 5000,
|
||||
"CarShoreline": 5000,
|
||||
"CoopPaidSandbox": 5000,
|
||||
"CoopPaidShoreline": 5000,
|
||||
"CoopPaidStreets": 5000,
|
||||
"CoopPaidLighthouse": 5000,
|
||||
"CarLighthouse": 5000,
|
||||
"CarExtractTime": 60,
|
||||
"ArmorExtract": false,
|
||||
"CoopPaid": false,
|
||||
"FenceGift": false,
|
||||
"CoopPaidInterchange": 5000,
|
||||
"CoopPaidWoods": 5000,
|
||||
"CoopPaidReserve": 5000,
|
||||
"NoBackpack": false,
|
||||
"FreeCoop": false,
|
||||
"CarInterchange": 5000,
|
||||
"CarWoods": 5000,
|
||||
"CarStreets": 5000,
|
||||
"CarCustoms": 5000,
|
||||
"ExtendedExtracts": false,
|
||||
"ChanceExtracts": false,
|
||||
"GearExtract": false
|
||||
},
|
||||
"NoRunThrough": false,
|
||||
"Timeacceleration": 7,
|
||||
"SafeExit": true,
|
||||
"SaveGearAfterDeath": false,
|
||||
"RaidEvents": {
|
||||
"DisableEvents": false,
|
||||
"SnowEvent": false,
|
||||
"KillaFactoryChance": 100,
|
||||
"GoonsFactoryChance": 100,
|
||||
"GoonsFactory": false,
|
||||
"BossesOnCustoms": false,
|
||||
"HoundsWoods": 30,
|
||||
"HoundsCustoms": 30,
|
||||
"HoundsShoreline": 30,
|
||||
"Christmas": false,
|
||||
"Halloween": false,
|
||||
"IncludeTagilla": false,
|
||||
"KillaFactory": false,
|
||||
"BossesOnReserve": false,
|
||||
"CultistsEverywhere": false,
|
||||
"RaidersEverywhere": false,
|
||||
"GlukharLabs": false
|
||||
},
|
||||
"LabInsurance": false,
|
||||
"EnableRaids": true,
|
||||
"Removelabkey": false,
|
||||
"RaidStartup": {
|
||||
"TimeBeforeDeployLocal": 10,
|
||||
"AIAmount": "AsOnline",
|
||||
"SaveLoot": true,
|
||||
"AIDifficulty": "AsOnline",
|
||||
"MIAEndofRaid": false,
|
||||
"TaggedAndCursed": false,
|
||||
"EnableBosses": true,
|
||||
"ScavWars": false
|
||||
}
|
||||
},
|
||||
"Fleamarket": {
|
||||
"FleaFIR": false,
|
||||
"FleaNoFIRSell": false,
|
||||
"EventOffers": false,
|
||||
"SellOffersAmount": 10,
|
||||
"FleaConditions": {
|
||||
"FleaFood_Min": 5,
|
||||
"FleaArmor_Min": 5,
|
||||
"FleaFood_Max": 100,
|
||||
"FleaArmor_Max": 100,
|
||||
"FleaMedical_Min": 60,
|
||||
"FleaSpec_Min": 2,
|
||||
"FleaMedical_Max": 100,
|
||||
"FleaSpec_Max": 100,
|
||||
"FleaWeapons_Min": 60,
|
||||
"FleaVests_Min": 5,
|
||||
"FleaKeys_Min": 97,
|
||||
"FleaWeapons_Max": 100,
|
||||
"FleaVests_Max": 100,
|
||||
"FleaKeys_Max": 100
|
||||
},
|
||||
"OverrideOffers": false,
|
||||
"FleaMarketLevel": 15,
|
||||
"FleaBlacklist": null,
|
||||
"DisableBSGList": false,
|
||||
"EnableFleamarket": false,
|
||||
"Sell_mult": 1.24,
|
||||
"Tradeoffer_max": 1,
|
||||
"Rep_loss": 0.03,
|
||||
"Rep_gain": 0.02,
|
||||
"Tradeoffer_min": 0,
|
||||
"Sell_chance": 50,
|
||||
"EnableFees": true,
|
||||
"DynamicOffers": {
|
||||
"ExpireThreshold": 1400,
|
||||
"Stack_min": 10,
|
||||
"PerOffer_min": 7,
|
||||
"Stack_max": 600,
|
||||
"PerOffer_max": 30,
|
||||
"Eurooffers": 8,
|
||||
"Dollaroffers": 14,
|
||||
"Roubleoffers": 78,
|
||||
"NonStack_min": 1,
|
||||
"Time_min": 6,
|
||||
"Price_min": 0.8,
|
||||
"NonStack_max": 10,
|
||||
"Time_max": 60,
|
||||
"Price_max": 1.4
|
||||
}
|
||||
},
|
||||
"Services": {
|
||||
"RepairBox": {
|
||||
"NoRandomRepair": false,
|
||||
"OpGunRepair": false,
|
||||
"ArmorSkillMult": 0.05,
|
||||
"WeaponMaintenanceSkillMult": 0.05,
|
||||
"IntellectSkillMultWeaponKit": 0.045,
|
||||
"IntellectSkillMultArmorKit": 0.03,
|
||||
"IntellectSkillLimitTraders": 0.6,
|
||||
"IntellectSkillLimitKit": 0.6,
|
||||
"OpArmorRepair": false,
|
||||
"RepairMult": 1
|
||||
},
|
||||
"EnableHealMarkup": false,
|
||||
"TherapistLvl1": 1,
|
||||
"TherapistLvl2": 1.1,
|
||||
"TherapistLvl3": 1.2,
|
||||
"TherapistLvl4": 1.35,
|
||||
"FreeHealLvl": 5,
|
||||
"FreeHealRaids": 30,
|
||||
"ReturnChanceTherapist": 85,
|
||||
"InsuranceMultTherapist": 0.25,
|
||||
"EnableServices": true,
|
||||
"ClothesAnySide": true,
|
||||
"InsuranceMultPrapor": 0.16,
|
||||
"InsuranceStorageTime": 72,
|
||||
"ReturnChancePrapor": 75,
|
||||
"ClothesLevelUnlock": true,
|
||||
"ClothesFree": true,
|
||||
"Prapor_Max": 36,
|
||||
"Prapor_Min": 24,
|
||||
"Therapist_Max": 24,
|
||||
"Therapist_Min": 12
|
||||
},
|
||||
"Quests": {
|
||||
"QuestCostMult": 1,
|
||||
"QuestRepToZero": false,
|
||||
"DailyQuests": {
|
||||
"MinKillsLR3": 5,
|
||||
"MaxKillsLR3": 20,
|
||||
"MinKillsLR2": 5,
|
||||
"MaxKillsLR2": 15,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 3,
|
||||
"Types": "Completion,Elimination,Exploration",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 2,
|
||||
"MaxKillsLR1": 4,
|
||||
"Access": 5,
|
||||
"QuestAmount": 3,
|
||||
"Lifespan": 1440
|
||||
},
|
||||
"WeeklyQuests": {
|
||||
"MinKillsLR3": 20,
|
||||
"MaxKillsLR3": 40,
|
||||
"MinKillsLR2": 15,
|
||||
"MaxKillsLR2": 40,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 10,
|
||||
"Types": "Completion,Elimination,Exploration",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 10,
|
||||
"MaxKillsLR1": 20,
|
||||
"Access": 15,
|
||||
"QuestAmount": 1,
|
||||
"Lifespan": 10080
|
||||
},
|
||||
"EnableQuests": false,
|
||||
"ScavQuests": {
|
||||
"MinKillsLR2": 3,
|
||||
"MaxKillsLR2": 15,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 3,
|
||||
"Types": "Elimination,Completion",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 1,
|
||||
"MaxKillsLR1": 3,
|
||||
"Access": 1,
|
||||
"QuestAmount": 1,
|
||||
"Lifespan": 1440
|
||||
}
|
||||
},
|
||||
"CSM": {
|
||||
"CustomPocket": true,
|
||||
"Pockets": {
|
||||
"DefaultPocket": false,
|
||||
"SpecGKeychain": true,
|
||||
"SpecSimpleWallet": false,
|
||||
"SpecWZWallet": true,
|
||||
"SpecKeycardHolder": false,
|
||||
"SpecKeytool": false,
|
||||
"SpecInjectorCase": false,
|
||||
"SpecSlots": 3,
|
||||
"FourthH": 0,
|
||||
"FourthV": 0,
|
||||
"ThirdH": 0,
|
||||
"ThirdV": 0,
|
||||
"SecondH": 1,
|
||||
"SecondV": 2,
|
||||
"FirstH": 1,
|
||||
"FirstV": 2
|
||||
},
|
||||
"Cases": {
|
||||
"GKeychain": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"KeycardHolderCase": {
|
||||
"VSize": 3,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"InjectorCase": {
|
||||
"VSize": 3,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"Holodilnick": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"PistolCase": {
|
||||
"VSize": 4,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"DocumentsCase": {
|
||||
"VSize": 4,
|
||||
"HSize": 4,
|
||||
"Filter": false
|
||||
},
|
||||
"Keytool": {
|
||||
"VSize": 4,
|
||||
"HSize": 4,
|
||||
"Filter": false
|
||||
},
|
||||
"SiccCase": {
|
||||
"VSize": 5,
|
||||
"HSize": 5,
|
||||
"Filter": false
|
||||
},
|
||||
"ThiccWeaponCase": {
|
||||
"VSize": 15,
|
||||
"HSize": 6,
|
||||
"Filter": false
|
||||
},
|
||||
"ThiccItemsCase": {
|
||||
"VSize": 14,
|
||||
"HSize": 14,
|
||||
"Filter": false
|
||||
},
|
||||
"MedicineCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"DogtagCase": {
|
||||
"VSize": 10,
|
||||
"HSize": 10,
|
||||
"Filter": false
|
||||
},
|
||||
"MagazineCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"AmmunitionCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"WeaponCase": {
|
||||
"VSize": 10,
|
||||
"HSize": 5,
|
||||
"Filter": false
|
||||
},
|
||||
"ItemsCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"GrenadeCase": {
|
||||
"VSize": 8,
|
||||
"HSize": 8,
|
||||
"Filter": false
|
||||
},
|
||||
"WZWallet": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"SimpleWallet": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"MoneyCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"LuckyScav": {
|
||||
"VSize": 14,
|
||||
"HSize": 14,
|
||||
"Filter": false
|
||||
}
|
||||
},
|
||||
"SecureContainers": {
|
||||
"KappaHSize": 3,
|
||||
"AlphaVSize": 2,
|
||||
"KappaVSize": 4,
|
||||
"AlphaHSize": 2,
|
||||
"GammaHSize": 3,
|
||||
"GammaVSize": 3,
|
||||
"BetaVSize": 2,
|
||||
"BetaHSize": 3,
|
||||
"EpsilonHSize": 4,
|
||||
"EpsilonVSize": 2,
|
||||
"DevVSize": 3,
|
||||
"DevHSize": 3,
|
||||
"WaistPouchHSize": 2,
|
||||
"WaistPouchVSize": 2
|
||||
},
|
||||
"EnableCSM": true
|
||||
},
|
||||
"Scav": {
|
||||
"SCAVPockets": {
|
||||
"FourthH": 1,
|
||||
"FourthV": 1,
|
||||
"ThirdH": 1,
|
||||
"ThirdV": 1,
|
||||
"SecondH": 1,
|
||||
"SecondV": 1,
|
||||
"FirstH": 1,
|
||||
"FirstV": 1
|
||||
},
|
||||
"HostileBosses": false,
|
||||
"FriendlyBosses": false,
|
||||
"CarBaseStanding": 0.25,
|
||||
"ScavTimer": 900,
|
||||
"ScavCustomPockets": false,
|
||||
"ScavLab": false,
|
||||
"FriendlyScavs": false,
|
||||
"HostileScavs": false,
|
||||
"StandingFriendlyKill": -0.04,
|
||||
"StandingPMCKill": 0.01,
|
||||
"Health": {
|
||||
"LeftArm": 60,
|
||||
"RightArm": 60,
|
||||
"Head": 35,
|
||||
"Chest": 85,
|
||||
"Stomach": 70,
|
||||
"LeftLeg": 65,
|
||||
"RightLeg": 65
|
||||
},
|
||||
"EnableScavHealth": false,
|
||||
"EnableScav": false
|
||||
},
|
||||
"Bots": {
|
||||
"AIChance": {
|
||||
"Kolontay": 30,
|
||||
"Kaban": 30,
|
||||
"TagillaNight": 30,
|
||||
"TrioLighthouse": 30,
|
||||
"TrioShoreline": 30,
|
||||
"TrioWoods": 30,
|
||||
"Zryachiy": 100,
|
||||
"CultistCustoms": 15,
|
||||
"CultistShoreline": 20,
|
||||
"Trio": 30,
|
||||
"RaiderLab": 40,
|
||||
"RaiderReserve": 35,
|
||||
"CultistFactory": 10,
|
||||
"CultistWoods": 20,
|
||||
"Rogue": 65,
|
||||
"Tagilla": 30,
|
||||
"Shturman": 30,
|
||||
"Glukhar": 30,
|
||||
"Sanitar": 30,
|
||||
"Reshala": 30,
|
||||
"Killa": 30
|
||||
},
|
||||
"ArmorDurab": {
|
||||
"PMCMax": 100,
|
||||
"FollowerMax": 100,
|
||||
"BossMax": 100,
|
||||
"MarksmanMax": 100,
|
||||
"ScavMax": 100,
|
||||
"RogueMax": 100,
|
||||
"RaiderMax": 100,
|
||||
"PMCMin": 95,
|
||||
"FollowerMin": 90,
|
||||
"BossMin": 90,
|
||||
"MarksmanMin": 90,
|
||||
"ScavMin": 50,
|
||||
"RogueMin": 90,
|
||||
"RaiderMin": 90
|
||||
},
|
||||
"WeaponDurab": {
|
||||
"PMCMax": 100,
|
||||
"FollowerMax": 100,
|
||||
"BossMax": 100,
|
||||
"MarksmanMax": 100,
|
||||
"ScavMax": 100,
|
||||
"RogueMax": 100,
|
||||
"RaiderMax": 100,
|
||||
"PMCMin": 95,
|
||||
"FollowerMin": 90,
|
||||
"BossMin": 100,
|
||||
"MarksmanMin": 90,
|
||||
"ScavMin": 50,
|
||||
"RogueMin": 90,
|
||||
"RaiderMin": 90
|
||||
},
|
||||
"EnableBots": false
|
||||
},
|
||||
"PMC": {
|
||||
"NameOverride": true,
|
||||
"ForceCustomWaves": false,
|
||||
"CustomWaveChance": 100,
|
||||
"PMCChance": {
|
||||
"PMCNamePrefix": 15,
|
||||
"PMCAllNamePrefix": 5,
|
||||
"PMCLooseWep": 15,
|
||||
"HostilePMC": 80,
|
||||
"PMCWepEnhance": 5
|
||||
},
|
||||
"LevelMargin": 10,
|
||||
"PMCNameList": "Sinistar\r\nMorgan\r\nMayoringram\r\nAssAssin\r\nGhostFenixx\r\nG10orgos\r\nFortis\r\nDaveyB0y\r\nMMX\r\nTabi\r\nJojo\r\njvs\r\ndspider\r\nHenny\r\nMylu\r\nXen0Xys\r\nShruggzilla\r\nDoddsy\r\nMoffed\r\nBeagle\r\nthorncp\r\nEagle\r\nBroCC\r\nCthulhu\r\nKaryash\r\nlilthicccums\r\nTobi\r\nEmilia\r\nParka4our\r\nDonmohnke\r\nSingularity\r\nTogay\r\nGodHimself\r\nThomaszhrets\r\nAsianLover69\r\nRodyChodes\r\nl6uc6if6er\r\nbllt\r\nSaltyDroog\r\nM_conniptions\r\ntagilla\r\nTricolorHen061\r\nWaffentrager\r\nHabit\r\nUmsirqualquerai\r\nshep\r\nCannabis\r\ntrappussy\r\nShiro\r\nChomp\r\nTheSparta\r\nLostQuasar\r\nssh\r\nLacyway\r\nKronzky\r\njbs4bmx\r\naleves\r\nFontaine\r\nCWX\r\nTwistedGA\r\nLimbo\r\nSamSWAT\r\nRevingly\r\nKattomine\r\nFox\r\nGamenator\r\nCarl\r\njrdenny1\r\nDecompresS\r\nCats\r\nAtonicX\r\nsyra\r\nStako\r\nCheiftan\r\nelectric\r\nImTheRickHere\r\nmajorlier\r\nbearPhone\r\nMasterChief\r\nThe2ndarbiter\r\ncowkiller19\r\nKarma\r\nJen14owns\r\nRubMyRubber\r\nFrumorn\r\nBuyingGF\r\nRekty\r\nRebrix\r\nDatPhatAsian\r\nChinaski\r\nChameleon\r\nNeCzar\r\nPonderingOrb\r\nSerakym\r\nArsanthania\r\nYT_Mark\r\nBeDaLek\r\nNukey\r\nOwl\r\nJoshtheOG\r\nbatkiller\r\nVampireKitten\r\nBadwolf_54\r\nJustin\r\nAgentMoulder\r\nLayerofBlubber\r\nD3ovm\r\nkiobu\r\n7AmatoR7\r\nKAcidi\r\nZ3R0\r\nSpiral\r\nXsos\r\nVortex\r\nalphakiller\r\nbiohazard\r\nli3raa\r\nDalamadur\r\ncentollojames\r\nlennyrod\r\nZooey\r\nMaoci\r\nMarchwin\r\nMiki__\r\nInnerMiddle\r\npersistent\r\nYarik85\r\nDeadLeaves\r\nSenko\r\nNexus4880\r\nSlickboi\r\nTallanX\r\ntrippy\r\nguidot\r\nJanuary\r\nCloudy\r\nMrElmoEN\r\nNekoKami\r\nDOKDOR\r\ndeathbricks\r\nHustlesofter\r\nVenican\r\nNickMillion\r\nJuncker\r\nPhantomInTime\r\nVox\r\nTraveler\r\nnimbul\r\nEreshkigal\r\nSerWolfik",
|
||||
"NamesEnable": true,
|
||||
"PMCRatio": 50,
|
||||
"AItoPMC": {
|
||||
"PMCtoCursedScav": 20,
|
||||
"SnipertoPMC": 0,
|
||||
"PMCtoScav": 25,
|
||||
"ExusectoPMC": 5,
|
||||
"PMCtoRaider": 7
|
||||
},
|
||||
"DisableLowLevelPMC": false,
|
||||
"LootableMelee": false,
|
||||
"EnablePMC": false
|
||||
},
|
||||
"Custom": {
|
||||
"EnableCustom": false,
|
||||
"DebugAI": false,
|
||||
"LoggerIntoServer": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
{
|
||||
"PresetNotes": "",
|
||||
"Items": {
|
||||
"HeatFactor": 1,
|
||||
"ExamineTime": 1,
|
||||
"ExamineKeys": false,
|
||||
"AddSignalPistolToSpec": false,
|
||||
"WeaponHeatOff": false,
|
||||
"SMGToHolster": false,
|
||||
"PistolToMain": false,
|
||||
"AllExaminedItems": false,
|
||||
"MisfireChance": 1,
|
||||
"EquipRigsWithArmors": false,
|
||||
"RemoveSecureContainerFilters": false,
|
||||
"MalfunctChanceMult": 1,
|
||||
"WeightChanger": 1,
|
||||
"ItemPriceMult": 1,
|
||||
"BSGCashStack": false,
|
||||
"RubStack": 500000,
|
||||
"DollarStack": 50000,
|
||||
"EuroStack": 50000,
|
||||
"AmmoLoadSpeed": 1,
|
||||
"LootExp": 1,
|
||||
"EnableItems": false,
|
||||
"ExamineExp": 1,
|
||||
"AmmoStacks": {
|
||||
"MarksmanRound": 40,
|
||||
"RifleRound": 60,
|
||||
"ShotgunRound": 20,
|
||||
"PistolRound": 50
|
||||
},
|
||||
"RemoveRaidRestr": false,
|
||||
"RemoveBackpacksRestrictions": false,
|
||||
"AvoidSingleKeys": false,
|
||||
"AvoidMarkedKeys": false,
|
||||
"KeyUseMult": 1,
|
||||
"KeycardUseMult": 1,
|
||||
"KeyDurabilityThreshold": 40,
|
||||
"IDChanger": false,
|
||||
"NoGearPenalty": false,
|
||||
"IDBox": "",
|
||||
"RemoveKeysUsageNumber": false,
|
||||
"RaidDrop": false
|
||||
},
|
||||
"Hideout": {
|
||||
"EnableStash": false,
|
||||
"Stash": {
|
||||
"StashLvl4": 68,
|
||||
"StashLvl3": 48,
|
||||
"StashLvl2": 38,
|
||||
"StashLvl1": 28
|
||||
},
|
||||
"Regeneration": {
|
||||
"OfflineRegen": false,
|
||||
"HealthRegen": 1,
|
||||
"HideoutHealth": false,
|
||||
"HideoutEnergy": false,
|
||||
"HideoutHydration": false,
|
||||
"HydrationRegen": 1,
|
||||
"EnergyRegen": 1
|
||||
},
|
||||
"WaterFilterTime": 325,
|
||||
"BitcoinTime": 2416,
|
||||
"MaxBitcoins": 3,
|
||||
"NoFuelMult": 1,
|
||||
"ScavCasePrice": 1,
|
||||
"ScavCaseTime": 1,
|
||||
"HideoutConstMult": 1,
|
||||
"HideoutProdMult": 1,
|
||||
"WaterFilterRate": 66,
|
||||
"GPUBoostRate": 1,
|
||||
"AirFilterRate": 1,
|
||||
"RemoveConstructionsRequirements": false,
|
||||
"RemoveSkillRequirements": false,
|
||||
"RemoveTraderLevelRequirements": false,
|
||||
"EnableHideout": false,
|
||||
"FuelConsumptionRate": 1
|
||||
},
|
||||
"Traders": {
|
||||
"Fence": {
|
||||
"ArmorDurability_Max": 60,
|
||||
"GunDurability_Max": 60,
|
||||
"ArmorDurability_Min": 35,
|
||||
"GunDurability_Min": 35,
|
||||
"PriceMult": 1.2,
|
||||
"PremiumAmountOnSale": 50,
|
||||
"PresetCount": 5,
|
||||
"StockTime_Min": 50,
|
||||
"StockTime_Max": 150,
|
||||
"AmountOnSale": 140,
|
||||
"PresetMult": 2,
|
||||
"Blacklist": "5c164d2286f774194c5e69fa\r\n543be6674bdc2df1348b4569\r\n5448bf274bdc2dfc2f8b456a\r\n5447bedf4bdc2d87278b4568\r\n6275303a9f372d6ea97f9ec7\r\n62e9103049c018f425059f38\r\n59f32bb586f774757e1e8442\r\n59f32c3b86f77472a31742f0\r\n627bce33f21bc425b06ab967\r\n62f109593b54472778797866\r\n5d52cc5ba4b9367408500062\r\n5d52d479a4b936793d58c76b\r\n5e99711486f7744bfc4af328\r\n62f10b79e7ee985f386b2f47\r\n633ffb5d419dbf4bea7004c6\r\n543be5dd4bdc2deb348b4569\r\n65649eb40bf0ed77b8044453\r\n5448e54d4bdc2dcc718b4568\r\n5a341c4086f77401f2541505\r\n5422acb9af1c889c16000029\r\n64d0b40fbe2eed70e254e2d4\r\n5fc22d7c187fea44d52eda44"
|
||||
},
|
||||
"RemoveTradeLimits": false,
|
||||
"QuestRedeemTime": 48,
|
||||
"TraderMarkup": {
|
||||
"Ragman": 62,
|
||||
"Peacekeeper": 45,
|
||||
"Fence": 40,
|
||||
"Prapor": 50,
|
||||
"Jaeger": 60,
|
||||
"Mechanic": 56,
|
||||
"Skier": 49,
|
||||
"Therapist": 63
|
||||
},
|
||||
"TraderSell": {
|
||||
"Ragman": 1,
|
||||
"Peacekeeper": 1,
|
||||
"Prapor": 1,
|
||||
"Jaeger": 1,
|
||||
"Mechanic": 1,
|
||||
"Skier": 1,
|
||||
"Therapist": 1
|
||||
},
|
||||
"MinDurabSell": 60,
|
||||
"RemoveTimeCondition": false,
|
||||
"AllQuestsAvailable": false,
|
||||
"RemoveBarterOffers": false,
|
||||
"RemoveCurrencyOffers": false,
|
||||
"IncreaseAssort": false,
|
||||
"UnlockQuestAssort": false,
|
||||
"EnableTraders": false,
|
||||
"FIRRestrictsQuests": false,
|
||||
"TradersLvl4": false,
|
||||
"FIRTrade": false
|
||||
},
|
||||
"Loot": {
|
||||
"Airdrops": {
|
||||
"Mixed": {
|
||||
"ArmorMin": 1,
|
||||
"ArmorMax": 5,
|
||||
"BarterMin": 15,
|
||||
"BarterMax": 35,
|
||||
"PresetMin": 3,
|
||||
"PresetMax": 5,
|
||||
"CratesMin": 1,
|
||||
"CratesMax": 2
|
||||
},
|
||||
"Medical": {
|
||||
"ArmorMin": 0,
|
||||
"ArmorMax": 0,
|
||||
"BarterMin": 17,
|
||||
"BarterMax": 28,
|
||||
"PresetMin": 0,
|
||||
"PresetMax": 0,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 0
|
||||
},
|
||||
"Barter": {
|
||||
"ArmorMin": 0,
|
||||
"ArmorMax": 0,
|
||||
"BarterMin": 20,
|
||||
"BarterMax": 35,
|
||||
"PresetMin": 0,
|
||||
"PresetMax": 0,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 0
|
||||
},
|
||||
"Weapon": {
|
||||
"ArmorMin": 3,
|
||||
"ArmorMax": 6,
|
||||
"BarterMin": 11,
|
||||
"BarterMax": 22,
|
||||
"PresetMin": 6,
|
||||
"PresetMax": 8,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 2
|
||||
},
|
||||
"Sandbox_air": 20,
|
||||
"Streets_air": 20,
|
||||
"AirtimeMin": 1,
|
||||
"AirtimeMax": 5,
|
||||
"Lighthouse_air": 20,
|
||||
"Bigmap_air": 25,
|
||||
"Interchange_air": 20,
|
||||
"Shoreline_air": 20,
|
||||
"Reserve_air": 20,
|
||||
"Woods_air": 25
|
||||
},
|
||||
"EnableLoot": false,
|
||||
"Locations": {
|
||||
"Streets": {
|
||||
"Loose": 3,
|
||||
"Container": 1
|
||||
},
|
||||
"Sandbox": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Lighthouse": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Bigmap": {
|
||||
"Loose": 2.5,
|
||||
"Container": 1
|
||||
},
|
||||
"Interchange": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"FactoryDay": {
|
||||
"Loose": 3.5,
|
||||
"Container": 1
|
||||
},
|
||||
"Laboratory": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Shoreline": {
|
||||
"Loose": 3.7,
|
||||
"Container": 1
|
||||
},
|
||||
"Reserve": {
|
||||
"Loose": 2.9,
|
||||
"Container": 1
|
||||
},
|
||||
"Woods": {
|
||||
"Loose": 1.9,
|
||||
"Container": 1
|
||||
},
|
||||
"FactoryNight": {
|
||||
"Loose": 3.5,
|
||||
"Container": 1
|
||||
},
|
||||
"AllContainers": false
|
||||
}
|
||||
},
|
||||
"Player": {
|
||||
"CharXP": {
|
||||
"ScavKill": 80,
|
||||
"ScavHMult": 1.1,
|
||||
"PMCKill": 175,
|
||||
"PMCHMult": 1.2
|
||||
},
|
||||
"RaidMult": {
|
||||
"MIA": 1,
|
||||
"Runner": 0.5,
|
||||
"Survived": 1.3,
|
||||
"Killed": 1
|
||||
},
|
||||
"EnableStats": false,
|
||||
"Skills": {
|
||||
"SkillFatigueReset": 200,
|
||||
"SkillFreshEffect": 1.3,
|
||||
"SkillFPoints": 1,
|
||||
"SkillPointsBeforeFatigue": 1,
|
||||
"SkillMinEffect": 0.0001,
|
||||
"SkillFatiguePerPoint": 0.6
|
||||
},
|
||||
"FallDamage": false,
|
||||
"BlackStomach": 5,
|
||||
"HydrationLoss": 1,
|
||||
"EnergyLoss": 1,
|
||||
"EnableHealth": false,
|
||||
"SkillProgMult": 0.4,
|
||||
"Health": {
|
||||
"LeftArm": 60,
|
||||
"RightArm": 60,
|
||||
"Head": 35,
|
||||
"Chest": 85,
|
||||
"Stomach": 70,
|
||||
"LeftLeg": 65,
|
||||
"RightLeg": 65
|
||||
},
|
||||
"WeaponSkillMult": 1,
|
||||
"EnablePlayer": false,
|
||||
"DiedHealth": {
|
||||
"Saveeffects": true,
|
||||
"Savehealth": true,
|
||||
"Health_blacked": 0.1,
|
||||
"Health_death": 0.3
|
||||
},
|
||||
"MaxStamina": 100,
|
||||
"UnlimitedStamina": false,
|
||||
"MaxHydration": 100,
|
||||
"MaxEnergy": 100
|
||||
},
|
||||
"Raids": {
|
||||
"SandboxAddLevel": false,
|
||||
"RaidTime": 0,
|
||||
"SaveQuestItems": false,
|
||||
"Exfils": {
|
||||
"CarSandbox": 5000,
|
||||
"CarShoreline": 5000,
|
||||
"CoopPaidSandbox": 5000,
|
||||
"CoopPaidShoreline": 5000,
|
||||
"CoopPaidStreets": 5000,
|
||||
"CoopPaidLighthouse": 5000,
|
||||
"CarLighthouse": 5000,
|
||||
"CarExtractTime": 60,
|
||||
"ArmorExtract": false,
|
||||
"CoopPaid": false,
|
||||
"FenceGift": false,
|
||||
"CoopPaidInterchange": 5000,
|
||||
"CoopPaidWoods": 5000,
|
||||
"CoopPaidReserve": 5000,
|
||||
"NoBackpack": false,
|
||||
"FreeCoop": false,
|
||||
"CarInterchange": 5000,
|
||||
"CarWoods": 5000,
|
||||
"CarStreets": 5000,
|
||||
"CarCustoms": 5000,
|
||||
"ExtendedExtracts": false,
|
||||
"ChanceExtracts": false,
|
||||
"GearExtract": false
|
||||
},
|
||||
"NoRunThrough": false,
|
||||
"Timeacceleration": 7,
|
||||
"SafeExit": false,
|
||||
"SaveGearAfterDeath": false,
|
||||
"RaidEvents": {
|
||||
"DisableEvents": false,
|
||||
"SnowEvent": false,
|
||||
"KillaFactoryChance": 100,
|
||||
"GoonsFactoryChance": 100,
|
||||
"GoonsFactory": false,
|
||||
"BossesOnCustoms": false,
|
||||
"HoundsWoods": 30,
|
||||
"HoundsCustoms": 30,
|
||||
"HoundsShoreline": 30,
|
||||
"Christmas": false,
|
||||
"Halloween": false,
|
||||
"IncludeTagilla": false,
|
||||
"KillaFactory": false,
|
||||
"BossesOnReserve": false,
|
||||
"CultistsEverywhere": false,
|
||||
"RaidersEverywhere": false,
|
||||
"GlukharLabs": false
|
||||
},
|
||||
"LabInsurance": false,
|
||||
"EnableRaids": false,
|
||||
"Removelabkey": false,
|
||||
"RaidStartup": {
|
||||
"TimeBeforeDeployLocal": 10,
|
||||
"AIAmount": "AsOnline",
|
||||
"SaveLoot": true,
|
||||
"AIDifficulty": "AsOnline",
|
||||
"MIAEndofRaid": true,
|
||||
"TaggedAndCursed": false,
|
||||
"EnableBosses": true,
|
||||
"ScavWars": false
|
||||
}
|
||||
},
|
||||
"Fleamarket": {
|
||||
"FleaFIR": false,
|
||||
"FleaNoFIRSell": false,
|
||||
"EventOffers": false,
|
||||
"SellOffersAmount": 10,
|
||||
"FleaConditions": {
|
||||
"FleaFood_Min": 5,
|
||||
"FleaArmor_Min": 5,
|
||||
"FleaFood_Max": 100,
|
||||
"FleaArmor_Max": 100,
|
||||
"FleaMedical_Min": 60,
|
||||
"FleaSpec_Min": 2,
|
||||
"FleaMedical_Max": 100,
|
||||
"FleaSpec_Max": 100,
|
||||
"FleaWeapons_Min": 60,
|
||||
"FleaVests_Min": 5,
|
||||
"FleaKeys_Min": 97,
|
||||
"FleaWeapons_Max": 100,
|
||||
"FleaVests_Max": 100,
|
||||
"FleaKeys_Max": 100
|
||||
},
|
||||
"OverrideOffers": false,
|
||||
"FleaMarketLevel": 15,
|
||||
"FleaBlacklist": null,
|
||||
"DisableBSGList": false,
|
||||
"EnableFleamarket": false,
|
||||
"Sell_mult": 1.24,
|
||||
"Tradeoffer_max": 1,
|
||||
"Rep_loss": 0.03,
|
||||
"Rep_gain": 0.02,
|
||||
"Tradeoffer_min": 0,
|
||||
"Sell_chance": 50,
|
||||
"EnableFees": true,
|
||||
"DynamicOffers": {
|
||||
"ExpireThreshold": 1400,
|
||||
"Stack_min": 10,
|
||||
"PerOffer_min": 7,
|
||||
"Stack_max": 600,
|
||||
"PerOffer_max": 30,
|
||||
"Eurooffers": 8,
|
||||
"Dollaroffers": 14,
|
||||
"Roubleoffers": 78,
|
||||
"NonStack_min": 1,
|
||||
"Time_min": 6,
|
||||
"Price_min": 0.8,
|
||||
"NonStack_max": 10,
|
||||
"Time_max": 60,
|
||||
"Price_max": 1.4
|
||||
}
|
||||
},
|
||||
"Services": {
|
||||
"RepairBox": {
|
||||
"NoRandomRepair": false,
|
||||
"OpGunRepair": false,
|
||||
"ArmorSkillMult": 0.05,
|
||||
"WeaponMaintenanceSkillMult": 0.05,
|
||||
"IntellectSkillMultWeaponKit": 0.045,
|
||||
"IntellectSkillMultArmorKit": 0.03,
|
||||
"IntellectSkillLimitTraders": 0.6,
|
||||
"IntellectSkillLimitKit": 0.6,
|
||||
"OpArmorRepair": false,
|
||||
"RepairMult": 1
|
||||
},
|
||||
"EnableHealMarkup": false,
|
||||
"TherapistLvl1": 1,
|
||||
"TherapistLvl2": 1.1,
|
||||
"TherapistLvl3": 1.2,
|
||||
"TherapistLvl4": 1.35,
|
||||
"FreeHealLvl": 5,
|
||||
"FreeHealRaids": 30,
|
||||
"ReturnChanceTherapist": 85,
|
||||
"InsuranceMultTherapist": 0.25,
|
||||
"EnableServices": false,
|
||||
"ClothesAnySide": false,
|
||||
"InsuranceMultPrapor": 0.16,
|
||||
"InsuranceStorageTime": 72,
|
||||
"ReturnChancePrapor": 75,
|
||||
"ClothesLevelUnlock": false,
|
||||
"ClothesFree": false,
|
||||
"Prapor_Max": 36,
|
||||
"Prapor_Min": 24,
|
||||
"Therapist_Max": 24,
|
||||
"Therapist_Min": 12
|
||||
},
|
||||
"Quests": {
|
||||
"QuestCostMult": 1,
|
||||
"QuestRepToZero": false,
|
||||
"DailyQuests": {
|
||||
"MinKillsLR3": 5,
|
||||
"MaxKillsLR3": 20,
|
||||
"MinKillsLR2": 5,
|
||||
"MaxKillsLR2": 15,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 3,
|
||||
"Types": "Completion,Elimination,Exploration",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 2,
|
||||
"MaxKillsLR1": 4,
|
||||
"Access": 5,
|
||||
"QuestAmount": 3,
|
||||
"Lifespan": 1440
|
||||
},
|
||||
"WeeklyQuests": {
|
||||
"MinKillsLR3": 20,
|
||||
"MaxKillsLR3": 40,
|
||||
"MinKillsLR2": 15,
|
||||
"MaxKillsLR2": 40,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 10,
|
||||
"Types": "Completion,Elimination,Exploration",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 10,
|
||||
"MaxKillsLR1": 20,
|
||||
"Access": 15,
|
||||
"QuestAmount": 1,
|
||||
"Lifespan": 10080
|
||||
},
|
||||
"EnableQuests": false,
|
||||
"ScavQuests": {
|
||||
"MinKillsLR2": 3,
|
||||
"MaxKillsLR2": 15,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 3,
|
||||
"Types": "Elimination,Completion",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 1,
|
||||
"MaxKillsLR1": 3,
|
||||
"Access": 1,
|
||||
"QuestAmount": 1,
|
||||
"Lifespan": 1440
|
||||
}
|
||||
},
|
||||
"CSM": {
|
||||
"CustomPocket": false,
|
||||
"Pockets": {
|
||||
"DefaultPocket": false,
|
||||
"SpecGKeychain": false,
|
||||
"SpecSimpleWallet": false,
|
||||
"SpecWZWallet": false,
|
||||
"SpecKeycardHolder": false,
|
||||
"SpecKeytool": false,
|
||||
"SpecInjectorCase": false,
|
||||
"SpecSlots": 3,
|
||||
"FourthH": 1,
|
||||
"FourthV": 1,
|
||||
"ThirdH": 1,
|
||||
"ThirdV": 1,
|
||||
"SecondH": 1,
|
||||
"SecondV": 1,
|
||||
"FirstH": 1,
|
||||
"FirstV": 1
|
||||
},
|
||||
"Cases": {
|
||||
"GKeychain": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"KeycardHolderCase": {
|
||||
"VSize": 3,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"InjectorCase": {
|
||||
"VSize": 3,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"Holodilnick": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"PistolCase": {
|
||||
"VSize": 4,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"DocumentsCase": {
|
||||
"VSize": 4,
|
||||
"HSize": 4,
|
||||
"Filter": false
|
||||
},
|
||||
"Keytool": {
|
||||
"VSize": 4,
|
||||
"HSize": 4,
|
||||
"Filter": false
|
||||
},
|
||||
"SiccCase": {
|
||||
"VSize": 5,
|
||||
"HSize": 5,
|
||||
"Filter": false
|
||||
},
|
||||
"ThiccWeaponCase": {
|
||||
"VSize": 15,
|
||||
"HSize": 6,
|
||||
"Filter": false
|
||||
},
|
||||
"ThiccItemsCase": {
|
||||
"VSize": 14,
|
||||
"HSize": 14,
|
||||
"Filter": false
|
||||
},
|
||||
"MedicineCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"DogtagCase": {
|
||||
"VSize": 10,
|
||||
"HSize": 10,
|
||||
"Filter": false
|
||||
},
|
||||
"MagazineCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"AmmunitionCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"WeaponCase": {
|
||||
"VSize": 10,
|
||||
"HSize": 5,
|
||||
"Filter": false
|
||||
},
|
||||
"ItemsCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"GrenadeCase": {
|
||||
"VSize": 8,
|
||||
"HSize": 8,
|
||||
"Filter": false
|
||||
},
|
||||
"WZWallet": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"SimpleWallet": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"MoneyCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"LuckyScav": {
|
||||
"VSize": 14,
|
||||
"HSize": 14,
|
||||
"Filter": false
|
||||
}
|
||||
},
|
||||
"SecureContainers": {
|
||||
"KappaHSize": 3,
|
||||
"AlphaVSize": 2,
|
||||
"KappaVSize": 4,
|
||||
"AlphaHSize": 2,
|
||||
"GammaHSize": 3,
|
||||
"GammaVSize": 3,
|
||||
"BetaVSize": 2,
|
||||
"BetaHSize": 3,
|
||||
"EpsilonHSize": 4,
|
||||
"EpsilonVSize": 2,
|
||||
"DevVSize": 3,
|
||||
"DevHSize": 3,
|
||||
"WaistPouchHSize": 2,
|
||||
"WaistPouchVSize": 2
|
||||
},
|
||||
"EnableCSM": false
|
||||
},
|
||||
"Scav": {
|
||||
"SCAVPockets": {
|
||||
"FourthH": 1,
|
||||
"FourthV": 1,
|
||||
"ThirdH": 1,
|
||||
"ThirdV": 1,
|
||||
"SecondH": 1,
|
||||
"SecondV": 1,
|
||||
"FirstH": 1,
|
||||
"FirstV": 1
|
||||
},
|
||||
"HostileBosses": false,
|
||||
"FriendlyBosses": false,
|
||||
"CarBaseStanding": 0.25,
|
||||
"ScavTimer": 900,
|
||||
"ScavCustomPockets": false,
|
||||
"ScavLab": false,
|
||||
"FriendlyScavs": false,
|
||||
"HostileScavs": false,
|
||||
"StandingFriendlyKill": -0.04,
|
||||
"StandingPMCKill": 0.01,
|
||||
"Health": {
|
||||
"LeftArm": 60,
|
||||
"RightArm": 60,
|
||||
"Head": 35,
|
||||
"Chest": 85,
|
||||
"Stomach": 70,
|
||||
"LeftLeg": 65,
|
||||
"RightLeg": 65
|
||||
},
|
||||
"EnableScavHealth": false,
|
||||
"EnableScav": false
|
||||
},
|
||||
"Bots": {
|
||||
"AIChance": {
|
||||
"Kolontay": 30,
|
||||
"Kaban": 30,
|
||||
"TagillaNight": 30,
|
||||
"TrioLighthouse": 30,
|
||||
"TrioShoreline": 30,
|
||||
"TrioWoods": 30,
|
||||
"Zryachiy": 100,
|
||||
"CultistCustoms": 15,
|
||||
"CultistShoreline": 20,
|
||||
"Trio": 30,
|
||||
"RaiderLab": 40,
|
||||
"RaiderReserve": 35,
|
||||
"CultistFactory": 10,
|
||||
"CultistWoods": 20,
|
||||
"Rogue": 65,
|
||||
"Tagilla": 30,
|
||||
"Shturman": 30,
|
||||
"Glukhar": 30,
|
||||
"Sanitar": 30,
|
||||
"Reshala": 30,
|
||||
"Killa": 30
|
||||
},
|
||||
"ArmorDurab": {
|
||||
"PMCMax": 100,
|
||||
"FollowerMax": 100,
|
||||
"BossMax": 100,
|
||||
"MarksmanMax": 100,
|
||||
"ScavMax": 100,
|
||||
"RogueMax": 100,
|
||||
"RaiderMax": 100,
|
||||
"PMCMin": 90,
|
||||
"FollowerMin": 90,
|
||||
"BossMin": 100,
|
||||
"MarksmanMin": 90,
|
||||
"ScavMin": 50,
|
||||
"RogueMin": 90,
|
||||
"RaiderMin": 90
|
||||
},
|
||||
"WeaponDurab": {
|
||||
"PMCMax": 100,
|
||||
"FollowerMax": 100,
|
||||
"BossMax": 100,
|
||||
"MarksmanMax": 100,
|
||||
"ScavMax": 100,
|
||||
"RogueMax": 100,
|
||||
"RaiderMax": 100,
|
||||
"PMCMin": 95,
|
||||
"FollowerMin": 80,
|
||||
"BossMin": 80,
|
||||
"MarksmanMin": 60,
|
||||
"ScavMin": 85,
|
||||
"RogueMin": 80,
|
||||
"RaiderMin": 80
|
||||
},
|
||||
"EnableBots": false
|
||||
},
|
||||
"PMC": {
|
||||
"NameOverride": false,
|
||||
"ForceCustomWaves": false,
|
||||
"CustomWaveChance": 100,
|
||||
"PMCChance": {
|
||||
"PMCNamePrefix": 15,
|
||||
"PMCAllNamePrefix": 5,
|
||||
"PMCLooseWep": 15,
|
||||
"HostilePMC": 80,
|
||||
"PMCWepEnhance": 5
|
||||
},
|
||||
"LevelMargin": 10,
|
||||
"PMCNameList": "Sinistar\r\nMorgan\r\nMayoringram\r\nAssAssin\r\nGhostFenixx\r\nG10orgos\r\nFortis\r\nDaveyB0y\r\nMMX\r\nTabi\r\nJojo\r\njvs\r\ndspider\r\nHenny\r\nMylu\r\nXen0Xys\r\nShruggzilla\r\nDoddsy\r\nMoffed\r\nBeagle\r\nthorncp\r\nEagle\r\nBroCC\r\nCthulhu\r\nKaryash\r\nlilthicccums\r\nTobi\r\nEmilia\r\nParka4our\r\nDonmohnke\r\nSingularity\r\nTogay\r\nGodHimself\r\nThomaszhrets\r\nAsianLover69\r\nRodyChodes\r\nl6uc6if6er\r\nbllt\r\nSaltyDroog\r\nM_conniptions\r\ntagilla\r\nTricolorHen061\r\nWaffentrager\r\nHabit\r\nUmsirqualquerai\r\nshep\r\nCannabis\r\ntrappussy\r\nShiro\r\nChomp\r\nTheSparta\r\nLostQuasar\r\nssh\r\nLacyway\r\nKronzky\r\njbs4bmx\r\naleves\r\nFontaine\r\nCWX\r\nTwistedGA\r\nLimbo\r\nSamSWAT\r\nRevingly\r\nKattomine\r\nFox\r\nGamenator\r\nCarl\r\njrdenny1\r\nDecompresS\r\nCats\r\nAtonicX\r\nsyra\r\nStako\r\nCheiftan\r\nelectric\r\nImTheRickHere\r\nmajorlier\r\nbearPhone\r\nMasterChief\r\nThe2ndarbiter\r\ncowkiller19\r\nKarma\r\nJen14owns\r\nRubMyRubber\r\nFrumorn\r\nBuyingGF\r\nRekty\r\nRebrix\r\nDatPhatAsian\r\nChinaski\r\nChameleon\r\nNeCzar\r\nPonderingOrb\r\nSerakym\r\nArsanthania\r\nYT_Mark\r\nBeDaLek\r\nNukey\r\nOwl\r\nJoshtheOG\r\nbatkiller\r\nVampireKitten\r\nBadwolf_54\r\nJustin\r\nAgentMoulder\r\nLayerofBlubber\r\nD3ovm\r\nkiobu\r\n7AmatoR7\r\nKAcidi\r\nZ3R0\r\nSpiral\r\nXsos\r\nVortex\r\nalphakiller\r\nbiohazard\r\nli3raa\r\nDalamadur\r\ncentollojames\r\nlennyrod\r\nZooey\r\nMaoci\r\nMarchwin\r\nMiki__\r\nInnerMiddle\r\npersistent\r\nYarik85\r\nDeadLeaves\r\nSenko\r\nNexus4880\r\nSlickboi\r\nTallanX\r\ntrippy\r\nguidot\r\nJanuary\r\nCloudy\r\nMrElmoEN\r\nNekoKami\r\nDOKDOR\r\ndeathbricks\r\nHustlesofter\r\nVenican\r\nNickMillion\r\nJuncker\r\nPhantomInTime\r\nVox\r\nTraveler\r\nnimbul\r\nEreshkigal\r\nSerWolfik",
|
||||
"NamesEnable": false,
|
||||
"PMCRatio": 50,
|
||||
"AItoPMC": {
|
||||
"PMCtoCursedScav": 20,
|
||||
"SnipertoPMC": 0,
|
||||
"PMCtoScav": 25,
|
||||
"ExusectoPMC": 5,
|
||||
"PMCtoRaider": 7
|
||||
},
|
||||
"DisableLowLevelPMC": false,
|
||||
"LootableMelee": false,
|
||||
"EnablePMC": false
|
||||
},
|
||||
"Custom": {
|
||||
"EnableCustom": false,
|
||||
"DebugAI": false,
|
||||
"LoggerIntoServer": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
{
|
||||
"PresetNotes": "",
|
||||
"Items": {
|
||||
"HeatFactor": 1,
|
||||
"ExamineTime": 1,
|
||||
"ExamineKeys": false,
|
||||
"AddSignalPistolToSpec": false,
|
||||
"WeaponHeatOff": false,
|
||||
"SMGToHolster": false,
|
||||
"PistolToMain": false,
|
||||
"AllExaminedItems": false,
|
||||
"MisfireChance": 1,
|
||||
"EquipRigsWithArmors": false,
|
||||
"RemoveSecureContainerFilters": false,
|
||||
"MalfunctChanceMult": 1,
|
||||
"WeightChanger": 1,
|
||||
"ItemPriceMult": 1,
|
||||
"BSGCashStack": false,
|
||||
"RubStack": 500000,
|
||||
"DollarStack": 50000,
|
||||
"EuroStack": 50000,
|
||||
"AmmoLoadSpeed": 1,
|
||||
"LootExp": 1,
|
||||
"EnableItems": false,
|
||||
"ExamineExp": 1,
|
||||
"AmmoStacks": {
|
||||
"MarksmanRound": 40,
|
||||
"RifleRound": 60,
|
||||
"ShotgunRound": 20,
|
||||
"PistolRound": 50
|
||||
},
|
||||
"RemoveRaidRestr": false,
|
||||
"RemoveBackpacksRestrictions": false,
|
||||
"AvoidSingleKeys": false,
|
||||
"AvoidMarkedKeys": false,
|
||||
"KeyUseMult": 1,
|
||||
"KeycardUseMult": 1,
|
||||
"KeyDurabilityThreshold": 40,
|
||||
"IDChanger": false,
|
||||
"NoGearPenalty": false,
|
||||
"IDBox": "",
|
||||
"RemoveKeysUsageNumber": false,
|
||||
"RaidDrop": false
|
||||
},
|
||||
"Hideout": {
|
||||
"EnableStash": false,
|
||||
"Stash": {
|
||||
"StashLvl4": 68,
|
||||
"StashLvl3": 48,
|
||||
"StashLvl2": 38,
|
||||
"StashLvl1": 28
|
||||
},
|
||||
"Regeneration": {
|
||||
"OfflineRegen": false,
|
||||
"HealthRegen": 1,
|
||||
"HideoutHealth": false,
|
||||
"HideoutEnergy": false,
|
||||
"HideoutHydration": false,
|
||||
"HydrationRegen": 1,
|
||||
"EnergyRegen": 1
|
||||
},
|
||||
"WaterFilterTime": 325,
|
||||
"BitcoinTime": 2416,
|
||||
"MaxBitcoins": 3,
|
||||
"NoFuelMult": 1,
|
||||
"ScavCasePrice": 1,
|
||||
"ScavCaseTime": 1,
|
||||
"HideoutConstMult": 0.01,
|
||||
"HideoutProdMult": 0.3,
|
||||
"WaterFilterRate": 66,
|
||||
"GPUBoostRate": 1,
|
||||
"AirFilterRate": 1,
|
||||
"RemoveConstructionsRequirements": false,
|
||||
"RemoveSkillRequirements": false,
|
||||
"RemoveTraderLevelRequirements": false,
|
||||
"EnableHideout": true,
|
||||
"FuelConsumptionRate": 1
|
||||
},
|
||||
"Traders": {
|
||||
"Fence": {
|
||||
"ArmorDurability_Max": 60,
|
||||
"GunDurability_Max": 60,
|
||||
"ArmorDurability_Min": 35,
|
||||
"GunDurability_Min": 35,
|
||||
"PriceMult": 1.2,
|
||||
"PremiumAmountOnSale": 50,
|
||||
"PresetCount": 5,
|
||||
"StockTime_Min": 50,
|
||||
"StockTime_Max": 150,
|
||||
"AmountOnSale": 140,
|
||||
"PresetMult": 2,
|
||||
"Blacklist": "5c164d2286f774194c5e69fa\r\n543be6674bdc2df1348b4569\r\n5448bf274bdc2dfc2f8b456a\r\n5447bedf4bdc2d87278b4568\r\n6275303a9f372d6ea97f9ec7\r\n62e9103049c018f425059f38\r\n59f32bb586f774757e1e8442\r\n59f32c3b86f77472a31742f0\r\n627bce33f21bc425b06ab967\r\n62f109593b54472778797866\r\n5d52cc5ba4b9367408500062\r\n5d52d479a4b936793d58c76b\r\n5e99711486f7744bfc4af328\r\n62f10b79e7ee985f386b2f47\r\n633ffb5d419dbf4bea7004c6\r\n543be5dd4bdc2deb348b4569\r\n65649eb40bf0ed77b8044453\r\n5448e54d4bdc2dcc718b4568\r\n5a341c4086f77401f2541505\r\n5422acb9af1c889c16000029\r\n64d0b40fbe2eed70e254e2d4\r\n5fc22d7c187fea44d52eda44"
|
||||
},
|
||||
"RemoveTradeLimits": true,
|
||||
"QuestRedeemTime": 48,
|
||||
"TraderMarkup": {
|
||||
"Ragman": 62,
|
||||
"Peacekeeper": 45,
|
||||
"Fence": 40,
|
||||
"Prapor": 50,
|
||||
"Jaeger": 60,
|
||||
"Mechanic": 56,
|
||||
"Skier": 49,
|
||||
"Therapist": 63
|
||||
},
|
||||
"TraderSell": {
|
||||
"Ragman": 1,
|
||||
"Peacekeeper": 1,
|
||||
"Prapor": 1,
|
||||
"Jaeger": 1,
|
||||
"Mechanic": 1,
|
||||
"Skier": 1,
|
||||
"Therapist": 1
|
||||
},
|
||||
"MinDurabSell": 60,
|
||||
"RemoveTimeCondition": true,
|
||||
"AllQuestsAvailable": false,
|
||||
"RemoveBarterOffers": false,
|
||||
"RemoveCurrencyOffers": false,
|
||||
"IncreaseAssort": true,
|
||||
"UnlockQuestAssort": false,
|
||||
"EnableTraders": true,
|
||||
"FIRRestrictsQuests": false,
|
||||
"TradersLvl4": false,
|
||||
"FIRTrade": false
|
||||
},
|
||||
"Loot": {
|
||||
"Airdrops": {
|
||||
"Mixed": {
|
||||
"ArmorMin": 1,
|
||||
"ArmorMax": 5,
|
||||
"BarterMin": 15,
|
||||
"BarterMax": 35,
|
||||
"PresetMin": 3,
|
||||
"PresetMax": 5,
|
||||
"CratesMin": 1,
|
||||
"CratesMax": 2
|
||||
},
|
||||
"Medical": {
|
||||
"ArmorMin": 0,
|
||||
"ArmorMax": 0,
|
||||
"BarterMin": 17,
|
||||
"BarterMax": 28,
|
||||
"PresetMin": 0,
|
||||
"PresetMax": 0,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 0
|
||||
},
|
||||
"Barter": {
|
||||
"ArmorMin": 0,
|
||||
"ArmorMax": 0,
|
||||
"BarterMin": 20,
|
||||
"BarterMax": 35,
|
||||
"PresetMin": 0,
|
||||
"PresetMax": 0,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 0
|
||||
},
|
||||
"Weapon": {
|
||||
"ArmorMin": 3,
|
||||
"ArmorMax": 6,
|
||||
"BarterMin": 11,
|
||||
"BarterMax": 22,
|
||||
"PresetMin": 6,
|
||||
"PresetMax": 8,
|
||||
"CratesMin": 0,
|
||||
"CratesMax": 2
|
||||
},
|
||||
"Sandbox_air": 20,
|
||||
"Streets_air": 20,
|
||||
"AirtimeMin": 1,
|
||||
"AirtimeMax": 5,
|
||||
"Lighthouse_air": 20,
|
||||
"Bigmap_air": 25,
|
||||
"Interchange_air": 20,
|
||||
"Shoreline_air": 20,
|
||||
"Reserve_air": 20,
|
||||
"Woods_air": 25
|
||||
},
|
||||
"EnableLoot": false,
|
||||
"Locations": {
|
||||
"Streets": {
|
||||
"Loose": 3,
|
||||
"Container": 1
|
||||
},
|
||||
"Sandbox": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Lighthouse": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Bigmap": {
|
||||
"Loose": 2.5,
|
||||
"Container": 1
|
||||
},
|
||||
"Interchange": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"FactoryDay": {
|
||||
"Loose": 3.5,
|
||||
"Container": 1
|
||||
},
|
||||
"Laboratory": {
|
||||
"Loose": 2.8,
|
||||
"Container": 1
|
||||
},
|
||||
"Shoreline": {
|
||||
"Loose": 3.7,
|
||||
"Container": 1
|
||||
},
|
||||
"Reserve": {
|
||||
"Loose": 2.9,
|
||||
"Container": 1
|
||||
},
|
||||
"Woods": {
|
||||
"Loose": 1.9,
|
||||
"Container": 1
|
||||
},
|
||||
"FactoryNight": {
|
||||
"Loose": 3.5,
|
||||
"Container": 1
|
||||
},
|
||||
"AllContainers": false
|
||||
}
|
||||
},
|
||||
"Player": {
|
||||
"CharXP": {
|
||||
"ScavKill": 80,
|
||||
"ScavHMult": 1.1,
|
||||
"PMCKill": 175,
|
||||
"PMCHMult": 1.2
|
||||
},
|
||||
"RaidMult": {
|
||||
"MIA": 1,
|
||||
"Runner": 0.5,
|
||||
"Survived": 1.3,
|
||||
"Killed": 1
|
||||
},
|
||||
"EnableStats": false,
|
||||
"Skills": {
|
||||
"SkillFatigueReset": 200,
|
||||
"SkillFreshEffect": 1.3,
|
||||
"SkillFPoints": 1,
|
||||
"SkillPointsBeforeFatigue": 1,
|
||||
"SkillMinEffect": 0.0001,
|
||||
"SkillFatiguePerPoint": 0.6
|
||||
},
|
||||
"FallDamage": false,
|
||||
"BlackStomach": 5,
|
||||
"HydrationLoss": 1,
|
||||
"EnergyLoss": 1,
|
||||
"EnableHealth": false,
|
||||
"SkillProgMult": 1,
|
||||
"Health": {
|
||||
"LeftArm": 60,
|
||||
"RightArm": 60,
|
||||
"Head": 35,
|
||||
"Chest": 85,
|
||||
"Stomach": 70,
|
||||
"LeftLeg": 65,
|
||||
"RightLeg": 65
|
||||
},
|
||||
"WeaponSkillMult": 1,
|
||||
"EnablePlayer": true,
|
||||
"DiedHealth": {
|
||||
"Saveeffects": true,
|
||||
"Savehealth": true,
|
||||
"Health_blacked": 0.1,
|
||||
"Health_death": 0.3
|
||||
},
|
||||
"MaxStamina": 100,
|
||||
"UnlimitedStamina": false,
|
||||
"MaxHydration": 100,
|
||||
"MaxEnergy": 100
|
||||
},
|
||||
"Raids": {
|
||||
"SandboxAddLevel": false,
|
||||
"RaidTime": 0,
|
||||
"SaveQuestItems": false,
|
||||
"Exfils": {
|
||||
"CarSandbox": 5000,
|
||||
"CarShoreline": 5000,
|
||||
"CoopPaidSandbox": 5000,
|
||||
"CoopPaidShoreline": 5000,
|
||||
"CoopPaidStreets": 5000,
|
||||
"CoopPaidLighthouse": 5000,
|
||||
"CarLighthouse": 5000,
|
||||
"CarExtractTime": 60,
|
||||
"ArmorExtract": false,
|
||||
"CoopPaid": true,
|
||||
"FenceGift": false,
|
||||
"CoopPaidInterchange": 5000,
|
||||
"CoopPaidWoods": 5000,
|
||||
"CoopPaidReserve": 5000,
|
||||
"NoBackpack": false,
|
||||
"FreeCoop": false,
|
||||
"CarInterchange": 5000,
|
||||
"CarWoods": 5000,
|
||||
"CarStreets": 5000,
|
||||
"CarCustoms": 5000,
|
||||
"ExtendedExtracts": false,
|
||||
"ChanceExtracts": false,
|
||||
"GearExtract": false
|
||||
},
|
||||
"NoRunThrough": false,
|
||||
"Timeacceleration": 7,
|
||||
"SafeExit": false,
|
||||
"SaveGearAfterDeath": false,
|
||||
"RaidEvents": {
|
||||
"DisableEvents": false,
|
||||
"SnowEvent": false,
|
||||
"KillaFactoryChance": 100,
|
||||
"GoonsFactoryChance": 100,
|
||||
"GoonsFactory": false,
|
||||
"BossesOnCustoms": false,
|
||||
"HoundsWoods": 30,
|
||||
"HoundsCustoms": 30,
|
||||
"HoundsShoreline": 30,
|
||||
"Christmas": false,
|
||||
"Halloween": false,
|
||||
"IncludeTagilla": false,
|
||||
"KillaFactory": false,
|
||||
"BossesOnReserve": false,
|
||||
"CultistsEverywhere": false,
|
||||
"RaidersEverywhere": false,
|
||||
"GlukharLabs": false
|
||||
},
|
||||
"LabInsurance": false,
|
||||
"EnableRaids": true,
|
||||
"Removelabkey": false,
|
||||
"RaidStartup": {
|
||||
"TimeBeforeDeployLocal": 10,
|
||||
"AIAmount": "AsOnline",
|
||||
"SaveLoot": true,
|
||||
"AIDifficulty": "AsOnline",
|
||||
"MIAEndofRaid": true,
|
||||
"TaggedAndCursed": false,
|
||||
"EnableBosses": true,
|
||||
"ScavWars": false
|
||||
}
|
||||
},
|
||||
"Fleamarket": {
|
||||
"FleaFIR": false,
|
||||
"FleaNoFIRSell": true,
|
||||
"EventOffers": false,
|
||||
"SellOffersAmount": 10,
|
||||
"FleaConditions": {
|
||||
"FleaFood_Min": 5,
|
||||
"FleaArmor_Min": 5,
|
||||
"FleaFood_Max": 100,
|
||||
"FleaArmor_Max": 100,
|
||||
"FleaMedical_Min": 60,
|
||||
"FleaSpec_Min": 2,
|
||||
"FleaMedical_Max": 100,
|
||||
"FleaSpec_Max": 100,
|
||||
"FleaWeapons_Min": 60,
|
||||
"FleaVests_Min": 5,
|
||||
"FleaKeys_Min": 97,
|
||||
"FleaWeapons_Max": 100,
|
||||
"FleaVests_Max": 100,
|
||||
"FleaKeys_Max": 100
|
||||
},
|
||||
"OverrideOffers": false,
|
||||
"FleaMarketLevel": 1,
|
||||
"FleaBlacklist": null,
|
||||
"DisableBSGList": false,
|
||||
"EnableFleamarket": true,
|
||||
"Sell_mult": 1.24,
|
||||
"Tradeoffer_max": 1,
|
||||
"Rep_loss": 0.03,
|
||||
"Rep_gain": 0.02,
|
||||
"Tradeoffer_min": 0,
|
||||
"Sell_chance": 50,
|
||||
"EnableFees": true,
|
||||
"DynamicOffers": {
|
||||
"ExpireThreshold": 1400,
|
||||
"Stack_min": 10,
|
||||
"PerOffer_min": 7,
|
||||
"Stack_max": 600,
|
||||
"PerOffer_max": 30,
|
||||
"Eurooffers": 8,
|
||||
"Dollaroffers": 14,
|
||||
"Roubleoffers": 78,
|
||||
"NonStack_min": 1,
|
||||
"Time_min": 6,
|
||||
"Price_min": 0.8,
|
||||
"NonStack_max": 10,
|
||||
"Time_max": 60,
|
||||
"Price_max": 1.4
|
||||
}
|
||||
},
|
||||
"Services": {
|
||||
"RepairBox": {
|
||||
"NoRandomRepair": false,
|
||||
"OpGunRepair": false,
|
||||
"ArmorSkillMult": 0.05,
|
||||
"WeaponMaintenanceSkillMult": 0.05,
|
||||
"IntellectSkillMultWeaponKit": 0.045,
|
||||
"IntellectSkillMultArmorKit": 0.03,
|
||||
"IntellectSkillLimitTraders": 0.6,
|
||||
"IntellectSkillLimitKit": 0.6,
|
||||
"OpArmorRepair": false,
|
||||
"RepairMult": 1
|
||||
},
|
||||
"EnableHealMarkup": false,
|
||||
"TherapistLvl1": 1,
|
||||
"TherapistLvl2": 1.1,
|
||||
"TherapistLvl3": 1.2,
|
||||
"TherapistLvl4": 1.35,
|
||||
"FreeHealLvl": 5,
|
||||
"FreeHealRaids": 30,
|
||||
"ReturnChanceTherapist": 85,
|
||||
"InsuranceMultTherapist": 0.25,
|
||||
"EnableServices": false,
|
||||
"ClothesAnySide": false,
|
||||
"InsuranceMultPrapor": 0.16,
|
||||
"InsuranceStorageTime": 72,
|
||||
"ReturnChancePrapor": 75,
|
||||
"ClothesLevelUnlock": false,
|
||||
"ClothesFree": false,
|
||||
"Prapor_Max": 36,
|
||||
"Prapor_Min": 24,
|
||||
"Therapist_Max": 24,
|
||||
"Therapist_Min": 12
|
||||
},
|
||||
"Quests": {
|
||||
"QuestCostMult": 1,
|
||||
"QuestRepToZero": false,
|
||||
"DailyQuests": {
|
||||
"MinKillsLR3": 5,
|
||||
"MaxKillsLR3": 20,
|
||||
"MinKillsLR2": 5,
|
||||
"MaxKillsLR2": 15,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 3,
|
||||
"Types": "Completion,Elimination,Exploration",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 2,
|
||||
"MaxKillsLR1": 4,
|
||||
"Access": 5,
|
||||
"QuestAmount": 3,
|
||||
"Lifespan": 1440
|
||||
},
|
||||
"WeeklyQuests": {
|
||||
"MinKillsLR3": 20,
|
||||
"MaxKillsLR3": 40,
|
||||
"MinKillsLR2": 15,
|
||||
"MaxKillsLR2": 40,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 10,
|
||||
"Types": "Completion,Elimination,Exploration",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 10,
|
||||
"MaxKillsLR1": 20,
|
||||
"Access": 15,
|
||||
"QuestAmount": 1,
|
||||
"Lifespan": 10080
|
||||
},
|
||||
"EnableQuests": false,
|
||||
"ScavQuests": {
|
||||
"MinKillsLR2": 3,
|
||||
"MaxKillsLR2": 15,
|
||||
"MinItems": 2,
|
||||
"MaxItems": 5,
|
||||
"Extracts": 3,
|
||||
"Types": "Elimination,Completion",
|
||||
"Spread": 0.5,
|
||||
"MinKillsLR1": 1,
|
||||
"MaxKillsLR1": 3,
|
||||
"Access": 1,
|
||||
"QuestAmount": 1,
|
||||
"Lifespan": 1440
|
||||
}
|
||||
},
|
||||
"CSM": {
|
||||
"CustomPocket": false,
|
||||
"Pockets": {
|
||||
"DefaultPocket": false,
|
||||
"SpecGKeychain": false,
|
||||
"SpecSimpleWallet": false,
|
||||
"SpecWZWallet": false,
|
||||
"SpecKeycardHolder": false,
|
||||
"SpecKeytool": false,
|
||||
"SpecInjectorCase": false,
|
||||
"SpecSlots": 3,
|
||||
"FourthH": 1,
|
||||
"FourthV": 1,
|
||||
"ThirdH": 1,
|
||||
"ThirdV": 1,
|
||||
"SecondH": 1,
|
||||
"SecondV": 1,
|
||||
"FirstH": 1,
|
||||
"FirstV": 1
|
||||
},
|
||||
"Cases": {
|
||||
"GKeychain": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"KeycardHolderCase": {
|
||||
"VSize": 3,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"InjectorCase": {
|
||||
"VSize": 3,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"Holodilnick": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"PistolCase": {
|
||||
"VSize": 4,
|
||||
"HSize": 3,
|
||||
"Filter": false
|
||||
},
|
||||
"DocumentsCase": {
|
||||
"VSize": 4,
|
||||
"HSize": 4,
|
||||
"Filter": false
|
||||
},
|
||||
"Keytool": {
|
||||
"VSize": 4,
|
||||
"HSize": 4,
|
||||
"Filter": false
|
||||
},
|
||||
"SiccCase": {
|
||||
"VSize": 5,
|
||||
"HSize": 5,
|
||||
"Filter": false
|
||||
},
|
||||
"ThiccWeaponCase": {
|
||||
"VSize": 15,
|
||||
"HSize": 6,
|
||||
"Filter": false
|
||||
},
|
||||
"ThiccItemsCase": {
|
||||
"VSize": 14,
|
||||
"HSize": 14,
|
||||
"Filter": false
|
||||
},
|
||||
"MedicineCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"DogtagCase": {
|
||||
"VSize": 10,
|
||||
"HSize": 10,
|
||||
"Filter": false
|
||||
},
|
||||
"MagazineCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"AmmunitionCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"WeaponCase": {
|
||||
"VSize": 10,
|
||||
"HSize": 5,
|
||||
"Filter": false
|
||||
},
|
||||
"ItemsCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"GrenadeCase": {
|
||||
"VSize": 8,
|
||||
"HSize": 8,
|
||||
"Filter": false
|
||||
},
|
||||
"WZWallet": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"SimpleWallet": {
|
||||
"VSize": 2,
|
||||
"HSize": 2,
|
||||
"Filter": false
|
||||
},
|
||||
"MoneyCase": {
|
||||
"VSize": 7,
|
||||
"HSize": 7,
|
||||
"Filter": false
|
||||
},
|
||||
"LuckyScav": {
|
||||
"VSize": 14,
|
||||
"HSize": 14,
|
||||
"Filter": false
|
||||
}
|
||||
},
|
||||
"SecureContainers": {
|
||||
"KappaHSize": 3,
|
||||
"AlphaVSize": 2,
|
||||
"KappaVSize": 4,
|
||||
"AlphaHSize": 2,
|
||||
"GammaHSize": 3,
|
||||
"GammaVSize": 3,
|
||||
"BetaVSize": 2,
|
||||
"BetaHSize": 3,
|
||||
"EpsilonHSize": 4,
|
||||
"EpsilonVSize": 2,
|
||||
"DevVSize": 3,
|
||||
"DevHSize": 3,
|
||||
"WaistPouchHSize": 2,
|
||||
"WaistPouchVSize": 2
|
||||
},
|
||||
"EnableCSM": false
|
||||
},
|
||||
"Scav": {
|
||||
"SCAVPockets": {
|
||||
"FourthH": 1,
|
||||
"FourthV": 1,
|
||||
"ThirdH": 1,
|
||||
"ThirdV": 1,
|
||||
"SecondH": 1,
|
||||
"SecondV": 1,
|
||||
"FirstH": 1,
|
||||
"FirstV": 1
|
||||
},
|
||||
"HostileBosses": false,
|
||||
"FriendlyBosses": false,
|
||||
"CarBaseStanding": 0.25,
|
||||
"ScavTimer": 900,
|
||||
"ScavCustomPockets": false,
|
||||
"ScavLab": false,
|
||||
"FriendlyScavs": false,
|
||||
"HostileScavs": false,
|
||||
"StandingFriendlyKill": -0.04,
|
||||
"StandingPMCKill": 0.01,
|
||||
"Health": {
|
||||
"LeftArm": 60,
|
||||
"RightArm": 60,
|
||||
"Head": 35,
|
||||
"Chest": 85,
|
||||
"Stomach": 70,
|
||||
"LeftLeg": 65,
|
||||
"RightLeg": 65
|
||||
},
|
||||
"EnableScavHealth": false,
|
||||
"EnableScav": false
|
||||
},
|
||||
"Bots": {
|
||||
"AIChance": {
|
||||
"Kolontay": 30,
|
||||
"Kaban": 30,
|
||||
"TagillaNight": 30,
|
||||
"TrioLighthouse": 30,
|
||||
"TrioShoreline": 30,
|
||||
"TrioWoods": 30,
|
||||
"Zryachiy": 100,
|
||||
"CultistCustoms": 15,
|
||||
"CultistShoreline": 20,
|
||||
"Trio": 30,
|
||||
"RaiderLab": 40,
|
||||
"RaiderReserve": 35,
|
||||
"CultistFactory": 10,
|
||||
"CultistWoods": 20,
|
||||
"Rogue": 65,
|
||||
"Tagilla": 30,
|
||||
"Shturman": 30,
|
||||
"Glukhar": 30,
|
||||
"Sanitar": 30,
|
||||
"Reshala": 30,
|
||||
"Killa": 30
|
||||
},
|
||||
"ArmorDurab": {
|
||||
"PMCMax": 100,
|
||||
"FollowerMax": 100,
|
||||
"BossMax": 100,
|
||||
"MarksmanMax": 100,
|
||||
"ScavMax": 100,
|
||||
"RogueMax": 100,
|
||||
"RaiderMax": 100,
|
||||
"PMCMin": 90,
|
||||
"FollowerMin": 90,
|
||||
"BossMin": 100,
|
||||
"MarksmanMin": 90,
|
||||
"ScavMin": 50,
|
||||
"RogueMin": 90,
|
||||
"RaiderMin": 90
|
||||
},
|
||||
"WeaponDurab": {
|
||||
"PMCMax": 100,
|
||||
"FollowerMax": 100,
|
||||
"BossMax": 100,
|
||||
"MarksmanMax": 100,
|
||||
"ScavMax": 100,
|
||||
"RogueMax": 100,
|
||||
"RaiderMax": 100,
|
||||
"PMCMin": 95,
|
||||
"FollowerMin": 80,
|
||||
"BossMin": 80,
|
||||
"MarksmanMin": 60,
|
||||
"ScavMin": 85,
|
||||
"RogueMin": 80,
|
||||
"RaiderMin": 80
|
||||
},
|
||||
"EnableBots": true
|
||||
},
|
||||
"PMC": {
|
||||
"NameOverride": false,
|
||||
"ForceCustomWaves": false,
|
||||
"CustomWaveChance": 100,
|
||||
"PMCChance": {
|
||||
"PMCNamePrefix": 15,
|
||||
"PMCAllNamePrefix": 5,
|
||||
"PMCLooseWep": 15,
|
||||
"HostilePMC": 80,
|
||||
"PMCWepEnhance": 5
|
||||
},
|
||||
"LevelMargin": 10,
|
||||
"PMCNameList": "Sinistar\r\nMorgan\r\nMayoringram\r\nAssAssin\r\nGhostFenixx\r\nG10orgos\r\nFortis\r\nDaveyB0y\r\nMMX\r\nTabi\r\nJojo\r\njvs\r\ndspider\r\nHenny\r\nMylu\r\nXen0Xys\r\nShruggzilla\r\nDoddsy\r\nMoffed\r\nBeagle\r\nthorncp\r\nEagle\r\nBroCC\r\nCthulhu\r\nKaryash\r\nlilthicccums\r\nTobi\r\nEmilia\r\nParka4our\r\nDonmohnke\r\nSingularity\r\nTogay\r\nGodHimself\r\nThomaszhrets\r\nAsianLover69\r\nRodyChodes\r\nl6uc6if6er\r\nbllt\r\nSaltyDroog\r\nM_conniptions\r\ntagilla\r\nTricolorHen061\r\nWaffentrager\r\nHabit\r\nUmsirqualquerai\r\nshep\r\nCannabis\r\ntrappussy\r\nShiro\r\nChomp\r\nTheSparta\r\nLostQuasar\r\nssh\r\nLacyway\r\nKronzky\r\njbs4bmx\r\naleves\r\nFontaine\r\nCWX\r\nTwistedGA\r\nLimbo\r\nSamSWAT\r\nRevingly\r\nKattomine\r\nFox\r\nGamenator\r\nCarl\r\njrdenny1\r\nDecompresS\r\nCats\r\nAtonicX\r\nsyra\r\nStako\r\nCheiftan\r\nelectric\r\nImTheRickHere\r\nmajorlier\r\nbearPhone\r\nMasterChief\r\nThe2ndarbiter\r\ncowkiller19\r\nKarma\r\nJen14owns\r\nRubMyRubber\r\nFrumorn\r\nBuyingGF\r\nRekty\r\nRebrix\r\nDatPhatAsian\r\nChinaski\r\nChameleon\r\nNeCzar\r\nPonderingOrb\r\nSerakym\r\nArsanthania\r\nYT_Mark\r\nBeDaLek\r\nNukey\r\nOwl\r\nJoshtheOG\r\nbatkiller\r\nVampireKitten\r\nBadwolf_54\r\nJustin\r\nAgentMoulder\r\nLayerofBlubber\r\nD3ovm\r\nkiobu\r\n7AmatoR7\r\nKAcidi\r\nZ3R0\r\nSpiral\r\nXsos\r\nVortex\r\nalphakiller\r\nbiohazard\r\nli3raa\r\nDalamadur\r\ncentollojames\r\nlennyrod\r\nZooey\r\nMaoci\r\nMarchwin\r\nMiki__\r\nInnerMiddle\r\npersistent\r\nYarik85\r\nDeadLeaves\r\nSenko\r\nNexus4880\r\nSlickboi\r\nTallanX\r\ntrippy\r\nguidot\r\nJanuary\r\nCloudy\r\nMrElmoEN\r\nNekoKami\r\nDOKDOR\r\ndeathbricks\r\nHustlesofter\r\nVenican\r\nNickMillion\r\nJuncker\r\nPhantomInTime\r\nVox\r\nTraveler\r\nnimbul\r\nEreshkigal\r\nSerWolfik",
|
||||
"NamesEnable": false,
|
||||
"PMCRatio": 50,
|
||||
"AItoPMC": {
|
||||
"PMCtoCursedScav": 20,
|
||||
"SnipertoPMC": 0,
|
||||
"PMCtoScav": 25,
|
||||
"ExusectoPMC": 5,
|
||||
"PMCtoRaider": 7
|
||||
},
|
||||
"DisableLowLevelPMC": false,
|
||||
"LootableMelee": false,
|
||||
"EnablePMC": false
|
||||
},
|
||||
"Custom": {
|
||||
"EnableCustom": true,
|
||||
"DebugAI": true,
|
||||
"LoggerIntoServer": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "SVM",
|
||||
"author": "GhostFenixx",
|
||||
"version": "1.8.2",
|
||||
"akiVersion": "3.8.x",
|
||||
"license": "CC BY-NC-ND",
|
||||
"devdependencies": {},
|
||||
"main": "src/SVM.js"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
## Description ##
|
||||
A server side mod that serves two main purposes for LootingBots.
|
||||
|
||||
1. Marks all items with DiscardLimits as InsuranceDisabled. It then disables the DiscardLimit settings for the server via the EnableDiscardLimits option in Server/database/globals.json. SPT PMC bots by default spawn with loot already in their backpacks, this loot is not marked Found In Raid and thus is subject to BSG's RMT protection logic. With discard limits enabled, when a bot drops their backback to swap to a new one any loot with discard limits in their bag will be deleted immediately when the bag is dropped. To avoid this we set the EnableDiscardLimits to false, and also make sure to flag all items with a DiscardLimit >= 0 as InsuranceDisabled to prevent items suchs as keys and cases to be insured.
|
||||
|
||||
2. Provide the option to clear out the loot that PMC/Scav bots start with in their backpacks. This does not include meds, ammo, grenades ect. These options can be found in the `NoDiscardLimit/config/config.json`.
|
||||
- `pmcSpawnWithLoot` - When set to `true`, PMCs will spawn with loot in their bags/pockets (default SPT behavior)
|
||||
- `scavSpawnWithLoot` - When set to `true`, Scavs will spawn with loot in the bags/pockets (default SPT behavior)
|
||||
|
||||
Default config:
|
||||
```
|
||||
{
|
||||
"pmcSpawnWithLoot": false,
|
||||
"scavSpawnWithLoot": true
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"pmcSpawnWithLoot": false,
|
||||
"scavSpawnWithLoot": true
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "NoDiscardLimit",
|
||||
"version": "1.4.0",
|
||||
"main": "src/mod.js",
|
||||
"license": "MIT",
|
||||
"author": "Skwizzy",
|
||||
"akiVersion": "~3.8",
|
||||
"scripts": {
|
||||
"setup": "npm i",
|
||||
"build": "node ./packageBuild.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.18.18",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"bestzip": "2.2.1",
|
||||
"eslint": "8.30.0",
|
||||
"fs-extra": "11.1.0",
|
||||
"glob": "8.0.3",
|
||||
"tsyringe": "4.7.0",
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Calibers = exports.ParentClasses = void 0;
|
||||
var ParentClasses;
|
||||
(function (ParentClasses) {
|
||||
ParentClasses["WEAPON"] = "5422acb9af1c889c16000029";
|
||||
ParentClasses["ARMORVEST"] = "5448e54d4bdc2dcc718b4568";
|
||||
ParentClasses["ARMOREDEQUIPMENT"] = "57bef4c42459772e8d35a53b";
|
||||
ParentClasses["HEADWEAR"] = "5a341c4086f77401f2541505";
|
||||
ParentClasses["FACECOVER"] = "5a341c4686f77469e155819e";
|
||||
ParentClasses["CHESTRIG"] = "5448e5284bdc2dcb718b4567";
|
||||
ParentClasses["BACKPACK"] = "5448e53e4bdc2d60728b4567";
|
||||
ParentClasses["VISORS"] = "5448e5724bdc2ddf718b4568";
|
||||
ParentClasses["FOOD"] = "5448e8d04bdc2ddf718b4569";
|
||||
ParentClasses["DRINK"] = "5448e8d64bdc2dce718b4568";
|
||||
ParentClasses["BARTER_ITEM"] = "5448eb774bdc2d0a728b4567";
|
||||
ParentClasses["INFO"] = "5448ecbe4bdc2d60728b4568";
|
||||
ParentClasses["MEDKIT"] = "5448f39d4bdc2d0a728b4568";
|
||||
ParentClasses["DRUGS"] = "5448f3a14bdc2d27728b4569";
|
||||
ParentClasses["STIMULATOR"] = "5448f3a64bdc2d60728b456a";
|
||||
ParentClasses["MEDICAL"] = "5448f3ac4bdc2dce718b4569";
|
||||
ParentClasses["MEDICAL_SUPPLIES"] = "57864c8c245977548867e7f1";
|
||||
ParentClasses["MOD"] = "5448fe124bdc2da5018b4567";
|
||||
ParentClasses["FUNCTIONAL_MOD"] = "550aa4154bdc2dd8348b456b";
|
||||
ParentClasses["FUEL"] = "5d650c3e815116009f6201d2";
|
||||
ParentClasses["GEAR_MOD"] = "55802f3e4bdc2de7118b4584";
|
||||
ParentClasses["STOCK"] = "55818a594bdc2db9688b456a";
|
||||
ParentClasses["FOREGRIP"] = "55818af64bdc2d5b648b4570";
|
||||
ParentClasses["MASTER_MOD"] = "55802f4a4bdc2ddb688b4569";
|
||||
ParentClasses["MOUNT"] = "55818b224bdc2dde698b456f";
|
||||
ParentClasses["MUZZLE"] = "5448fe394bdc2d0d028b456c";
|
||||
ParentClasses["SIGHTS"] = "5448fe7a4bdc2d6f028b456b";
|
||||
ParentClasses["MEDS"] = "543be5664bdc2dd4348b4569";
|
||||
ParentClasses["MONEY"] = "543be5dd4bdc2deb348b4569";
|
||||
ParentClasses["NIGHTVISION"] = "5a2c3a9486f774688b05e574";
|
||||
ParentClasses["THEMALVISION"] = "5d21f59b6dbe99052b54ef83";
|
||||
ParentClasses["KEY"] = "543be5e94bdc2df1348b4568";
|
||||
ParentClasses["KEY_MECHANICAL"] = "5c99f98d86f7745c314214b3";
|
||||
ParentClasses["KEYCARD"] = "5c164d2286f774194c5e69fa";
|
||||
ParentClasses["EQUIPMENT"] = "543be5f84bdc2dd4348b456a";
|
||||
ParentClasses["THROW_WEAPON"] = "543be6564bdc2df4348b4568";
|
||||
ParentClasses["FOOD_DRINK"] = "543be6674bdc2df1348b4569";
|
||||
ParentClasses["PISTOL"] = "5447b5cf4bdc2d65278b4567";
|
||||
ParentClasses["SMG"] = "5447b5e04bdc2d62278b4567";
|
||||
ParentClasses["ASSAULT_RIFLE"] = "5447b5f14bdc2d61278b4567";
|
||||
ParentClasses["ASSAULT_CARBINE"] = "5447b5fc4bdc2d87278b4567";
|
||||
ParentClasses["SHOTGUN"] = "5447b6094bdc2dc3278b4567";
|
||||
ParentClasses["MARKSMAN_RIFLE"] = "5447b6194bdc2d67278b4567";
|
||||
ParentClasses["SNIPER_RIFLE"] = "5447b6254bdc2dc3278b4568";
|
||||
ParentClasses["MACHINE_GUN"] = "5447bed64bdc2d97278b4568";
|
||||
ParentClasses["GRENADE_LAUNCHER"] = "5447bedf4bdc2d87278b4568";
|
||||
ParentClasses["SPECIAL_WEAPON"] = "5447bee84bdc2dc3278b4569";
|
||||
ParentClasses["SPEC_ITEM"] = "5447e0e74bdc2d3c308b4567";
|
||||
ParentClasses["KNIFE"] = "5447e1d04bdc2dff2f8b4567";
|
||||
ParentClasses["AMMO"] = "5485a8684bdc2da71d8b4567";
|
||||
ParentClasses["AMMO_BOX"] = "543be5cb4bdc2deb348b4568";
|
||||
ParentClasses["LOOT_CONTAINER"] = "566965d44bdc2d814c8b4571";
|
||||
ParentClasses["MOD_CONTAINER"] = "5448bf274bdc2dfc2f8b456a";
|
||||
ParentClasses["SEARCHABLE_ITEM"] = "566168634bdc2d144c8b456c";
|
||||
ParentClasses["STASH"] = "566abbb64bdc2d144c8b457d";
|
||||
ParentClasses["SORTING_TABLE"] = "6050cac987d3f925bf016837";
|
||||
ParentClasses["LOCKABLE_CONTAINER"] = "5671435f4bdc2d96058b4569";
|
||||
ParentClasses["SIMPLE_CONTAINER"] = "5795f317245977243854e041";
|
||||
ParentClasses["INVENTORY"] = "55d720f24bdc2d88028b456d";
|
||||
ParentClasses["STATIONARY_CONTAINER"] = "567583764bdc2d98058b456e";
|
||||
ParentClasses["POCKETS"] = "557596e64bdc2dc2118b4571";
|
||||
ParentClasses["ARMBAND"] = "5b3f15d486f77432d0509248";
|
||||
ParentClasses["DOG_TAG_USEC"] = "59f32c3b86f77472a31742f0";
|
||||
ParentClasses["DOG_TAG_BEAR"] = "59f32bb586f774757e1e8442";
|
||||
ParentClasses["JEWELRY"] = "57864a3d24597754843f8721";
|
||||
ParentClasses["ELECTRONICS"] = "57864a66245977548f04a81f";
|
||||
ParentClasses["BUILDING_MATERIAL"] = "57864ada245977548638de91";
|
||||
ParentClasses["TOOL"] = "57864bb7245977548b3b66c2";
|
||||
ParentClasses["HOUSEHOLD_GOODS"] = "57864c322459775490116fbf";
|
||||
ParentClasses["LUBRICANT"] = "57864e4c24597754843f8723";
|
||||
ParentClasses["BATTERY"] = "57864ee62459775490116fc1";
|
||||
ParentClasses["ASSAULT_SCOPE"] = "55818add4bdc2d5b648b456f";
|
||||
ParentClasses["TACTICAL_COMBO"] = "55818b164bdc2ddc698b456c";
|
||||
ParentClasses["FLASHLIGHT"] = "55818b084bdc2d5b648b4571";
|
||||
ParentClasses["MAGAZINE"] = "5448bc234bdc2d3c308b4569";
|
||||
ParentClasses["LIGHT_LASER"] = "55818b0e4bdc2dde698b456e";
|
||||
ParentClasses["FLASH_HIDER"] = "550aa4bf4bdc2dd6348b456b";
|
||||
ParentClasses["COLLIMATOR"] = "55818ad54bdc2ddc698b4569";
|
||||
ParentClasses["COMPACT_COLLIMATOR"] = "55818acf4bdc2dde698b456b";
|
||||
ParentClasses["COMPENSATOR"] = "550aa4af4bdc2dd4348b456e";
|
||||
ParentClasses["OPTIC_SCOPE"] = "55818ae44bdc2dde698b456c";
|
||||
ParentClasses["SPECIAL_SCOPE"] = "55818aeb4bdc2ddc698b456a";
|
||||
ParentClasses["OTHER"] = "590c745b86f7743cc433c5f2";
|
||||
ParentClasses["SILENCER"] = "550aa4cd4bdc2dd8348b456c";
|
||||
ParentClasses["PORTABLE_RANGE_FINDER"] = "61605ddea09d851a0a0c1bbc";
|
||||
ParentClasses["ITEM"] = "54009119af1c881c07000029";
|
||||
ParentClasses["CYLINDER_MAGAZINE"] = "610720f290b75a49ff2e5e25";
|
||||
ParentClasses["MAP"] = "567849dd4bdc2d150f8b456e";
|
||||
ParentClasses["REPAIRKITS"] = "616eb7aea207f41933308f46";
|
||||
ParentClasses["COMPASS"] = "5f4fbaaca5573a5ac31db429";
|
||||
ParentClasses["HEADSET"] = "5645bcb74bdc2ded0b8b4578";
|
||||
ParentClasses["GASBLOCK"] = "56ea9461d2720b67698b456f";
|
||||
})(ParentClasses || (exports.ParentClasses = ParentClasses = {}));
|
||||
var Calibers;
|
||||
(function (Calibers) {
|
||||
Calibers["_9x19mm"] = "Caliber9x19PARA";
|
||||
Calibers["_9x18mm"] = "Caliber9x18PM";
|
||||
Calibers["_9x21mm"] = "Caliber9x21";
|
||||
Calibers["_9x39mm"] = "Caliber9x39";
|
||||
Calibers["_45ACP"] = "Caliber1143x23ACP";
|
||||
Calibers["_46x30mm"] = "Caliber46x30";
|
||||
Calibers["_57x28mm"] = "Caliber57x28";
|
||||
Calibers["_762x25mm"] = "Caliber762x25TT";
|
||||
Calibers["_366TKM"] = "Caliber366TKM";
|
||||
Calibers["_762x39mm"] = "Caliber762x39";
|
||||
Calibers["_762x51mm"] = "Caliber762x51";
|
||||
Calibers["_762x54rmm"] = "Caliber762x54R";
|
||||
Calibers["_300BLK"] = "Caliber762x35";
|
||||
Calibers["_556x45mm"] = "Caliber556x45NATO";
|
||||
Calibers["_545x39mm"] = "Caliber545x39";
|
||||
Calibers["_127x55mm"] = "Caliber127x55";
|
||||
Calibers["_338mag"] = "Caliber86x70";
|
||||
Calibers["_357mag"] = "Caliber9x33R";
|
||||
Calibers["_127x108mm"] = "Caliber127x108";
|
||||
Calibers["_40x46mm"] = "Caliber40x46";
|
||||
Calibers["_40x53mm"] = "Caliber40mmRU";
|
||||
Calibers["_26x75mm"] = "Caliber26x75";
|
||||
Calibers["_12ga"] = "Caliber12g";
|
||||
Calibers["_20ga"] = "Caliber20g";
|
||||
Calibers["_23x75mm"] = "Caliber23x75";
|
||||
})(Calibers || (exports.Calibers = Calibers = {}));
|
||||
//# sourceMappingURL=enums.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"enums.js","sourceRoot":"","sources":["enums.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,122 @@
|
||||
export const enum ParentClasses {
|
||||
WEAPON = "5422acb9af1c889c16000029",
|
||||
ARMORVEST = "5448e54d4bdc2dcc718b4568",
|
||||
ARMOREDEQUIPMENT = "57bef4c42459772e8d35a53b",
|
||||
HEADWEAR = "5a341c4086f77401f2541505",
|
||||
FACECOVER = "5a341c4686f77469e155819e",
|
||||
CHESTRIG = "5448e5284bdc2dcb718b4567",
|
||||
BACKPACK = "5448e53e4bdc2d60728b4567",
|
||||
VISORS = "5448e5724bdc2ddf718b4568",
|
||||
FOOD = "5448e8d04bdc2ddf718b4569",
|
||||
DRINK = "5448e8d64bdc2dce718b4568",
|
||||
BARTER_ITEM = "5448eb774bdc2d0a728b4567",
|
||||
INFO = "5448ecbe4bdc2d60728b4568",
|
||||
MEDKIT = "5448f39d4bdc2d0a728b4568",
|
||||
DRUGS = "5448f3a14bdc2d27728b4569",
|
||||
STIMULATOR = "5448f3a64bdc2d60728b456a",
|
||||
MEDICAL = "5448f3ac4bdc2dce718b4569",
|
||||
MEDICAL_SUPPLIES = "57864c8c245977548867e7f1",
|
||||
MOD = "5448fe124bdc2da5018b4567",
|
||||
FUNCTIONAL_MOD = "550aa4154bdc2dd8348b456b",
|
||||
FUEL = "5d650c3e815116009f6201d2",
|
||||
GEAR_MOD = "55802f3e4bdc2de7118b4584",
|
||||
STOCK = "55818a594bdc2db9688b456a",
|
||||
FOREGRIP = "55818af64bdc2d5b648b4570",
|
||||
MASTER_MOD = "55802f4a4bdc2ddb688b4569",
|
||||
MOUNT = "55818b224bdc2dde698b456f",
|
||||
MUZZLE = "5448fe394bdc2d0d028b456c",
|
||||
SIGHTS = "5448fe7a4bdc2d6f028b456b",
|
||||
MEDS = "543be5664bdc2dd4348b4569",
|
||||
MONEY = "543be5dd4bdc2deb348b4569",
|
||||
NIGHTVISION = "5a2c3a9486f774688b05e574",
|
||||
THEMALVISION = "5d21f59b6dbe99052b54ef83",
|
||||
KEY = "543be5e94bdc2df1348b4568",
|
||||
KEY_MECHANICAL = "5c99f98d86f7745c314214b3",
|
||||
KEYCARD = "5c164d2286f774194c5e69fa",
|
||||
EQUIPMENT = "543be5f84bdc2dd4348b456a",
|
||||
THROW_WEAPON = "543be6564bdc2df4348b4568",
|
||||
FOOD_DRINK = "543be6674bdc2df1348b4569",
|
||||
PISTOL = "5447b5cf4bdc2d65278b4567",
|
||||
SMG = "5447b5e04bdc2d62278b4567",
|
||||
ASSAULT_RIFLE = "5447b5f14bdc2d61278b4567",
|
||||
ASSAULT_CARBINE = "5447b5fc4bdc2d87278b4567",
|
||||
SHOTGUN = "5447b6094bdc2dc3278b4567",
|
||||
MARKSMAN_RIFLE = "5447b6194bdc2d67278b4567",
|
||||
SNIPER_RIFLE = "5447b6254bdc2dc3278b4568",
|
||||
MACHINE_GUN = "5447bed64bdc2d97278b4568",
|
||||
GRENADE_LAUNCHER = "5447bedf4bdc2d87278b4568",
|
||||
SPECIAL_WEAPON = "5447bee84bdc2dc3278b4569",
|
||||
SPEC_ITEM = "5447e0e74bdc2d3c308b4567",
|
||||
KNIFE = "5447e1d04bdc2dff2f8b4567",
|
||||
AMMO = "5485a8684bdc2da71d8b4567",
|
||||
AMMO_BOX = "543be5cb4bdc2deb348b4568",
|
||||
LOOT_CONTAINER = "566965d44bdc2d814c8b4571",
|
||||
MOD_CONTAINER = "5448bf274bdc2dfc2f8b456a",
|
||||
SEARCHABLE_ITEM = "566168634bdc2d144c8b456c",
|
||||
STASH = "566abbb64bdc2d144c8b457d",
|
||||
SORTING_TABLE = "6050cac987d3f925bf016837",
|
||||
LOCKABLE_CONTAINER = "5671435f4bdc2d96058b4569",
|
||||
SIMPLE_CONTAINER = "5795f317245977243854e041",
|
||||
INVENTORY = "55d720f24bdc2d88028b456d",
|
||||
STATIONARY_CONTAINER = "567583764bdc2d98058b456e",
|
||||
POCKETS = "557596e64bdc2dc2118b4571",
|
||||
ARMBAND = "5b3f15d486f77432d0509248",
|
||||
DOG_TAG_USEC = "59f32c3b86f77472a31742f0",
|
||||
DOG_TAG_BEAR = "59f32bb586f774757e1e8442",
|
||||
JEWELRY = "57864a3d24597754843f8721",
|
||||
ELECTRONICS = "57864a66245977548f04a81f",
|
||||
BUILDING_MATERIAL = "57864ada245977548638de91",
|
||||
TOOL = "57864bb7245977548b3b66c2",
|
||||
HOUSEHOLD_GOODS = "57864c322459775490116fbf",
|
||||
LUBRICANT = "57864e4c24597754843f8723",
|
||||
BATTERY = "57864ee62459775490116fc1",
|
||||
ASSAULT_SCOPE = "55818add4bdc2d5b648b456f",
|
||||
TACTICAL_COMBO = "55818b164bdc2ddc698b456c",
|
||||
FLASHLIGHT = "55818b084bdc2d5b648b4571",
|
||||
MAGAZINE = "5448bc234bdc2d3c308b4569",
|
||||
LIGHT_LASER = "55818b0e4bdc2dde698b456e",
|
||||
FLASH_HIDER = "550aa4bf4bdc2dd6348b456b",
|
||||
COLLIMATOR = "55818ad54bdc2ddc698b4569",
|
||||
COMPACT_COLLIMATOR = "55818acf4bdc2dde698b456b",
|
||||
COMPENSATOR = "550aa4af4bdc2dd4348b456e",
|
||||
OPTIC_SCOPE = "55818ae44bdc2dde698b456c",
|
||||
SPECIAL_SCOPE = "55818aeb4bdc2ddc698b456a",
|
||||
OTHER = "590c745b86f7743cc433c5f2",
|
||||
SILENCER = "550aa4cd4bdc2dd8348b456c",
|
||||
PORTABLE_RANGE_FINDER = "61605ddea09d851a0a0c1bbc",
|
||||
ITEM = "54009119af1c881c07000029",
|
||||
CYLINDER_MAGAZINE = "610720f290b75a49ff2e5e25",
|
||||
MAP = "567849dd4bdc2d150f8b456e",
|
||||
REPAIRKITS = "616eb7aea207f41933308f46",
|
||||
COMPASS = "5f4fbaaca5573a5ac31db429",
|
||||
HEADSET = "5645bcb74bdc2ded0b8b4578",
|
||||
GASBLOCK = "56ea9461d2720b67698b456f"
|
||||
}
|
||||
|
||||
export const enum Calibers {
|
||||
_9x19mm = "Caliber9x19PARA",
|
||||
_9x18mm = "Caliber9x18PM",
|
||||
_9x21mm = "Caliber9x21",
|
||||
_9x39mm = "Caliber9x39",
|
||||
_45ACP = "Caliber1143x23ACP",
|
||||
_46x30mm = "Caliber46x30",
|
||||
_57x28mm = "Caliber57x28",
|
||||
_762x25mm = "Caliber762x25TT",
|
||||
_366TKM = "Caliber366TKM",
|
||||
_762x39mm = "Caliber762x39",
|
||||
_762x51mm = "Caliber762x51",
|
||||
_762x54rmm = "Caliber762x54R",
|
||||
_300BLK = "Caliber762x35",
|
||||
_556x45mm = "Caliber556x45NATO",
|
||||
_545x39mm = "Caliber545x39",
|
||||
_127x55mm = "Caliber127x55",
|
||||
_338mag = "Caliber86x70",
|
||||
_357mag = "Caliber9x33R",
|
||||
_127x108mm = "Caliber127x108",
|
||||
_40x46mm = "Caliber40x46",
|
||||
_40x53mm = "Caliber40mmRU",
|
||||
_26x75mm = "Caliber26x75",
|
||||
_12ga = "Caliber12g",
|
||||
_20ga = "Caliber20g",
|
||||
_23x75mm = "Caliber23x75"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ConfigTypes_1 = require("/snapshot/project/obj/models/enums/ConfigTypes");
|
||||
const config_json_1 = __importDefault(require("../config/config.json"));
|
||||
class DisableDiscardLimits {
|
||||
postDBLoad(container) {
|
||||
const databaseServer = container.resolve("DatabaseServer");
|
||||
const configServer = container.resolve("ConfigServer");
|
||||
const pmcConfig = configServer.getConfig(ConfigTypes_1.ConfigTypes.PMC);
|
||||
const { logInfo } = useLogger(container);
|
||||
const tables = databaseServer.getTables();
|
||||
/**
|
||||
* Set the item generation weights for backpackLoot, vestLoot, and pocketLoot to zero to prevent extra loot items from spawning on the specified bot type
|
||||
* @param botTypes
|
||||
*/
|
||||
const emptyInventory = (botTypes) => {
|
||||
botTypes.forEach((type) => {
|
||||
logInfo(`Removing loot from ${type}`);
|
||||
const backpackWeights = tables.bots.types[type].generation.items.backpackLoot.weights;
|
||||
const vestWeights = tables.bots.types[type].generation.items.vestLoot.weights;
|
||||
const pocketWeights = tables.bots.types[type].generation.items.pocketLoot.weights;
|
||||
Object.keys(backpackWeights).forEach(weight => backpackWeights[weight] = 0);
|
||||
Object.keys(vestWeights).forEach(weight => vestWeights[weight] = 0);
|
||||
Object.keys(pocketWeights).forEach(weight => pocketWeights[weight] = 0);
|
||||
});
|
||||
};
|
||||
if (!config_json_1.default.pmcSpawnWithLoot) {
|
||||
emptyInventory(["usec", "bear"]);
|
||||
// Do not allow weapons to spawn in PMC bags
|
||||
pmcConfig.looseWeaponInBackpackLootMinMax.max = 0;
|
||||
}
|
||||
if (!config_json_1.default.scavSpawnWithLoot) {
|
||||
emptyInventory(["assault"]);
|
||||
}
|
||||
logInfo("Marking items with DiscardLimits as InsuranceDisabled");
|
||||
for (let itemId in tables.templates.items) {
|
||||
const template = tables.templates.items[itemId];
|
||||
/**
|
||||
* When we set DiscardLimitsEnabled to false further down, this will cause some items to be able to be insured when they normally should not be.
|
||||
* The DiscardLimit property is used by BSG for RMT protections and their code internally treats things with discard limits as not insurable.
|
||||
* For items that have a DiscardLimit >= 0, we need to manually flag them as InsuranceDisabled to make sure they still cannot be insured by the player.
|
||||
* Do not disable insurance if the item is marked as always available for insurance.
|
||||
*/
|
||||
if (template._props.DiscardLimit >= 0 &&
|
||||
!template._props.IsAlwaysAvailableForInsurance) {
|
||||
template._props.InsuranceDisabled = true;
|
||||
}
|
||||
}
|
||||
tables.globals.config.DiscardLimitsEnabled = false;
|
||||
logInfo("Global config DiscardLimitsEnabled set to false");
|
||||
}
|
||||
}
|
||||
function useLogger(container) {
|
||||
const logger = container.resolve("WinstonLogger");
|
||||
return {
|
||||
logInfo: (message) => {
|
||||
logger.info(`[NoDiscardLimit] ${message}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
module.exports = { mod: new DisableDiscardLimits() };
|
||||
//# sourceMappingURL=mod.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"mod.js","sourceRoot":"","sources":["mod.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
|
||||
import { IPostDBLoadMod } from "@spt-aki/models/external/IPostDBLoadMod";
|
||||
import { DatabaseServer } from "@spt-aki/servers/DatabaseServer";
|
||||
import { ILogger } from "@spt-aki/models/spt/utils/ILogger";
|
||||
import { ConfigServer } from "@spt-aki/servers/ConfigServer";
|
||||
import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes";
|
||||
import { IPmcConfig } from "@spt-aki/models/spt/config/IPmcConfig";
|
||||
|
||||
|
||||
import config from "../config/config.json";
|
||||
|
||||
class DisableDiscardLimits implements IPostDBLoadMod {
|
||||
public postDBLoad(container: DependencyContainer): void {
|
||||
const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
|
||||
const configServer = container.resolve<ConfigServer>("ConfigServer");
|
||||
const pmcConfig = configServer.getConfig<IPmcConfig>(ConfigTypes.PMC);
|
||||
const { logInfo } = useLogger(container);
|
||||
|
||||
const tables = databaseServer.getTables();
|
||||
|
||||
/**
|
||||
* Set the item generation weights for backpackLoot, vestLoot, and pocketLoot to zero to prevent extra loot items from spawning on the specified bot type
|
||||
* @param botTypes
|
||||
*/
|
||||
const emptyInventory = (botTypes: string[]) => {
|
||||
botTypes.forEach((type) => {
|
||||
logInfo(`Removing loot from ${type}`);
|
||||
const backpackWeights = tables.bots.types[type].generation.items.backpackLoot.weights;
|
||||
const vestWeights = tables.bots.types[type].generation.items.vestLoot.weights;
|
||||
const pocketWeights = tables.bots.types[type].generation.items.pocketLoot.weights;
|
||||
|
||||
Object.keys(backpackWeights).forEach(weight => backpackWeights[weight] = 0);
|
||||
Object.keys(vestWeights).forEach(weight => vestWeights[weight] = 0);
|
||||
Object.keys(pocketWeights).forEach(weight => pocketWeights[weight] = 0);
|
||||
});
|
||||
};
|
||||
|
||||
if (!config.pmcSpawnWithLoot) {
|
||||
emptyInventory(["usec", "bear"]);
|
||||
// Do not allow weapons to spawn in PMC bags
|
||||
pmcConfig.looseWeaponInBackpackLootMinMax.max = 0;
|
||||
}
|
||||
|
||||
if (!config.scavSpawnWithLoot) {
|
||||
emptyInventory(["assault"]);
|
||||
}
|
||||
|
||||
logInfo("Marking items with DiscardLimits as InsuranceDisabled");
|
||||
for (let itemId in tables.templates.items) {
|
||||
const template = tables.templates.items[itemId];
|
||||
/**
|
||||
* When we set DiscardLimitsEnabled to false further down, this will cause some items to be able to be insured when they normally should not be.
|
||||
* The DiscardLimit property is used by BSG for RMT protections and their code internally treats things with discard limits as not insurable.
|
||||
* For items that have a DiscardLimit >= 0, we need to manually flag them as InsuranceDisabled to make sure they still cannot be insured by the player.
|
||||
* Do not disable insurance if the item is marked as always available for insurance.
|
||||
*/
|
||||
if (
|
||||
template._props.DiscardLimit >= 0 &&
|
||||
!template._props.IsAlwaysAvailableForInsurance
|
||||
) {
|
||||
template._props.InsuranceDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
tables.globals.config.DiscardLimitsEnabled = false;
|
||||
logInfo("Global config DiscardLimitsEnabled set to false");
|
||||
}
|
||||
}
|
||||
|
||||
function useLogger(container: DependencyContainer) {
|
||||
const logger = container.resolve<ILogger>("WinstonLogger");
|
||||
return {
|
||||
logInfo: (message: string) => {
|
||||
logger.info(`[NoDiscardLimit] ${message}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { mod: new DisableDiscardLimits() };
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 acidphantasm
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Welcome to Harry Hideout by acidphantasm
|
||||
|
||||
This mod is designed and built to ease constructing and upgrading your hideout pre-flea market. You can continue to use it post-flea as Harry contains all items used in construction & upgrades.
|
||||
|
||||
## **Configuration options**
|
||||
|
||||
- itemPriceMultiplier
|
||||
- useFleaPrices
|
||||
- useBarters
|
||||
|
||||
Adjust itemPriceMultiplier to adjust item value for both flea & non-flea price.
|
||||
If you disable useFleaPrices, the item price will revert to values in items.json
|
||||
If you disable useBarters, the high end items will be available for Roubles
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"itemPriceMultiplier": 1,
|
||||
"useFleaPrices": true,
|
||||
"useBarters": true,
|
||||
|
||||
"enableConsoleDebug": false
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
{
|
||||
"nonBarterItems": [
|
||||
{
|
||||
"itemID": "569668774bdc2da2298b4568",
|
||||
"price": 140
|
||||
},
|
||||
{
|
||||
"itemID": "5696686a4bdc2da3298b456a",
|
||||
"price": 130
|
||||
},
|
||||
{
|
||||
"itemID": "57347b8b24597737dd42e192",
|
||||
"price": 17000
|
||||
},
|
||||
{
|
||||
"itemID": "590c2c9c86f774245b1f03f2",
|
||||
"price": 8000
|
||||
},
|
||||
{
|
||||
"itemID": "5c13cef886f774072e618e82",
|
||||
"price": 6000
|
||||
},
|
||||
{
|
||||
"itemID": "57347c93245977448d35f6e3",
|
||||
"price": 35000
|
||||
},
|
||||
{
|
||||
"itemID": "5c13cd2486f774072c757944",
|
||||
"price": 8000
|
||||
},
|
||||
{
|
||||
"itemID": "62a0a098de7ac8199358053b",
|
||||
"price": 18000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b3f2d86f774253763b735",
|
||||
"price": 28000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b3a5d86f774252167ba22",
|
||||
"price": 26000
|
||||
},
|
||||
{
|
||||
"itemID": "544fb25a4bdc2dfb738b4567",
|
||||
"price": 2000
|
||||
},
|
||||
{
|
||||
"itemID": "62a0a043cf4a99369e2624a5",
|
||||
"price": 25000
|
||||
},
|
||||
{
|
||||
"itemID": "57347c77245977448d35f6e2",
|
||||
"price": 22000
|
||||
},
|
||||
{
|
||||
"itemID": "57347c5b245977448d35f6e1",
|
||||
"price": 29000
|
||||
},
|
||||
{
|
||||
"itemID": "544fb5454bdc2df8738b456a",
|
||||
"price": 19000
|
||||
},
|
||||
{
|
||||
"itemID": "56742c284bdc2d98058b456d",
|
||||
"price": 12000
|
||||
},
|
||||
{
|
||||
"itemID": "590a3c0a86f774385a33c450",
|
||||
"price": 13000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b2fa286f77425227d1674",
|
||||
"price": 87000
|
||||
},
|
||||
{
|
||||
"itemID": "5733279d245977289b77ec24",
|
||||
"price": 43000
|
||||
},
|
||||
{
|
||||
"itemID": "5734779624597737e04bf329",
|
||||
"price": 17000
|
||||
},
|
||||
{
|
||||
"itemID": "61bf7b6302b3924be92fa8c3",
|
||||
"price": 27000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b317c86f7742523398392",
|
||||
"price": 38000
|
||||
},
|
||||
{
|
||||
"itemID": "59e35ef086f7741777737012",
|
||||
"price": 16000
|
||||
},
|
||||
{
|
||||
"itemID": "590c5bbd86f774785762df04",
|
||||
"price": 11000
|
||||
},
|
||||
{
|
||||
"itemID": "590c31c586f774245e3141b2",
|
||||
"price": 24000
|
||||
},
|
||||
{
|
||||
"itemID": "57347c1124597737fb1379e3",
|
||||
"price": 21000
|
||||
},
|
||||
{
|
||||
"itemID": "5e2af2bc86f7746d3f3c33fc",
|
||||
"price": 12000
|
||||
},
|
||||
{
|
||||
"itemID": "590a373286f774287540368b",
|
||||
"price": 28000
|
||||
},
|
||||
{
|
||||
"itemID": "5af04b6486f774195a3ebb49",
|
||||
"price": 22000
|
||||
},
|
||||
{
|
||||
"itemID": "60391b0fb847c71012789415",
|
||||
"price": 20000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b313086f77425227d1678",
|
||||
"price": 20000
|
||||
},
|
||||
{
|
||||
"itemID": "59e36c6f86f774176c10a2a7",
|
||||
"price": 27000
|
||||
},
|
||||
{
|
||||
"itemID": "62a09ee4cf4a99369e262453",
|
||||
"price": 7000
|
||||
},
|
||||
{
|
||||
"itemID": "5b4335ba86f7744d2837a264",
|
||||
"price": 14000
|
||||
},
|
||||
{
|
||||
"itemID": "5e831507ea0a7c419c2f9bd9",
|
||||
"price": 59000
|
||||
},
|
||||
{
|
||||
"itemID": "59e3606886f77417674759a5",
|
||||
"price": 30000
|
||||
},
|
||||
{
|
||||
"itemID": "619cc01e0a7c3a1a2731940c",
|
||||
"price": 17000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b392c86f77425243e98fe",
|
||||
"price": 19000
|
||||
},
|
||||
{
|
||||
"itemID": "5c06779c86f77426e00dd782",
|
||||
"price": 13000
|
||||
},
|
||||
{
|
||||
"itemID": "59e35cbb86f7741778269d83",
|
||||
"price": 26000
|
||||
},
|
||||
{
|
||||
"itemID": "59e35de086f7741778269d84",
|
||||
"price": 44000
|
||||
},
|
||||
{
|
||||
"itemID": "5e2af29386f7746d4159f077",
|
||||
"price": 26000
|
||||
},
|
||||
{
|
||||
"itemID": "61bf83814088ec1a363d7097",
|
||||
"price": 24000
|
||||
},
|
||||
{
|
||||
"itemID": "590c2e1186f77425357b6124",
|
||||
"price": 44000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1c819a86f774771b0acd6c",
|
||||
"price": 26000
|
||||
},
|
||||
{
|
||||
"itemID": "62a0a0bb621468534a797ad5",
|
||||
"price": 25000
|
||||
},
|
||||
{
|
||||
"itemID": "62a0a124de7ac81993580542",
|
||||
"price": 45000
|
||||
},
|
||||
{
|
||||
"itemID": "574eb85c245977648157eec3",
|
||||
"price": 24000
|
||||
},
|
||||
{
|
||||
"itemID": "619cbfeb6b8a1b37a54eebfa",
|
||||
"price": 61000
|
||||
},
|
||||
{
|
||||
"itemID": "63a0b208f444d32d6f03ea1e",
|
||||
"price": 145000
|
||||
},
|
||||
{
|
||||
"itemID": "590a3b0486f7743954552bdb",
|
||||
"price": 23000
|
||||
},
|
||||
{
|
||||
"itemID": "590c311186f77424d1667482",
|
||||
"price": 16000
|
||||
},
|
||||
{
|
||||
"itemID": "59faf98186f774067b6be103",
|
||||
"price": 17000
|
||||
},
|
||||
{
|
||||
"itemID": "5d40419286f774318526545f",
|
||||
"price": 18000
|
||||
},
|
||||
{
|
||||
"itemID": "590c346786f77423e50ed342",
|
||||
"price": 32000
|
||||
},
|
||||
{
|
||||
"itemID": "590a3cd386f77436f20848cb",
|
||||
"price": 24000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b304286f774253763a528",
|
||||
"price": 30000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b2ffd86f77425243e8d17",
|
||||
"price": 26000
|
||||
},
|
||||
{
|
||||
"itemID": "590c392f86f77444754deb29",
|
||||
"price": 38000
|
||||
},
|
||||
{
|
||||
"itemID": "5734781f24597737e04bf32a",
|
||||
"price": 24000
|
||||
},
|
||||
{
|
||||
"itemID": "590a391c86f774385a33c404",
|
||||
"price": 25000
|
||||
},
|
||||
{
|
||||
"itemID": "5bc9b355d4351e6d1509862a",
|
||||
"price": 24000
|
||||
},
|
||||
{
|
||||
"itemID": "60391a8b3364dc22b04d0ce5",
|
||||
"price": 45000
|
||||
},
|
||||
{
|
||||
"itemID": "5734795124597738002c6176",
|
||||
"price": 19000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1c774f86f7746d6620f8db",
|
||||
"price": 14000
|
||||
},
|
||||
{
|
||||
"itemID": "619cbf476b8a1b37a54eebf8",
|
||||
"price": 19000
|
||||
},
|
||||
{
|
||||
"itemID": "57347c2e24597744902c94a1",
|
||||
"price": 60000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b327086f7742525194449",
|
||||
"price": 45000
|
||||
},
|
||||
{
|
||||
"itemID": "5af0534a86f7743b6f354284",
|
||||
"price": 89000
|
||||
},
|
||||
{
|
||||
"itemID": "5e2af22086f7746d3f3c33fa",
|
||||
"price": 12000
|
||||
},
|
||||
{
|
||||
"itemID": "5c06782b86f77426df5407d2",
|
||||
"price": 15000
|
||||
},
|
||||
{
|
||||
"itemID": "590c639286f774151567fa95",
|
||||
"price": 22000
|
||||
},
|
||||
{
|
||||
"itemID": "5d0378d486f77420421a5ff4",
|
||||
"price": 340000
|
||||
},
|
||||
{
|
||||
"itemID": "590c595c86f7747884343ad7",
|
||||
"price": 48000
|
||||
},
|
||||
{
|
||||
"itemID": "5d0375ff86f774186372f685",
|
||||
"price": 34000
|
||||
},
|
||||
{
|
||||
"itemID": "5d03775b86f774203e7e0c4b",
|
||||
"price": 100000
|
||||
},
|
||||
{
|
||||
"itemID": "5af0484c86f7740f02001f7f",
|
||||
"price": 12000
|
||||
},
|
||||
{
|
||||
"itemID": "59e35abd86f7741778269d82",
|
||||
"price": 25000
|
||||
},
|
||||
{
|
||||
"itemID": "5e2af00086f7746d3f3c33f7",
|
||||
"price": 13000
|
||||
},
|
||||
{
|
||||
"itemID": "590c35a486f774273531c822",
|
||||
"price": 35000
|
||||
},
|
||||
{
|
||||
"itemID": "60391afc25aff57af81f7085",
|
||||
"price": 74000
|
||||
},
|
||||
{
|
||||
"itemID": "5e2aedd986f7746d404f3aa4",
|
||||
"price": 180000
|
||||
},
|
||||
{
|
||||
"itemID": "573478bc24597738002c6175",
|
||||
"price": 19000
|
||||
},
|
||||
{
|
||||
"itemID": "573474f924597738002c6174",
|
||||
"price": 5000
|
||||
},
|
||||
{
|
||||
"itemID": "590c645c86f77412b01304d9",
|
||||
"price": 26000
|
||||
},
|
||||
{
|
||||
"itemID": "590c651286f7741e566b6461",
|
||||
"price": 38000
|
||||
},
|
||||
{
|
||||
"itemID": "62a09e73af34e73a266d932a",
|
||||
"price": 26000
|
||||
},
|
||||
{
|
||||
"itemID": "590c621186f774138d11ea29",
|
||||
"price": 19000
|
||||
},
|
||||
{
|
||||
"itemID": "590a386e86f77429692b27ab",
|
||||
"price": 13000
|
||||
},
|
||||
{
|
||||
"itemID": "59e3639286f7741777737013",
|
||||
"price": 105000
|
||||
},
|
||||
{
|
||||
"itemID": "5d235a5986f77443f6329bc6",
|
||||
"price": 50000
|
||||
},
|
||||
{
|
||||
"itemID": "5734758f24597738025ee253",
|
||||
"price": 28000
|
||||
},
|
||||
{
|
||||
"itemID": "59faf7ca86f7740dbe19f6c2",
|
||||
"price": 55000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b376e86f774252519444e",
|
||||
"price": 325000
|
||||
},
|
||||
{
|
||||
"itemID": "5bc9bc53d4351e00367fbcee",
|
||||
"price": 57000
|
||||
},
|
||||
{
|
||||
"itemID": "57347cd0245977445a2d6ff1",
|
||||
"price": 12000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b39a386f774252339976f",
|
||||
"price": 15000
|
||||
},
|
||||
{
|
||||
"itemID": "5d1b32c186f774252167a530",
|
||||
"price": 27000
|
||||
},
|
||||
{
|
||||
"itemID": "619cbfccbedcde2f5b3f7bdd",
|
||||
"price": 75000
|
||||
},
|
||||
{
|
||||
"itemID": "5c052f6886f7746b1e3db148",
|
||||
"price": 90000
|
||||
},
|
||||
{
|
||||
"itemID": "62a0a16d0b9d3c46de5b6e97",
|
||||
"price": 155000
|
||||
},
|
||||
{
|
||||
"itemID": "61bf7c024770ee6f9c6b8b53",
|
||||
"price": 65000
|
||||
},
|
||||
{
|
||||
"itemID": "5c12613b86f7743bbe2c3f76",
|
||||
"price": 375000
|
||||
},
|
||||
{
|
||||
"itemID": "5c05300686f7746dce784e5d",
|
||||
"price": 125000
|
||||
}
|
||||
],
|
||||
"barterItems": [
|
||||
{
|
||||
"itemID": "5c0530ee86f774697952d952",
|
||||
"price": 1290504,
|
||||
"barterAmount": 3,
|
||||
"barterItemID": "59faff1d86f7746c51718c9c"
|
||||
},
|
||||
{
|
||||
"itemID": "6389c85357baa773a825b356",
|
||||
"price": 4596724,
|
||||
"barterAmount": 8,
|
||||
"barterItemID": "59faff1d86f7746c51718c9c"
|
||||
},
|
||||
{
|
||||
"itemID": "6389c7f115805221fb410466",
|
||||
"price": 1932786,
|
||||
"barterAmount": 4,
|
||||
"barterItemID": "59faff1d86f7746c51718c9c"
|
||||
},
|
||||
{
|
||||
"itemID": "5d03794386f77420415576f5",
|
||||
"price": 217169,
|
||||
"barterAmount": 1,
|
||||
"barterItemID": "59faff1d86f7746c51718c9c"
|
||||
},
|
||||
{
|
||||
"itemID": "5b4391a586f7745321235ab2",
|
||||
"price": 37189,
|
||||
"barterAmount": 3,
|
||||
"barterItemID": "5991b51486f77447b112d44f"
|
||||
},
|
||||
{
|
||||
"itemID": "5b7c710788a4506dec015957",
|
||||
"price": 1081368,
|
||||
"barterAmount": 2,
|
||||
"barterItemID": "59faff1d86f7746c51718c9c"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"_id": "HarryHideout",
|
||||
"working": true,
|
||||
"availableInRaid": false,
|
||||
"items_buy": {
|
||||
"category": [
|
||||
"5448eb774bdc2d0a728b4567"
|
||||
],
|
||||
"id_list": []
|
||||
},
|
||||
"items_buy_prohibited": {},
|
||||
"customization_seller": false,
|
||||
"name": "Harry",
|
||||
"surname": "Hideout",
|
||||
"nickname": "Hideout Harry",
|
||||
"location": "I'm sellin', what are you buyin'?",
|
||||
"avatar": "/files/trader/avatar/harry.jpg",
|
||||
"balance_rub": 5000000,
|
||||
"balance_dol": 0,
|
||||
"balance_eur": 0,
|
||||
"unlockedByDefault": true,
|
||||
"discount": 0,
|
||||
"discount_end": 0,
|
||||
"buyer_up": true,
|
||||
"currency": "RUB",
|
||||
"nextResupply": 1615141448,
|
||||
"repair": {
|
||||
"availability": false,
|
||||
"quality": "2",
|
||||
"excluded_id_list": [],
|
||||
"excluded_category": [],
|
||||
"currency": "5449016a4bdc2d6f028b456f",
|
||||
"currency_coefficient": 1,
|
||||
"price_rate": 10
|
||||
},
|
||||
"insurance": {
|
||||
"availability": false,
|
||||
"min_payment": 0,
|
||||
"min_return_hour": 0,
|
||||
"max_return_hour": 0,
|
||||
"max_storage_time": 99,
|
||||
"excluded_category": []
|
||||
},
|
||||
"gridHeight": 150,
|
||||
"loyaltyLevels": [{
|
||||
"minLevel": 1,
|
||||
"minSalesSum": 0,
|
||||
"minStanding": 0,
|
||||
"buy_price_coef": 38,
|
||||
"repair_price_coef": 175,
|
||||
"insurance_price_coef": 10,
|
||||
"exchange_price_coef": 0,
|
||||
"heal_price_coef": 0
|
||||
}
|
||||
],
|
||||
"sell_category": [
|
||||
"82e7fac0b7495d72d4083356",
|
||||
"ac705d3440c1407645e33579",
|
||||
"dc97aee367144dc03389405d",
|
||||
"7ffcc96aa06c7e90940330c5",
|
||||
"e8f46e3ad74b9d862121f9dc",
|
||||
"5b47574386f77428ca22b33e",
|
||||
"5b47574386f77428ca22b33f",
|
||||
"5b5f78dc86f77409407a7f8e",
|
||||
"5b47574386f77428ca22b346",
|
||||
"5b47574386f77428ca22b340",
|
||||
"5b47574386f77428ca22b344",
|
||||
"5b47574386f77428ca22b342",
|
||||
"5b47574386f77428ca22b341",
|
||||
"5b47574386f77428ca22b345",
|
||||
"5b47574386f77428ca22b343",
|
||||
"5b5f71b386f774093f2ecf11",
|
||||
"5b5f71c186f77409407a7ec0",
|
||||
"5b5f71de86f774093f2ecf13",
|
||||
"5b5f724186f77447ed5636ad",
|
||||
"5b5f736886f774094242f193",
|
||||
"5b5f73ec86f774093e6cb4fd",
|
||||
"5b5f74cc86f77447ec5d770a",
|
||||
"5b5f750686f774093e6cb503",
|
||||
"5b5f751486f77447ec5d770c",
|
||||
"5b5f752e86f774093e6cb505",
|
||||
"5b5f754a86f774094242f19b",
|
||||
"5b5f755f86f77447ec5d770e",
|
||||
"5b5f757486f774093e6cb507",
|
||||
"5b5f75b986f77447ec5d7710",
|
||||
"5b5f75c686f774094242f19f",
|
||||
"5b5f75e486f77447ec5d7712",
|
||||
"5b5f760586f774093e6cb509",
|
||||
"5b5f761f86f774094242f1a1",
|
||||
"5b5f764186f77447ec5d7714"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "HarryHideout",
|
||||
"version": "1.0.3",
|
||||
"main": "src/mod.js",
|
||||
"license": "MIT",
|
||||
"author": "acidphantasm",
|
||||
"akiVersion": "~3.8",
|
||||
"loadBefore": [],
|
||||
"loadAfter": [],
|
||||
"incompatibilities": [],
|
||||
"contributors": [],
|
||||
"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",
|
||||
"os": "^0.1",
|
||||
"tsyringe": "4.8.0",
|
||||
"typescript": "5.4",
|
||||
"winston": "3.12"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
@@ -0,0 +1,126 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FluentAssortConstructor = void 0;
|
||||
class FluentAssortConstructor {
|
||||
itemsToSell = [];
|
||||
barterScheme = {};
|
||||
loyaltyLevel = {};
|
||||
hashUtil;
|
||||
logger;
|
||||
constructor(hashutil, logger) {
|
||||
this.hashUtil = hashutil;
|
||||
this.logger = logger;
|
||||
}
|
||||
/**
|
||||
* Start selling item with tpl
|
||||
* @param itemTpl Tpl id of the item you want trader to sell
|
||||
* @param itemId Optional - set your own Id, otherwise unique id will be generated
|
||||
*/
|
||||
createSingleAssortItem(itemTpl, itemId = undefined) {
|
||||
// Create item ready for insertion into assort table
|
||||
const newItemToAdd = {
|
||||
_id: !itemId ? this.hashUtil.generate() : itemId,
|
||||
_tpl: itemTpl,
|
||||
parentId: "hideout", // Should always be "hideout"
|
||||
slotId: "hideout", // Should always be "hideout"
|
||||
upd: {
|
||||
UnlimitedCount: false,
|
||||
StackObjectsCount: 100
|
||||
}
|
||||
};
|
||||
this.itemsToSell.push(newItemToAdd);
|
||||
return this;
|
||||
}
|
||||
createComplexAssortItem(items) {
|
||||
items[0].parentId = "hideout";
|
||||
items[0].slotId = "hideout";
|
||||
if (!items[0].upd) {
|
||||
items[0].upd = {};
|
||||
}
|
||||
items[0].upd.UnlimitedCount = false;
|
||||
items[0].upd.StackObjectsCount = 100;
|
||||
this.itemsToSell.push(...items);
|
||||
return this;
|
||||
}
|
||||
addStackCount(stackCount) {
|
||||
this.itemsToSell[0].upd.StackObjectsCount = stackCount;
|
||||
return this;
|
||||
}
|
||||
addUnlimitedStackCount() {
|
||||
this.itemsToSell[0].upd.StackObjectsCount = 999999;
|
||||
this.itemsToSell[0].upd.UnlimitedCount = true;
|
||||
return this;
|
||||
}
|
||||
makeStackCountUnlimited() {
|
||||
this.itemsToSell[0].upd.StackObjectsCount = 999999;
|
||||
return this;
|
||||
}
|
||||
addBuyRestriction(maxBuyLimit) {
|
||||
this.itemsToSell[0].upd.BuyRestrictionMax = maxBuyLimit;
|
||||
this.itemsToSell[0].upd.BuyRestrictionCurrent = 0;
|
||||
return this;
|
||||
}
|
||||
addLoyaltyLevel(level) {
|
||||
this.loyaltyLevel[this.itemsToSell[0]._id] = level;
|
||||
return this;
|
||||
}
|
||||
addMoneyCost(currencyType, amount) {
|
||||
this.barterScheme[this.itemsToSell[0]._id] = [
|
||||
[
|
||||
{
|
||||
count: amount,
|
||||
_tpl: currencyType
|
||||
}
|
||||
]
|
||||
];
|
||||
return this;
|
||||
}
|
||||
addBarterCost(itemTpl, count) {
|
||||
const sellableItemId = this.itemsToSell[0]._id;
|
||||
// No data at all, create
|
||||
if (Object.keys(this.barterScheme).length === 0) {
|
||||
this.barterScheme[sellableItemId] = [[
|
||||
{
|
||||
count: count,
|
||||
_tpl: itemTpl
|
||||
}
|
||||
]];
|
||||
}
|
||||
else {
|
||||
// Item already exists, add to
|
||||
const existingData = this.barterScheme[sellableItemId][0].find(x => x._tpl === itemTpl);
|
||||
if (existingData) {
|
||||
// itemtpl already a barter for item, add to count
|
||||
existingData.count += count;
|
||||
}
|
||||
else {
|
||||
// No barter for item, add it fresh
|
||||
this.barterScheme[sellableItemId][0].push({
|
||||
count: count,
|
||||
_tpl: itemTpl
|
||||
});
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Reset objet ready for reuse
|
||||
* @returns
|
||||
*/
|
||||
export(data) {
|
||||
const itemBeingSoldId = this.itemsToSell[0]._id;
|
||||
if (data.assort.items.find(x => x._id === itemBeingSoldId)) {
|
||||
this.logger.error(`Unable to add complex item with item key ${this.itemsToSell[0]._id}, key already used`);
|
||||
return;
|
||||
}
|
||||
data.assort.items.push(...this.itemsToSell);
|
||||
data.assort.barter_scheme[itemBeingSoldId] = this.barterScheme[itemBeingSoldId];
|
||||
data.assort.loyal_level_items[itemBeingSoldId] = this.loyaltyLevel[itemBeingSoldId];
|
||||
this.itemsToSell = [];
|
||||
this.barterScheme = {};
|
||||
this.loyaltyLevel = {};
|
||||
return this;
|
||||
}
|
||||
}
|
||||
exports.FluentAssortConstructor = FluentAssortConstructor;
|
||||
//# sourceMappingURL=fluentTraderAssortCreator.js.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user