Complete Remake of the Modpack

This commit is contained in:
2025-05-16 00:38:39 -04:00
parent db58ebbefb
commit a57e637d5f
10423 changed files with 13308 additions and 2630183 deletions
+28
View File
@@ -0,0 +1,28 @@
[General]
gameName=spt
modid=0
version=d2025.5.14.0
newestVersion=
category="-1,"
nexusFileStatus=1
installationFile=drunkgeko-gekos_betterprogression.zip
repository=Nexus
ignoredVersion=
comments=
notes=
nexusDescription=
url=
hasCustomURL=true
lastNexusQuery=
lastNexusUpdate=
nexusLastModified=2025-05-14T13:02:20Z
nexusCategory=0
converted=false
validated=false
color=@Variant(\0\0\0\x43\0\xff\xff\0\0\0\0\0\0\0\0)
tracked=0
[installedFiles]
1\modid=0
1\fileid=0
size=1
@@ -0,0 +1 @@
Not much to see here
@@ -0,0 +1,27 @@
{
"Started": {
},
"Success": {
//Stirrup rewards an Ammunition Case
"596b455186f77457cb50eccb": {
"availableInGameEditions": [],
"findInRaid": false,
"id": "67dfe1d6c86d01e21c962c36",
"index": 0,
"items": [
{
"_id": "67dfe1d2e5b3ffda5e44b529",
"_tpl": "5aafbde786f774389d0cbc0f", //Ammo case
"upd": {
"StackObjectsCount": 1
}
}
],
"target": "67dfe1d2e5b3ffda5e44b529",
"type": "Item",
"unknown": false,
"value": 1
}
},
}
@@ -0,0 +1,234 @@
{
//New buffs then referenced by custom items
"customBuffs":
{
"BuffsSuperSoldier": [
{
"AbsoluteValue": true,
"BuffType": "SkillRate",
"Chance": 1,
"Delay": 1,
"Duration": 1800,
"SkillName": "Endurance",
"Value": 30
},
{
"AbsoluteValue": true,
"BuffType": "SkillRate",
"Chance": 1,
"Delay": 1,
"Duration": 1800,
"SkillName": "Strength",
"Value": 30
},
{
"AbsoluteValue": true,
"BuffType": "SkillRate",
"Chance": 1,
"Delay": 1,
"Duration": 1800,
"SkillName": "StressResistance",
"Value": 20
},
{
"AbsoluteValue": true,
"BuffType": "HealthRate",
"Chance": 1,
"Delay": 1,
"Duration": 1800,
"SkillName": "",
"Value": 3
},
{
"AbsoluteValue": true,
"BuffType": "QuantumTunnelling",
"Chance": 1,
"Delay": 1,
"Duration": 1800,
"SkillName": "",
"Value": 0
},
{
"AbsoluteValue": false,
"BuffType": "WeightLimit",
"Chance": 1,
"Delay": 1,
"Duration": 1800,
"SkillName": "",
"Value": 1
},
{
"AbsoluteValue": false,
"BuffType": "DamageModifier",
"Chance": 1,
"Delay": 1,
"Duration": 1800,
"SkillName": "",
"Value": -0.5
},
{
"AbsoluteValue": true,
"BuffType": "StaminaRate",
"Chance": 1,
"Delay": 1,
"Duration": 3000,
"Value": 3,
"SkillName": ""
},
{
"AbsoluteValue": true,
"BuffType": "BodyTemperature",
"Chance": 1,
"Delay": 10,
"Duration": 1800,
"SkillName": "",
"Value": 4
},
{
"AbsoluteValue": true,
"BuffType": "HydrationRate",
"Chance": 0.7,
"Delay": 60,
"Duration": 1800,
"SkillName": "",
"Value": -0.7
},
{
"AbsoluteValue": true,
"BuffType": "EnergyRate",
"Chance": 0.7,
"Delay": 60,
"Duration": 1800,
"SkillName": "",
"Value": -0.6
},
{
"AbsoluteValue": true,
"BuffType": "HealthRate",
"Chance": 0.7,
"Delay": 1800,
"Duration": 300,
"SkillName": "",
"Value": -3
},
],
},
//New item templates to be added
"customItems":
{
"67c9a2f88fcabd70c9b35c08": {
"_id": "67c9a2f88fcabd70c9b35c08",
"_name": "SUP_Compound",
"_parent": "5448f3a64bdc2d60728b456a",
"_props": {
"AnimationVariantsNumber": 0,
"BackgroundColor": "orange",
"CanRequireOnRagfair": false,
"CanSellOnRagfair": false,
"ConflictingItems": [],
"Description": "",
"DiscardLimit": 2,
"DiscardingBlock": false,
"DropSoundType": "None",
"ExamineExperience": 100,
"ExamineTime": 1,
"ExaminedByDefault": false,
"ExtraSizeDown": 0,
"ExtraSizeForceAdd": false,
"ExtraSizeLeft": 0,
"ExtraSizeRight": 0,
"ExtraSizeUp": 0,
"Height": 1,
"HideEntrails": false,
"InsuranceDisabled": false,
"IsAlwaysAvailableForInsurance": false,
"IsLockedafterEquip": false,
"IsSpecialSlotOnly": false,
"IsUnbuyable": false,
"IsUndiscardable": false,
"IsUngivable": false,
"IsUnremovable": false,
"IsUnsaleable": false,
"ItemSound": "med_stimulator",
"LootExperience": 10,
"MaxHpResource": 0,
"MergesWithChildren": false,
"Name": "S.U.P. Compound",
"NotShownInSlot": false,
"Prefab": {
"path": "assets/content/weapons/usable_items/item_syringe/item_stimulator_sj9_tglabs_loot.bundle",
"rcid": ""
},
"QuestItem": false,
"QuestStashMaxCount": 0,
"RagFairCommissionModifier": 1,
"RarityPvE": "Superrare",
"RepairCost": 0,
"RepairSpeed": 0,
"ShortName": "S.U.P.",
"StackMaxSize": 1,
"StackObjectsCount": 1,
"StimulatorBuffs": "BuffsSuperSoldier",
"Unlootable": false,
"UnlootableFromSide": [],
"UnlootableFromSlot": "FirstPrimaryWeapon",
"UsePrefab": {
"path": "assets/content/weapons/usable_items/item_syringe/item_stimulator_sj9_tglabs_container.bundle",
"rcid": ""
},
"Weight": 0.05,
"Width": 1,
"effects_damage": [],
"effects_health": [],
"hpResourceRate": 0,
"medEffectType": "duringUse",
"medUseTime": 2
},
"_proto": "544fb3f34bdc2d03748b456a",
"_type": "Item"
},
},
"customLocales": {
"67c9a2f88fcabd70c9b35c08": {
"Name": "S.U.P. Compound Injector",
"ShortName": "S.U.P.",
"Description": "A cutting-edge military-grade stimulant, the Strategic Ultra-Performance (S.U.P.) Compound is designed for elite operators requiring peak combat efficiency. Injected directly into the bloodstream, this powerful serum enhances speed, strength, reflexes, and pain resistance for a short but critical duration. Originally an untested experimental drug, the compound was intended for special forces but was never officially approved due to unpredictable side effects. While it grants immense battlefield superiority, users risk severe metabolic crashes, uncontrolled aggression, and potential long-term damage. Use wisely; supremacy on the battlefield comes at a price."
}
},
//Add custom trades
"customTrades": {
//Therapist
"54cb57776803fa99248b456e": {
"items": [
{
"_id": "67c99cb6d072a85372ecd09e",
"_tpl": "67c9a2f88fcabd70c9b35c08", //SUP stim
"parentId": "hideout",
"slotId": "hideout",
"upd": {
"UnlimitedCount": true,
"StackObjectsCount": 9999999,
"BuyRestrictionMax": 3,
"BuyRestrictionCurrent": 0
}
},
],
"barter_scheme": {
"67c99cb6d072a85372ecd09e": [ //SUP stim
[
{
"count": 1320000,
"_tpl": "5449016a4bdc2d6f028b456f"
}
]
],
},
"loyal_level_items": {
"67c99cb6d072a85372ecd09e": 4, //SUP stim
}
}
}
}
@@ -0,0 +1,83 @@
{
//Prapor
"54cb50c76803fa8b248b4571": {
"items": {
"67d43f2a227bcdfcdd58ecf3": {
"_id": "67d43f2a227bcdfcdd58ecf3",
"_tpl": "617fd91e5539a84ec44ce155", //RGN impact nade
"parentId": "hideout",
"slotId": "hideout",
"upd": {
"UnlimitedCount": true,
"StackObjectsCount": 9999999,
"BuyRestrictionMax": 3,
"BuyRestrictionCurrent": 0
}
},
},
"barter_scheme": {
"67d43f2a227bcdfcdd58ecf3": [ //RGN impact nade
[
{
"count": 25000,
"_tpl": "5449016a4bdc2d6f028b456f"
}
]
],
},
"loyal_level_items": {
"67d43f2a227bcdfcdd58ecf3": 4, //RGN impact nade
},
"questlocks": {
"67d43f2a227bcdfcdd58ecf3": { //RGN impact nade
"quest": "639670029113f06a7c3b2377", //Following the Bread Crumbs (Lightkeeper)
"rewardId": "67d44502588ce6c75823d67c",
"targetId": "67d4451e5d4569a3b28ae368",
},
}
},
//Ref
"6617beeaa9cfa777ca915b7c": {
"items": {
"67dfe267d9d392d28b7e63d4": {
"_id": "67dfe267d9d392d28b7e63d4",
"_tpl": "66bc98a01a47be227a5e956e", //Streamer Item Case
"parentId": "hideout",
"slotId": "hideout",
"upd": {
"UnlimitedCount": true,
"StackObjectsCount": 9999999,
"BuyRestrictionMax": 3,
"BuyRestrictionCurrent": 0
}
},
},
"barter_scheme": {
"67dfe267d9d392d28b7e63d4": [ //Streamer Item Case
[
{
"count": 50,
"_tpl": "5d235b4d86f7742e017bc88a"
}
]
],
},
"loyal_level_items": {
"67dfe267d9d392d28b7e63d4": 1, //Streamer Item Case
},
"questlocks": {
}
}
}
@@ -0,0 +1,71 @@
{
"AdditionalQuestRewards": {
"Started": {
//Network Provider - Part 1 gives Gamma container (as initial equipment)
"625d6ff5ddc94657c21a1625":
{
"availableInGameEditions": [],
"findInRaid": false,
"id": "67c7102265c41f16d88520dc",
"index": 0,
"items": [
{
"_id": "67c7101e8891bcc8c33b3ba5",
"_tpl": "5857a8bc2459772bad15db29", //Gamma container
"upd": {
"StackObjectsCount": 1
}
}
],
"target": "67c7101e8891bcc8c33b3ba5",
"type": "Item",
"unknown": false,
"value": 1
}
},
"Success": {
//Delivery from the Past gives Alpha container
"59674eb386f774539f14813a":
{
"availableInGameEditions": [],
"findInRaid": false,
"id": "67c70fbb4a41e8e5d609745a",
"index": 0,
"items": [
{
"_id": "67c70fb55bfaa2c4455f7dff",
"_tpl": "544a11ac4bdc2d470e8b456a", //Alpha container
"upd": {
"StackObjectsCount": 1
}
}
],
"target": "67c70fb55bfaa2c4455f7dff",
"type": "Item",
"unknown": false,
"value": 1
},
//Setup gives Beta container
"5c1234c286f77406fa13baeb" :
{
"availableInGameEditions": [],
"findInRaid": false,
"id": "67c7064b2261718aee538635",
"index": 0,
"items": [
{
"_id": "67c70634853d4fee9264efe1",
"_tpl": "5857a8b324597729ab0a0e7d", //Beta container
"upd": {
"StackObjectsCount": 1
}
}
],
"target": "67c70634853d4fee9264efe1",
"type": "Item",
"unknown": false,
"value": 1
},
}
}
}
@@ -0,0 +1,865 @@
//// GEKO'S BETTER PROGRESSION CONFIGURATION FILE ////
//
// Welcome to the config file. Nearly everything this mod does can be disabled or tweaked to your liking.
// All text after "//" in a given line is a "comment" and the mod won't read it at all, such as these lines over here
// You can find IDs for items and other things on https://moxopixel.com/tarkynator/. Failing that you can explore the SPT_Data/Server/database folder in your SPT installation to find the ID you need
// A few times "Deltas" are mentioned in this config file. Delta is a fancy word to mean "change", a negative delta will reduce a value and a positive one will increase it
// All ranges are considered to be inclusive of the lower bound but exclusive of the upper one. For example a [10, 40] range will be all values from 10 to 40 with 10 included but 40 excluded
// If you encounter any bugs please do let me know either on the GitHub page or as a comment on the SPT Hub page for the mod
//
{
//Apply changes to secure containers and the ways to obtain them
//Most safe containers have been made slightly bigger (2 to 4 extra slots on each)
//Alpha is obtainable from Delivery from the Past
//Beta is obtainable from Setup
//Gamma is obtainable from accepting Network Provider - Part 1
"secureContainerProgression": {
"enable": true,
//Set the container you start a new profile with. Doesn't work if mod gets added after the profile has already been created, please start fresh
"starterContainer": "5732ee6a24597719ae0c0281", //Waist Pouch
//Set the grid sizes of each container. The format for each is [ [*grid 1 width*, *grid 1 height*], [*grid 2 width*, *grid 2 height*] ] and so on for any amount of grids.
//Grids will be laid out horizontally in the game no matter what so don't go too crazy
"sizeChanges": {
"544a11ac4bdc2d470e8b456a": [[2,2], [1,2]], //Alpha
"5857a8b324597729ab0a0e7d": [[1,2], [2,2], [1,2]], //Beta
"59db794186f77448bc595262": [[1,2], [3,2], [1,2]], //Epsilon
"5857a8bc2459772bad15db29": [[3,3], [1,3]], //Gamma
"5c093ca986f7740a1867ab12": [[1,2], [3,4], [1,2]], //Kappa
},
//More options can be found in the advanced config folder in the secureChanges.json5 file
},
//This mod is intended to (and balanced around) playing without the flea market. Change this setting at your own peril!
"fleaMarketChanges": {
"disableFleaMarket": true,
//If true, keys that are tradable on the flea market by default will still be tradable even if disableFleaMarket is true
"stillAllowKeys": true,
//Even if the flea market is disabled the following items will still be sold (and sellable) on it
"fleaWhitelist": [
]
},
//Reduce the amounts of items required by each hideout upgrade. All items required beyond a certain threshold will be multiplied by the given factor. Only the amount that goes past the threshold is mutliplied
//For example if the threshold is 3 and the factor is 0.5 if a build would require 15 (= 3 + 12) nuts it will instead require 3 + (12 * 0.5) = 9 nuts
"hideoutBuildsChanges": {
"enable": true,
"threshold": 3,
"factor": 0.5,
"roundDown": true,
},
//Stash changes
"stashProgression" : {
"enable": true,
//What stash level should a new profile start with?
"startingStashLevel": 1,
//Amount of lines in each stash level
"stashSizes": [40, 55, 70, 85, 100],
//Multiply ruble cost of each stage of stash upgrade by this much
"stashUpgradeCostFactor": 0.4,
//Change loyalty requirements for stash upgrades by this much
"stashUpgradeLoyaltyDelta": -1,
},
//Make skill xp gain faster and buff some skill bonuses
//Also optionally adds a system that provides skill points that can be freely allocated as you level up your PMC
"skillChanges": {
"enable": true,
//Multiply earned skill XP by this much if it's fresh XP (XP for a skill you haven't leveled recently this raid)
"SkillFreshEffectiveness": 2,
//How many full skill points are considered to be fresh before going back to normal XP earn rates
"SkillFreshPoints": 2,
//After this many points have been earned at a normal XP rate (so after the fresh points) skill fatigue kicks in
"SkillPointsBeforeFatigue": 1,
//Skill fatigue gradually reduces XP earned down to a minimum multiplier that is specified here
"SkillMinEffectiveness": 0.4,
//Multiply the amount of XP gained for each skill and/or the effectiveness of the various skill buffs
"CustomMultipliers": {
//Flat out multiply the amount of xp earned for skills
"GlobalXPMultiplier": 1,
//Specific multipliers per-skill. If unspecified it's considered to be 1
//Both global and specific multipliers apply, this does NOT override the global
"SkillXPMultipliers": {
"Metabolism" : 0.125,
"Charisma": 0.7,
},
//Multiply the buff effect of specific skill buffs
"SkillBuffMultipliers": {
"StrengthBuffLiftWeightInc": 2.5,
"MagDrillsLoadSpeed": 3,
"MagDrillsUnloadSpeed": 3,
"MagDrillsInventoryCheckSpeed": 2,
"CharismaDailyQuestsRerollDiscount": 10,
"CharismaHealingDiscount": 10,
"CharismaInsuranceDiscount": 10,
"CharismaExfiltrationDiscount": 10,
"SurgeryReducePenalty": 2,
"SurgerySpeed": 2,
"SearchSpeed": 1.5,
//Complete list of all buffs that can be modified:
//EnduranceBuffEnduranceInc
//EnduranceBuffJumpCostRed
//EnduranceBuffBreathTimeInc
//EnduranceBuffRestorationTimeRed
//EnduranceBreathElite
//EnduranceHands
//StrengthBuffLiftWeightInc
//StrengthBuffSprintSpeedInc
//StrengthBuffJumpHeightInc
//StrengthBuffThrowDistanceInc
//StrengthBuffMeleePowerInc
//StrengthBuffElite
//StrengthBuffMeleeCrits
//StrengthBuffAim
//VitalityBuffBleedChanceRed
//VitalityBuffSurviobilityInc
//VitalityBuffRegeneration
//VitalityBuffBleedStop
//HealthBreakChanceRed
//HealthOfflineRegenerationInc
//HealthEnergy
//HealthHydration
//HealthEliteAbsorbDamage
//HealthElitePosion
//StressPainChance
//StressTremor
//StressBerserk
//MetabolismRatioPlus
//MetabolismEnergyExpenses
//MetabolismPhysicsForget
//MetabolismPhysicsForget2
//MetabolismEliteBuffNoDyhydration
//MetabolismEliteNoForget
//PerceptionHearing
//PerceptionFov
//PerceptionLootDot
//PerceptionmEliteNoIdea
//IntellectLearningSpeed
//IntellectWeaponMaintance
//IntellectEliteNaturalLearner
//IntellectEliteAmmoCounter
//IntellectEliteContainerScope
//IntellectRepairPointsCostReduction
//AttentionLootSpeed
//AttentionRareLoot
//AttentionEliteLuckySearch
//AttentionEliteExtraLootExp
//MagDrillsLoadSpeed
//MagDrillsUnloadSpeed
//MagDrillsInventoryCheckSpeed
//MagDrillsInventoryCheckAccuracy
//MagDrillsInstantCheck
//MagDrillsLoadProgression
//CharismaBuff1
//CharismaBuff2
//CharismaEliteBuff1
//CharismaEliteBuff2
//CharismaDailyQuestsRerollDiscount
//CharismaHealingDiscount
//CharismaInsuranceDiscount
//CharismaExfiltrationDiscount
//CharismaScavCaseDiscount
//CharismaFenceRepPenaltyReduction
//CharismaAdditionalDailyQuests
//WeaponReloadBuff
//WeaponRecoilBuff
//WeaponSwapBuff
//DrawSpeed
//DrawElite
//DrawTremor
//DrawSound
//AimMasterElite
//AimMasterWiggle
//AimMasterSpeed
//RecoilControlImprove
//RecoilControlElite
//CovertMovementSoundVolume
//ProneMovementSpeed
//ProneMovementVolume
//ProneMovementElite
//TroubleFixing
//TroubleFixingAmmoElite
//TroubleFixingExamineMalfElite
//WeaponErgonomicsBuff
//WeaponDoubleMastering
//WeaponDoubleActionRecoilReduceBuff
//WeaponStiffHands
//ThrowingStrengthBuff
//ThrowingEnergyExpenses
//ThrowingWeaponsBuffElite
//CovertMovementSpeed
//CovertMovementElite
//CovertMovementLoud
//CovertMovementEquipment
//SearchSpeed
//SearchDouble
//SurgeryReducePenalty
//SurgerySpeed
//HideoutResourceConsumption
//HideoutZoneBonusBoost
//HideoutExtraSlots
//CraftingElite
//CraftingSingleTimeReduce
//CraftingContinueTimeReduce
//MetabolismPoisonTime
//MetabolismMiscDebuffTime
//ImmunityMiscEffects
//ImmunityPoisonBuff
//ImmunityPainKiller
//ImmunityPoisonChance
//ImmunityMiscEffectsChance
//WeaponDurabilityLossOnShotReduce
//WearAmountRepairGunsReducePerLevel
},
},
//Earn a customizable amount of freely allocatable skill points with each PMC level
"SkillPointsSystem": {
"enable": true,
//Decimal values are fine. An extra point will be awarded when the decimal part ticks over 1. For example 1.5 points per level means that every other level you'll get 2 points instead of 1
"skillPointsPerLevel": 1,
//The game keeps track of skill levels behind the scenes even when you got past level 51. If this is true any point allocated into a skill that is level 51 will be refunded as the skill naturally keeps leveling up.
//This keeps going until the skill reaches level 51 "naturally" without the help of the skill points
//For example say that you have a skill at level 51 out of which 40 are naturally earned levels and 11 have been allocated with this system.
//Then as the skill continues to overlevel past 51 (which is the cap) you will gradually get your 11 points back if this option is set to true
"automaticallyRefundOverflows": true,
//If true, players can freely deallocate invested points. I suggest keeping this off as it otherwise encourages moving skill points around constantly
"enableDeallocation": false,
}
},
//A series of changes that make Ref work much better in SPT by tying him to PMC kills
//The mods "Bosses Have Lega Medals" and "Ref - SPT Friendly Quests" are STRONGLY reccomended, this section is ment to complement those two
//Bosses Have Lega Medals: https://hub.sp-tarkov.com/files/file/2109-bosses-have-lega-medals/
//Ref - SPT Friendly Quests: https://hub.sp-tarkov.com/files/file/2108-ref-spt-friendly-quests/
"refChanges": {
"enable": true,
//If true, Ref will buy items for GP coins instead of RUB
//I strongly reccomend keeping refOnlyBuysDogtags to true if you intend on keeping this option enabled
"refBuysInGPCoins": true,
//Ref won't buy anything except dogtags (and Lega Medals if enabled)
"refOnlyBuysDogtags": true,
"refAlsoBuysLegaMedals": true, //Works regardless of the value of the previous option
//Gain standing/rep for Ref every time you get a PMC kill
"refStandingOnKill": {
"enable": true,
//Amount of standing/rep to be gained depending on the level of the player killed
"repByKillLevel": [
{
"levelRange": [0, 10],
"rep": 0.003
},
{
"levelRange": [10, 30],
"rep": 0.004
},
{
"levelRange": [30, 50],
"rep": 0.006
},
{
"levelRange": [50, 9999],
"rep": 0.01
}
]
},
},
//A series of buffs to the SICC container
"SICCBuffs": {
"enable": true,
//SICC will be able to hold everything that the Docs case can (on top of what the SICC already can carry)
"canHoldWhatDocsCan": true,
//Additional items that can be put in a SICC case
"additionalWhitelistedItems": [
"59fafd4b86f7745ca07e1232", //Key tool
]
},
//A variety of changes to bitcoin farming. The defualt configuration is aimed at making bitcoin farming less powerful to start with but more powerful at high GPU counts
//Keep in mind that with the rest of the mod with default settings GPUs will be much harder to obtain than normal, which is intended and accounted for in the balancing of this part
"bitcoinChanges": {
"enable": true,
//Remove all non-barter trades for GPUs
"cannotBuyGPU": true,
//Should the value of a BitCoin be overridden to be a set value?
"overrideValue": true,
"value": 648000, //This is the actual item value. Traders will buy them at a lower amount since they never pay full price for items
//Desmos graph to visualize the following two values: https://www.desmos.com/calculator/osralvxw4g
//Flatly multiply the rate of btc produced by this much (variable m in Desmos)
"btcFarmSpeedMult": 0.68,
//How effective is each GPU in boosting the production rate (variable E in Desmos)
"gpuBoostRate": 0.164,
//How many bitcoins fit in the bitcoin farm
"btcCapacity": 5,
},
//Change initial trader reputation
"overrideInitialStanding": {
"enable": true,
//Default starting reputation
"defaultOverride": 0.05,
//Change the default stating on a per-trader basis
"indivudalOverrides": {
"579dc571d53a0658a154fbec": 1, //Fence
}
},
//Miscellaneous changes
"misc": {
//Remove Found in Raid requirements from various places
"removeFirFromQuests": true,
"removeFirFromHideout": true,
"removeFirFromFlea": true,
//Multiply amounts of crafted products and crafting times in hideout
"craftProductMultiplier": 2,
"craftTimeMultiplier": 0.5,
//Enable the additional quest rewards (separate from secure containers quest rewards)
//More settings can be found in the advanced/additionalQuestRewards.json5 config file
"enableExtraQuestRewards": true,
//Enable or disable the custom trades defined in the advanced config customTrades.json5
"addCustomTrades": true,
//Change maximum stack size for the given items
"stackSizeOverride": {
//Special items used in quests
//Unfortunately BSG's spaghetti code makes it so that placing one of these consumes the entire stack instead of just one unit so i disabled the changes. Feel free to re-enable them if you still want them
//To do so remove the "//" at the start of the desired lines
//"5991b51486f77447b112d44f": 3, //MS2000 Marker
//"5ac78a9b86f7741cca0bbd8d": 3, //Signal Jammer
//"5b4391a586f7745321235ab2": 3, //Wi-fi Camera
//Others
//"6656560053eaaa7a23349c86": 5, //Lega Medals
},
//Change sizes of containers
"ContanerSizeChanges": {
//Vanilla items
"5aafbde786f774389d0cbc0f": [8, 8], //Ammunition case buff from 7x7 to 8x8
"5c127c4486f7745625356c13": [8, 8], //Magazine case buff from 7x7 to 8x8
"5aafbcd986f7745e590fff23": [8, 8], //Medicine case buff from 7x7 to 8x8
//WTT-Pack 'n' Strap items (does nothing if mod is not installed)
"12403f74773f49be6a2d84b7": [2, 3], //Small Medical Pouch nerf down to 2x3
"ae9e418fd5d4c4eec4a0e6ea": [2, 2], //Small Ammunition Pouch nerf down to 2x2
//Tactical Gear Component items (does nothing if mod is not installed)
"672e2e7526ba61dbb88be7ff": [4, 2], //TGC First Aid container nerf down to 4x2
"672e2e758808bacbb9d5abc4": [2, 3], //TGC Ammo Pouch nerf down to 2x3
},
//Change the price of items that are sold for rubles
"priceChanges": {
//WTT-Pack 'n' Strap items (does nothing if mod is not installed)
"12403f74773f49be6a2d84b7": 1442000, //Small Medical Pouch
"ae9e418fd5d4c4eec4a0e6ea": 620920, //Small Ammunition Pouch
//Tactical Gear Component items (does nothing if mod is not installed)
"672e2e7526ba61dbb88be7ff": 6080000, //TGC First Aid container
"672e2e758808bacbb9d5abc4": 3880000, //TGC Ammo Pouch
},
},
//Module responsible for algorithmically rebalancing most military items in the game
//This module calculates a loyalty score for each item that can also be decimal. The actual in-game loyalty level of the item will be the floor of the loyalty score. So for example a loyalty score of 3.8 will correspond to loyalty level 3
//The exact way in which this works will become more apparent by looking at the specific rules for each class of items (for example the ammoRules)
"algorithmicalRebalancing": {
"enable": true,
//If false, items that end up having a loyalty score greater or equal than 5 will be hidden (level >4 traders do not exist)
//If it's true items with loyalty over 4 will be brought back to 4
"clampToMaxLevel": true,
//If true, all trades that are locked behind quests will be treated as if "clampToMaxLevel" is true rgardless of its actual value
//If this is false then some quest-locked items could potentially be unobtainable even when unlocked since they would end up at loyalty level 5+
"forceClampingOfQuestlockedItems": true,
//Change the loyalty score of all items this mod affects (for example barter items are unaffected) by this much if the item trade is a barter or if it's quest locked
"barterDelta": -0.7,
"questLockDelta": -1,
"logBartersAndLocks": false, //For debugging
//Traders that will not be affected by the rebalancing module
"excludeTraders": [
"579dc571d53a0658a154fbec", //Fence
],
//Modify the loyalty score of all offers on the given trader. Only affects items that are already being algorithmically rebalanced (ammo, wepaons)
"deltaByTrader": {
"6617beeaa9cfa777ca915b7c": -0.7, //Ref
},
//Ammo rebalancing module
"ammoRules": {
"enable": true,
"logChanges": false, //For debugging
//Change every ammunition loyalty level by this much
"globalDelta": 0,
//If an ammunition falls between the ammunition ranges specified here then it will get the corresponding base loyalty score
//Loyalty score is then further modified by the other rules
"defaultBaseLoyaltyByPen": 1,
"ammoBaseLoyaltyByPen": [
{
"penInterval": [0, 14],
"baseLoyalty": 0.5
},
{
"penInterval": [14, 17],
"baseLoyalty": 1
},
{
"penInterval": [17, 21],
"baseLoyalty": 1.5
},
{
"penInterval": [21, 27],
"baseLoyalty": 2
},
{
"penInterval": [27, 31],
"baseLoyalty": 2.6
},
{
"penInterval": [31, 37],
"baseLoyalty": 3
},
{
"penInterval": [37, 41],
"baseLoyalty": 3.7
},
{
"penInterval": [41, 47],
"baseLoyalty": 4
},
{
"penInterval": [47, 99999],
"baseLoyalty": 5
}
],
//Modify the loyalty score of an ammunition depending on its caliber
"caliberRules": [
{
"caliber": "Caliber12g",
"loyaltyDelta": -2
},
{
"caliber": "Caliber762x54R",
"loyaltyDelta": -2
},
{
"caliber": "Caliber762x39",
"loyaltyDelta": 0
},
{
"caliber": "Caliber762x51",
"loyaltyDelta": -1
},
{
"caliber": "Caliber20g",
"loyaltyDelta": -3
},
{
"caliber": "Caliber366TKM",
"loyaltyDelta": -0.6
},
{
"caliber": "Caliber9x19PARA",
"loyaltyDelta": 1.4
},
{
"caliber": "Caliber9x18PM",
"loyaltyDelta": 1
},
{
"caliber": "Caliber127x55",
"loyaltyDelta": -1
},
{
"caliber": "Caliber9x21",
"loyaltyDelta": 0.5
},
{
"caliber": "Caliber752x25TT",
"loyaltyDelta": 0.5
},
{
"caliber": "Caliber86x70",
"loyaltyDelta": -2
},
{
"caliber": "Caliber556x45NATO",
"loyaltyDelta": 0.4
},
{
"caliber": "Caliber545x39",
"loyaltyDelta": 0.3
},
{
"caliber": "Caliber127x33",
"loyaltyDelta": -0.5
}
],
//If the damage of an ammunition falls within one (or more) of the intervals then its loyalty score will be modified by the corresponding delta
"damageRules": [
{
"damageInterval": [85, 90],
"loyaltyDelta": 0.5
},
{
"damageInterval": [90, 100],
"loyaltyDelta": 1
},
{
"damageInterval": [100, 120],
"loyaltyDelta": 1.5
},
{
"damageInterval": [120, 255],
"loyaltyDelta": 2
},
{
"damageInterval": [255, 360],
"loyaltyDelta": 3
},
{
"damageInterval": [360, 9999],
"loyaltyDelta": 4
}
],
//Disable the ammo balancing module on the following calibers
"ignoreCalibers": [
"Caliber26x75", //Flare gun
],
//Settings specific to hideout crafts of ammo
"craftSettings": {
//If disabled ammo crafts will not be changed by this mod
"enable": true,
//If loyalty score falls between the given ranges then the ammunition craft will end up in the corresponding workbench level
"loyaltyToLevelRanges": [
{
"range": [-9999, 2.5],
"level": 1
},
{
"range": [2.5, 3.5],
"level": 2
},
{
"range": [3.5, 9999],
"level": 3
}
]
},
},
//Weapons rebalancing module
"weaponRules": {
"enable": true,
"logChanges": false, //For debugging
//Change every weapon loyalty level by this much
"globalDelta": 0,
//All weapons will bring all of their default parts to their same loyalty level. If multiple weapons share the same default parts the lowest level is picked
//For example if this is true the default m4 gas block will always be found on the same loyalty level as the m4
"attachmentsFollowDefaultBuild": true,
//Add this delta if the weapon contains any attachments that cannot be purchased at the trader level the weapon would otherwise be placed at
//Won't affect attachments that are part of the default build of the weapon
//If attachmentsFollowDefaultBuild is set to false i reccomend setting this to 0
//If this is different than 0 the mod will take a few seconds longer to load when starting the server (still no performance impact when playing, no worries)
"advancedAttachmentsDelta": 1,
//Base loyalty score of each weapon is decided by its caliber. If the caliber of the weapon is not listed below then the default value will be picked
//Loyalty score is then further modified by the other rules
"defaultBaseLoyalty": 2,
"weaponBaseLoyaltyByCaliber": [
{
"caliber": "Caliber762x51",
"baseLoyalty": 3.8
},
{
"caliber": "Caliber556x45NATO",
"baseLoyalty": 3
},
{
"caliber": "Caliber545x39",
"baseLoyalty": 3
},
{
"caliber": "Caliber762x39",
"baseLoyalty": 3
},
{
"caliber": "Caliber57x28",
"baseLoyalty": 2.5
},
{
"caliber": "Caliber46x30",
"baseLoyalty": 2.5
},
{
"caliber": "Caliber9x19PARA",
"baseLoyalty": 1
},
{
"caliber": "Caliber366TKM",
"baseLoyalty": 2
},
{
"caliber": "Caliber9x39",
"baseLoyalty": 2.5
},
{
"caliber": "Caliber127x55",
"baseLoyalty": 3.5
},
{
"caliber": "Caliber23x75",
"baseLoyalty": 5
},
{
"caliber": "Caliber9x18PM",
"baseLoyalty": 1
},
{
"caliber": "Caliber9x18PMM",
"baseLoyalty": 1
},
{
"caliber": "Caliber762x25TT",
"baseLoyalty": 1
},
{
"caliber": "Caliber86x70",
"baseLoyalty": 6
},
{
"caliber": "Caliber1143x23ACP",
"baseLoyalty": 2
},
{
"caliber": "Caliber762x54R",
"baseLoyalty": 3.5
},
{
"caliber": "Caliber68x51",
"baseLoyalty": 4.9
},
{
"caliber": "Caliber127x33",
"baseLoyalty": 4
},
{
"caliber": "Caliber12g",
"baseLoyalty": 3.7
},
{
"caliber": "Caliber762x39",
"baseLoyalty": 3
},
],
//Modify the loyalty score depending on the BEST fire mode available to the weapon
//Manual includes bolt-action, pump-action and single-shot
//Semiauto includes semi automatic, double-action and double barrel
//Fullauto and burst are self-explanatory
"fireModeRules": [
{
"mode" : "manual",
"delta" : -2
},
{
"mode" : "semiauto",
"delta" : -1
},
{
"mode" : "burst",
"delta" : -0.5
},
{
"mode" : "fullauto",
"delta" : 0
}
],
//If the fire rate of a weapon falls within one (or more) of the intervals then its loyalty score will be modified by the corresponding delta
//THIS ONLY AFFECTS FULL AUTO AND BURST WEAPONS
"fireRateRules": [
{
"rateInterval": [0, 620],
"delta": -0.7
},
{
"rateInterval": [620, 760],
"delta": -0.5
},
{
"rateInterval": [900, 99999],
"delta": 1
}
],
//If multiple weapons share the same niche then the better ones will be upshifted to higher trader levels (how a niche is defined is configurable below)
//For example if both the UMP and the Vector.45 happen to both fall into loyalty level 3 then the Vector will be upshifted to level 4 (if the power level rules below define the Vector as a better weapon than the UMP and with default niche definition)
//Loyalty level for shifting is checked after all other rules have already been applied.
//The shifting applies recursively. This means that if, for example, three weapons of three different power levels all share the same niche and loyalty level then one will stay at that level, one will be shifted once and the other will be shifted twice
"upshiftRules": {
"enable": true,
"shiftAmount": 1,
//If true, the worse weapon will be shifted down instead of the better one being shifted up
"shiftDownInstead": false,
//How is a wapon niche defined for shifting?
"devideNicheByFiremode": true, //If true, weapons of different fire modes will be considered as parts of different niches. Only cares about the best available fire mode and only considers "manual", "semi auto", "burst" and "fullauto"
"devideNicheByCaliber": true, //If true, weapons of different calibers will be considered as parts of different niches
"devideNicheByBarterType": true, //If true, items sold for currency and items recieved from barters will be considered of two different niches
"devideNicheByQuestLock": true, //If true, items locked behind quests and items that aren't so will be considered of two different niches
//Power levels of weapons. This is used to determine which weapons are better than which others. Has only meaning in a relative sense (to define an order between weapons of same niche), the absolute value is meaningless.
//For example if we want to define the Vepr Hunter as a weaker weapon compared to the RSASS we only need to set their power levels so that the VPO's is lower, the actual numbers don't matter
//Once again, the power level is compared for a possible shift only between weapons that share the same caliber AND fire mode AND loyalty level (with default niche settings)
//For example the power level of the Vepr Hunter will never compared to that of the SCAR-H nor that of the STM-9
//If two weapons in the same niche and loyalty level have the same power level then neither will be shifted
"powerLevels": {
//// SEMI-AUTO 7.62x51 ////
"5c501a4d2e221602b412b540" : 1, //Vepr Hunter
"5f2a9575926fd9352339381f" : 2, //RFB
"5aafa857e5b5b00018480968" : 2, //M1A
"6176aca650224f204c1da3fb" : 3, //G28
"5a367e5dc4a282000e49738f" : 3, //RSASS
"5df8ce05b11454561e39243b" : 3, //SR-25
//// FULL-AUTO SHOTGUNS ////
"66ffa9b66e19cc902401c5e8": 1, //AA-12 Gen 1
"67124dcfa3541f2a1f0e788b": 1, //AA-12 Gen 2
"674fe9a75e51f1c47c04ec23": 2, //Auto Saiga-12k
//// FULL-AUTO 3x39 ////
"644674a13d52156624001fbc": 1, //9A-91
"645e0c6b3b381ede770e1cc9": 2, //VSK-94
"57838ad32459774a17445cd2": 3, //VSS
"57c44b372459772d2b39b8ce": 4, //AS-VAL
"651450ce0e00edc794068371": 4, //SR-3M
//// BOLT-ACTION 7.62×51 ////
"5bfea6e90db834001b7347f3": 1, //M700
"5df24cf80dee1b22f862e9bc": 2, //T-5000M
"588892092459774ac91d4b11": 3, //DVL
//// FULL-AUTO 9x19 ////
"5926bb2186f7744b1c6c6e60": 1, //MP5
"66992b349950f5f4cd06029f": 1, //Uzi
"58948c8e86f77409493f7266": 2, //MPX
"6680304edadb7aa61d00cef0": 2, //Uzi-pro
"668e71a8dadf42204c032ce1": 2, //Uzi-pro SMG
//// BOLT-ACTION 7.62x54R ////
"5ae08f0a5acfc408fb1398a1": 1, //Mosin
"5bfd297f0db834001a669119": 1, //Mosin infantry
"55801eed4bdc2d89578b4588": 2, //SV-98
//// FULL-AUTO 7.62x51 ////
"5b0bbe4e5acfc40dc528a72d": 1, //SA58
"5dcbd56fdbd3d91b3e5468d5": 2, //Black MDR
"6165ac306ef05c2ce828ef74": 2, //Scar-H (FDE)
"6183afd850224f204c1da514": 2, //Scar-H
"676176d362e0497044079f4c": 3, //Scar-H X-17
//// .45 PISTOLS ////
"5e81c3cbac2bb513793cdc75": 1, //m1911
"6193a720f8ee7e52e42109ed": 2, //USP .45
}
}
},
//Modify loyalty score of the given items by the given amount
//This only affects items that are already affected by at least one other algorithmical rebalancing module (so items such as ammo if ammo rebalancing is enabled)
"explicitLoyaltyDelta": {
//ID of the specific trade, NOT the id of the item being sold
"trades": {
},
//ID of the item being sold
"items": {
"57dc2fa62459775949412633": -0.7, //AKS-74U
"583990e32459771419544dd2": -0.7, //AKS-74UN
"5839a40f24597726f856b511": -0.7, //AKS-74UB
"62e7c4fba689e8c9c50dfc38": -1, //AUG A1
"61f7c9e189e6fb1a5e3ea78d": -2, //MP-18
"606587252535c57a13424cfd": +1, //Mutant
"5bb2475ed4351e00853264e3": +1, //HK 416A5
"6513ef33e06849f06c0957ca": +1, //RPD
"65268d8ecb944ff1e90ea385": +2, //RPDN
"60db29ce99594040e04c4a27": -1, //Revolving shotgun
"5580223e4bdc2d1c128b457f": -2, //MP-43
"5bfd297f0db834001a669119": -1, //Mosin infantry
"6499849fc93611967b034949": +0.7, //Ak-12
"628a60ae6b1d481ff772e9c8": +0.7, //RD-704
"576165642459773c7a400233": +1, //Saiga-12k
"674fe9a75e51f1c47c04ec23": +1, //Saiga-12k automatic
}
},
//Ignore all previous calculations and set the given items or trades to the given levels
//Items and trades specified here will also be ignored by all other systems (for example they won't count for the shifting system)
"explicitLoyaltyOverride": {
//ID of the specific trade, NOT the id of the item being sold
"trades": {
},
//ID of the item being sold
"items": {
"627e14b21713922ded6f2c15": 4, //AXMC
}
}
},
//Development options
"dev": {
//Hide info logging of each change being applied when mod is loading
"muteProgressOnServerLoad": false,
//When an error occurs show the whole error message
"showFullError": true,
}
}
@@ -0,0 +1,35 @@
{
"name": "gekos_betterprogression",
"version": "1.1.3",
"sptVersion": "~3.11.1",
"loadBefore": [
"DewardianDev-AlgorithmicLevelProgression"
],
"loadAfter": [
"MoxoPixel-Painter - Trader"
],
"incompatibilities": [],
"isBundleMod": false,
"main": "src/mod.js",
"scripts": {
"setup": "npm i",
"build": "node ./build.mjs",
"buildinfo": "node ./build.mjs --verbose"
},
"devDependencies": {
"@types/node": "20.11",
"@typescript-eslint/eslint-plugin": "7.2",
"@typescript-eslint/parser": "7.2",
"archiver": "^6.0",
"eslint": "8.57",
"fs-extra": "11.2",
"ignore": "^5.2",
"json5": "^2.2.3",
"tsyringe": "4.8.0",
"typescript": "5.4",
"winston": "3.12"
},
"author": "DrunkGeko",
"contributors": [],
"license": "MIT"
}
@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateAmmoLoyalty = calculateAmmoLoyalty;
exports.scoreAmmo = scoreAmmo;
exports.rebalanceAmmoCrafts = rebalanceAmmoCrafts;
const BaseClasses_1 = require("C:/snapshot/project/obj/models/enums/BaseClasses");
const utils_1 = require("../utils");
function calculateAmmoLoyalty(item, context) {
const itemTemplate = context.tables.templates.items[item._tpl];
return scoreAmmo(itemTemplate, context);
}
function scoreAmmo(itemTemplate, context) {
const config = context.config.algorithmicalRebalancing.ammoRules;
let loyalty = config.defaultBaseLoyaltyByPen;
//Base level from penetration
for (const rule of config.ammoBaseLoyaltyByPen) {
if (itemTemplate._props.PenetrationPower >= rule.penInterval[0] && itemTemplate._props.PenetrationPower < rule.penInterval[1]) {
loyalty = rule.baseLoyalty;
}
}
//Modify by caliber
for (const rule of config.caliberRules) {
if (itemTemplate._props.Caliber == rule.caliber) {
loyalty += rule.loyaltyDelta;
}
}
//Modify by damage (accounting for projectile count)
for (const rule of config.damageRules) {
const totalDamage = itemTemplate._props.Damage * itemTemplate._props.ProjectileCount;
if (totalDamage >= rule.damageInterval[0] && totalDamage < rule.damageInterval[1]) {
loyalty += rule.loyaltyDelta;
}
}
loyalty += config.globalDelta;
return loyalty;
}
function rebalanceAmmoCrafts(context) {
const config = context.config.algorithmicalRebalancing.ammoRules;
const crafts = context.tables.hideout.production.recipes;
for (const craft of crafts) {
const ammoId = craft.endProduct;
if (!context.itemHelper.isOfBaseclass(ammoId, BaseClasses_1.BaseClasses.AMMO))
continue; //Exclude non-ammo
let score = scoreAmmo(context.tables.templates.items[ammoId], context);
if ((0, utils_1.isQuestLockedCraft)(craft))
score += context.config.algorithmicalRebalancing.questLockDelta;
for (const map of config.craftSettings.loyaltyToLevelRanges) {
if (score >= map.range[0] && score < map.range[1])
(0, utils_1.setAreaLevelRequirement)(craft, map.level);
}
}
}
//# sourceMappingURL=ammo.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "ammo.js",
"sourceRoot": "",
"sources": [
"ammo.ts"
],
"names": [],
"mappings": "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"
}
@@ -0,0 +1,74 @@
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
import { Context } from "../contex";
import { BaseClasses } from "@spt/models/enums/BaseClasses";
import { isQuestLockedCraft, setAreaLevelRequirement } from "../utils";
export function calculateAmmoLoyalty(item: IItem, context: Context): number
{
const itemTemplate: ITemplateItem = context.tables.templates.items[item._tpl];
return scoreAmmo(itemTemplate, context);
}
export function scoreAmmo(itemTemplate: ITemplateItem, context: Context): number
{
const config = context.config.algorithmicalRebalancing.ammoRules;
let loyalty: number = config.defaultBaseLoyaltyByPen;
//Base level from penetration
for (const rule of config.ammoBaseLoyaltyByPen)
{
if (itemTemplate._props.PenetrationPower >= rule.penInterval[0] && itemTemplate._props.PenetrationPower < rule.penInterval[1])
{
loyalty = rule.baseLoyalty;
}
}
//Modify by caliber
for (const rule of config.caliberRules)
{
if (itemTemplate._props.Caliber == rule.caliber)
{
loyalty += rule.loyaltyDelta;
}
}
//Modify by damage (accounting for projectile count)
for (const rule of config.damageRules)
{
const totalDamage = itemTemplate._props.Damage * itemTemplate._props.ProjectileCount;
if (totalDamage >= rule.damageInterval[0] && totalDamage < rule.damageInterval[1])
{
loyalty += rule.loyaltyDelta;
}
}
loyalty += config.globalDelta;
return loyalty
}
export function rebalanceAmmoCrafts(context: Context): void
{
const config = context.config.algorithmicalRebalancing.ammoRules;
const crafts = context.tables.hideout.production.recipes;
for (const craft of crafts)
{
const ammoId: string = craft.endProduct;
if (!context.itemHelper.isOfBaseclass(ammoId, BaseClasses.AMMO)) continue; //Exclude non-ammo
let score: number = scoreAmmo(context.tables.templates.items[ammoId], context);
if (isQuestLockedCraft(craft)) score += context.config.algorithmicalRebalancing.questLockDelta;
for (const map of config.craftSettings.loyaltyToLevelRanges)
{
if (score >= map.range[0] && score < map.range[1]) setAreaLevelRequirement(craft, map.level);
}
}
}
@@ -0,0 +1,135 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.algorithmicallyRebalance = algorithmicallyRebalance;
const ammo_1 = require("./ammo");
const weapon_1 = require("./weapon");
const utils_1 = require("../utils");
const BaseClasses_1 = require("C:/snapshot/project/obj/models/enums/BaseClasses");
const types_1 = require("./types");
function algorithmicallyRebalance(context) {
//Alias
const config = context.config.algorithmicalRebalancing;
const itemHelper = context.itemHelper;
const traders = Object.values(context.tables.traders);
//Organize changed items by loyalty level so that they can be easily accessed by the shifting system
//[trade, loyaltyScore, trader, logChanges][index][loyaltyLevel]
const changedItems = {};
//Loop over each trader
for (const trader of traders) {
const loyaltyLevels = trader?.assort?.loyal_level_items;
if (loyaltyLevels == null)
continue;
const itemsForSale = trader?.assort?.items;
if (itemsForSale == null)
continue;
if (config.excludeTraders.includes(trader.base._id))
continue;
for (const item of itemsForSale) {
if (Object.keys(config.explicitLoyaltyOverride.trades).includes(item._id))
continue;
if (Object.keys(config.explicitLoyaltyOverride.items).includes(item._tpl))
continue;
let thisItem;
//// AMMO ////
if (config.ammoRules.enable) {
let ammoOrBox = false;
let ammo;
let loyaltyScore;
if (itemHelper.isOfBaseclass(item._tpl, BaseClasses_1.BaseClasses.AMMO)) {
loyaltyScore = (0, ammo_1.calculateAmmoLoyalty)(item, context);
ammo = item._tpl;
ammoOrBox = true;
}
else if (itemHelper.isOfBaseclass(item._tpl, BaseClasses_1.BaseClasses.AMMO_BOX)) {
ammo = context.tables.templates.items[item._tpl]._props.StackSlots[0]._props.filters[0].Filter[0];
loyaltyScore = (0, ammo_1.scoreAmmo)(context.tables.templates.items[ammo], context);
ammoOrBox = true;
}
if (ammoOrBox && !config.ammoRules.ignoreCalibers.includes(context.tables.templates.items[ammo]._props.Caliber)) {
thisItem = new types_1.ChangedItem(item, loyaltyScore, trader, config.ammoRules.logChanges, false);
}
}
//// WEAPONS ////
if (config.weaponRules.enable
&& itemHelper.isOfBaseclass(item._tpl, BaseClasses_1.BaseClasses.WEAPON)
&& !itemHelper.isOfBaseclass(item._tpl, BaseClasses_1.BaseClasses.SPECIAL_WEAPON)) //ToDo: double check melee and grenades (and possibly other exceptions)
{
const loyaltyScore = (0, weapon_1.calculateWeaponLoyalty)(item, itemsForSale, context);
thisItem = new types_1.ChangedItem(item, loyaltyScore, trader, config.weaponRules.logChanges, true);
}
if (thisItem != null) //If item is being affected by the mod
{
//Final modifications
if ((0, utils_1.isQuestLocked)(item, trader)) {
thisItem.score += config.questLockDelta;
if (config.logBartersAndLocks)
context.logger.info(context.tables.templates.items[item._tpl]._name + " is a quest-locked item\t(Trade ID: " + item._id + ")");
}
if ((0, utils_1.isBarterTrade)(item, trader)) {
thisItem.score += config.barterDelta;
if (config.logBartersAndLocks)
context.logger.info(context.tables.templates.items[item._tpl]._name + " is a bartered item\t(Trade ID: " + item._id + ")");
}
//By-trader delta
if (trader.base._id in config.deltaByTrader)
thisItem.score += config.deltaByTrader[trader.base._id];
//Explicit deltas
const tradeD = config.explicitLoyaltyDelta.trades[thisItem.trade._id];
const itemD = config.explicitLoyaltyDelta.items[thisItem.trade._tpl];
if (!isNaN(tradeD))
thisItem.score += tradeD;
if (!isNaN(itemD))
thisItem.score += itemD;
const level = (0, utils_1.loyaltyFromScore)(thisItem.score, config.clampToMaxLevel);
if (changedItems[level] == null) {
changedItems[level] = [thisItem];
}
else {
changedItems[level].push(thisItem);
}
}
}
}
if (config.weaponRules.upshiftRules.enable)
(0, weapon_1.weaponShifting)(changedItems, context);
if (config.weaponRules.attachmentsFollowDefaultBuild)
(0, weapon_1.followDefaultBuild)(changedItems, context);
if (config.weaponRules.advancedAttachmentsDelta != 0)
(0, weapon_1.penalizeAdvancedAttachments)(changedItems, context);
//Apply changes
for (const changesInLevel of Object.values(changedItems)) {
if (changesInLevel == null || changesInLevel.length == 0)
continue;
for (const changedItem of changesInLevel) {
let doClamp = config.clampToMaxLevel;
if (config.forceClampingOfQuestlockedItems && (0, utils_1.isQuestLocked)(changedItem.trade, changedItem.trader))
doClamp = true;
if (changedItem.logChange)
context.logger.info(`Setting ${context.tables.templates.items[changedItem.trade._tpl]._name} at loyalty level ${(0, utils_1.loyaltyFromScore)(changedItem.score, doClamp)} (${changedItem.score})`);
(0, utils_1.setLoyalty)(changedItem.trade._id, changedItem.score, changedItem.trader, doClamp);
}
}
//Overrides
for (const trader of traders) {
const loyaltyLevels = trader?.assort?.loyal_level_items;
if (loyaltyLevels == null)
continue;
const itemsForSale = trader?.assort?.items;
if (itemsForSale == null)
continue;
if (config.excludeTraders.includes(trader.base._id))
continue;
for (const item of trader.assort.items) {
let override = null;
override = config.explicitLoyaltyOverride.trades[item._id];
if (override == null)
override = config.explicitLoyaltyOverride.items[item._tpl];
if (override == null)
continue;
(0, utils_1.setLoyalty)(item._id, override, trader, config.clampToMaxLevel);
}
}
if (config.ammoRules.craftSettings.enable)
(0, ammo_1.rebalanceAmmoCrafts)(context);
}
//# sourceMappingURL=core.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,166 @@
import { ItemHelper } from "@spt/helpers/ItemHelper";
import { Context } from "../contex";
import { calculateAmmoLoyalty, rebalanceAmmoCrafts, scoreAmmo } from "./ammo";
import { calculateWeaponLoyalty, followDefaultBuild, penalizeAdvancedAttachments, weaponShifting as shiftWeapons } from "./weapon";
import { isBarterTrade, isQuestLocked, loyaltyFromScore, setLoyalty } from "../utils";
import { BaseClasses } from "@spt/models/enums/BaseClasses";
import { ChangedItem } from "./types";
export function algorithmicallyRebalance(context: Context): void
{
//Alias
const config = context.config.algorithmicalRebalancing;
const itemHelper: ItemHelper = context.itemHelper;
const traders = Object.values(context.tables.traders);
//Organize changed items by loyalty level so that they can be easily accessed by the shifting system
//[trade, loyaltyScore, trader, logChanges][index][loyaltyLevel]
const changedItems: { [level: number] : ChangedItem[] } = {};
//Loop over each trader
for (const trader of traders)
{
const loyaltyLevels = trader?.assort?.loyal_level_items;
if (loyaltyLevels == null) continue;
const itemsForSale = trader?.assort?.items;
if (itemsForSale == null) continue;
if (config.excludeTraders.includes(trader.base._id)) continue;
for (const item of itemsForSale)
{
if (Object.keys(config.explicitLoyaltyOverride.trades).includes(item._id)) continue;
if (Object.keys(config.explicitLoyaltyOverride.items).includes(item._tpl)) continue;
let thisItem: ChangedItem;
//// AMMO ////
if (config.ammoRules.enable)
{
let ammoOrBox = false;
let ammo;
let loyaltyScore;
if (itemHelper.isOfBaseclass(item._tpl, BaseClasses.AMMO))
{
loyaltyScore = calculateAmmoLoyalty(item, context);
ammo = item._tpl;
ammoOrBox = true;
}
else if (itemHelper.isOfBaseclass(item._tpl, BaseClasses.AMMO_BOX))
{
ammo = context.tables.templates.items[item._tpl]._props.StackSlots[0]._props.filters[0].Filter[0];
loyaltyScore = scoreAmmo(context.tables.templates.items[ammo], context)
ammoOrBox = true
}
if (ammoOrBox && !config.ammoRules.ignoreCalibers.includes(context.tables.templates.items[ammo]._props.Caliber))
{
thisItem = new ChangedItem(
item,
loyaltyScore,
trader,
config.ammoRules.logChanges,
false
);
}
}
//// WEAPONS ////
if (config.weaponRules.enable
&& itemHelper.isOfBaseclass(item._tpl, BaseClasses.WEAPON)
&& !itemHelper.isOfBaseclass(item._tpl, BaseClasses.SPECIAL_WEAPON)) //ToDo: double check melee and grenades (and possibly other exceptions)
{
const loyaltyScore = calculateWeaponLoyalty(item, itemsForSale, context);
thisItem = new ChangedItem(
item,
loyaltyScore,
trader,
config.weaponRules.logChanges,
true
);
}
if (thisItem != null) //If item is being affected by the mod
{
//Final modifications
if (isQuestLocked(item, trader))
{
thisItem.score += config.questLockDelta;
if (config.logBartersAndLocks) context.logger.info(context.tables.templates.items[item._tpl]._name + " is a quest-locked item\t(Trade ID: " + item._id + ")");
}
if (isBarterTrade(item, trader))
{
thisItem.score += config.barterDelta;
if (config.logBartersAndLocks) context.logger.info(context.tables.templates.items[item._tpl]._name + " is a bartered item\t(Trade ID: " + item._id + ")");
}
//By-trader delta
if (trader.base._id in config.deltaByTrader) thisItem.score += config.deltaByTrader[trader.base._id]
//Explicit deltas
const tradeD = config.explicitLoyaltyDelta.trades[thisItem.trade._id];
const itemD = config.explicitLoyaltyDelta.items[thisItem.trade._tpl];
if (!isNaN(tradeD)) thisItem.score += tradeD;
if (!isNaN(itemD)) thisItem.score += itemD;
const level = loyaltyFromScore(thisItem.score, config.clampToMaxLevel);
if (changedItems[level] == null)
{
changedItems[level] = [thisItem];
}
else
{
changedItems[level].push(thisItem);
}
}
}
}
if (config.weaponRules.upshiftRules.enable) shiftWeapons(changedItems, context);
if (config.weaponRules.attachmentsFollowDefaultBuild) followDefaultBuild(changedItems, context);
if (config.weaponRules.advancedAttachmentsDelta != 0) penalizeAdvancedAttachments(changedItems, context);
//Apply changes
for (const changesInLevel of Object.values(changedItems))
{
if (changesInLevel == null || changesInLevel.length == 0) continue;
for (const changedItem of changesInLevel)
{
let doClamp: boolean = config.clampToMaxLevel;
if (config.forceClampingOfQuestlockedItems && isQuestLocked(changedItem.trade, changedItem.trader)) doClamp = true;
if (changedItem.logChange) context.logger.info(`Setting ${context.tables.templates.items[changedItem.trade._tpl]._name} at loyalty level ${loyaltyFromScore(changedItem.score, doClamp)} (${changedItem.score})`);
setLoyalty(changedItem.trade._id, changedItem.score, changedItem.trader, doClamp);
}
}
//Overrides
for (const trader of traders)
{
const loyaltyLevels = trader?.assort?.loyal_level_items;
if (loyaltyLevels == null) continue;
const itemsForSale = trader?.assort?.items;
if (itemsForSale == null) continue;
if (config.excludeTraders.includes(trader.base._id)) continue;
for (const item of trader.assort.items)
{
let override: number | null = null;
override = config.explicitLoyaltyOverride.trades[item._id];
if (override == null) override = config.explicitLoyaltyOverride.items[item._tpl];
if (override == null) continue;
setLoyalty(item._id, override, trader, config.clampToMaxLevel);
}
}
if (config.ammoRules.craftSettings.enable) rebalanceAmmoCrafts(context);
}
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChangedItem = void 0;
class ChangedItem {
trade;
score;
trader;
logChange;
isWeapon;
constructor(trade, score, trader, logChange, isWeapon) {
this.trade = trade;
this.score = score;
this.trader = trader;
this.logChange = logChange;
this.isWeapon = isWeapon;
}
}
exports.ChangedItem = ChangedItem;
//# sourceMappingURL=types.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "types.js",
"sourceRoot": "",
"sources": [
"types.ts"
],
"names": [],
"mappings": ";;;AAGA,MAAa,WAAW;IAEpB,KAAK,CAAQ;IAAC,KAAK,CAAS;IAAC,MAAM,CAAU;IAAC,SAAS,CAAU;IAAC,QAAQ,CAAU;IAEpF,YAAY,KAAY,EAAE,KAAa,EAAE,MAAe,EAAE,SAAkB,EAAE,QAAiB;QAE3F,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;CACJ;AAZD,kCAYC"
}
@@ -0,0 +1,16 @@
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ITrader } from "@spt/models/eft/common/tables/ITrader";
export class ChangedItem
{
trade: IItem; score: number; trader: ITrader; logChange: boolean; isWeapon: boolean;
constructor(trade: IItem, score: number, trader: ITrader, logChange: boolean, isWeapon: boolean)
{
this.trade = trade;
this.score = score;
this.trader = trader;
this.logChange = logChange;
this.isWeapon = isWeapon;
}
}
@@ -0,0 +1,141 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateWeaponLoyalty = calculateWeaponLoyalty;
exports.followDefaultBuild = followDefaultBuild;
exports.penalizeAdvancedAttachments = penalizeAdvancedAttachments;
exports.weaponShifting = weaponShifting;
const utils_1 = require("../utils");
function calculateWeaponLoyalty(item, assort, context) {
const config = context.config.algorithmicalRebalancing.weaponRules;
const tables = context.tables;
const itemTemplate = tables.templates.items[item._tpl];
const fireMode = (0, utils_1.bestFiremode)(itemTemplate);
const fireRate = itemTemplate._props.bFirerate;
let loyalty = config.defaultBaseLoyalty;
//Base loyalty level based on caliber
for (const byCaliber of config.weaponBaseLoyaltyByCaliber) {
if (byCaliber.caliber == itemTemplate._props.ammoCaliber)
loyalty = byCaliber.baseLoyalty;
}
//Account for best available fire mode
for (const byMode of config.fireModeRules) {
if (byMode.mode == fireMode)
loyalty += byMode.delta;
}
//Account for fire rate if applicable
if (fireMode == "fullauto" || fireMode == "burst")
for (const byRate of config.fireRateRules) {
if (fireRate >= byRate.rateInterval[0] && fireRate < byRate.rateInterval[1])
loyalty += byRate.delta;
}
return loyalty + config.globalDelta;
}
//Move attachments that are part of some default build to the same tier of the weapon their are part of the build for
function followDefaultBuild(changedItems, context) {
const changesById = (0, utils_1.indexById)(changedItems);
for (const weaponChange of Object.values(changesById)) {
if (!weaponChange.isWeapon)
continue;
const parts = (0, utils_1.getDefaultAttachments)(weaponChange.trade._tpl, context);
for (const part of parts) {
const level = (0, utils_1.loyaltyFromScore)(weaponChange.score, context.config.algorithmicalRebalancing.clampToMaxLevel);
const partTrades = (0, utils_1.findTrades)(part, context);
for (const partTrade of partTrades) {
const oldPartChange = changesById[partTrade.trade._id];
if (oldPartChange != null) {
const oldLevel = (0, utils_1.loyaltyFromScore)(oldPartChange.score, context.config.algorithmicalRebalancing.clampToMaxLevel);
if (oldLevel <= level) {
continue;
}
else {
changedItems[oldLevel] = changedItems[oldLevel].filter((item) => item.trade._id != partTrade.trade._id);
}
}
changedItems[level].push({
trade: partTrade.trade,
score: weaponChange.score,
trader: partTrade.trader,
logChange: false,
isWeapon: false
});
}
}
}
}
function penalizeAdvancedAttachments(changedItems, context) {
const config = context.config.algorithmicalRebalancing;
const toPenalize = [];
for (const [tier, changes] of Object.entries(changedItems))
for (const change of changes) {
if (!change.isWeapon)
continue;
if (!(0, utils_1.canAllAttachmentsBePurchased)(change.trade, change.trader.assort.items, true, true, Number(tier), (0, utils_1.getDefaultAttachments)(change.trade._tpl, context), (0, utils_1.indexById)(changedItems), context)) {
toPenalize.push([change, Number(tier)]);
//context.logger.info(`Penalizing ${context.tables.templates.items[change.trade._tpl]._name}`);
}
}
for (const [change, tier] of toPenalize) {
changedItems[tier] = changedItems[tier].filter((item) => item.trade._id != change.trade._id);
change.score += config.weaponRules.advancedAttachmentsDelta;
const newLevel = (0, utils_1.loyaltyFromScore)(change.score, config.clampToMaxLevel);
if (changedItems[newLevel] == null) {
changedItems[newLevel] = [change];
}
else {
changedItems[newLevel].push(change);
}
}
}
function weaponShifting(changedItems, context) {
const config = context.config.algorithmicalRebalancing;
const reverse = config.weaponRules.upshiftRules.shiftDownInstead;
//Shifting system
//WARNING!!! HORRIBLE CODE AHEAD!!!
const keys = Object.keys(changedItems).sort();
for (let i = 0; i < keys.length; i++) {
const index = reverse ? keys.length - i - 1 : i; //Reverse order
const changesInLevel = changedItems[keys[index]];
if (changesInLevel == null || changesInLevel.length == 0)
continue;
const toShift = new Set();
for (let i = 0; i < changesInLevel.length; i++) {
if (toShift.has(i))
continue;
if (!changesInLevel[i].isWeapon)
continue;
for (let j = i + 1; j < changesInLevel.length; j++) {
if (toShift.has(j))
continue;
if (!changesInLevel[j].isWeapon)
continue;
const a = changesInLevel[i];
const b = changesInLevel[j];
if ((0, utils_1.shareSameNiche)(a.trade, b.trade, a.trader, b.trader, context)) {
const aPowerLevel = config.weaponRules.upshiftRules.powerLevels[a.trade._tpl];
const bPowerLevel = config.weaponRules.upshiftRules.powerLevels[b.trade._tpl];
if (aPowerLevel == null || bPowerLevel == null || aPowerLevel == bPowerLevel)
continue;
if (aPowerLevel < bPowerLevel) {
toShift.add(reverse ? i : j);
}
else {
toShift.add(reverse ? j : i);
}
}
}
}
for (const shiftIndex of toShift) {
const change = changesInLevel[shiftIndex];
change.score += config.weaponRules.upshiftRules.shiftAmount * (reverse ? -1 : 1);
const newLevel = (0, utils_1.loyaltyFromScore)(change.score, config.clampToMaxLevel);
changedItems[keys[index]] = changedItems[keys[index]].filter(item => item.trade._id != change.trade._id);
if (changedItems[newLevel] == null) {
changedItems[newLevel] = [change];
}
else {
changedItems[newLevel].push(change);
}
}
}
}
//# sourceMappingURL=weapon.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,183 @@
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
import { Context } from "../contex";
import { bestFiremode, canAllAttachmentsBePurchased, findTrades, getDefaultAttachments, indexById, loyaltyFromScore, shareSameNiche } from "../utils";
import { ChangedItem } from "./types";
export function calculateWeaponLoyalty(item: IItem, assort: IItem[], context: Context): number
{
const config = context.config.algorithmicalRebalancing.weaponRules;
const tables = context.tables;
const itemTemplate: ITemplateItem = tables.templates.items[item._tpl];
const fireMode = bestFiremode(itemTemplate);
const fireRate = itemTemplate._props.bFirerate;
let loyalty: number = config.defaultBaseLoyalty;
//Base loyalty level based on caliber
for (const byCaliber of config.weaponBaseLoyaltyByCaliber)
{
if (byCaliber.caliber == itemTemplate._props.ammoCaliber) loyalty = byCaliber.baseLoyalty;
}
//Account for best available fire mode
for (const byMode of config.fireModeRules)
{
if (byMode.mode == fireMode) loyalty += byMode.delta;
}
//Account for fire rate if applicable
if (fireMode == "fullauto" || fireMode == "burst") for (const byRate of config.fireRateRules)
{
if (fireRate >= byRate.rateInterval[0] && fireRate < byRate.rateInterval[1]) loyalty += byRate.delta;
}
return loyalty + config.globalDelta;
}
//Move attachments that are part of some default build to the same tier of the weapon their are part of the build for
export function followDefaultBuild(changedItems: Record<number, ChangedItem[]>, context: Context): void
{
const changesById = indexById(changedItems);
for (const weaponChange of Object.values(changesById))
{
if (!weaponChange.isWeapon) continue;
const parts = getDefaultAttachments(weaponChange.trade._tpl, context);
for (const part of parts)
{
const level = loyaltyFromScore(weaponChange.score, context.config.algorithmicalRebalancing.clampToMaxLevel);
const partTrades = findTrades(part, context);
for (const partTrade of partTrades)
{
const oldPartChange = changesById[partTrade.trade._id];
if (oldPartChange != null)
{
const oldLevel = loyaltyFromScore(oldPartChange.score, context.config.algorithmicalRebalancing.clampToMaxLevel);
if (oldLevel <= level)
{
continue;
}
else
{
changedItems[oldLevel] = changedItems[oldLevel].filter((item) => item.trade._id != partTrade.trade._id);
}
}
changedItems[level].push({
trade: partTrade.trade,
score: weaponChange.score,
trader: partTrade.trader,
logChange: false,
isWeapon: false
})
}
}
}
}
export function penalizeAdvancedAttachments(changedItems: Record<number, ChangedItem[]>, context: Context): void
{
const config = context.config.algorithmicalRebalancing;
const toPenalize: [ChangedItem, number][] = [];
for (const [tier, changes] of Object.entries(changedItems)) for (const change of changes)
{
if (!change.isWeapon) continue;
if (!canAllAttachmentsBePurchased(
change.trade, change.trader.assort.items, true, true, Number(tier),
getDefaultAttachments(change.trade._tpl, context), indexById(changedItems),
context))
{
toPenalize.push([change, Number(tier)]);
//context.logger.info(`Penalizing ${context.tables.templates.items[change.trade._tpl]._name}`);
}
}
for (const [change, tier] of toPenalize)
{
changedItems[tier] = changedItems[tier].filter((item) => item.trade._id != change.trade._id);
change.score += config.weaponRules.advancedAttachmentsDelta;
const newLevel = loyaltyFromScore(change.score, config.clampToMaxLevel);
if (changedItems[newLevel] == null)
{
changedItems[newLevel] = [change];
}
else
{
changedItems[newLevel].push(change);
}
}
}
export function weaponShifting(changedItems: { [level: number] : ChangedItem[] }, context: Context): void
{
const config = context.config.algorithmicalRebalancing;
const reverse = config.weaponRules.upshiftRules.shiftDownInstead;
//Shifting system
//WARNING!!! HORRIBLE CODE AHEAD!!!
const keys = Object.keys(changedItems).sort();
for (let i = 0; i < keys.length; i++)
{
const index = reverse ? keys.length - i - 1 : i; //Reverse order
const changesInLevel = changedItems[keys[index]];
if (changesInLevel == null || changesInLevel.length == 0) continue;
const toShift: Set<number> = new Set<number>();
for (let i = 0; i < changesInLevel.length; i++)
{
if (toShift.has(i)) continue;
if (!changesInLevel[i].isWeapon) continue;
for (let j = i + 1; j < changesInLevel.length; j++)
{
if (toShift.has(j)) continue;
if (!changesInLevel[j].isWeapon) continue;
const a = changesInLevel[i]; const b = changesInLevel[j];
if (shareSameNiche(a.trade, b.trade, a.trader, b.trader, context))
{
const aPowerLevel = config.weaponRules.upshiftRules.powerLevels[a.trade._tpl];
const bPowerLevel = config.weaponRules.upshiftRules.powerLevels[b.trade._tpl];
if (aPowerLevel == null || bPowerLevel == null || aPowerLevel == bPowerLevel) continue;
if (aPowerLevel < bPowerLevel)
{
toShift.add(reverse ? i : j);
}
else
{
toShift.add(reverse ? j : i);
}
}
}
}
for (const shiftIndex of toShift)
{
const change = changesInLevel[shiftIndex];
change.score += config.weaponRules.upshiftRules.shiftAmount * (reverse ? -1 : 1);
const newLevel = loyaltyFromScore(change.score, config.clampToMaxLevel);
changedItems[keys[index]] = changedItems[keys[index]].filter(item => item.trade._id != change.trade._id);
if (changedItems[newLevel] == null)
{
changedItems[newLevel] = [change];
}
else
{
changedItems[newLevel].push(change);
}
}
}
}
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Context = void 0;
class Context {
database;
tables;
logger;
itemHelper;
presetHelper;
sptConfig;
hashUtil;
config;
constructor(_config, /*Not sure how to type this, dynamically type checked languages are weird*/ _database, _tables, _logger, _itemHelper, _preetHelper, _sptConfig, _hashUtil) {
this.database = _database;
this.tables = _tables;
this.logger = _logger;
this.config = _config;
this.itemHelper = _itemHelper;
this.presetHelper = _preetHelper;
this.sptConfig = _sptConfig;
this.hashUtil = _hashUtil;
}
logObject(object) {
this.logger?.info(JSON.stringify(object, null, 2));
}
}
exports.Context = Context;
//# sourceMappingURL=contex.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "contex.js",
"sourceRoot": "",
"sources": [
"contex.ts"
],
"names": [],
"mappings": ";;;AAQA,MAAa,OAAO;IAGT,QAAQ,CAAiB;IACzB,MAAM,CAAkB;IACxB,MAAM,CAAgB;IACtB,UAAU,CAAa;IACvB,YAAY,CAAe;IAC3B,SAAS,CAAe;IACxB,QAAQ,CAAW;IACnB,MAAM,CAAC;IAEd,YACI,OAAa,EAAE,2EAA2E,CAC1F,SAA0B,EAAE,OAAyB,EAAE,OAAuB,EAAE,WAAwB,EAAE,YAA2B,EAAE,UAAyB,EAAE,SAAoB;QAGtL,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QACtB,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;IAC9B,CAAC;IAEM,SAAS,CAAC,MAAW;QAExB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;CAEJ;AAhCD,0BAgCC"
}
@@ -0,0 +1,41 @@
import { ItemHelper } from "@spt/helpers/ItemHelper";
import { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { LoggerWrapper } from "./loggerWrapper";
import { PresetHelper } from "@spt/helpers/PresetHelper";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { HashUtil } from "@spt/utils/HashUtil";
export class Context
{
public database: DatabaseServer;
public tables: IDatabaseTables;
public logger: LoggerWrapper;
public itemHelper: ItemHelper;
public presetHelper: PresetHelper;
public sptConfig: ConfigServer;
public hashUtil: HashUtil;
public config;
constructor(
_config?: any, /*Not sure how to type this, dynamically type checked languages are weird*/
_database?: DatabaseServer, _tables?: IDatabaseTables, _logger?: LoggerWrapper, _itemHelper?: ItemHelper, _preetHelper?: PresetHelper, _sptConfig?: ConfigServer, _hashUtil?: HashUtil
)
{
this.database = _database;
this.tables = _tables;
this.logger = _logger;
this.config = _config;
this.itemHelper = _itemHelper;
this.presetHelper = _preetHelper;
this.sptConfig = _sptConfig;
this.hashUtil = _hashUtil;
}
public logObject(object: any): void
{
this.logger?.info(JSON.stringify(object, null, 2));
}
}
@@ -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.addNewItems = addNewItems;
const fs_1 = __importDefault(require("fs"));
// eslint-disable-next-line @typescript-eslint/naming-convention
const json5_1 = __importDefault(require("C:/snapshot/project/node_modules/json5"));
const utils_1 = require("../utils");
function addNewItems(context) {
//Read custom items config
const fileContent = fs_1.default.readFileSync((0, utils_1.getModFolder)() + "/config/advanced/customItems.json5", "utf-8");
const itemsConfig = json5_1.default.parse(fileContent);
const buffs = context.tables.globals.config.Health.Effects.Stimulator.Buffs;
const items = context.tables.templates.items;
const locales = context.tables.locales.global;
//Add the buffs
for (const [buff, effects] of Object.entries(itemsConfig.customBuffs)) {
buffs[buff] = effects;
}
//Add the items
for (const [id, item] of Object.entries(itemsConfig.customItems)) {
items[id] = item;
}
//Add the locales
for (const [item, locale] of Object.entries(itemsConfig.customLocales)) {
(0, utils_1.addToLocale)(locales, item, locale.Name, locale.ShortName, locale.Description);
}
//Add the trades
for (const [traderID, additions] of Object.entries(itemsConfig.customTrades)) {
const trader = context.tables.traders[traderID];
for (const trade of additions.items) {
trader.assort.items.push(trade);
}
for (const [trade, scheme] of Object.entries(additions.barter_scheme)) {
trader.assort.barter_scheme[trade] = scheme;
}
for (const [trade, loyalty] of Object.entries(additions.loyal_level_items)) {
trader.assort.loyal_level_items[trade] = loyalty;
}
}
}
//# sourceMappingURL=items.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "items.js",
"sourceRoot": "",
"sources": [
"items.ts"
],
"names": [],
"mappings": ";;;;;AASA,kCAsDC;AA/DD,4CAAoB;AACpB,gEAAgE;AAChE,mFAA2D;AAE3D,oCAAqD;AAKrD,SAAgB,WAAW,CAAC,OAAgB;IAExC,0BAA0B;IAC1B,MAAM,WAAW,GAAG,YAAE,CAAC,YAAY,CAAC,IAAA,oBAAY,GAAE,GAAG,oCAAoC,EAAE,OAAO,CAAC,CAAC;IACpG,MAAM,WAAW,GAAG,eAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAE7C,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC;IAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;IAE9C,eAAe;IACf,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,EACrE,CAAC;QACG,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,eAAe;IACf,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,WAA4C,CAAC,EACjG,CAAC;QACG,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,iBAAiB;IACjB,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,aAAuF,CAAC,EAChJ,CAAC;QACG,IAAA,mBAAW,EAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;IACjF,CAAC;IAED,gBAAgB;IAChB,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAC5B,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,YAItB,CACJ,EAEL,CAAC;QACG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,KAAK,EACnC,CAAC;YACG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,EACrE,CAAC;YACG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;QAChD,CAAC;QAED,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAC1E,CAAC;YACG,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;QACrD,CAAC;IACL,CAAC;AACL,CAAC"
}
@@ -0,0 +1,64 @@
import fs from "fs";
// eslint-disable-next-line @typescript-eslint/naming-convention
import JSON5 from "json5";
import { Context } from "../contex";
import { addToLocale, getModFolder } from "../utils";
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { IBarterScheme } from "@spt/models/eft/common/tables/ITrader";
export function addNewItems(context: Context): void
{
//Read custom items config
const fileContent = fs.readFileSync(getModFolder() + "/config/advanced/customItems.json5", "utf-8");
const itemsConfig = JSON5.parse(fileContent);
const buffs = context.tables.globals.config.Health.Effects.Stimulator.Buffs;
const items = context.tables.templates.items;
const locales = context.tables.locales.global;
//Add the buffs
for (const [buff, effects] of Object.entries(itemsConfig.customBuffs))
{
buffs[buff] = effects;
}
//Add the items
for (const [id, item] of Object.entries(itemsConfig.customItems as Record<string, ITemplateItem>))
{
items[id] = item;
}
//Add the locales
for (const [item, locale] of Object.entries(itemsConfig.customLocales as Record<string, {Name: string, ShortName: string, Description: string}>))
{
addToLocale(locales, item, locale.Name, locale.ShortName, locale.Description)
}
//Add the trades
for (const [traderID, additions] of
Object.entries(itemsConfig.customTrades as
Record<
// eslint-disable-next-line @typescript-eslint/naming-convention
string, {items: IItem[], barter_scheme: Record<string, IBarterScheme[][]>, loyal_level_items: Record<string, number>}
>
)
)
{
const trader = context.tables.traders[traderID];
for (const trade of additions.items)
{
trader.assort.items.push(trade);
}
for (const [trade, scheme] of Object.entries(additions.barter_scheme))
{
trader.assort.barter_scheme[trade] = scheme;
}
for (const [trade, loyalty] of Object.entries(additions.loyal_level_items))
{
trader.assort.loyal_level_items[trade] = loyalty;
}
}
}
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoggerWrapper = void 0;
class LoggerWrapper {
prefix;
logger;
modName;
modVersion;
constructor(_logger, preLoader) {
this.logger = _logger;
try {
let modDetails = preLoader.getImportedModDetails()["Gekos_BetterProgression"];
if (modDetails == null) {
modDetails = preLoader.getImportedModDetails()["drunkgeko-gekos_betterprogression"];
}
this.modName = modDetails.name;
this.modVersion = modDetails.version;
this.prefix = `[${this.modName}-${this.modVersion}] `;
}
catch (error) {
this.modName = "gekos_betterprogression";
this.modVersion = "??";
this.prefix = `[${this.modName}-${this.modVersion}] `;
this.warning("Could not retrieve mod info!");
this.warning("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
info(message) {
this.logger.info(this.prefix + message);
}
writeToLogFile(data) {
if (typeof data === "string") {
data += this.prefix;
}
this.logger.writeToLogFile(data);
}
log(data, color, backgroundColor) {
if (typeof data === "string") {
data += this.prefix;
}
this.logger.log(data, backgroundColor);
}
logWithColor(data, textColor, backgroundColor) {
if (typeof data === "string") {
data += this.prefix;
}
this.logger.logWithColor(data, textColor, backgroundColor);
}
error(data) {
this.logger.error(`${this.prefix}${data}`);
}
warning(data) {
this.logger.warning(this.prefix + data);
}
success(data) {
this.logger.success(this.prefix + data);
}
debug(data, onlyShowInConsole) {
if (typeof data === "string") {
data += this.prefix;
}
this.logger.debug(data, onlyShowInConsole);
}
}
exports.LoggerWrapper = LoggerWrapper;
//# sourceMappingURL=loggerWrapper.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "loggerWrapper.js",
"sourceRoot": "",
"sources": [
"loggerWrapper.ts"
],
"names": [],
"mappings": ";;;AAMA,MAAa,aAAa;IAGb,MAAM,CAAS;IACf,MAAM,CAAU;IAChB,OAAO,CAAS;IAChB,UAAU,CAAS;IAE5B,YAAY,OAAgB,EAAE,SAA0B;QAEpD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;QAEtB,IACA,CAAC;YACG,IAAI,UAAU,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC,yBAAyB,CAAC,CAAC;YAC9E,IAAI,UAAU,IAAI,IAAI,EACtB,CAAC;gBACG,UAAU,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC,mCAAmC,CAAC,CAAC;YACxF,CAAC;YAED,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC;YACrC,IAAI,CAAC,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC;QAC1D,CAAC;QACD,OAAO,KAAK,EACZ,CAAC;YACG,IAAI,CAAC,OAAO,GAAG,yBAAyB,CAAC;YACzC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,CAAC,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC;YACtD,IAAI,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC;YAC7C,IAAI,CAAC,OAAO,CAAC,gBAAgB,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC;QACnG,CAAC;IACL,CAAC;IAEM,IAAI,CAAC,OAAe;QAEvB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,cAAc,CAAC,IAAoB;QAE/B,IAAI,OAAO,IAAI,KAAK,QAAQ,EAC5B,CAAC;YACG,IAAI,IAAI,IAAI,CAAC,MAAM,CAAA;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,GAAG,CAAC,IAA8C,EAAE,KAAa,EAAE,eAAwB;QAEvF,IAAI,OAAO,IAAI,KAAK,QAAQ,EAC5B,CAAC;YACG,IAAI,IAAI,IAAI,CAAC,MAAM,CAAA;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IAC3C,CAAC;IAED,YAAY,CAAC,IAAsC,EAAE,SAAuB,EAAE,eAAoC;QAE9G,IAAI,OAAO,IAAI,KAAK,QAAQ,EAC5B,CAAC;YACG,IAAI,IAAI,IAAI,CAAC,MAAM,CAAA;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,IAAY;QAEd,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,CAAC,IAAY;QAEhB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,CAAC,IAAY;QAEhB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,IAAsC,EAAE,iBAA2B;QAErE,IAAI,OAAO,IAAI,KAAK,QAAQ,EAC5B,CAAC;YACG,IAAI,IAAI,IAAI,CAAC,MAAM,CAAA;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IAC/C,CAAC;CAEJ;AA1FD,sCA0FC"
}
@@ -0,0 +1,97 @@
import { IDaum } from "@spt/models/eft/itemEvent/IItemEventRouterRequest";
import { LogBackgroundColor } from "@spt/models/spt/logging/LogBackgroundColor";
import { LogTextColor } from "@spt/models/spt/logging/LogTextColor";
import { ILogger } from "@spt/models/spt/utils/ILogger"
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
export class LoggerWrapper
{
readonly prefix: string;
readonly logger: ILogger;
readonly modName: string;
readonly modVersion: string;
constructor(_logger: ILogger, preLoader: PreSptModLoader)
{
this.logger = _logger;
try
{
let modDetails = preLoader.getImportedModDetails()["Gekos_BetterProgression"];
if (modDetails == null)
{
modDetails = preLoader.getImportedModDetails()["drunkgeko-gekos_betterprogression"];
}
this.modName = modDetails.name;
this.modVersion = modDetails.version;
this.prefix = `[${this.modName}-${this.modVersion}] `;
}
catch (error)
{
this.modName = "gekos_betterprogression";
this.modVersion = "??";
this.prefix = `[${this.modName}-${this.modVersion}] `;
this.warning("Could not retrieve mod info!");
this.warning("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
public info(message: string): void
{
this.logger.info(this.prefix + message);
}
writeToLogFile(data: string | IDaum): void
{
if (typeof data === "string")
{
data += this.prefix
}
this.logger.writeToLogFile(data);
}
log(data: string | Record<string, unknown> | Error, color: string, backgroundColor?: string): void
{
if (typeof data === "string")
{
data += this.prefix
}
this.logger.log(data, backgroundColor);
}
logWithColor(data: string | Record<string, unknown>, textColor: LogTextColor, backgroundColor?: LogBackgroundColor): void
{
if (typeof data === "string")
{
data += this.prefix
}
this.logger.logWithColor(data, textColor, backgroundColor);
}
error(data: string): void
{
this.logger.error(`${this.prefix}${data}`);
}
warning(data: string): void
{
this.logger.warning(this.prefix + data);
}
success(data: string): void
{
this.logger.success(this.prefix + data);
}
debug(data: string | Record<string, unknown>, onlyShowInConsole?: boolean): void
{
if (typeof data === "string")
{
data += this.prefix
}
this.logger.debug(data, onlyShowInConsole);
}
}
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addGPtoPMC = addGPtoPMC;
function addGPtoPMC(context) {
//Thanks to AcidPhantasm and his Bosses Have Lega Medals mod for giving me an example on how to implement this feature
//The mod can be found here: https://hub.sp-tarkov.com/files/file/2109-bosses-have-lega-medals/
const config = context.config.misc.pmcsHaveGPCoins;
const factor = config.GPCoinWeightProportional;
for (const botType in context.tables.bots.types) {
if (!botType.includes("usec") && !botType.includes("bear") && !botType.includes("pmc"))
continue;
if (botType == "exusec" || botType == "infectedpmc")
continue;
const pockets = context.tables.bots.types[botType].inventory.items.Pockets;
let totalWeight = Object.values(pockets).reduce((a, b) => a + b, 0);
pockets["5d235b4d86f7742e017bc88a"] = Math.max(1, totalWeight * factor);
const backpack = context.tables.bots.types[botType].inventory.items.Backpack;
totalWeight = Object.values(backpack).reduce((a, b) => a + b, 0);
backpack["5d235b4d86f7742e017bc88a"] = Math.max(1, totalWeight * factor);
context.logger.info(`weight for ${botType} is ${pockets["5d235b4d86f7742e017bc88a"]}`);
}
}
//# sourceMappingURL=addGPtoPMC.js.map
@@ -0,0 +1 @@
{"version":3,"file":"addGPtoPMC.js","sourceRoot":"","sources":["addGPtoPMC.ts"],"names":[],"mappings":";;AAEA,gCAyBC;AAzBD,SAAgB,UAAU,CAAC,OAAgB;IAEvC,sHAAsH;IACtH,+FAA+F;IAE/F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,CAAC,wBAAwB,CAAC;IAE/C,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAC/C,CAAC;QACG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,SAAS;QACjG,IAAI,OAAO,IAAI,QAAQ,IAAI,OAAO,IAAI,aAAa;YAAE,SAAS;QAE9D,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC;QAC3E,IAAI,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,0BAA0B,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC;QAExE,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC;QAC7E,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACjE,QAAQ,CAAC,0BAA0B,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC;QAGzE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,OAAO,OAAO,OAAO,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;AAEL,CAAC"}
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeBitcoinFarming = changeBitcoinFarming;
const ItemTpl_1 = require("C:/snapshot/project/obj/models/enums/ItemTpl");
const utils_1 = require("../utils");
function changeBitcoinFarming(context) {
const config = context.config.bitcoinChanges;
//Set bitcoin value
if (config.overrideValue) {
context.tables.templates.handbook.Items.find((item) => item.Id === ItemTpl_1.ItemTpl.BARTER_PHYSICAL_BITCOIN).Price = config.value;
}
//Multiply btc production rate
const btcProds = context.tables.hideout.production.recipes.filter((production) => production.endProduct === ItemTpl_1.ItemTpl.BARTER_PHYSICAL_BITCOIN);
for (const p of btcProds) {
p.productionTime = Math.round(p.productionTime / config.btcFarmSpeedMult);
p.productionLimitCount = config.btcCapacity;
}
//Apply boost rate
context.tables.hideout.settings.gpuBoostRate = config.gpuBoostRate;
//Remove all non-barters for GPUs
if (config.cannotBuyGPU)
for (const trader of Object.values(context.tables.traders)) {
if (trader.assort == null)
continue;
trader.assort.items = trader.assort.items.filter(assort => assort._tpl != "57347ca924597744596b4e71" || (0, utils_1.isBarterTrade)(assort, trader));
}
}
//# sourceMappingURL=bitcoinChanges.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "bitcoinChanges.js",
"sourceRoot": "",
"sources": [
"bitcoinChanges.ts"
],
"names": [],
"mappings": ";;AAIA,oDA2BC;AA/BD,0EAAuE;AAEvE,oCAAyC;AAEzC,SAAgB,oBAAoB,CAAC,OAAgB;IAEjD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC;IAE7C,mBAAmB;IACnB,IAAI,MAAM,CAAC,aAAa,EACxB,CAAC;QACG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,iBAAO,CAAC,uBAAuB,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC7H,CAAC;IAED,8BAA8B;IAC9B,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,KAAK,iBAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7I,KAAK,MAAM,CAAC,IAAI,QAAQ,EACxB,CAAC;QACG,CAAC,CAAC,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,cAAc,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC1E,CAAC,CAAC,oBAAoB,GAAG,MAAM,CAAC,WAAW,CAAC;IAChD,CAAC;IAED,kBAAkB;IAClB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;IAEnE,iCAAiC;IACjC,IAAI,MAAM,CAAC,YAAY;QAAE,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EACnF,CAAC;YACG,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI;gBAAE,SAAS;YACpC,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,0BAA0B,IAAI,IAAA,qBAAa,EAAC,MAAM,EAAE,MAAM,CAAC,CAAE,CAAC;QAC5I,CAAC;AACL,CAAC"
}
@@ -0,0 +1,32 @@
import { ItemTpl } from "@spt/models/enums/ItemTpl";
import { Context } from "../contex";
import { isBarterTrade } from "../utils";
export function changeBitcoinFarming(context: Context): void
{
const config = context.config.bitcoinChanges;
//Set bitcoin value
if (config.overrideValue)
{
context.tables.templates.handbook.Items.find((item) => item.Id === ItemTpl.BARTER_PHYSICAL_BITCOIN).Price = config.value;
}
//Multiply btc production rate
const btcProds = context.tables.hideout.production.recipes.filter((production) => production.endProduct === ItemTpl.BARTER_PHYSICAL_BITCOIN);
for (const p of btcProds)
{
p.productionTime = Math.round(p.productionTime / config.btcFarmSpeedMult);
p.productionLimitCount = config.btcCapacity;
}
//Apply boost rate
context.tables.hideout.settings.gpuBoostRate = config.gpuBoostRate;
//Remove all non-barters for GPUs
if (config.cannotBuyGPU) for (const trader of Object.values(context.tables.traders))
{
if (trader.assort == null) continue;
trader.assort.items = trader.assort.items.filter(assort => assort._tpl != "57347ca924597744596b4e71" || isBarterTrade(assort, trader) );
}
}
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buffSICCCase = buffSICCCase;
const ItemTpl_1 = require("C:/snapshot/project/obj/models/enums/ItemTpl");
function buffSICCCase(context) {
//Thanks to ODT and his Softcore mod for this part of the code, most of this function is taken from there with appropriate modifications
//Link to the mod: https://hub.sp-tarkov.com/files/file/998-softcore-proper-singleplayer-experience-for-spt/
const config = context.config.SICCBuffs;
const docsFilter = config.canHoldWhatDocsCan ? context.tables.templates.items?.[ItemTpl_1.ItemTpl.CONTAINER_DOCUMENTS_CASE]._props.Grids?.[0]._props.filters[0].Filter : [];
const siccFilter = context.tables.templates.items?.[ItemTpl_1.ItemTpl.CONTAINER_SICC]._props.Grids?.[0]._props.filters[0].Filter;
const additionalItems = config.additionalWhitelistedItems;
if (!docsFilter || !siccFilter) {
context.logger.warning("HideoutContainers: doSiccCaseBuff: docsFilter or siccFilter not found");
return;
}
const mergeFilters = [...new Set([...docsFilter, ...siccFilter, ...additionalItems])];
context.tables.templates.items[ItemTpl_1.ItemTpl.CONTAINER_SICC]._props.Grids[0]._props.filters[0].Filter = mergeFilters;
}
//# sourceMappingURL=buffSICCCase.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "buffSICCCase.js",
"sourceRoot": "",
"sources": [
"buffSICCCase.ts"
],
"names": [],
"mappings": ";;AAGA,oCAmBC;AAtBD,0EAAuE;AAGvE,SAAgB,YAAY,CAAC,OAAgB;IAEzC,wIAAwI;IACxI,4GAA4G;IAE5G,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;IAExC,MAAM,UAAU,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,iBAAO,CAAC,wBAAwB,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAClK,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,iBAAO,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACvH,MAAM,eAAe,GAAG,MAAM,CAAC,0BAA0B,CAAC;IAE1D,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,EAAE,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAA;QAC/F,OAAM;IACV,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,UAAU,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;IACrF,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,iBAAO,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,YAAY,CAAA;AAElH,CAAC"
}
@@ -0,0 +1,23 @@
import { ItemTpl } from "@spt/models/enums/ItemTpl";
import { Context } from "../contex";
export function buffSICCCase(context: Context): void
{
//Thanks to ODT and his Softcore mod for this part of the code, most of this function is taken from there with appropriate modifications
//Link to the mod: https://hub.sp-tarkov.com/files/file/998-softcore-proper-singleplayer-experience-for-spt/
const config = context.config.SICCBuffs;
const docsFilter = config.canHoldWhatDocsCan ? context.tables.templates.items?.[ItemTpl.CONTAINER_DOCUMENTS_CASE]._props.Grids?.[0]._props.filters[0].Filter : [];
const siccFilter = context.tables.templates.items?.[ItemTpl.CONTAINER_SICC]._props.Grids?.[0]._props.filters[0].Filter;
const additionalItems = config.additionalWhitelistedItems;
if (!docsFilter || !siccFilter) {
context.logger.warning("HideoutContainers: doSiccCaseBuff: docsFilter or siccFilter not found")
return
}
const mergeFilters = [...new Set([...docsFilter, ...siccFilter, ...additionalItems])]
context.tables.templates.items[ItemTpl.CONTAINER_SICC]._props.Grids[0]._props.filters[0].Filter = mergeFilters
}
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeHidehoutBuildCosts = changeHidehoutBuildCosts;
const utils_1 = require("../utils");
function changeHidehoutBuildCosts(context) {
const config = context.config.hideoutBuildsChanges;
const hideoutAreas = context.tables.hideout.areas;
for (const area of hideoutAreas)
for (const stage of Object.values(area.stages)) {
const nonCurrencyReq = stage.requirements.filter((req) => !utils_1.currencies.includes(req.templateId));
for (const req of nonCurrencyReq) {
if (req.count) {
req.count -= config.threshold;
if (req.count > 0)
req.count *= config.factor;
req.count += config.threshold;
if (config.roundDown) {
req.count = Math.floor(req.count);
}
else {
req.count = Math.ceil(req.count);
}
}
}
}
}
//# sourceMappingURL=buildChanges.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "buildChanges.js",
"sourceRoot": "",
"sources": [
"buildChanges.ts"
],
"names": [],
"mappings": ";;AAGA,4DA0BC;AA5BD,oCAAsC;AAEtC,SAAgB,wBAAwB,CAAC,OAAgB;IAErD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC;IACnD,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;IAElD,KAAK,MAAM,IAAI,IAAI,YAAY;QAAE,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAC/E,CAAC;YACG,MAAM,cAAc,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,kBAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAA;YAC/F,KAAK,MAAM,GAAG,IAAI,cAAc,EAChC,CAAC;gBACG,IAAI,GAAG,CAAC,KAAK,EACb,CAAC;oBACG,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,CAAC;oBAC9B,IAAI,GAAG,CAAC,KAAK,GAAG,CAAC;wBAAE,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC;oBAC9C,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,CAAC;oBAC9B,IAAI,MAAM,CAAC,SAAS,EACpB,CAAC;wBACG,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACtC,CAAC;yBAED,CAAC;wBACG,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACrC,CAAC;gBACL,CAAC;YACL,CAAC;QACL,CAAC;AACL,CAAC"
}
@@ -0,0 +1,30 @@
import { Context } from "../contex";
import { currencies } from "../utils";
export function changeHidehoutBuildCosts(context: Context): void
{
const config = context.config.hideoutBuildsChanges;
const hideoutAreas = context.tables.hideout.areas;
for (const area of hideoutAreas) for (const stage of Object.values(area.stages))
{
const nonCurrencyReq = stage.requirements.filter((req) => !currencies.includes(req.templateId))
for (const req of nonCurrencyReq)
{
if (req.count)
{
req.count -= config.threshold;
if (req.count > 0) req.count *= config.factor;
req.count += config.threshold;
if (config.roundDown)
{
req.count = Math.floor(req.count);
}
else
{
req.count = Math.ceil(req.count);
}
}
}
}
}
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeContainer = changeContainer;
function changeContainer(context) {
const changes = context.config.misc.ContanerSizeChanges;
for (const [key, size] of Object.entries(changes)) {
if (context.tables.templates.items[key] == null)
continue;
context.tables.templates.items[key]._props.Grids[0]._props.cellsH = size[0];
context.tables.templates.items[key]._props.Grids[0]._props.cellsV = size[1];
}
}
//# sourceMappingURL=changeContainerSizes.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "changeContainerSizes.js",
"sourceRoot": "",
"sources": [
"changeContainerSizes.ts"
],
"names": [],
"mappings": ";;AAEA,0CAUC;AAVD,SAAgB,eAAe,CAAC,OAAgB;IAE5C,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC;IAExD,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EACjD,CAAC;QACG,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI;YAAE,SAAS;QAC1D,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5E,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAChF,CAAC;AACL,CAAC"
}
@@ -0,0 +1,13 @@
import { Context } from "../contex";
export function changeContainer(context: Context): void
{
const changes = context.config.misc.ContanerSizeChanges;
for (const [key, size] of Object.entries(changes))
{
if (context.tables.templates.items[key] == null) continue;
context.tables.templates.items[key]._props.Grids[0]._props.cellsH = size[0];
context.tables.templates.items[key]._props.Grids[0]._props.cellsV = size[1];
}
}
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeCrafts = changeCrafts;
const ItemTpl_1 = require("C:/snapshot/project/obj/models/enums/ItemTpl");
function changeCrafts(context) {
//Do not affect crafts that output these
const forbiddenCrafts = [
ItemTpl_1.ItemTpl.BARTER_PHYSICAL_BITCOIN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_GEARCRATE_BLUE_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_GEARCRATE_GREEN_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_GEARCRATE_VIOLET_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_JEWELRYCRATE_BLUE_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_JEWELRYCRATE_GREEN_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_JEWELRYCRATE_VIOLET_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_JUNKCRATE_BLUE_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_JUNKCRATE_GREEN_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_JUNKCRATE_VIOLET_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_BLUE_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_GREEN_OPEN,
ItemTpl_1.ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_VIOLET_OPEN,
ItemTpl_1.ItemTpl.DRINK_CANISTER_WITH_PURIFIED_WATER,
ItemTpl_1.ItemTpl.DRINK_BOTTLE_OF_FIERCE_HATCHLING_MOONSHINE
];
const config = context.config.misc;
const crafts = context.tables.hideout.production.recipes;
const nonBtc = crafts.filter((production) => !forbiddenCrafts.includes(production.endProduct));
for (const craft of nonBtc) {
craft.count *= config.craftProductMultiplier;
craft.productionTime *= config.craftTimeMultiplier;
}
}
//# sourceMappingURL=craftChanges.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "craftChanges.js",
"sourceRoot": "",
"sources": [
"craftChanges.ts"
],
"names": [],
"mappings": ";;AAGA,oCA+BC;AAlCD,0EAAuE;AAGvE,SAAgB,YAAY,CAAC,OAAgB;IAGzC,wCAAwC;IACxC,MAAM,eAAe,GAAG;QACpB,iBAAO,CAAC,uBAAuB;QAC/B,iBAAO,CAAC,6CAA6C;QACrD,iBAAO,CAAC,8CAA8C;QACtD,iBAAO,CAAC,+CAA+C;QACvD,iBAAO,CAAC,gDAAgD;QACxD,iBAAO,CAAC,iDAAiD;QACzD,iBAAO,CAAC,kDAAkD;QAC1D,iBAAO,CAAC,6CAA6C;QACrD,iBAAO,CAAC,8CAA8C;QACtD,iBAAO,CAAC,+CAA+C;QACvD,iBAAO,CAAC,+CAA+C;QACvD,iBAAO,CAAC,gDAAgD;QACxD,iBAAO,CAAC,iDAAiD;QACzD,iBAAO,CAAC,kCAAkC;QAC1C,iBAAO,CAAC,0CAA0C;KACzC,CAAC;IAEd,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;IACnC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;IACzD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IAE/F,KAAK,MAAM,KAAK,IAAI,MAAM,EAC1B,CAAC;QACG,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,sBAAsB,CAAC;QAC7C,KAAK,CAAC,cAAc,IAAI,MAAM,CAAC,mBAAmB,CAAC;IACvD,CAAC;AACL,CAAC"
}
@@ -0,0 +1,35 @@
import { ItemTpl } from "@spt/models/enums/ItemTpl";
import { Context } from "../contex";
export function changeCrafts(context: Context): void
{
//Do not affect crafts that output these
const forbiddenCrafts = [
ItemTpl.BARTER_PHYSICAL_BITCOIN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_GEARCRATE_BLUE_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_GEARCRATE_GREEN_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_GEARCRATE_VIOLET_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_JEWELRYCRATE_BLUE_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_JEWELRYCRATE_GREEN_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_JEWELRYCRATE_VIOLET_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_JUNKCRATE_BLUE_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_JUNKCRATE_GREEN_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_JUNKCRATE_VIOLET_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_BLUE_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_GREEN_OPEN,
ItemTpl.RANDOMLOOTCONTAINER_ARENA_WEAPONCRATE_VIOLET_OPEN,
ItemTpl.DRINK_CANISTER_WITH_PURIFIED_WATER,
ItemTpl.DRINK_BOTTLE_OF_FIERCE_HATCHLING_MOONSHINE
] as string[];
const config = context.config.misc;
const crafts = context.tables.hideout.production.recipes;
const nonBtc = crafts.filter((production) => !forbiddenCrafts.includes(production.endProduct));
for (const craft of nonBtc)
{
craft.count *= config.craftProductMultiplier;
craft.productionTime *= config.craftTimeMultiplier;
}
}
@@ -0,0 +1,31 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addCustomTrades = addCustomTrades;
const fs_1 = __importDefault(require("fs"));
// eslint-disable-next-line @typescript-eslint/naming-convention
const json5_1 = __importDefault(require("C:/snapshot/project/node_modules/json5"));
const utils_1 = require("../utils");
function addCustomTrades(context) {
//Read custom trades config
const fileContent = fs_1.default.readFileSync((0, utils_1.getModFolder)() + "/config/advanced/customTrades.json5", "utf-8");
const tradesConfig = json5_1.default.parse(fileContent);
for (const [traderID, additions] of Object.entries(tradesConfig)) {
const trader = context.tables.traders[traderID];
for (const trade of Object.values(additions.items)) {
trader.assort.items.push(trade);
}
for (const [trade, scheme] of Object.entries(additions.barter_scheme)) {
trader.assort.barter_scheme[trade] = scheme;
}
for (const [trade, loyalty] of Object.entries(additions.loyal_level_items)) {
trader.assort.loyal_level_items[trade] = loyalty;
}
for (const [trade, lockProps] of Object.entries(additions.questlocks)) {
(0, utils_1.lockBehindQuest)(context, traderID, trade, lockProps.quest, additions.items[trade]._tpl, lockProps.rewardId, lockProps.targetId);
}
}
}
//# sourceMappingURL=customTrades.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "customTrades.js",
"sourceRoot": "",
"sources": [
"customTrades.ts"
],
"names": [],
"mappings": ";;;;;AAQA,0CA0CC;AAlDD,4CAAoB;AACpB,gEAAgE;AAChE,mFAA2D;AAE3D,oCAAyD;AAIzD,SAAgB,eAAe,CAAC,OAAgB;IAE5C,2BAA2B;IAC3B,MAAM,WAAW,GAAG,YAAE,CAAC,YAAY,CAAC,IAAA,oBAAY,GAAE,GAAG,qCAAqC,EAAE,OAAO,CAAC,CAAC;IACrG,MAAM,YAAY,GAAG,eAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAE9C,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAC5B,MAAM,CAAC,OAAO,CAAC,YAUV,CACJ,EAEL,CAAC;QACG,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAClD,CAAC;YACG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,EACrE,CAAC;YACG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;QAChD,CAAC;QAED,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAC1E,CAAC;YACG,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;QACrD,CAAC;QAED,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,EACrE,CAAC;YACG,IAAA,uBAAe,EAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;QACpI,CAAC;IACL,CAAC;AACL,CAAC"
}
@@ -0,0 +1,51 @@
import fs from "fs";
// eslint-disable-next-line @typescript-eslint/naming-convention
import JSON5 from "json5";
import { Context } from "../contex";
import { getModFolder, lockBehindQuest } from "../utils";
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { IBarterScheme } from "@spt/models/eft/common/tables/ITrader";
export function addCustomTrades(context: Context): void
{
//Read custom trades config
const fileContent = fs.readFileSync(getModFolder() + "/config/advanced/customTrades.json5", "utf-8");
const tradesConfig = JSON5.parse(fileContent);
for (const [traderID, additions] of
Object.entries(tradesConfig as
Record<
string, {
items: Record<string, IItem>,
// eslint-disable-next-line @typescript-eslint/naming-convention
barter_scheme: Record<string, IBarterScheme[][]>,
// eslint-disable-next-line @typescript-eslint/naming-convention
loyal_level_items: Record<string, number>,
questlocks: Record<string, {quest: string, rewardId: string, targetId: string}>
}
>
)
)
{
const trader = context.tables.traders[traderID];
for (const trade of Object.values(additions.items))
{
trader.assort.items.push(trade);
}
for (const [trade, scheme] of Object.entries(additions.barter_scheme))
{
trader.assort.barter_scheme[trade] = scheme;
}
for (const [trade, loyalty] of Object.entries(additions.loyal_level_items))
{
trader.assort.loyal_level_items[trade] = loyalty;
}
for (const [trade, lockProps] of Object.entries(additions.questlocks))
{
lockBehindQuest(context, traderID, trade, lockProps.quest, additions.items[trade]._tpl, lockProps.rewardId, lockProps.targetId);
}
}
}
@@ -0,0 +1,52 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeFirFromQuests = removeFirFromQuests;
exports.removeFirFromFlea = removeFirFromFlea;
exports.removeFirFromHideout = removeFirFromHideout;
exports.removeFirFromRepeatables = removeFirFromRepeatables;
const ConfigTypes_1 = require("C:/snapshot/project/obj/models/enums/ConfigTypes");
function removeFirFromQuests(context) {
const quests = context.tables.templates.quests;
const locales = context.tables.locales.global;
const foundInRaidRegex = new RegExp("Find.*in raid", "i");
const inRaidRegex = new RegExp("in raid", "i");
for (const quest of Object.values(quests)) {
const sets = [quest.conditions.AvailableForFinish, quest.conditions.AvailableForStart, quest.conditions.Fail, quest.conditions.Started, quest.conditions.Success];
for (const set of sets) {
if (set == null)
continue;
//Allow handing over of non-FiR items
for (const cond of set) {
if (cond.conditionType == "HandoverItem" || cond.conditionType === "FindItem") {
cond.onlyFoundInRaid = false;
}
}
}
}
//Change the locales to remove "in raid"
for (const [lang, locale] of Object.entries(locales))
for (const [key, text] of Object.entries(locale)) {
if (foundInRaidRegex.test(text)) {
locales[lang][key] = text.replace(inRaidRegex, "");
}
}
}
function removeFirFromFlea(context) {
context.tables.globals.config.RagFair.isOnlyFoundInRaidAllowed = false;
}
function removeFirFromHideout(context) {
const hideoutAreas = context.tables.hideout.areas;
for (const area of hideoutAreas)
for (const stage of Object.values(area.stages)) {
const itemReq = stage.requirements.filter((req) => req.type == "Item");
for (const req of itemReq) {
req.isSpawnedInSession = false;
}
}
}
function removeFirFromRepeatables(context) {
for (const repType of context.sptConfig.getConfig(ConfigTypes_1.ConfigTypes.QUEST).repeatableQuests) {
repType.questConfig.Completion.requiredItemsAreFiR = false;
}
}
//# sourceMappingURL=firChanges.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "firChanges.js",
"sourceRoot": "",
"sources": [
"firChanges.ts"
],
"names": [],
"mappings": ";;AAQA,kDAkCC;AAED,8CAGC;AAED,oDAeC;AAED,4DAMC;AAnED,kFAA+E;AAG/E,SAAgB,mBAAmB,CAAC,OAAgB;IAEhD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;IAC9C,MAAM,gBAAgB,GAAG,IAAI,MAAM,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAE/C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EACzC,CAAC;QACG,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,kBAAkB,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;QAEjK,KAAK,MAAM,GAAG,IAAI,IAAI,EACtB,CAAC;YACG,IAAI,GAAG,IAAI,IAAI;gBAAE,SAAS;YAE1B,qCAAqC;YACrC,KAAK,MAAM,IAAI,IAAI,GAAG,EACtB,CAAC;gBACG,IAAI,IAAI,CAAC,aAAa,IAAI,cAAc,IAAI,IAAI,CAAC,aAAa,KAAK,UAAU,EAC7E,CAAC;oBACG,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;gBACjC,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;IAED,wCAAwC;IACxC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EACtG,CAAC;YACG,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAC/B,CAAC;gBACG,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACvD,CAAC;QACL,CAAC;AACL,CAAC;AAED,SAAgB,iBAAiB,CAAC,OAAgB;IAE9C,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,wBAAwB,GAAG,KAAK,CAAC;AAC3E,CAAC;AAED,SAAgB,oBAAoB,CAAC,OAAgB;IAGjD,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;IAElD,KAAK,MAAM,IAAI,IAAI,YAAY;QAAE,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAC/E,CAAC;YACG,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC;YAEvE,KAAK,MAAM,GAAG,IAAI,OAA8B,EAChD,CAAC;gBACG,GAAG,CAAC,kBAAkB,GAAG,KAAK,CAAC;YACnC,CAAC;QACL,CAAC;AAEL,CAAC;AAED,SAAgB,wBAAwB,CAAC,OAAgB;IAErD,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,SAAS,CAAe,yBAAW,CAAC,KAAK,CAAC,CAAC,gBAAgB,EACnG,CAAC;QACG,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,mBAAmB,GAAG,KAAK,CAAC;IAC/D,CAAC;AACL,CAAC"
}
@@ -0,0 +1,73 @@
import { IStageRequirement } from "@spt/models/eft/hideout/IHideoutArea";
import { Context } from "../contex";
import { DependencyContainer } from "tsyringe";
import { RepeatableQuestGenerator } from "@spt/generators/RepeatableQuestGenerator";
import { IQuestCondition } from "@spt/models/eft/common/tables/IQuest";
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
import { IQuestConfig } from "@spt/models/spt/config/IQuestConfig";
export function removeFirFromQuests(context: Context): void
{
const quests = context.tables.templates.quests;
const locales = context.tables.locales.global;
const foundInRaidRegex = new RegExp("Find.*in raid", "i");
const inRaidRegex = new RegExp("in raid", "i");
for (const quest of Object.values(quests))
{
const sets = [quest.conditions.AvailableForFinish, quest.conditions.AvailableForStart, quest.conditions.Fail, quest.conditions.Started, quest.conditions.Success]
for (const set of sets)
{
if (set == null) continue;
//Allow handing over of non-FiR items
for (const cond of set)
{
if (cond.conditionType == "HandoverItem" || cond.conditionType === "FindItem")
{
cond.onlyFoundInRaid = false;
}
}
}
}
//Change the locales to remove "in raid"
for (const [lang, locale] of Object.entries(locales)) for (const [key, text] of Object.entries(locale))
{
if (foundInRaidRegex.test(text))
{
locales[lang][key] = text.replace(inRaidRegex, "");
}
}
}
export function removeFirFromFlea(context: Context): void
{
context.tables.globals.config.RagFair.isOnlyFoundInRaidAllowed = false;
}
export function removeFirFromHideout(context: Context): void
{
const hideoutAreas = context.tables.hideout.areas;
for (const area of hideoutAreas) for (const stage of Object.values(area.stages))
{
const itemReq = stage.requirements.filter((req) => req.type == "Item");
for (const req of itemReq as IStageRequirement[])
{
req.isSpawnedInSession = false;
}
}
}
export function removeFirFromRepeatables(context: Context): void
{
for (const repType of context.sptConfig.getConfig<IQuestConfig>(ConfigTypes.QUEST).repeatableQuests)
{
repType.questConfig.Completion.requiredItemsAreFiR = false;
}
}
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.disableFleaMarket = disableFleaMarket;
const BaseClasses_1 = require("C:/snapshot/project/obj/models/enums/BaseClasses");
function disableFleaMarket(context) {
const allItems = context.tables.templates.items;
const config = context.config.fleaMarketChanges;
for (const [id, item] of Object.entries(allItems)) {
let allowed = config.fleaWhitelist.includes(id);
if (config.stillAllowKeys && context.itemHelper.isOfBaseclass(id, BaseClasses_1.BaseClasses.KEY) && item._props.CanSellOnRagfair)
allowed = true;
item._props.CanRequireOnRagfair = allowed && item._props.CanRequireOnRagfair;
item._props.CanSellOnRagfair = allowed;
}
}
//# sourceMappingURL=fleaChanges.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "fleaChanges.js",
"sourceRoot": "",
"sources": [
"fleaChanges.ts"
],
"names": [],
"mappings": ";;AAGA,8CAYC;AAfD,kFAA+E;AAG/E,SAAgB,iBAAiB,CAAC,OAAgB;IAE9C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAEhD,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EACjD,CAAC;QACG,IAAI,OAAO,GAAY,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACzD,IAAI,MAAM,CAAC,cAAc,IAAI,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,EAAE,yBAAW,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB;YAAE,OAAO,GAAG,IAAI,CAAC;QACnI,IAAI,CAAC,MAAM,CAAC,mBAAmB,GAAG,OAAO,IAAI,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC;QAC7E,IAAI,CAAC,MAAM,CAAC,gBAAgB,GAAG,OAAO,CAAC;IAC3C,CAAC;AACL,CAAC"
}
@@ -0,0 +1,16 @@
import { BaseClasses } from "@spt/models/enums/BaseClasses";
import { Context } from "../contex";
export function disableFleaMarket(context: Context): void
{
const allItems = context.tables.templates.items;
const config = context.config.fleaMarketChanges;
for (const [id, item] of Object.entries(allItems))
{
let allowed: boolean = config.fleaWhitelist.includes(id);
if (config.stillAllowKeys && context.itemHelper.isOfBaseclass(id, BaseClasses.KEY) && item._props.CanSellOnRagfair) allowed = true;
item._props.CanRequireOnRagfair = allowed && item._props.CanRequireOnRagfair;
item._props.CanSellOnRagfair = allowed;
}
}
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changePrices = changePrices;
function changePrices(context) {
const config = context.config.misc.priceChanges;
for (const [itemId, change] of Object.entries(config)) {
//Set handbook price
const handbookItem = context.tables.templates.handbook.Items.find((item) => item.Id === itemId);
if (handbookItem == null)
continue;
handbookItem.Price = change;
//Change trader assorts
for (const trader of Object.values(context.tables.traders)) {
if (trader.assort == null)
continue;
const assorts = trader.assort.items.filter((item) => item._tpl == itemId);
for (const assort of assorts) {
const scheme = trader.assort.barter_scheme[assort._id][0][0];
if (scheme == null)
continue;
if (scheme._tpl == "5449016a4bdc2d6f028b456f") {
scheme.count = change;
}
}
}
}
}
//# sourceMappingURL=priceChanging.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "priceChanging.js",
"sourceRoot": "",
"sources": [
"priceChanging.ts"
],
"names": [],
"mappings": ";;AAGA,oCA8BC;AA9BD,SAAgB,YAAY,CAAC,OAAgB;IAGzC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;IAEhD,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAgC,CAAC,EAC/E,CAAC;QACG,oBAAoB;QACpB,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;QAChG,IAAI,YAAY,IAAI,IAAI;YAAE,SAAS;QACnC,YAAY,CAAC,KAAK,GAAG,MAAM,CAAC;QAE5B,uBAAuB;QACvB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAC1D,CAAC;YACG,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI;gBAAE,SAAS;YACpC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC;YAE1E,KAAK,MAAM,MAAM,IAAI,OAAO,EAC5B,CAAC;gBACG,MAAM,MAAM,GAAkB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5E,IAAI,MAAM,IAAI,IAAI;oBAAE,SAAS;gBAC7B,IAAI,MAAM,CAAC,IAAI,IAAI,0BAA0B,EAC7C,CAAC;oBACG,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC;gBAC1B,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;AAEL,CAAC"
}
@@ -0,0 +1,34 @@
import { IBarterScheme } from "@spt/models/eft/common/tables/ITrader";
import { Context } from "../contex";
export function changePrices(context: Context): void
{
const config = context.config.misc.priceChanges;
for (const [itemId, change] of Object.entries(config as Record<string, number>))
{
//Set handbook price
const handbookItem = context.tables.templates.handbook.Items.find((item) => item.Id === itemId);
if (handbookItem == null) continue;
handbookItem.Price = change;
//Change trader assorts
for (const trader of Object.values(context.tables.traders))
{
if (trader.assort == null) continue;
const assorts = trader.assort.items.filter((item) => item._tpl == itemId);
for (const assort of assorts)
{
const scheme: IBarterScheme = trader.assort.barter_scheme[assort._id][0][0];
if (scheme == null) continue;
if (scheme._tpl == "5449016a4bdc2d6f028b456f")
{
scheme.count = change;
}
}
}
}
}
@@ -0,0 +1,24 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addAdditionalQuestRewards = addAdditionalQuestRewards;
const fs_1 = __importDefault(require("fs"));
// eslint-disable-next-line @typescript-eslint/naming-convention
const json5_1 = __importDefault(require("C:/snapshot/project/node_modules/json5"));
const utils_1 = require("../utils");
function addAdditionalQuestRewards(context) {
//Read rewards config
const fileContent = fs_1.default.readFileSync((0, utils_1.getModFolder)() + "/config/advanced/additionalQuestRewards.json5", "utf-8");
const secureConfig = json5_1.default.parse(fileContent);
for (const [condition, questsWithRewards] of Object.entries(secureConfig)) {
for (const [quest, extraReward] of Object.entries(questsWithRewards)) {
if (condition == "Success")
context.tables.templates.quests[quest].rewards.Success.push(extraReward);
if (condition == "Started")
context.tables.templates.quests[quest].rewards.Started.push(extraReward);
}
}
}
//# sourceMappingURL=questRewards..js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "questRewards..js",
"sourceRoot": "",
"sources": [
"questRewards..ts"
],
"names": [],
"mappings": ";;;;;AAOA,8DAgBC;AArBD,4CAAoB;AACpB,gEAAgE;AAChE,mFAA2D;AAC3D,oCAAwC;AAExC,SAAgB,yBAAyB,CAAC,OAAgB;IAGtD,qBAAqB;IACrB,MAAM,WAAW,GAAG,YAAE,CAAC,YAAY,CAAC,IAAA,oBAAY,GAAE,GAAG,+CAA+C,EAAE,OAAO,CAAC,CAAC;IAC/G,MAAM,YAAY,GAAG,eAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAE9C,KAAK,MAAM,CAAC,SAAS,EAAE,iBAAiB,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EACzE,CAAC;QACG,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EACpE,CAAC;YACG,IAAI,SAAS,IAAI,SAAS;gBAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,WAAsB,CAAC,CAAC;YAChH,IAAI,SAAS,IAAI,SAAS;gBAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,WAAsB,CAAC,CAAC;QACpH,CAAC;IACL,CAAC;AAEL,CAAC"
}
@@ -0,0 +1,24 @@
import { IReward } from "@spt/models/eft/common/tables/IReward";
import { Context } from "../contex";
import fs from "fs";
// eslint-disable-next-line @typescript-eslint/naming-convention
import JSON5 from "json5";
import { getModFolder } from "../utils";
export function addAdditionalQuestRewards(context: Context): void
{
//Read rewards config
const fileContent = fs.readFileSync(getModFolder() + "/config/advanced/additionalQuestRewards.json5", "utf-8");
const secureConfig = JSON5.parse(fileContent);
for (const [condition, questsWithRewards] of Object.entries(secureConfig))
{
for (const [quest, extraReward] of Object.entries(questsWithRewards))
{
if (condition == "Success") context.tables.templates.quests[quest].rewards.Success.push(extraReward as IReward);
if (condition == "Started") context.tables.templates.quests[quest].rewards.Started.push(extraReward as IReward);
}
}
}
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addSupportForGPTraders = addSupportForGPTraders;
exports.changeRefPurchasingOptions = changeRefPurchasingOptions;
exports.gainRefRepOnKill = gainRefRepOnKill;
const utils_1 = require("../utils");
const ItemTpl_1 = require("C:/snapshot/project/obj/models/enums/ItemTpl");
function addSupportForGPTraders(context, container) {
container.afterResolution("TraderController", (_t, result) => {
// We want to replace the original method logic with something different
const old = result.getItemPrices.bind(result);
result.getItemPrices = (sessionId, traderId) => {
const oldOut = old(sessionId, traderId); //Add old logic back in
oldOut.currencyCourses["5d235b4d86f7742e017bc88a"] = 7500; //ToDo: configurable?
return oldOut;
};
}, { frequency: "Always" });
}
function changeRefPurchasingOptions(context) {
const ref = context.tables.traders["6617beeaa9cfa777ca915b7c"];
const config = context.config.refChanges;
if (config.refBuysInGPCoins)
ref.base.currency = "GP";
if (config.refOnlyBuysDogtags) {
ref.base.items_buy.category = [];
ref.base.items_buy.id_list = (0, utils_1.getDogtagsList)(context);
}
if (config.refAlsoBuysLegaMedals)
ref.base.items_buy.id_list.push(ItemTpl_1.ItemTpl.BARTER_LEGA_MEDAL);
}
function gainRefRepOnKill(context, container) {
container.afterResolution("LocationLifecycleService", (_t, result) => {
// We want to replace the original method logic with something different
const old = result.handlePostRaidPmc.bind(result);
result.handlePostRaidPmc = (sessionId, fullProfile, scavProfile, isDead, isSurvived, isTransfer, request, locationName) => {
old(sessionId, fullProfile, scavProfile, isDead, isSurvived, isTransfer, request, locationName); //Add old logic back in
let pmcKills = [];
try {
//Filter PMC kills
pmcKills = request.results.profile.Stats.Eft.Victims.filter((victim) => ["pmcbear", "pmcusec"].includes(victim.Role.toLowerCase()));
}
catch (error) {
this.context.logger.error("Error Details:" + "Something went wrong when trying to tally up PMC kills!");
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
try {
fullProfile.characters.pmc.TradersInfo["6617beeaa9cfa777ca915b7c"].standing += repByKills(pmcKills, context);
}
catch (error) {
this.context.logger.error("Error Details:" + "Something went wrong when trying add Ref rep for PMC kills!");
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
};
// The modifier Always makes sure this replacement method is ALWAYS replaced
}, { frequency: "Always" });
}
function repByKills(pmcKills, context) {
let totalRep = 0;
for (const kill of pmcKills)
for (const repRange of context.config.refChanges.refStandingOnKill.repByKillLevel) {
if (kill.Level >= repRange.levelRange[0] && kill.Level < repRange.levelRange[1])
totalRep += repRange.rep;
}
return totalRep;
}
//# sourceMappingURL=refRepRework.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "refRepRework.js",
"sourceRoot": "",
"sources": [
"refRepRework.ts"
],
"names": [],
"mappings": "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"
}
@@ -0,0 +1,96 @@
import { DependencyContainer } from "tsyringe";
import { Context } from "../contex";
import { LocationLifecycleService } from "@spt/services/LocationLifecycleService";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { IEndLocalRaidRequestData } from "@spt/models/eft/match/IEndLocalRaidRequestData";
import { IVictim } from "@spt/models/eft/common/tables/IBotBase";
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
import { TraderController } from "@spt/controllers/TraderController";
import { getDogtagsList } from "../utils";
import { ItemTpl } from "@spt/models/enums/ItemTpl";
export function addSupportForGPTraders(context: Context, container: DependencyContainer): void
{
container.afterResolution("TraderController", (_t, result: TraderController) =>
{
// We want to replace the original method logic with something different
const old = result.getItemPrices.bind(result);
result.getItemPrices = (sessionId: string, traderId: string) =>
{
const oldOut = old(sessionId, traderId); //Add old logic back in
oldOut.currencyCourses["5d235b4d86f7742e017bc88a"] = 7500; //ToDo: configurable?
return oldOut;
};
}, {frequency: "Always"});
}
export function changeRefPurchasingOptions(context: Context): void
{
const ref = context.tables.traders["6617beeaa9cfa777ca915b7c"];
const config = context.config.refChanges;
if (config.refBuysInGPCoins) ref.base.currency = "GP";
if (config.refOnlyBuysDogtags)
{
ref.base.items_buy.category = [];
ref.base.items_buy.id_list = getDogtagsList(context);
}
if (config.refAlsoBuysLegaMedals) ref.base.items_buy.id_list.push(ItemTpl.BARTER_LEGA_MEDAL);
}
export function gainRefRepOnKill(context: Context, container: DependencyContainer): void
{
container.afterResolution("LocationLifecycleService", (_t, result: LocationLifecycleService) =>
{
// We want to replace the original method logic with something different
const old = (result as any).handlePostRaidPmc.bind(result);
(result as any).handlePostRaidPmc = (sessionId: string, fullProfile: ISptProfile, scavProfile: IPmcData, isDead: boolean, isSurvived: boolean, isTransfer: boolean, request: IEndLocalRaidRequestData, locationName: string) =>
{
old(sessionId, fullProfile, scavProfile, isDead, isSurvived, isTransfer, request, locationName); //Add old logic back in
let pmcKills: IVictim[] = [];
try
{
//Filter PMC kills
pmcKills = request.results.profile.Stats.Eft.Victims.filter(
(victim) => ["pmcbear", "pmcusec"].includes(victim.Role.toLowerCase()));
}
catch (error)
{
this.context.logger.error("Error Details:" + "Something went wrong when trying to tally up PMC kills!");
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
try
{
fullProfile.characters.pmc.TradersInfo["6617beeaa9cfa777ca915b7c"].standing += repByKills(pmcKills, context);
}
catch (error)
{
this.context.logger.error("Error Details:" + "Something went wrong when trying add Ref rep for PMC kills!");
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
};
// The modifier Always makes sure this replacement method is ALWAYS replaced
}, {frequency: "Always"});
}
function repByKills(pmcKills: IVictim[], context: Context): number
{
let totalRep: number = 0;
for (const kill of pmcKills) for (const repRange of context.config.refChanges.refStandingOnKill.repByKillLevel)
{
if (kill.Level >= repRange.levelRange[0] && kill.Level < repRange.levelRange[1]) totalRep += repRange.rep;
}
return totalRep;
}
@@ -0,0 +1,64 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.applySecureContainerChanges = applySecureContainerChanges;
const fs_1 = __importDefault(require("fs"));
// eslint-disable-next-line @typescript-eslint/naming-convention
const json5_1 = __importDefault(require("C:/snapshot/project/node_modules/json5"));
const utils_1 = require("../utils");
function applySecureContainerChanges(context) {
//Read secure container config
const fileContent = fs_1.default.readFileSync((0, utils_1.getModFolder)() + "/config/advanced/secureChanges.json5", "utf-8");
const secureConfig = json5_1.default.parse(fileContent);
const config = context.config.secureContainerProgression;
//Change container grids
for (const container of Object.keys(config.sizeChanges)) {
const containerItem = context.tables.templates.items[container];
const gridTemplate = containerItem._props.Grids[0];
const gridSizes = config.sizeChanges[container];
const newGrids = [];
for (let i = 0; i < gridSizes.length; i++) {
const size = gridSizes[i];
newGrids.push({
_name: (i + 1).toString(),
_id: context.hashUtil.generate(),
_parent: "664a55d84a90fc2c8a6305c9",
_props: {
filters: gridTemplate._props.filters,
cellsH: size[0],
cellsV: size[1],
minCount: 0,
maxCount: 0,
maxWeight: 0,
isSortingTable: false
},
_proto: "55d329c24bdc2d892f8b4567"
});
}
containerItem._props.Grids = newGrids;
}
//Add quest rewards
for (const [condition, questsWithRewards] of Object.entries(secureConfig.AdditionalQuestRewards)) {
for (const [quest, extraReward] of Object.entries(questsWithRewards)) {
if (condition == "Success")
context.tables.templates.quests[quest].rewards.Success.push(extraReward);
if (condition == "Started")
context.tables.templates.quests[quest].rewards.Started.push(extraReward);
}
}
//Start with Waist Pouch
const profileTemplates = context.tables.templates.profiles;
for (const profile of Object.values(profileTemplates)) {
const bearContainer = profile.bear.character.Inventory.items.find((x) => x.slotId === "SecuredContainer");
if (bearContainer) {
bearContainer._tpl = context.config.secureContainerProgression.starterContainer;
}
const usecContainer = profile.usec.character.Inventory.items.find((x) => x.slotId === "SecuredContainer");
if (usecContainer) {
usecContainer._tpl = context.config.secureContainerProgression.starterContainer;
}
}
}
//# sourceMappingURL=secureContainer.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "secureContainer.js",
"sourceRoot": "",
"sources": [
"secureContainer.ts"
],
"names": [],
"mappings": "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"
}
@@ -0,0 +1,76 @@
import { Context } from "../contex";
import fs from "fs";
// eslint-disable-next-line @typescript-eslint/naming-convention
import JSON5 from "json5";
import { getModFolder } from "../utils";
import { IReward } from "@spt/models/eft/common/tables/IReward";
export function applySecureContainerChanges(context: Context): void
{
//Read secure container config
const fileContent = fs.readFileSync(getModFolder() + "/config/advanced/secureChanges.json5", "utf-8");
const secureConfig = JSON5.parse(fileContent);
const config = context.config.secureContainerProgression;
//Change container grids
for (const container of Object.keys(config.sizeChanges))
{
const containerItem = context.tables.templates.items[container];
const gridTemplate = containerItem._props.Grids[0];
const gridSizes = config.sizeChanges[container];
const newGrids = [];
for (let i = 0; i < gridSizes.length; i++)
{
const size = gridSizes[i];
newGrids.push(
{
_name: (i+1).toString(),
_id: context.hashUtil.generate(),
_parent: "664a55d84a90fc2c8a6305c9",
_props: {
filters: gridTemplate._props.filters,
cellsH: size[0],
cellsV: size[1],
minCount: 0,
maxCount: 0,
maxWeight: 0,
isSortingTable: false
},
_proto: "55d329c24bdc2d892f8b4567"
}
);
}
containerItem._props.Grids = newGrids;
}
//Add quest rewards
for (const [condition, questsWithRewards] of Object.entries(secureConfig.AdditionalQuestRewards))
{
for (const [quest, extraReward] of Object.entries(questsWithRewards))
{
if (condition == "Success") context.tables.templates.quests[quest].rewards.Success.push(extraReward as IReward);
if (condition == "Started") context.tables.templates.quests[quest].rewards.Started.push(extraReward as IReward);
}
}
//Start with Waist Pouch
const profileTemplates = context.tables.templates.profiles
for (const profile of Object.values(profileTemplates))
{
const bearContainer = profile.bear.character.Inventory.items.find((x) => x.slotId === "SecuredContainer")
if (bearContainer)
{
bearContainer._tpl = context.config.secureContainerProgression.starterContainer;
}
const usecContainer = profile.usec.character.Inventory.items.find((x) => x.slotId === "SecuredContainer")
if (usecContainer)
{
usecContainer._tpl = context.config.secureContainerProgression.starterContainer;
}
}
}
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeSkills = changeSkills;
function changeSkills(context) {
context.tables.globals.config.SkillFreshEffectiveness = context.config.skillChanges.SkillFreshEffectiveness;
context.tables.globals.config.SkillFreshPoints = context.config.skillChanges.SkillFreshPoints;
context.tables.globals.config.SkillMinEffectiveness = context.config.skillChanges.SkillMinEffectiveness;
context.tables.globals.config.SkillPointsBeforeFatigue = context.config.skillChanges.SkillPointsBeforeFatigue;
}
//# sourceMappingURL=skills.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "skills.js",
"sourceRoot": "",
"sources": [
"skills.ts"
],
"names": [],
"mappings": ";;AAEA,oCAMC;AAND,SAAgB,YAAY,CAAC,OAAgB;IAEzC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,uBAAuB,CAAC;IAC5G,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,gBAAgB,CAAC;IAC9F,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,qBAAqB,CAAC;IACxG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,wBAAwB,CAAC;AAClH,CAAC"
}
@@ -0,0 +1,9 @@
import { Context } from "../contex";
export function changeSkills(context: Context): void
{
context.tables.globals.config.SkillFreshEffectiveness = context.config.skillChanges.SkillFreshEffectiveness;
context.tables.globals.config.SkillFreshPoints = context.config.skillChanges.SkillFreshPoints;
context.tables.globals.config.SkillMinEffectiveness = context.config.skillChanges.SkillMinEffectiveness;
context.tables.globals.config.SkillPointsBeforeFatigue = context.config.skillChanges.SkillPointsBeforeFatigue;
}
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeStackSizes = changeStackSizes;
function changeStackSizes(context) {
//Change stack sizes
for (const [item, stackSize] of Object.entries(context.config.misc.stackSizeOverride)) {
context.tables.templates.items[item]._props.StackMaxSize = stackSize;
}
}
//# sourceMappingURL=stackSizeChanges.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "stackSizeChanges.js",
"sourceRoot": "",
"sources": [
"stackSizeChanges.ts"
],
"names": [],
"mappings": ";;AAEA,4CAOC;AAPD,SAAgB,gBAAgB,CAAC,OAAgB;IAE7C,oBAAoB;IACpB,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,iBAA2C,CAAC,EAC/G,CAAC;QACG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,YAAY,GAAG,SAAS,CAAC;IACzE,CAAC;AACL,CAAC"
}
@@ -0,0 +1,10 @@
import { Context } from "../contex";
export function changeStackSizes(context: Context): void
{
//Change stack sizes
for (const [item, stackSize] of Object.entries(context.config.misc.stackSizeOverride as Record<string, number>))
{
context.tables.templates.items[item]._props.StackMaxSize = stackSize;
}
}
@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setStartingReputation = setStartingReputation;
function setStartingReputation(context) {
const config = context.config.overrideInitialStanding;
const profileTemplates = context.tables.templates.profiles;
for (const profile of Object.values(profileTemplates))
for (const template of [profile.bear, profile.usec]) {
template.trader.initialStanding["default"] = config.defaultOverride;
for (const trader of Object.keys(context.tables.traders)) {
template.trader.initialStanding[trader] = config.defaultOverride;
}
for (const [trader, standing] of Object.entries(config.indivudalOverrides)) {
template.trader.initialStanding[trader] = standing;
}
}
}
//# sourceMappingURL=startingRep.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "startingRep.js",
"sourceRoot": "",
"sources": [
"startingRep.ts"
],
"names": [],
"mappings": ";;AAGA,sDAoBC;AApBD,SAAgB,qBAAqB,CAAC,OAAgB;IAElD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAC;IACtD,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC;IAE3D,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC;QAAE,KAAK,MAAM,QAAQ,IAAI,CAAE,OAAyB,CAAC,IAAI,EAAG,OAAyB,CAAC,IAAI,CAAC,EAChJ,CAAC;YACG,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC;YAEpE,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EACxD,CAAC;gBACG,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC;YACrE,CAAC;YAED,KAAK,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,kBAA4C,CAAC,EACpG,CAAC;gBACG,QAAQ,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC;YACvD,CAAC;QAEL,CAAC;AACL,CAAC"
}
@@ -0,0 +1,24 @@
import { IProfileSides } from "@spt/models/eft/common/tables/IProfileTemplate";
import { Context } from "../contex";
export function setStartingReputation(context: Context): void
{
const config = context.config.overrideInitialStanding;
const profileTemplates = context.tables.templates.profiles;
for (const profile of Object.values(profileTemplates)) for (const template of [(profile as IProfileSides).bear, (profile as IProfileSides).usec])
{
template.trader.initialStanding["default"] = config.defaultOverride;
for (const trader of Object.keys(context.tables.traders))
{
template.trader.initialStanding[trader] = config.defaultOverride;
}
for (const [trader, standing] of Object.entries(config.indivudalOverrides as Record<string, number>))
{
template.trader.initialStanding[trader] = standing;
}
}
}
@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.changeStashProgression = changeStashProgression;
const HideoutAreas_1 = require("C:/snapshot/project/obj/models/enums/HideoutAreas");
const ItemTpl_1 = require("C:/snapshot/project/obj/models/enums/ItemTpl");
const utils_1 = require("../utils");
function changeStashProgression(context) {
//Thanks to ODT and his Softcore mod for this part of the code, most of this function is taken from there with appropriate modifications
//Link to the mod: https://hub.sp-tarkov.com/files/file/998-softcore-proper-singleplayer-experience-for-spt/
const config = context.config.stashProgression;
const profs = context.tables.templates.profiles;
const basicStashBonuses = {
id: "64f5b9e5fa34f11b380756c0",
templateId: ItemTpl_1.ItemTpl.STASH_STANDARD_STASH_10X30,
type: "StashSize"
};
const startingStashes = [
ItemTpl_1.ItemTpl.STASH_STANDARD_STASH_10X30,
ItemTpl_1.ItemTpl.STASH_LEFT_BEHIND_STASH_10X40,
ItemTpl_1.ItemTpl.STASH_PREPARE_FOR_ESCAPE_STASH_10X50,
ItemTpl_1.ItemTpl.STASH_EDGE_OF_DARKNESS_STASH_10X68,
ItemTpl_1.ItemTpl.STASH_THE_UNHEARD_EDITION_STASH_10X72
];
for (const [profile, _] of Object.entries(profs)) {
for (const sidekey of Object.keys(_)) {
if (sidekey === "descriptionLocaleKey") {
continue;
}
const side = profs[profile][sidekey];
const hideoutArea = side.character.Hideout?.Areas.find((area) => area.type === HideoutAreas_1.HideoutAreas.STASH);
if (!hideoutArea) {
context.logger.warning(`HideoutOptionsChanger: doProgressiveStash: hideoutArea for profile ${_} not found`);
continue;
}
hideoutArea.level = 1;
const startingStashItems = side.character.Inventory.items.filter((item) => startingStashes.includes(item._tpl));
for (const item of startingStashItems) {
item._tpl = ItemTpl_1.ItemTpl.STASH_STANDARD_STASH_10X30;
}
// Fix for Unheard profiles
side.character.Bonuses = side.character.Bonuses.filter((x) => x.type !== "StashSize");
side.character.Bonuses.push(basicStashBonuses);
}
}
//Change stash sizes
const stashUpdates = {
[ItemTpl_1.ItemTpl.STASH_STANDARD_STASH_10X30]: config.stashSizes[0],
[ItemTpl_1.ItemTpl.STASH_LEFT_BEHIND_STASH_10X40]: config.stashSizes[1],
[ItemTpl_1.ItemTpl.STASH_PREPARE_FOR_ESCAPE_STASH_10X50]: config.stashSizes[2],
[ItemTpl_1.ItemTpl.STASH_EDGE_OF_DARKNESS_STASH_10X68]: config.stashSizes[3],
[ItemTpl_1.ItemTpl.STASH_THE_UNHEARD_EDITION_STASH_10X72]: config.stashSizes[4]
};
for (const [itemTpl, stashSize] of Object.entries(stashUpdates)) {
const stashItem = context.tables.templates.items[itemTpl];
if (stashItem?._props?.Grids?.[0]?._props) {
stashItem._props.Grids[0]._props.cellsV = stashSize;
}
else {
context.logger.warning(`HideoutOptions: doBiggerStash: Failed to modify stash with Tpl ${itemTpl}, skipping`);
}
}
//Change construction price
const hideoutStashStages = context.tables.hideout.areas.find((area) => area.type === HideoutAreas_1.HideoutAreas.STASH).stages;
for (const stage of Object.values(hideoutStashStages)) {
const currencyRequirements = stage.requirements.filter((req) => utils_1.currencies.includes(req.templateId));
for (const currencyRequirement of currencyRequirements) {
if (currencyRequirement.count) {
currencyRequirement.count *= config.stashUpgradeCostFactor;
}
}
const loyaltylevels = stage.requirements.filter((req) => req.loyaltyLevel !== undefined);
for (const loyaltyLevel of loyaltylevels) {
if (loyaltyLevel.loyaltyLevel !== undefined) {
loyaltyLevel.loyaltyLevel += config.stashUpgradeLoyaltyDelta;
}
}
}
}
//# sourceMappingURL=stashChanges.js.map
@@ -0,0 +1,10 @@
{
"version": 3,
"file": "stashChanges.js",
"sourceRoot": "",
"sources": [
"stashChanges.ts"
],
"names": [],
"mappings": "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"
}
@@ -0,0 +1,104 @@
import { HideoutAreas } from "@spt/models/enums/HideoutAreas";
import { Context } from "../contex";
import { IProfileSides } from "@spt/models/eft/common/tables/IProfileTemplate";
import { ItemTpl } from "@spt/models/enums/ItemTpl";
import { Money } from "@spt/models/enums/Money";
import { currencies } from "../utils";
export function changeStashProgression(context: Context): void
{
//Thanks to ODT and his Softcore mod for this part of the code, most of this function is taken from there with appropriate modifications
//Link to the mod: https://hub.sp-tarkov.com/files/file/998-softcore-proper-singleplayer-experience-for-spt/
const config = context.config.stashProgression;
const profs = context.tables.templates.profiles;
const basicStashBonuses = {
id: "64f5b9e5fa34f11b380756c0",
templateId: ItemTpl.STASH_STANDARD_STASH_10X30,
type: "StashSize"
}
const startingStashes = [
ItemTpl.STASH_STANDARD_STASH_10X30,
ItemTpl.STASH_LEFT_BEHIND_STASH_10X40,
ItemTpl.STASH_PREPARE_FOR_ESCAPE_STASH_10X50,
ItemTpl.STASH_EDGE_OF_DARKNESS_STASH_10X68,
ItemTpl.STASH_THE_UNHEARD_EDITION_STASH_10X72
]
for (const [profile, _] of Object.entries(profs))
{
for (const sidekey of Object.keys(_) as (keyof IProfileSides)[])
{
if (sidekey === "descriptionLocaleKey")
{
continue
}
const side = profs[profile][sidekey]
const hideoutArea = side.character.Hideout?.Areas.find((area) => area.type === HideoutAreas.STASH)
if (!hideoutArea)
{
context.logger.warning(`HideoutOptionsChanger: doProgressiveStash: hideoutArea for profile ${_} not found`)
continue
}
hideoutArea.level = 1
const startingStashItems = side.character.Inventory.items.filter((item) => startingStashes.includes(item._tpl))
for (const item of startingStashItems)
{
item._tpl = ItemTpl.STASH_STANDARD_STASH_10X30
}
// Fix for Unheard profiles
side.character.Bonuses = side.character.Bonuses.filter((x) => x.type !== "StashSize")
side.character.Bonuses.push(basicStashBonuses)
}
}
//Change stash sizes
const stashUpdates: Record<string, number> = {
[ItemTpl.STASH_STANDARD_STASH_10X30]: config.stashSizes[0],
[ItemTpl.STASH_LEFT_BEHIND_STASH_10X40]: config.stashSizes[1],
[ItemTpl.STASH_PREPARE_FOR_ESCAPE_STASH_10X50]: config.stashSizes[2],
[ItemTpl.STASH_EDGE_OF_DARKNESS_STASH_10X68]: config.stashSizes[3],
[ItemTpl.STASH_THE_UNHEARD_EDITION_STASH_10X72]: config.stashSizes[4]
}
for (const [itemTpl, stashSize] of Object.entries(stashUpdates))
{
const stashItem = context.tables.templates.items![itemTpl]
if (stashItem?._props?.Grids?.[0]?._props)
{
stashItem._props.Grids[0]._props.cellsV = stashSize;
}
else
{
context.logger.warning(`HideoutOptions: doBiggerStash: Failed to modify stash with Tpl ${itemTpl}, skipping`);
}
}
//Change construction price
const hideoutStashStages = context.tables.hideout.areas.find((area) => area.type === HideoutAreas.STASH).stages;
for (const stage of Object.values(hideoutStashStages))
{
const currencyRequirements = stage.requirements.filter((req) => currencies.includes(req.templateId))
for (const currencyRequirement of currencyRequirements)
{
if (currencyRequirement.count)
{
currencyRequirement.count *= config.stashUpgradeCostFactor;
}
}
const loyaltylevels = stage.requirements.filter((req) => req.loyaltyLevel !== undefined)
for (const loyaltyLevel of loyaltylevels)
{
if (loyaltyLevel.loyaltyLevel !== undefined)
{
loyaltyLevel.loyaltyLevel += config.stashUpgradeLoyaltyDelta;
}
}
}
}
@@ -0,0 +1,197 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mod = void 0;
const fs_1 = __importDefault(require("fs"));
// eslint-disable-next-line @typescript-eslint/naming-convention
const json5_1 = __importDefault(require("C:/snapshot/project/node_modules/json5"));
const contex_1 = require("./contex");
const core_1 = require("./algoRebalancing/core");
const secureContainer_1 = require("./miscChanges/secureContainer");
const utils_1 = require("./utils");
const stashChanges_1 = require("./miscChanges/stashChanges");
const fleaChanges_1 = require("./miscChanges/fleaChanges");
const buildChanges_1 = require("./miscChanges/buildChanges");
const skills_1 = require("./miscChanges/skills");
const craftChanges_1 = require("./miscChanges/craftChanges");
const refRepRework_1 = require("./miscChanges/refRepRework");
const buffSICCCase_1 = require("./miscChanges/buffSICCCase");
const firChanges_1 = require("./miscChanges/firChanges");
const customTrades_1 = require("./miscChanges/customTrades");
const bitcoinChanges_1 = require("./miscChanges/bitcoinChanges");
const priceChanging_1 = require("./miscChanges/priceChanging");
const loggerWrapper_1 = require("./loggerWrapper");
const stackSizeChanges_1 = require("./miscChanges/stackSizeChanges");
const startingRep_1 = require("./miscChanges/startingRep");
const path_1 = require("path");
const questRewards_1 = require("./miscChanges/questRewards.");
class Mod {
context;
preSptLoad(container) {
this.context = new contex_1.Context();
const preLoader = container.resolve("PreSptModLoader");
this.context.logger = new loggerWrapper_1.LoggerWrapper(container.resolve("WinstonLogger"), preLoader);
this.safelyReadConfig();
this.setupRouterForClientSideData(container);
this.safeApplyEarlyModifications(container);
}
postDBLoad(container) {
this.context.database = container.resolve("DatabaseServer");
this.context.sptConfig = container.resolve("ConfigServer");
this.context.tables = this.context.database.getTables();
this.context.itemHelper = container.resolve("ItemHelper");
container.resolve("PresetController").initialize();
this.context.presetHelper = container.resolve("PresetHelper");
this.context.hashUtil = container.resolve("HashUtil");
this.safelyApplyChanges();
this.context.logger.success("Finished applying all changes!");
}
///////////////////////////////////////////////////////////////////////////
setupRouterForClientSideData(container) {
const staticRouter = container.resolve("StaticRouterModService");
try {
staticRouter.registerStaticRouter("skillpointsconfigrouter", [
{
url: "/server-config-router/skillpoints",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
action: (_url, _info, _sessionID, _output) => {
return Promise.resolve(JSON.stringify({ response: this.context.config.skillChanges.SkillPointsSystem }));
}
}
], "skillpointsconfig");
}
catch (error) {
this.context.logger.error("Failed to send skill points config data over to the client!");
if (this.context.config.dev.showFullError) {
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
try {
staticRouter.registerStaticRouter("skillsconfigrouter", [
{
url: "/server-config-router/skillsconfig",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
action: (_url, _info, _sessionID, _output) => {
return Promise.resolve(JSON.stringify({ response: this.context.config.skillChanges.CustomMultipliers }));
}
}
], "skillsconfig");
}
catch (error) {
this.context.logger.error("Failed to send skills config data over to the client!");
if (this.context.config.dev.showFullError) {
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
}
safeApplyEarlyModifications(container) {
try {
if (this.context.config.refChanges.enable && this.context.config.refChanges.refStandingOnKill.enable)
(0, refRepRework_1.gainRefRepOnKill)(this.context, container);
}
catch (error) {
this.context.logger.error("Failed to inject ref rep on PMC kill function!");
if (this.context.config.dev.showFullError) {
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
//For ref buying items for GP coins. Requires client patching for fixing wrong GP icon
try {
(0, refRepRework_1.addSupportForGPTraders)(this.context, container);
}
catch (error) {
this.context.logger.error("Failed to patch in support for GP trading!");
if (this.context.config.dev.showFullError) {
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
}
safelyReadConfig() {
try {
const fileContent = fs_1.default.readFileSync((0, path_1.join)((0, utils_1.getModFolder)(), "/config/config.json5"), "utf-8");
this.context.config = json5_1.default.parse(fileContent);
}
catch (error) {
this.context.logger.error("Main config file failed to load!");
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
safelyApplyChanges() {
const cfg = this.context.config;
const log = cfg.dev.muteProgressOnServerLoad ? null : this.context.logger;
log?.info("Running algorithmical rebalancing...");
this.safelyRunIf(cfg.algorithmicalRebalancing.enable, () => (0, core_1.algorithmicallyRebalance)(this.context), "Failed to run algorithmical rebalancing!");
log?.info("Done!");
log?.info("Changing stack sizes...");
this.safelyRunIf(true, () => (0, stackSizeChanges_1.changeStackSizes)(this.context), "Failed to apply changes to stack sizes!");
log?.info("Done!");
log?.info("Applying secure container changes...");
this.safelyRunIf(cfg.secureContainerProgression.enable, () => (0, secureContainer_1.applySecureContainerChanges)(this.context), "Failed to apply secure container changes!");
log?.info("Done!");
log?.info("Applying stash progression changes...");
this.safelyRunIf(cfg.stashProgression.enable, () => (0, stashChanges_1.changeStashProgression)(this.context), "Failed to apply stash progression changes!");
log?.info("Done!");
log?.info("Disabling flea market...");
this.safelyRunIf(cfg.fleaMarketChanges.disableFleaMarket, () => (0, fleaChanges_1.disableFleaMarket)(this.context), "Failed to disable flea market!");
log?.info("Done!");
log?.info("Applying changes to hideout build costs...");
this.safelyRunIf(cfg.hideoutBuildsChanges.enable, () => (0, buildChanges_1.changeHidehoutBuildCosts)(this.context), "Failed to apply changes to hideout build costs!");
log?.info("Done!");
log?.info("Applying changes to skills...");
this.safelyRunIf(cfg.skillChanges.enable, () => (0, skills_1.changeSkills)(this.context), "Failed to apply changes to skills!");
log?.info("Done!");
log?.info("Applying changes to craft times and output counts...");
this.safelyRunIf(true, () => (0, craftChanges_1.changeCrafts)(this.context), "Failed to apply changes to craft times and output counts!");
log?.info("Done!");
log?.info("Applying changes to item prices...");
this.safelyRunIf(true, () => (0, priceChanging_1.changePrices)(this.context), "Failed to apply changes to item prices!");
log?.info("Done!");
log?.info("Applying changes to SICC container...");
this.safelyRunIf(cfg.SICCBuffs.enable, () => (0, buffSICCCase_1.buffSICCCase)(this.context), "Failed to apply changes to SICC container!");
log?.info("Done!");
log?.info("Removing FiR requirements...");
this.safelyRunIf(cfg.misc.removeFirFromQuests, () => (0, firChanges_1.removeFirFromQuests)(this.context), "Failed to remove FiR requirements form quests!");
this.safelyRunIf(cfg.misc.removeFirFromQuests, () => (0, firChanges_1.removeFirFromRepeatables)(this.context), "Failed to remove FiR requirements from repeatable quests!");
this.safelyRunIf(cfg.misc.removeFirFromHideout, () => (0, firChanges_1.removeFirFromHideout)(this.context), "Failed to remove FiR requirements from hideout builds!");
this.safelyRunIf(cfg.misc.removeFirFromFlea, () => (0, firChanges_1.removeFirFromFlea)(this.context), "Failed to remove FiR requirements from flea market listings!");
log?.info("Done!");
log?.info("Adding custom trades...");
this.safelyRunIf(cfg.misc.addCustomTrades, () => (0, customTrades_1.addCustomTrades)(this.context), "Failed to add custom trades!");
log?.info("Done!");
log?.info("Applying changes to bitcoin farming...");
this.safelyRunIf(cfg.bitcoinChanges.enable, () => (0, bitcoinChanges_1.changeBitcoinFarming)(this.context), "Failed to apply changes to bitcoin farming!");
log?.info("Done!");
log?.info("Setting initial trader standing...");
this.safelyRunIf(cfg.bitcoinChanges.enable, () => (0, startingRep_1.setStartingReputation)(this.context), "Failed to set initial trader standing!");
log?.info("Done!");
log?.info("Applying changes to Ref item purchasing...");
this.safelyRunIf(cfg.refChanges.enable, () => (0, refRepRework_1.changeRefPurchasingOptions)(this.context), "Failed to apply changes to Ref item purchasing!");
log?.info("Done!");
log?.info("Adding additional quest rewards...");
this.safelyRunIf(cfg.misc.enableExtraQuestRewards, () => (0, questRewards_1.addAdditionalQuestRewards)(this.context), "Failed to add additional quest rewards!");
log?.info("Done!");
}
safelyRunIf(condition, func, message) {
try {
if (condition) {
func();
}
}
catch (error) {
this.context.logger.error(message);
if (this.context.config.dev.showFullError) {
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
}
}
exports.mod = new Mod();
//# sourceMappingURL=mod.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,274 @@
import fs from "fs";
// eslint-disable-next-line @typescript-eslint/naming-convention
import JSON5 from "json5";
import { DependencyContainer } from "tsyringe";
import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { Context } from "./contex";
import { algorithmicallyRebalance } from "./algoRebalancing/core";
import { applySecureContainerChanges } from "./miscChanges/secureContainer";
import { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
import { getModFolder } from "./utils";
import { changeStashProgression } from "./miscChanges/stashChanges";
import { disableFleaMarket } from "./miscChanges/fleaChanges";
import { changeHidehoutBuildCosts } from "./miscChanges/buildChanges";
import { changeSkills } from "./miscChanges/skills";
import { ItemHelper } from "@spt/helpers/ItemHelper";
import { changeCrafts } from "./miscChanges/craftChanges";
import { addSupportForGPTraders, gainRefRepOnKill, changeRefPurchasingOptions } from "./miscChanges/refRepRework";
import { buffSICCCase } from "./miscChanges/buffSICCCase";
import { removeFirFromFlea, removeFirFromHideout, removeFirFromQuests, removeFirFromRepeatables } from "./miscChanges/firChanges";
import { addCustomTrades } from "./miscChanges/customTrades";
import { changeBitcoinFarming } from "./miscChanges/bitcoinChanges";
import { changePrices } from "./miscChanges/priceChanging";
import { LoggerWrapper } from "./loggerWrapper";
import { changeStackSizes } from "./miscChanges/stackSizeChanges";
import { PresetHelper } from "@spt/helpers/PresetHelper";
import { PresetController } from "@spt/controllers/PresetController";
import { setStartingReputation } from "./miscChanges/startingRep";
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
import { join } from "path";
import { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
import { addAdditionalQuestRewards } from "./miscChanges/questRewards.";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { HashUtil } from "@spt/utils/HashUtil";
class Mod implements IPostDBLoadMod, IPreSptLoadMod
{
private context: Context;
preSptLoad(container: DependencyContainer): void
{
this.context = new Context();
const preLoader = container.resolve<PreSptModLoader>("PreSptModLoader");
this.context.logger = new LoggerWrapper(container.resolve<ILogger>("WinstonLogger"), preLoader);
this.safelyReadConfig();
this.setupRouterForClientSideData(container);
this.safeApplyEarlyModifications(container);
}
postDBLoad(container: DependencyContainer): void
{
this.context.database = container.resolve<DatabaseServer>("DatabaseServer");
this.context.sptConfig = container.resolve<ConfigServer>("ConfigServer");
this.context.tables = this.context.database.getTables();
this.context.itemHelper = container.resolve<ItemHelper>("ItemHelper");
container.resolve<PresetController>("PresetController").initialize();
this.context.presetHelper = container.resolve<PresetHelper>("PresetHelper");
this.context.hashUtil = container.resolve<HashUtil>("HashUtil");
this.safelyApplyChanges();
this.context.logger.success("Finished applying all changes!");
}
///////////////////////////////////////////////////////////////////////////
setupRouterForClientSideData(container: DependencyContainer): void
{
const staticRouter = container.resolve<StaticRouterModService>("StaticRouterModService");
try
{
staticRouter.registerStaticRouter(
"skillpointsconfigrouter",
[
{
url: "/server-config-router/skillpoints",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
action: (_url: string, _info: any, _sessionID: string, _output: string): Promise<string> =>
{
return Promise.resolve(JSON.stringify({response: this.context.config.skillChanges.SkillPointsSystem}));
}
}
],
"skillpointsconfig"
)
}
catch (error)
{
this.context.logger.error("Failed to send skill points config data over to the client!");
if (this.context.config.dev.showFullError)
{
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
try
{
staticRouter.registerStaticRouter(
"skillsconfigrouter",
[
{
url: "/server-config-router/skillsconfig",
// eslint-disable-next-line @typescript-eslint/no-unused-vars
action: (_url: string, _info: any, _sessionID: string, _output: string): Promise<string> =>
{
return Promise.resolve(JSON.stringify({response: this.context.config.skillChanges.CustomMultipliers}));
}
}
],
"skillsconfig"
)
}
catch (error)
{
this.context.logger.error("Failed to send skills config data over to the client!");
if (this.context.config.dev.showFullError)
{
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
}
safeApplyEarlyModifications(container: DependencyContainer): void
{
try
{
if (this.context.config.refChanges.enable && this.context.config.refChanges.refStandingOnKill.enable) gainRefRepOnKill(this.context, container);
}
catch (error)
{
this.context.logger.error("Failed to inject ref rep on PMC kill function!");
if (this.context.config.dev.showFullError)
{
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
//For ref buying items for GP coins. Requires client patching for fixing wrong GP icon
try
{
addSupportForGPTraders(this.context, container);
}
catch (error)
{
this.context.logger.error("Failed to patch in support for GP trading!");
if (this.context.config.dev.showFullError)
{
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
}
safelyReadConfig(): void
{
try
{
const fileContent = fs.readFileSync(join(getModFolder(),"/config/config.json5"), "utf-8");
this.context.config = JSON5.parse(fileContent);
}
catch (error)
{
this.context.logger.error("Main config file failed to load!")
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
safelyApplyChanges(): void
{
const cfg = this.context.config;
const log = cfg.dev.muteProgressOnServerLoad ? null : this.context.logger;
log?.info("Running algorithmical rebalancing...");
this.safelyRunIf(cfg.algorithmicalRebalancing.enable, () => algorithmicallyRebalance(this.context), "Failed to run algorithmical rebalancing!");
log?.info("Done!");
log?.info("Changing stack sizes...");
this.safelyRunIf(true, () => changeStackSizes(this.context), "Failed to apply changes to stack sizes!");
log?.info("Done!");
log?.info("Applying secure container changes...");
this.safelyRunIf(cfg.secureContainerProgression.enable, () => applySecureContainerChanges(this.context), "Failed to apply secure container changes!");
log?.info("Done!");
log?.info("Applying stash progression changes...");
this.safelyRunIf(cfg.stashProgression.enable, () => changeStashProgression(this.context), "Failed to apply stash progression changes!");
log?.info("Done!");
log?.info("Disabling flea market...");
this.safelyRunIf(cfg.fleaMarketChanges.disableFleaMarket, () => disableFleaMarket(this.context), "Failed to disable flea market!");
log?.info("Done!");
log?.info("Applying changes to hideout build costs...");
this.safelyRunIf(cfg.hideoutBuildsChanges.enable, () => changeHidehoutBuildCosts(this.context), "Failed to apply changes to hideout build costs!");
log?.info("Done!");
log?.info("Applying changes to skills...");
this.safelyRunIf(cfg.skillChanges.enable, () => changeSkills(this.context), "Failed to apply changes to skills!");
log?.info("Done!");
log?.info("Applying changes to craft times and output counts...");
this.safelyRunIf(true, () => changeCrafts(this.context), "Failed to apply changes to craft times and output counts!");
log?.info("Done!");
log?.info("Applying changes to item prices...");
this.safelyRunIf(true, () => changePrices(this.context), "Failed to apply changes to item prices!");
log?.info("Done!");
log?.info("Applying changes to SICC container...");
this.safelyRunIf(cfg.SICCBuffs.enable, () => buffSICCCase(this.context), "Failed to apply changes to SICC container!");
log?.info("Done!");
log?.info("Removing FiR requirements...");
this.safelyRunIf(cfg.misc.removeFirFromQuests, () => removeFirFromQuests(this.context), "Failed to remove FiR requirements form quests!");
this.safelyRunIf(cfg.misc.removeFirFromQuests, () => removeFirFromRepeatables(this.context), "Failed to remove FiR requirements from repeatable quests!")
this.safelyRunIf(cfg.misc.removeFirFromHideout, () => removeFirFromHideout(this.context), "Failed to remove FiR requirements from hideout builds!");
this.safelyRunIf(cfg.misc.removeFirFromFlea, () => removeFirFromFlea(this.context), "Failed to remove FiR requirements from flea market listings!");
log?.info("Done!");
log?.info("Adding custom trades...");
this.safelyRunIf(cfg.misc.addCustomTrades, () => addCustomTrades(this.context), "Failed to add custom trades!");
log?.info("Done!");
log?.info("Applying changes to bitcoin farming...");
this.safelyRunIf(cfg.bitcoinChanges.enable, () => changeBitcoinFarming(this.context), "Failed to apply changes to bitcoin farming!");
log?.info("Done!");
log?.info("Setting initial trader standing...");
this.safelyRunIf(cfg.bitcoinChanges.enable, () => setStartingReputation(this.context), "Failed to set initial trader standing!");
log?.info("Done!");
log?.info("Applying changes to Ref item purchasing...");
this.safelyRunIf(cfg.refChanges.enable, () => changeRefPurchasingOptions(this.context), "Failed to apply changes to Ref item purchasing!");
log?.info("Done!");
log?.info("Adding additional quest rewards...");
this.safelyRunIf(cfg.misc.enableExtraQuestRewards, () => addAdditionalQuestRewards(this.context), "Failed to add additional quest rewards!");
log?.info("Done!");
}
safelyRunIf(condition: boolean, func: () => void, message: string): void
{
try
{
if (condition)
{
func();
}
}
catch (error)
{
this.context.logger.error(message);
if (this.context.config.dev.showFullError)
{
this.context.logger.error("Error Details:" + error);
this.context.logger.error("Stack Trace:\n" + (error instanceof Error ? error.stack : "No stack available"));
}
}
}
}
export const mod = new Mod();
@@ -0,0 +1,288 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.currencies = void 0;
exports.getDogtagsList = getDogtagsList;
exports.findTrades = findTrades;
exports.getDefaultAttachments = getDefaultAttachments;
exports.unrollAttachments = unrollAttachments;
exports.containsAttachment = containsAttachment;
exports.indexById = indexById;
exports.canBePurchased = canBePurchased;
exports.canAllAttachmentsBePurchased = canAllAttachmentsBePurchased;
exports.lockBehindQuest = lockBehindQuest;
exports.setAreaLevelRequirement = setAreaLevelRequirement;
exports.isQuestLockedCraft = isQuestLockedCraft;
exports.addToLocale = addToLocale;
exports.shareSameNiche = shareSameNiche;
exports.loyaltyFromScore = loyaltyFromScore;
exports.setLoyalty = setLoyalty;
exports.isBarterTrade = isBarterTrade;
exports.isQuestLocked = isQuestLocked;
exports.getModFolder = getModFolder;
exports.bestFiremode = bestFiremode;
exports.pickBestFiremode = pickBestFiremode;
const RewardType_1 = require("C:/snapshot/project/obj/models/enums/RewardType");
const path_1 = require("path");
exports.currencies = [
"5449016a4bdc2d6f028b456f", //Roubles
"5696686a4bdc2da3298b456a", //Dollars
"569668774bdc2da2298b4568", //Euroes
"5d235b4d86f7742e017bc88a", //GP Coin
"6656560053eaaa7a23349c86" //Lega medal
];
const purchasability = {};
function getDogtagsList(context) {
const list = [];
for (const [id, item] of Object.entries(context.tables.templates.items)) {
if (item._props.DogTagQualities)
list.push(id);
}
return list;
}
//Find and return the trader-trade pair that sell the given item
function findTrades(itemId, context) {
const found = [];
for (const trader of Object.values(context.tables.traders)) {
if (trader.assort == null)
continue;
for (const trade of trader.assort.items) {
if (trade._tpl == itemId)
found.push({
trader: trader,
trade: trade
});
}
}
return found;
}
function getDefaultAttachments(weaponID, context) {
const preset = context.presetHelper.getDefaultWeaponPresets[weaponID];
if (preset == null || preset == undefined)
return [];
return preset._items.map((item) => item._tpl);
}
//Returns the list of all attachments connected to the given item (recursively)
function unrollAttachments(item, assort) {
const attachments = [];
const newAttachments = assort.filter((candidate) => candidate.parentId == item._id && candidate._tpl != item._tpl);
attachments.push(...newAttachments);
for (const att of newAttachments) {
attachments.push(...unrollAttachments(att, assort));
}
return attachments;
}
//Does the given item from the given assort contain the given attachment? (as an item table id)
function containsAttachment(item, assort, attachmentID, context) {
//Shortcut items without slots
const template = context.tables.templates.items[item._tpl];
if (template == null) {
context.logger.warning(`Trader item ${item} with table ID ${item._tpl} couldn't be found in the tables!\nMaybe some mod is adding a trade that sells a non-existing item?`);
//Skip the shortcut if the template cannot be found
}
else {
if (template._props.Slots == null)
return false;
if (template._props.Slots.length == 0)
return false;
}
return unrollAttachments(item, assort).map((item) => item._tpl).includes(attachmentID);
}
function indexById(byTier) {
const byId = {};
for (const items of Object.values(byTier))
for (const item of items) {
byId[item.trade._id] = item;
}
return byId;
}
//Can the given item be purchased before or at the given loyalty tier cutoff? Can optionally exclude barters
//Trade IDs in skip will not be considered as valid purchases (mostly to avoid self-referencing when used in canAllAttachmentsBePurchased)
//If an item is present in tierOverrides the corresponding loyalty level will be used instead of that of the trader
//Careful about the dynamic programming part with purchasability. If this function previously found an item to be purchasable it will say it is again without checking even if things changed
function canBePurchased(itemID, excludeBarters, excludeQuestlocks, tierCutoff, skip, tierOverrides, context) {
if (purchasability[itemID] <= tierCutoff)
return true;
for (const trader of Object.values(context.tables.traders)) {
if (trader.assort == null)
continue;
for (const trade of trader.assort.items) {
const loyalty = tierOverrides[trade._id] != null ? tierOverrides[trade._id].score : trader.assort.loyal_level_items[trade._id];
if (loyaltyFromScore(loyalty, context.config.algorithmicalRebalancing.clampToMaxLevel) > tierCutoff)
continue;
if ((trade._tpl === itemID && trader.assort.barter_scheme[trade._id] != null)
|| containsAttachment(trade, trader.assort.items, itemID, context)) //It's a match!
{
if (excludeBarters && isBarterTrade(trade, trader))
continue;
if (excludeQuestlocks && isQuestLocked(trade, trader))
continue;
if (skip.includes(trade._id))
continue;
if (itemID in purchasability) {
purchasability[itemID] = Math.min(purchasability[itemID], loyalty);
}
else {
purchasability[itemID] = loyalty;
}
return true;
}
}
}
return false;
}
//Can all the attachments on the given item be purchased before the given loyalty tier cutoff? Can optionally exclude barters
//Attachments in the skip list will not be checked
function canAllAttachmentsBePurchased(item, assort, excludeBarters, excludeQuestlocks, tierCutoff, skip, tierOverrides, context) {
const attachments = unrollAttachments(item, assort);
for (const att of attachments) {
if (skip.includes(att._tpl))
continue;
if (!canBePurchased(att._tpl, excludeBarters, excludeQuestlocks, tierCutoff, [item._id], tierOverrides, context)) {
//context.logger.info(`Unpurchasable attachment: ${context.tables.templates.items[att._tpl]._name}`)
return false;
}
}
//context.logger.success(`Did not penalize ${context.tables.templates.items[item._tpl]._name}`);
return true;
}
function lockBehindQuest(context, traderID, trade, lock, itemID, rewardID, targetID) {
const trader = context.tables.traders[traderID];
trader.questassort.success[trade] = lock;
const questRewards = context.tables.templates.quests[lock].rewards.Success;
questRewards.push({
type: RewardType_1.RewardType.ASSORTMENT_UNLOCK,
index: questRewards.length,
unknown: false,
traderId: traderID,
target: targetID,
items: [
{
_id: targetID,
_tpl: itemID
}
],
id: rewardID,
availableInGameEditions: []
});
}
function setAreaLevelRequirement(craft, level) {
for (const req of craft.requirements) {
if (req.requiredLevel != null)
req.requiredLevel = level;
}
}
function isQuestLockedCraft(craft) {
for (const req of craft.requirements) {
if (req.questId != null)
return true;
}
return false;
}
function addToLocale(locales, id, name, shortname, description) {
for (const locale of Object.values(locales)) {
locale[`${id} Name`] = name;
locale[`${id} ShortName`] = shortname;
locale[`${id} Description`] = description;
}
}
function shareSameNiche(a, b, aTrader, bTrader, context) {
const c = context.config.algorithmicalRebalancing.weaponRules.upshiftRules;
const aTempl = context.tables.templates.items[a._tpl];
const bTempl = context.tables.templates.items[b._tpl];
if (c.devideNicheByFiremode) {
if (bestFiremode(aTempl) != bestFiremode(bTempl))
return false;
}
if (c.devideNicheByCaliber) {
if (aTempl._props.ammoCaliber != bTempl._props.ammoCaliber)
return false;
}
if (c.devideNicheByBarterType) {
if (isBarterTrade(a, aTrader) != isBarterTrade(b, bTrader))
return false;
}
if (c.devideNicheByQuestLock) {
if (isQuestLocked(a, aTrader) != isQuestLocked(b, bTrader))
return false;
}
return true;
}
function loyaltyFromScore(score, capToMax) {
const maxLevel = capToMax ? 4 : 999; //Set max level to a dummy value if config states that loyalty > 4 is to be hidden from all trader levels
return Math.max(1, Math.min(maxLevel, Math.floor(score))); //Floor and clamp between 1 and the max level
}
//Sets the loyalty level of the given sale item on the given trader
//If capToMax is false the loyalty level will be allowed to go beyond 4
function setLoyalty(itemID, loyalty, trader, capToMax) {
if (loyalty == null)
loyalty = 0;
trader.assort.loyal_level_items[itemID] = loyaltyFromScore(loyalty, capToMax);
}
//Checks if the given trade item is offered for barter on the given trader
//Anything that can't be purchased for RUB, EUR, USD, GP or Lega Medals is considered a barter
function isBarterTrade(trade, trader) {
const scheme = trader.assort.barter_scheme[trade._id];
if (scheme == null)
return false;
for (const ask of scheme)
for (const ask1 of ask) //Cursed, i know
{
if (!exports.currencies.includes(ask1._tpl))
return true;
}
return false;
}
//Checks if the given trade item is quest locked on the given trader
function isQuestLocked(trade, trader) {
const questLocks = [
...Object.keys(trader.questassort["success"]),
...Object.keys(trader.questassort["started"]),
...Object.keys(trader.questassort["fail"])
];
for (const questLockedTrade of questLocks) {
if (trade._id == questLockedTrade)
return true;
}
return false;
}
//Returns the path to the mod folder
function getModFolder() {
return (0, path_1.join)(__dirname, "../"); // "./user/mods/Gekos_BetterProgression";
}
function bestFiremode(item) {
const res = pickBestFiremode(item._props.weapFireType, item._props.BoltAction || !item._props.CanQueueSecondShot);
if (res == "")
console.log(item);
return res;
}
//Returns one of "manual", "semiauto" or "fullauto" depending on which is the best fire mode available between the provided choices
function pickBestFiremode(modes, isManual) {
if (modes == null) {
return "";
}
const ranks = {
"none": -9999,
"manual": 0,
"doublet": 1,
"semiauto": 1,
"doubleaction": 1,
"single": isManual ? -100 : 1,
"burst": 2,
"fullauto": 3
};
let bestMode = "none";
for (const mode of modes) {
if (ranks[bestMode] < ranks[mode])
bestMode = mode;
}
if (isManual)
if (ranks[bestMode] < ranks["manual"])
bestMode = "manual";
//Rename for simplicity
if (bestMode == "manual" || bestMode == "pumpaction" || (isManual && bestMode == "single"))
return "manual";
if (bestMode == "single" || bestMode == "doubleaction" || bestMode == "doublet")
return "semiauto";
return bestMode;
}
//# sourceMappingURL=utils.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,352 @@
import { IItem } from "@spt/models/eft/common/tables/IItem";
import { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
import { IBarterScheme, ITrader } from "@spt/models/eft/common/tables/ITrader";
import { Context } from "./contex";
import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
import { RewardType } from "@spt/models/enums/RewardType";
import { ChangedItem } from "./algoRebalancing/types";
import { join } from "path";
export const currencies: string[] = [
"5449016a4bdc2d6f028b456f", //Roubles
"5696686a4bdc2da3298b456a", //Dollars
"569668774bdc2da2298b4568", //Euroes
"5d235b4d86f7742e017bc88a", //GP Coin
"6656560053eaaa7a23349c86" //Lega medal
]
const purchasability: Record<string, number> = {};
export function getDogtagsList(context: Context): string[]
{
const list = [];
for (const [id, item] of Object.entries(context.tables.templates.items))
{
if (item._props.DogTagQualities) list.push(id);
}
return list;
}
//Find and return the trader-trade pair that sell the given item
export function findTrades(itemId: string, context: Context): {trader: ITrader, trade: IItem}[]
{
const found: {trader: ITrader, trade: IItem}[] = [];
for (const trader of Object.values(context.tables.traders))
{
if (trader.assort == null) continue;
for (const trade of trader.assort.items)
{
if (trade._tpl == itemId) found.push({
trader: trader,
trade: trade
})
}
}
return found;
}
export function getDefaultAttachments(weaponID: string, context: Context): string[]
{
const preset = context.presetHelper.getDefaultWeaponPresets[weaponID];
if (preset == null || preset == undefined) return [];
return preset._items.map((item) => item._tpl);
}
//Returns the list of all attachments connected to the given item (recursively)
export function unrollAttachments(item: IItem, assort: IItem[]): IItem[]
{
const attachments: IItem[] = [];
const newAttachments = assort.filter( (candidate) => candidate.parentId == item._id && candidate._tpl != item._tpl );
attachments.push(...newAttachments);
for (const att of newAttachments)
{
attachments.push(...unrollAttachments(att, assort));
}
return attachments;
}
//Does the given item from the given assort contain the given attachment? (as an item table id)
export function containsAttachment(item: IItem, assort: IItem[], attachmentID: string, context: Context): boolean
{
//Shortcut items without slots
const template = context.tables.templates.items[item._tpl];
if (template == null)
{
context.logger.warning(`Trader item ${item} with table ID ${item._tpl} couldn't be found in the tables!\nMaybe some mod is adding a trade that sells a non-existing item?`);
//Skip the shortcut if the template cannot be found
}
else
{
if (template._props.Slots == null) return false
if (template._props.Slots.length == 0) return false;
}
return unrollAttachments(item, assort).map( (item) => item._tpl ).includes(attachmentID);
}
export function indexById(byTier: Record<number, ChangedItem[]>): Record<string, ChangedItem>
{
const byId: Record<string, ChangedItem> = {};
for (const items of Object.values(byTier)) for (const item of items)
{
byId[item.trade._id] = item;
}
return byId;
}
//Can the given item be purchased before or at the given loyalty tier cutoff? Can optionally exclude barters
//Trade IDs in skip will not be considered as valid purchases (mostly to avoid self-referencing when used in canAllAttachmentsBePurchased)
//If an item is present in tierOverrides the corresponding loyalty level will be used instead of that of the trader
//Careful about the dynamic programming part with purchasability. If this function previously found an item to be purchasable it will say it is again without checking even if things changed
export function canBePurchased(itemID: string, excludeBarters: boolean, excludeQuestlocks: boolean, tierCutoff: number, skip: string[], tierOverrides: Record<string, ChangedItem>, context: Context): boolean
{
if (purchasability[itemID] <= tierCutoff) return true;
for (const trader of Object.values(context.tables.traders))
{
if (trader.assort == null) continue;
for (const trade of trader.assort.items)
{
const loyalty = tierOverrides[trade._id] != null ? tierOverrides[trade._id].score : trader.assort.loyal_level_items[trade._id]
if (loyaltyFromScore(loyalty, context.config.algorithmicalRebalancing.clampToMaxLevel) > tierCutoff) continue;
if
(
(trade._tpl === itemID && trader.assort.barter_scheme[trade._id] != null)
|| containsAttachment(trade, trader.assort.items, itemID, context)
) //It's a match!
{
if (excludeBarters && isBarterTrade(trade, trader)) continue;
if (excludeQuestlocks && isQuestLocked(trade, trader)) continue;
if (skip.includes(trade._id)) continue;
if (itemID in purchasability)
{
purchasability[itemID] = Math.min(purchasability[itemID], loyalty);
}
else
{
purchasability[itemID] = loyalty;
}
return true;
}
}
}
return false;
}
//Can all the attachments on the given item be purchased before the given loyalty tier cutoff? Can optionally exclude barters
//Attachments in the skip list will not be checked
export function canAllAttachmentsBePurchased(item: IItem, assort: IItem[], excludeBarters: boolean, excludeQuestlocks: boolean, tierCutoff: number, skip: string[], tierOverrides: Record<string, ChangedItem>, context: Context): boolean
{
const attachments = unrollAttachments(item, assort);
for (const att of attachments)
{
if (skip.includes(att._tpl)) continue;
if (!canBePurchased(att._tpl, excludeBarters, excludeQuestlocks, tierCutoff, [item._id], tierOverrides, context))
{
//context.logger.info(`Unpurchasable attachment: ${context.tables.templates.items[att._tpl]._name}`)
return false;
}
}
//context.logger.success(`Did not penalize ${context.tables.templates.items[item._tpl]._name}`);
return true;
}
export function lockBehindQuest(context: Context, traderID: string, trade: string, lock: string, itemID: string, rewardID: string, targetID: string): void
{
const trader = context.tables.traders[traderID];
trader.questassort.success[trade] = lock;
const questRewards = context.tables.templates.quests[lock].rewards.Success;
questRewards.push(
{
type: RewardType.ASSORTMENT_UNLOCK,
index: questRewards.length,
unknown: false,
traderId: traderID,
target: targetID,
items: [
{
_id: targetID,
_tpl: itemID
}
],
id: rewardID,
availableInGameEditions: []
}
)
}
export function setAreaLevelRequirement(craft: IHideoutProduction, level: number): void
{
for (const req of craft.requirements)
{
if (req.requiredLevel != null) req.requiredLevel = level;
}
}
export function isQuestLockedCraft(craft: IHideoutProduction): boolean
{
for (const req of craft.requirements)
{
if (req.questId != null) return true;
}
return false;
}
export function addToLocale(locales: Record<string, Record<string, string>>, id:string, name:string, shortname:string, description:string): void
{
for (const locale of Object.values(locales))
{
locale[`${id} Name`] = name;
locale[`${id} ShortName`] = shortname;
locale[`${id} Description`] = description;
}
}
export function shareSameNiche(a: IItem, b: IItem, aTrader: ITrader, bTrader: ITrader, context: Context) : boolean
{
const c = context.config.algorithmicalRebalancing.weaponRules.upshiftRules;
const aTempl = context.tables.templates.items[a._tpl];
const bTempl = context.tables.templates.items[b._tpl];
if (c.devideNicheByFiremode)
{
if (bestFiremode(aTempl) != bestFiremode(bTempl)) return false;
}
if (c.devideNicheByCaliber)
{
if (aTempl._props.ammoCaliber != bTempl._props.ammoCaliber) return false;
}
if (c.devideNicheByBarterType)
{
if (isBarterTrade(a, aTrader) != isBarterTrade(b, bTrader)) return false;
}
if (c.devideNicheByQuestLock)
{
if (isQuestLocked(a, aTrader) != isQuestLocked(b, bTrader)) return false;
}
return true;
}
export function loyaltyFromScore(score: number, capToMax: boolean): number
{
const maxLevel = capToMax ? 4 : 999 //Set max level to a dummy value if config states that loyalty > 4 is to be hidden from all trader levels
return Math.max(1, Math.min(maxLevel, Math.floor(score))); //Floor and clamp between 1 and the max level
}
//Sets the loyalty level of the given sale item on the given trader
//If capToMax is false the loyalty level will be allowed to go beyond 4
export function setLoyalty(itemID: string, loyalty: number, trader: ITrader, capToMax: boolean): void
{
if (loyalty == null) loyalty = 0;
trader.assort.loyal_level_items[itemID] = loyaltyFromScore(loyalty, capToMax);
}
//Checks if the given trade item is offered for barter on the given trader
//Anything that can't be purchased for RUB, EUR, USD, GP or Lega Medals is considered a barter
export function isBarterTrade(trade: IItem, trader: ITrader): boolean
{
const scheme: IBarterScheme[][] = trader.assort.barter_scheme[trade._id];
if (scheme == null) return false;
for (const ask of scheme) for (const ask1 of ask) //Cursed, i know
{
if (!currencies.includes(ask1._tpl)) return true;
}
return false;
}
//Checks if the given trade item is quest locked on the given trader
export function isQuestLocked(trade: IItem, trader: ITrader): boolean
{
const questLocks = [
...Object.keys(trader.questassort["success"]),
...Object.keys(trader.questassort["started"]),
...Object.keys(trader.questassort["fail"]) ];
for (const questLockedTrade of questLocks)
{
if (trade._id == questLockedTrade) return true;
}
return false;
}
//Returns the path to the mod folder
export function getModFolder(): string
{
return join(__dirname, "../"); // "./user/mods/Gekos_BetterProgression";
}
export function bestFiremode(item: ITemplateItem): string
{
const res = pickBestFiremode(item._props.weapFireType, item._props.BoltAction || !item._props.CanQueueSecondShot);
if (res == "") console.log(item);
return res;
}
//Returns one of "manual", "semiauto" or "fullauto" depending on which is the best fire mode available between the provided choices
export function pickBestFiremode(modes: string[], isManual: boolean): string
{
if (modes == null)
{
return "";
}
const ranks: { [mode: string] : number } =
{
"none" : -9999,
"manual" : 0,
"doublet" : 1,
"semiauto" : 1,
"doubleaction": 1,
"single" : isManual ? -100 : 1,
"burst" : 2,
"fullauto" : 3
};
let bestMode: string = "none";
for (const mode of modes)
{
if (ranks[bestMode] < ranks[mode]) bestMode = mode;
}
if (isManual) if (ranks[bestMode] < ranks["manual"]) bestMode = "manual";
//Rename for simplicity
if (bestMode == "manual" || bestMode == "pumpaction" || (isManual && bestMode == "single")) return "manual";
if (bestMode == "single" || bestMode == "doubleaction" || bestMode == "doublet") return "semiauto";
return bestMode;
}