feat(key storage)
This commit is contained in:
parent
131cac7ad5
commit
a4a3802857
14 changed files with 1543 additions and 0 deletions
BIN
BepInEx/plugins/DrakiaXYZ-GildedKeyStorage.dll
Normal file
BIN
BepInEx/plugins/DrakiaXYZ-GildedKeyStorage.dll
Normal file
Binary file not shown.
7
user/mods/Jehree-GildedKeyStorage/LICENSE.txt
Normal file
7
user/mods/Jehree-GildedKeyStorage/LICENSE.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Gilded Key Storage © 2023 by Jehree is licensed under Attribution-NonCommercial-NoDerivatives 4.0 International.
|
||||||
|
To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/
|
||||||
|
|
||||||
|
PERMISSION TO USE THIS CONTENT:
|
||||||
|
If you want to iterate on this project in some way shoot me a message on the
|
||||||
|
SPT-AKI website and there is a pretty high chance I will be fine with it.
|
||||||
|
My profile: https://hub.sp-tarkov.com/user/32691-jehree/
|
||||||
44
user/mods/Jehree-GildedKeyStorage/bundles.json
Normal file
44
user/mods/Jehree-GildedKeyStorage/bundles.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"manifest": [
|
||||||
|
{
|
||||||
|
"key": "CaseBundles/golden_keychain1.bundle",
|
||||||
|
"dependencyKeys": [
|
||||||
|
"assets/commonassets/physics/physicsmaterials.bundle",
|
||||||
|
"shaders",
|
||||||
|
"cubemaps"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "CaseBundles/golden_keychain2.bundle",
|
||||||
|
"dependencyKeys": [
|
||||||
|
"assets/commonassets/physics/physicsmaterials.bundle",
|
||||||
|
"shaders",
|
||||||
|
"cubemaps"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "CaseBundles/golden_keychain3.bundle",
|
||||||
|
"dependencyKeys": [
|
||||||
|
"assets/commonassets/physics/physicsmaterials.bundle",
|
||||||
|
"shaders",
|
||||||
|
"cubemaps"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "CaseBundles/golden_keycard_case.bundle",
|
||||||
|
"dependencyKeys": [
|
||||||
|
"shaders",
|
||||||
|
"assets/commonassets/physics/physicsmaterials.bundle",
|
||||||
|
"cubemaps"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "CaseBundles/golden_key_pouch.bundle",
|
||||||
|
"dependencyKeys": [
|
||||||
|
"shaders",
|
||||||
|
"cubemaps",
|
||||||
|
"assets/commonassets/physics/physicsmaterials.bundle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
73
user/mods/Jehree-GildedKeyStorage/config/barters.json
Normal file
73
user/mods/Jehree-GildedKeyStorage/config/barters.json
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"Keytool Barter for Keychain": {
|
||||||
|
"id": "62a09d3bcf4a99369e262447",
|
||||||
|
"trader": "mechanic",
|
||||||
|
"trader_loyalty_level": 1,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 3,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "59fafd4b86f7745ca07e1232"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"Prokill Barter for Keychain": {
|
||||||
|
"id": "62a09d3bcf4a99369e262447",
|
||||||
|
"trader": "jaeger",
|
||||||
|
"trader_loyalty_level": 1,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 3,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5c1267ee86f77416ec610f72"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"Chains Barter for Keychain": {
|
||||||
|
"id": "62a09d3bcf4a99369e262447",
|
||||||
|
"trader": "ragman",
|
||||||
|
"trader_loyalty_level": 1,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 3,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"_tpl": "573474f924597738002c6174"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5734758f24597738025ee253"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"Barter for Cottage Key": {
|
||||||
|
"id": "5a0eb6ac86f7743124037a28",
|
||||||
|
"trader": "prapor",
|
||||||
|
"trader_loyalty_level": 2,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 1,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5c12613b86f7743bbe2c3f76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "590c37d286f77443be3d7827"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "590c621186f774138d11ea29"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
405
user/mods/Jehree-GildedKeyStorage/config/cases.json
Normal file
405
user/mods/Jehree-GildedKeyStorage/config/cases.json
Normal file
|
|
@ -0,0 +1,405 @@
|
||||||
|
{
|
||||||
|
"Golden Key Pouch": {
|
||||||
|
"case_type": "container",
|
||||||
|
|
||||||
|
"id": "Golden_Key_Pouch",
|
||||||
|
"item_name": "Secure Key Box",
|
||||||
|
"item_short_name": "Key Box",
|
||||||
|
"item_description": "Secure and compact storage for your Golden Keychains and Golden Keycard Holder, as well as some extra space for loose keys.",
|
||||||
|
"flea_price": 3000000,
|
||||||
|
|
||||||
|
"trader": "therapist",
|
||||||
|
"trader_loyalty_level": 2,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 1,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5c052e6986f7746b207bc3c9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5bc9bc53d4351e00367fbcee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "59fafd4b86f7745ca07e1232"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 300000,
|
||||||
|
"_tpl": "5449016a4bdc2d6f028b456f"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"ExternalSize": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"Grids": [
|
||||||
|
{
|
||||||
|
"width": 1,
|
||||||
|
"height": 4,
|
||||||
|
"included_filter": [
|
||||||
|
"Golden_Keychain1",
|
||||||
|
"Golden_Keychain2",
|
||||||
|
"Golden_Keychain3",
|
||||||
|
"Golden_Keycard_Case"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"width": 5,
|
||||||
|
"height": 5,
|
||||||
|
"included_filter": ["543be5e94bdc2df1348b4568"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"Golden Keychain Mk. I": {
|
||||||
|
"case_type": "slots",
|
||||||
|
|
||||||
|
"id": "Golden_Keychain1",
|
||||||
|
"item_name": "Golden Keychain Mk. I",
|
||||||
|
"item_short_name": "Mk. I",
|
||||||
|
"item_description": "One of 3 gold keychains. This one contains all Ground Zero, Factory, Woods, Customs, Interchange, and Misc keys.",
|
||||||
|
"flea_price": 1000000,
|
||||||
|
"sound": "keys",
|
||||||
|
|
||||||
|
"trader": "therapist",
|
||||||
|
"trader_loyalty_level": 1,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 1,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "62a09d3bcf4a99369e262447"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"_tpl": "5734758f24597738025ee253"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5448ba0b4bdc2d02308b456c"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"ExternalSize": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"slot_ids": [
|
||||||
|
"658199aa38c79576a2569e13",
|
||||||
|
"6581998038c79576a2569e11",
|
||||||
|
"658199972dc4e60f6d556a2f",
|
||||||
|
|
||||||
|
"5448ba0b4bdc2d02308b456c",
|
||||||
|
"57a349b2245977762b199ec7",
|
||||||
|
"593858c486f774253a24cb52",
|
||||||
|
|
||||||
|
"591afe0186f77431bd616a11",
|
||||||
|
"591ae8f986f77406f854be45",
|
||||||
|
"5d08d21286f774736e7c94c3",
|
||||||
|
"664d3db6db5dea2bad286955",
|
||||||
|
"6761a6ccd9bbb27ad703c48a",
|
||||||
|
|
||||||
|
"5938994586f774523a425196",
|
||||||
|
"5914578086f774123569ffa4",
|
||||||
|
"5672c92d4bdc2d180f8b4567",
|
||||||
|
"5938504186f7740991483f30",
|
||||||
|
"59148c8a86f774197930e983",
|
||||||
|
"5780cf942459777df90dcb72",
|
||||||
|
"5780cf9e2459777df90dcb73",
|
||||||
|
"5780cfa52459777dfb276eb1",
|
||||||
|
"593aa4be86f77457f56379f8",
|
||||||
|
"5780cda02459777b272ede61",
|
||||||
|
"5780cf722459777a5108b9a1",
|
||||||
|
"5780cf7f2459777de4559322",
|
||||||
|
"5780cf692459777de4559321",
|
||||||
|
"59136a4486f774447a1ed172",
|
||||||
|
"591383f186f7744a4c5edcf3",
|
||||||
|
"591382d986f774465a6413a7",
|
||||||
|
"59136e1e86f774432f15d133",
|
||||||
|
"59387a4986f77401cc236e62",
|
||||||
|
"5938603e86f77435642354f4",
|
||||||
|
"5780d0652459777df90dcb74",
|
||||||
|
"5913877a86f774432f15d444",
|
||||||
|
"5780d07a2459777de4559324",
|
||||||
|
"5913611c86f77479e0084092",
|
||||||
|
"5938144586f77473c2087145",
|
||||||
|
"593962ca86f774068014d9af",
|
||||||
|
"5937ee6486f77408994ba448",
|
||||||
|
"5780d0532459777a5108b9a2",
|
||||||
|
"5913915886f774123603c392",
|
||||||
|
"5da743f586f7744014504f72",
|
||||||
|
"664d4b0103ef2c61246afb56",
|
||||||
|
"6761a6f90575f25e020816a4",
|
||||||
|
|
||||||
|
"5ad5d49886f77455f9731921",
|
||||||
|
"5ad5db3786f7743568421cce",
|
||||||
|
"5e42c71586f7747f245e1343",
|
||||||
|
"5ad5d64486f774079b080af8",
|
||||||
|
"5ad5ccd186f774446d5706e9",
|
||||||
|
"5ad5cfbd86f7742c825d6104",
|
||||||
|
"5ad5d20586f77449be26d877",
|
||||||
|
"5ad7217186f7746744498875",
|
||||||
|
"5ad7242b86f7740a6a3abd43",
|
||||||
|
"5ad7247386f7747487619dc3",
|
||||||
|
"5ad5d7d286f77450166e0a89",
|
||||||
|
"5addaffe86f77470b455f900",
|
||||||
|
"664d3dd590294949fe2d81b7",
|
||||||
|
|
||||||
|
"5913651986f774432f15d132",
|
||||||
|
"59136f6f86f774447a1ed173",
|
||||||
|
"59148f8286f7741b951ea113",
|
||||||
|
"5a0f075686f7745bcc42ee12",
|
||||||
|
"5d80cb8786f774405611c7d9",
|
||||||
|
"62a09ec84f842e1bd12da3f2",
|
||||||
|
"6391fcf5744e45201147080f"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"Golden Keychain Mk. II": {
|
||||||
|
"case_type": "slots",
|
||||||
|
|
||||||
|
"id": "Golden_Keychain2",
|
||||||
|
"item_name": "Golden Keychain Mk. II",
|
||||||
|
"item_short_name": "Mk. II",
|
||||||
|
"item_description": "One of 3 gold keychains. This one contains all Lighthouse and Shoreline keys.",
|
||||||
|
"flea_price": 1000000,
|
||||||
|
"sound": "keys",
|
||||||
|
|
||||||
|
"trader": "ragman",
|
||||||
|
"trader_loyalty_level": 1,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 1,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "62a09d3bcf4a99369e262447"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"_tpl": "5734758f24597738025ee253"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5a0eb6ac86f7743124037a28"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"ExternalSize": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"slot_ids": [
|
||||||
|
"61a64428a8c6aa1b795f0ba1",
|
||||||
|
"61a6444b8c141d68246e2d2f",
|
||||||
|
"61a64492ba05ef10d62adcc1",
|
||||||
|
"61aa5aed32a4743c3453d319",
|
||||||
|
"61aa5b518f5e7a39b41416e2",
|
||||||
|
"61aa5b7db225ac1ead7957c1",
|
||||||
|
"61aa5ba8018e9821b7368da9",
|
||||||
|
"61aa81fcb225ac1ead7957c3",
|
||||||
|
"62987c658081af308d7558c6",
|
||||||
|
"62987cb98081af308d7558c8",
|
||||||
|
"62987da96188c076bc0d8c51",
|
||||||
|
"62987dfc402c7f69bf010923",
|
||||||
|
"62987e26a77ec735f90a2995",
|
||||||
|
"62a9cb937377a65d7b070cef",
|
||||||
|
"664d3de85f2355673b09aed5",
|
||||||
|
"66265d7be65f224b2e17c6aa",
|
||||||
|
"5a0eb38b86f774153b320eb0",
|
||||||
|
"5a0eb6ac86f7743124037a28",
|
||||||
|
"5a0f068686f7745b0d4ea242",
|
||||||
|
"5d8e15b686f774445103b190",
|
||||||
|
"5a0dc45586f7742f6b0b73e3",
|
||||||
|
"5a0dc95c86f77452440fc675",
|
||||||
|
"5a144dfd86f77445cb5a0982",
|
||||||
|
"5a0ec6d286f7742c0b518fb5",
|
||||||
|
"5a0ee30786f774023b6ee08f",
|
||||||
|
"5a13eebd86f7746fd639aa93",
|
||||||
|
"5a13ef0686f7746e5a411744",
|
||||||
|
"5a0ee34586f774023b6ee092",
|
||||||
|
"5a0ee37f86f774023657a86f",
|
||||||
|
"5a1452ee86f7746f33111763",
|
||||||
|
"5a13ef7e86f7741290491063",
|
||||||
|
"5a13f46386f7741dd7384b04",
|
||||||
|
"5a0eff2986f7741fd654e684",
|
||||||
|
"5a0ea64786f7741707720468",
|
||||||
|
"5eff09cd30a7dc22fd1ddfed",
|
||||||
|
"5a144bdb86f7741d374bbde0",
|
||||||
|
"5a0ee4b586f7743698200d22",
|
||||||
|
"5a13f24186f77410e57c5626",
|
||||||
|
"5a13f35286f77413ef1436b0",
|
||||||
|
"5a145d4786f7744cbb6f4a12",
|
||||||
|
"5a145d7b86f7744cbb6f4a13",
|
||||||
|
"5a0eec9686f77402ac5c39f2",
|
||||||
|
"5a0eecf686f7740350630097",
|
||||||
|
"5a0eed4386f77405112912aa",
|
||||||
|
"5a145ebb86f77458f1796f05",
|
||||||
|
"5a0eee1486f77402aa773226",
|
||||||
|
"5a0ea79b86f7741d4a35298e",
|
||||||
|
"5a0f08bc86f77478f33b84c2",
|
||||||
|
"5a0f0f5886f7741c4e32a472",
|
||||||
|
"5a0ea69f86f7741cd5406619",
|
||||||
|
"5a0ec70e86f7742c0b518fba",
|
||||||
|
"5a0ee62286f774369454a7ac",
|
||||||
|
"5a0ee72c86f77436955d3435",
|
||||||
|
"5a0ee76686f7743698200d5c",
|
||||||
|
"5a0eeb1a86f774688b70aa5c",
|
||||||
|
"5a0eeb8e86f77461257ed71a",
|
||||||
|
"5a0eebed86f77461230ddb3d",
|
||||||
|
"5a0eedb386f77403506300be",
|
||||||
|
"5a0f006986f7741ffd2fe484",
|
||||||
|
"5a0f045e86f7745b0f0d0e42",
|
||||||
|
"5a13ee1986f774794d4c14cd",
|
||||||
|
"664d3ddfdda2e85aca370d75"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"Golden Keychain Mk. III": {
|
||||||
|
"case_type": "slots",
|
||||||
|
|
||||||
|
"id": "Golden_Keychain3",
|
||||||
|
"item_name": "Golden Keychain Mk. III",
|
||||||
|
"item_short_name": "Mk. III",
|
||||||
|
"item_description": "One of 3 gold keychains. This one contains all Streets, Reserve, and Labs keys.",
|
||||||
|
"flea_price": 1000000,
|
||||||
|
"sound": "keys",
|
||||||
|
|
||||||
|
"trader": "skier",
|
||||||
|
"trader_loyalty_level": 1,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 1,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "62a09d3bcf4a99369e262447"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"_tpl": "5734758f24597738025ee253"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5780d0532459777a5108b9a2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"ExternalSize": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"slot_ids": [
|
||||||
|
"6398fd8ad3de3849057f5128", "63a39667c9b3aa4b61683e98",
|
||||||
|
"63a397d3af870e651d58e65b", "63a399193901f439517cafb6",
|
||||||
|
"63a39c69af870e651d58e6aa", "63a39c7964283b5e9c56b280",
|
||||||
|
"63a39cb1c9b3aa4b61683ee2", "63a39ce4cd6db0635c1975fa",
|
||||||
|
"63a39df18a56922e82001f25", "63a39dfe3901f439517cafba",
|
||||||
|
"63a39e1d234195315d4020bd", "63a39e49cd6db0635c1975fc",
|
||||||
|
"63a39f08cd6db0635c197600", "63a39f18c2d53c2c6839c1d3",
|
||||||
|
"63a39f6e64283b5e9c56b289", "63a39fc0af870e651d58e6ae",
|
||||||
|
"63a39fd1c9b3aa4b61683efb", "63a39fdf1e21260da44a0256",
|
||||||
|
"63a3a93f8a56922e82001f5d", "63a71e781031ac76fe773c7d",
|
||||||
|
"64ccc1ec1779ad6ba200a137",
|
||||||
|
"64ccc1d4a0f13c24561edf27", "64ccc1f4ff54fb38131acf27",
|
||||||
|
"63a71e922b25f7513905ca20",
|
||||||
|
"63a71e86b7f4570d3a293169",
|
||||||
|
"63a71eb5b7f4570d3a29316b", "63a71ed21031ac76fe773c7f",
|
||||||
|
"64ccc1fe088064307e14a6f7", "64ccc206793ca11c8f450a38",
|
||||||
|
"64ccc2111779ad6ba200a139", "64ccc246ff54fb38131acf29",
|
||||||
|
"64ccc24de61ea448b507d34d", "64d4b23dc1b37504b41ac2b6",
|
||||||
|
"64ccc268c41e91416064ebc7", "64ce572331dd890873175115",
|
||||||
|
"64ccc25f95763a1ae376e447",
|
||||||
|
|
||||||
|
"6582dbf0b8d7830efc45016f", "6582dbe43a2e5248357dbe9a",
|
||||||
|
"6582dc5740562727a654ebb1", "6582dc4b6ba9e979af6b79f4",
|
||||||
|
|
||||||
|
"5d80c60f86f77440373c4ece", "5d80c62a86f7744036212b3f",
|
||||||
|
"5d80c66d86f774405611c7d6", "5d80c6c586f77440351beef1",
|
||||||
|
"5d80c6fc86f774403a401e3c", "5d80c78786f774403a401e3e",
|
||||||
|
"5d80c88d86f77440556dbf07", "5d80c8f586f77440373c4ed0",
|
||||||
|
"5d80c93086f7744036212b41", "5d80c95986f77440351beef3",
|
||||||
|
"5d80ca9086f774403a401e40", "5d80cab086f77440535be201",
|
||||||
|
"5d80cb3886f77440556dbf09", "5d80cb5686f77440545d1286",
|
||||||
|
"5d80cbd886f77470855c26c2", "5d80ccac86f77470841ff452",
|
||||||
|
"5d80ccdd86f77474f7575e02", "5d80cd1a86f77402aa362f42",
|
||||||
|
"5d8e0db586f7744450412a42", "5d8e0e0e86f774321140eb56",
|
||||||
|
"5d8e3ecc86f774414c78d05e", "5d947d3886f774447b415893",
|
||||||
|
"5d947d4e86f774447b415895", "5d95d6be86f77424444eb3a7",
|
||||||
|
"5d95d6fa86f77424484aa5e9", "5d9f1fa686f774726974a992",
|
||||||
|
"5da46e3886f774653b7a83fe", "5da5cdcd86f774529238fb9b",
|
||||||
|
"5ede7a8229445733cb4c18e2", "5ede7b0c6d23e5473e6e8c66",
|
||||||
|
|
||||||
|
"5c1e2a1e86f77431ea0ea84c",
|
||||||
|
"5c1f79a086f7746ed066fb8f", "5c1e2d1f86f77431e9280bee"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"Golden Keycard Case": {
|
||||||
|
"case_type": "slots",
|
||||||
|
|
||||||
|
"id": "Golden_Keycard_Case",
|
||||||
|
"item_name": "Golden Keycard Holder",
|
||||||
|
"item_short_name": "Keycards",
|
||||||
|
"item_description": "A shiny keycard case that can hold every keycard in the game, including a few Labs Access cards.",
|
||||||
|
"flea_price": 1000000,
|
||||||
|
"sound": "container_plastic",
|
||||||
|
|
||||||
|
"trader": "peacekeeper",
|
||||||
|
"trader_loyalty_level": 1,
|
||||||
|
"unlimited_stock": true,
|
||||||
|
"stock_amount": 1,
|
||||||
|
|
||||||
|
"barter": [
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "619cbf9e0a7c3a1a2731940a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"_tpl": "5734758f24597738025ee253"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"_tpl": "5c94bbff86f7747ee735c08f"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"ExternalSize": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
},
|
||||||
|
|
||||||
|
"slot_ids": [
|
||||||
|
"5c1d0f4986f7744bb01837fa",
|
||||||
|
"5c1d0c5f86f7744bb2683cf0",
|
||||||
|
"5c1d0dc586f7744baf2e7b79",
|
||||||
|
"5c1d0d6d86f7744bb2683e1f",
|
||||||
|
"5efde6b4f5448336730dbd61",
|
||||||
|
"5c1e495a86f7743109743dfb",
|
||||||
|
"5e42c81886f7742a01529f57",
|
||||||
|
"5e42c83786f7742a021fdf3c",
|
||||||
|
"5c1d0efb86f7744baf2e7b7b",
|
||||||
|
"66acd6702b17692df20144c0",
|
||||||
|
"6711039f9e648049e50b3307",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f",
|
||||||
|
"5c94bbff86f7747ee735c08f"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"key_insurance_enabled": false,
|
||||||
|
"cases_insurance_enabled": false,
|
||||||
|
"cases_flea_banned": true,
|
||||||
|
"weightless_keys": true,
|
||||||
|
"no_key_use_limit": false,
|
||||||
|
"keys_are_discardable": true,
|
||||||
|
"all_keys_in_secure": true,
|
||||||
|
"allow_cases_in_special": false,
|
||||||
|
"allow_cases_in_secure": true,
|
||||||
|
"allow_cases_in_backpacks": true,
|
||||||
|
"cases_allowed_in": [],
|
||||||
|
"cases_disallowed_in": [],
|
||||||
|
"use_finite_keys_list": false,
|
||||||
|
"finite_keys_list":[
|
||||||
|
"5780cf7f2459777de4559322", // Dorm room 314 marked key, Uses: 10
|
||||||
|
"5937ee6486f77408994ba448", // Machinery key, Uses: 1
|
||||||
|
"593962ca86f774068014d9af", // Unknown key, Uses: 1
|
||||||
|
"5c94bbff86f7747ee735c08f", // TerraGroup Labs access keycard, Uses: 1
|
||||||
|
"5d08d21286f774736e7c94c3", // Shturman's stash key, Uses: 1
|
||||||
|
"5d80c60f86f77440373c4ece", // RB-BK marked key, Uses: 10
|
||||||
|
"5d80c62a86f7744036212b3f", // RB-VO marked key, Uses: 10
|
||||||
|
"5ede7a8229445733cb4c18e2", // RB-PKPM marked key, Uses: 10
|
||||||
|
"5efde6b4f5448336730dbd61", // Keycard with a blue marking, Uses: 1
|
||||||
|
"62987dfc402c7f69bf010923", // Shared bedroom marked key, Uses: 10
|
||||||
|
"6391fcf5744e45201147080f", // Primorsky Ave apartment key, Uses: 1
|
||||||
|
"6398fd8ad3de3849057f5128", // Backup hideout key, Uses: 1
|
||||||
|
"63a397d3af870e651d58e65b", // Car dealership closed section key, Uses: 1
|
||||||
|
"63a39e1d234195315d4020bd", // Primorsky 46-48 skybridge key, Uses: 1
|
||||||
|
"63a3a93f8a56922e82001f5d", // Abandoned factory marked key, Uses: 10
|
||||||
|
"64ccc25f95763a1ae376e447", // Mysterious room marked key, Uses: 10
|
||||||
|
"64ce572331dd890873175115", // Aspect company office key, Uses: 1
|
||||||
|
"64d4b23dc1b37504b41ac2b6", // Rusted bloody key, Uses: 1
|
||||||
|
"658199aa38c79576a2569e13", // TerraGroup science office key, Uses: 1
|
||||||
|
"6582dbf0b8d7830efc45016f", // Relaxation room key, Uses: 2
|
||||||
|
"664d3db6db5dea2bad286955", // Shatun's hideout key, Uses: 5
|
||||||
|
"664d3dd590294949fe2d81b7", // Grumpy's hideout key, Uses: 5
|
||||||
|
"664d3ddfdda2e85aca370d75", // Voron's hideout key, Uses: 5
|
||||||
|
"664d3de85f2355673b09aed5", // Leon's hideout key, Uses: 5
|
||||||
|
"664d4b0103ef2c61246afb56", // Dorm overseer key, Uses: 2
|
||||||
|
"66acd6702b17692df20144c0", // TerraGroup storage room keycard, Uses: 10
|
||||||
|
"6711039f9e648049e50b3307", // TerraGroup Labs residential unit keycard , Uses: 2
|
||||||
|
//"5e42c81886f7742a01529f57", // Object #11SR keycard, Uses: 10
|
||||||
|
//"5e42c83786f7742a021fdf3c", // Object #21WS keycard, Uses: 10
|
||||||
|
//"5c1d0c5f86f7744bb2683cf0", // TerraGroup Labs keycard (Blue), Uses: 10
|
||||||
|
//"5c1d0d6d86f7744bb2683e1f", // TerraGroup Labs keycard (Yellow), Uses: 10
|
||||||
|
//"5c1d0dc586f7744baf2e7b79", // TerraGroup Labs keycard (Green), Uses: 10
|
||||||
|
//"5c1d0efb86f7744baf2e7b7b", // TerraGroup Labs keycard (Red), Uses: 10
|
||||||
|
//"5c1d0f4986f7744bb01837fa", // TerraGroup Labs keycard (Black), Uses: 10
|
||||||
|
//"5c1e495a86f7743109743dfb", // TerraGroup Labs keycard (Violet), Uses: 10
|
||||||
|
],
|
||||||
|
|
||||||
|
"debug": {
|
||||||
|
"log_missing_keys": false,
|
||||||
|
"log_rare_keys": false,
|
||||||
|
"give_profile_all_keys": false,
|
||||||
|
"force_remove_debug_items_on_start": false
|
||||||
|
}
|
||||||
|
}
|
||||||
27
user/mods/Jehree-GildedKeyStorage/package.json
Normal file
27
user/mods/Jehree-GildedKeyStorage/package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "Gilded Key Storage",
|
||||||
|
"version": "1.6.2",
|
||||||
|
"main": "src/mod.js",
|
||||||
|
"license": "CC BY-NC-ND 4.0",
|
||||||
|
"author": "Jehree",
|
||||||
|
"sptVersion": "~3.11.0",
|
||||||
|
"isBundleMod": true,
|
||||||
|
"scripts": {
|
||||||
|
"setup": "npm i",
|
||||||
|
"build": "node ./build.mjs",
|
||||||
|
"buildinfo": "node ./build.mjs --verbose"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "22.10.5",
|
||||||
|
"@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.7.3",
|
||||||
|
"winston": "3.17.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
213
user/mods/Jehree-GildedKeyStorage/src/debug.ts
Normal file
213
user/mods/Jehree-GildedKeyStorage/src/debug.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
/* eslint-disable @typescript-eslint/brace-style */
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { LogTextColor } from "@spt/models/spt/logging/LogTextColor";
|
||||||
|
import type { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
|
||||||
|
import { BaseClasses } from "@spt/models/enums/BaseClasses";
|
||||||
|
import type { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||||
|
import type { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
import type { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
|
import * as cases from "../config/cases.json";
|
||||||
|
|
||||||
|
const keysInConfig:Array<string> = [
|
||||||
|
...cases["Golden Keycard Case"].slot_ids,
|
||||||
|
...cases["Golden Keychain Mk. I"].slot_ids,
|
||||||
|
...cases["Golden Keychain Mk. II"].slot_ids,
|
||||||
|
...cases["Golden Keychain Mk. III"].slot_ids
|
||||||
|
]
|
||||||
|
|
||||||
|
export class Debug
|
||||||
|
{
|
||||||
|
debugConfig: any;
|
||||||
|
|
||||||
|
constructor(debugConfig: any)
|
||||||
|
{
|
||||||
|
this.debugConfig = debugConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
logMissingKeys(logger:ILogger, itemHelper:ItemHelper, dbItems:Record<string, ITemplateItem>, dbLocales: Record<string, string>, ignoredKeyList: string[]):void{
|
||||||
|
if (!this.debugConfig.log_missing_keys) return
|
||||||
|
|
||||||
|
logger.log("[Gilded Key Storage]: Keys missing from config: ", LogTextColor.MAGENTA)
|
||||||
|
logger.log("-------------------------------------------", LogTextColor.YELLOW)
|
||||||
|
|
||||||
|
for (const itemID in dbItems){
|
||||||
|
const thisItem = dbItems[itemID]
|
||||||
|
|
||||||
|
// Skip if the item is in our ignore list
|
||||||
|
if (ignoredKeyList.includes(itemID)) continue;
|
||||||
|
|
||||||
|
// Skip items that aren't items
|
||||||
|
if (thisItem._type !== "Item") continue;
|
||||||
|
|
||||||
|
// Skip non-keys
|
||||||
|
if (!itemHelper.isOfBaseclass(thisItem._id, BaseClasses.KEY)) continue;
|
||||||
|
|
||||||
|
// Skip quest keys
|
||||||
|
if (thisItem._props.QuestItem) continue;
|
||||||
|
|
||||||
|
if (this.isKeyMissing(itemID)){
|
||||||
|
|
||||||
|
logger.log(dbLocales[`${itemID} Name`], LogTextColor.MAGENTA)
|
||||||
|
logger.log(itemID, LogTextColor.MAGENTA)
|
||||||
|
logger.log("-------------------------------------------", LogTextColor.YELLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logRareKeys(logger:ILogger, itemHelper:ItemHelper, dbItems:Record<string, ITemplateItem>, dbLocales: Record<string, string>):void{
|
||||||
|
if (!this.debugConfig.log_rare_keys) return
|
||||||
|
|
||||||
|
logger.log("[Gilded Key Storage]: Rare key list: ", LogTextColor.CYAN)
|
||||||
|
logger.log("-------------------------------------------", LogTextColor.YELLOW)
|
||||||
|
|
||||||
|
for (const itemID in dbItems){
|
||||||
|
const thisItem = dbItems[itemID]
|
||||||
|
|
||||||
|
// Skip items that aren't items
|
||||||
|
if (thisItem._type !== "Item") continue;
|
||||||
|
|
||||||
|
// Skip non-keys
|
||||||
|
if (!itemHelper.isOfBaseclass(thisItem._id, BaseClasses.KEY)) continue;
|
||||||
|
|
||||||
|
// Skip quest keys
|
||||||
|
if (thisItem._props.QuestItem) continue;
|
||||||
|
|
||||||
|
if (thisItem._props.MaximumNumberOfUsage <= 10){
|
||||||
|
logger.log(` "${itemID}", // ${dbLocales[`${itemID} Name`]}, Uses: ${thisItem._props.MaximumNumberOfUsage}`, LogTextColor.CYAN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isKeyMissing(keyId:string):boolean{
|
||||||
|
if (keysInConfig.includes(keyId)){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
giveProfileAllKeysAndGildedCases(staticRouterModService:StaticRouterModService, saveServer: SaveServer, logger:ILogger):void{
|
||||||
|
if (!this.debugConfig.give_profile_all_keys) return
|
||||||
|
|
||||||
|
staticRouterModService.registerStaticRouter(
|
||||||
|
"On_Game_Start_Gilded_Key_Storage",
|
||||||
|
[{
|
||||||
|
url: "/client/game/start",
|
||||||
|
action: async (url, info, sessionId, output) => {
|
||||||
|
|
||||||
|
const profile = saveServer.getProfile(sessionId)
|
||||||
|
const profileInventory = profile.characters?.pmc?.Inventory
|
||||||
|
|
||||||
|
if (!profileInventory){
|
||||||
|
logger.log("New profile detected! load to stash, then close and reopen SPT to receive all keys and gilded cases", LogTextColor.RED)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemIdsToPush = this.getArrayOfKeysAndCases()
|
||||||
|
|
||||||
|
let xVal = 0
|
||||||
|
let yVal = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < itemIdsToPush.length; i++){
|
||||||
|
const thisItemId = itemIdsToPush[i]
|
||||||
|
|
||||||
|
xVal++
|
||||||
|
|
||||||
|
if (xVal > 9){
|
||||||
|
xVal = 0
|
||||||
|
yVal += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
profileInventory.items.push(
|
||||||
|
{
|
||||||
|
_id: `${thisItemId}_gilded_debug_id`,
|
||||||
|
_tpl: thisItemId,
|
||||||
|
parentId: profileInventory.stash,
|
||||||
|
slotId: "hideout",
|
||||||
|
location: {
|
||||||
|
x: xVal,
|
||||||
|
y: yVal,
|
||||||
|
r: "Horizontal",
|
||||||
|
isSearched: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
profile.characters.pmc.Encyclopedia[thisItemId] = true
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"spt"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllDebugInstanceIdsFromProfile(staticRouterModService:StaticRouterModService, saveServer: SaveServer):void{
|
||||||
|
|
||||||
|
if (!this.debugConfig.give_profile_all_keys && !this.debugConfig.force_remove_debug_items_on_start) return
|
||||||
|
|
||||||
|
let urlHook = "/client/game/logout"
|
||||||
|
if (this.debugConfig.force_remove_debug_items_on_start){
|
||||||
|
urlHook = "/client/game/start"
|
||||||
|
}
|
||||||
|
|
||||||
|
staticRouterModService.registerStaticRouter(
|
||||||
|
"On_Logout_Gilded_Key_Storage",
|
||||||
|
[{
|
||||||
|
url: urlHook,
|
||||||
|
action: async (url, info, sessionId, output) => {
|
||||||
|
|
||||||
|
const profile = saveServer.getProfile(sessionId)
|
||||||
|
const profileInventory = profile.characters?.pmc?.Inventory
|
||||||
|
const profileItems = profileInventory.items
|
||||||
|
|
||||||
|
if (!profileInventory){return output}
|
||||||
|
|
||||||
|
for (let i = profileItems.length; i > 0; i--){
|
||||||
|
|
||||||
|
const itemKey = i-1
|
||||||
|
|
||||||
|
if (profileItems[itemKey]._id.includes("_gilded_debug_id")){
|
||||||
|
|
||||||
|
profileInventory.items.splice(itemKey, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"spt"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
|
getArrayOfKeysAndCases():Array<any>{
|
||||||
|
const keysAndCases = [
|
||||||
|
...keysInConfig,
|
||||||
|
cases["Golden Key Pouch"].id,
|
||||||
|
cases["Golden Keycard Case"].id,
|
||||||
|
cases["Golden Keychain Mk. I"].id,
|
||||||
|
cases["Golden Keychain Mk. II"].id,
|
||||||
|
cases["Golden Keychain Mk. III"].id
|
||||||
|
]
|
||||||
|
|
||||||
|
for (let i = keysAndCases.length; i > 0; i--){
|
||||||
|
const top = i-1
|
||||||
|
|
||||||
|
for (let x = keysAndCases.length; x > 0; x--){
|
||||||
|
const bottom = x-1
|
||||||
|
|
||||||
|
if (top !== bottom){
|
||||||
|
|
||||||
|
if (keysAndCases[top] === keysAndCases[bottom]){
|
||||||
|
|
||||||
|
keysAndCases.splice(bottom, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keysAndCases
|
||||||
|
}
|
||||||
|
}
|
||||||
715
user/mods/Jehree-GildedKeyStorage/src/mod.ts
Normal file
715
user/mods/Jehree-GildedKeyStorage/src/mod.ts
Normal file
|
|
@ -0,0 +1,715 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
/* eslint-disable @typescript-eslint/brace-style */
|
||||||
|
import type { DependencyContainer } from "tsyringe";
|
||||||
|
import type { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
|
import type { StaticRouterModService } from "@spt/services/mod/staticRouter/StaticRouterModService";
|
||||||
|
import type { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
import type { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
|
||||||
|
import type { IPreSptLoadMod } from "@spt/models/external/IPreSptLoadMod";
|
||||||
|
import type { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
|
import type { HashUtil } from "@spt/utils/HashUtil";
|
||||||
|
import type { DatabaseServer } from "@spt/servers/DatabaseServer";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { BaseClasses } from "@spt/models/enums/BaseClasses";
|
||||||
|
import { ItemTpl } from "@spt/models/enums/ItemTpl";
|
||||||
|
import { Traders } from "@spt/models/enums/Traders";
|
||||||
|
import { LogTextColor } from "@spt/models/spt/logging/LogTextColor";
|
||||||
|
import type { GameController } from "@spt/controllers/GameController";
|
||||||
|
import type { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||||
|
import type { ITrader } from "@spt/models/eft/common/tables/ITrader";
|
||||||
|
import type { ITemplateItem } from "@spt/models/eft/common/tables/ITemplateItem";
|
||||||
|
import { FileSystemSync } from "@spt/utils/FileSystemSync";
|
||||||
|
import { Debug } from "./debug";
|
||||||
|
|
||||||
|
import barters from "../config/barters.json";
|
||||||
|
import cases from "../config/cases.json";
|
||||||
|
|
||||||
|
import path from "path";
|
||||||
|
import { copyFileSync, existsSync } from "fs";
|
||||||
|
import JSON5 from "json5";
|
||||||
|
|
||||||
|
class Mod implements IPostDBLoadMod, IPreSptLoadMod {
|
||||||
|
private HANDBOOK_GEARCASES = "5b5f6fa186f77409407a7eb7";
|
||||||
|
newIdMap = {
|
||||||
|
Golden_Key_Pouch: "661cb36922c9e10dc2d9514b",
|
||||||
|
Golden_Keycard_Case: "661cb36f5441dc730e28bcb0",
|
||||||
|
Golden_Keychain1: "661cb372e5eb56290da76c3e",
|
||||||
|
Golden_Keychain2: "661cb3743bf00d3d145518b3",
|
||||||
|
Golden_Keychain3: "661cb376b16226f648eb0cdc"
|
||||||
|
};
|
||||||
|
|
||||||
|
// These are keys that BSG added with no actual use, or drop chance. Ignore them for now
|
||||||
|
// These should be confirmed every client update to still be unused
|
||||||
|
private ignoredKeyList = [
|
||||||
|
"5671446a4bdc2d97058b4569",
|
||||||
|
"57518f7724597720a31c09ab",
|
||||||
|
"57518fd424597720c85dbaaa",
|
||||||
|
"5751916f24597720a27126df",
|
||||||
|
"5751961824597720a31c09ac",
|
||||||
|
"590de4a286f77423d9312a32",
|
||||||
|
"590de52486f774226a0c24c2",
|
||||||
|
"61a6446f4b5f8b70f451b166",
|
||||||
|
"63a39ddda3a2b32b5f6e007a",
|
||||||
|
"63a39e0f64283b5e9c56b282",
|
||||||
|
"63a39e5b234195315d4020bf",
|
||||||
|
"63a39e6acd6db0635c1975fe",
|
||||||
|
"63a71f1a0aa9fb29da61c537",
|
||||||
|
"63a71f3b0aa9fb29da61c539",
|
||||||
|
"658199a0490414548c0fa83b",
|
||||||
|
"6582dc63cafcd9485374dbc5"
|
||||||
|
];
|
||||||
|
|
||||||
|
logger: ILogger
|
||||||
|
modName: string
|
||||||
|
modVersion: string
|
||||||
|
container: DependencyContainer;
|
||||||
|
profileHelper: ProfileHelper;
|
||||||
|
itemHelper: ItemHelper;
|
||||||
|
fileSystemSync: FileSystemSync;
|
||||||
|
config: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.modName = "Gilded Key Storage";
|
||||||
|
}
|
||||||
|
|
||||||
|
public preSptLoad(container: DependencyContainer): void {
|
||||||
|
this.container = container;
|
||||||
|
|
||||||
|
const staticRouterModService = container.resolve<StaticRouterModService>("StaticRouterModService")
|
||||||
|
const saveServer = container.resolve<SaveServer>("SaveServer")
|
||||||
|
const logger = container.resolve<ILogger>("WinstonLogger")
|
||||||
|
this.profileHelper = container.resolve<ProfileHelper>("ProfileHelper");
|
||||||
|
this.itemHelper = container.resolve<ItemHelper>("ItemHelper");
|
||||||
|
this.fileSystemSync = container.resolve<FileSystemSync>("FileSystemSync");
|
||||||
|
|
||||||
|
// Load our config
|
||||||
|
this.loadConfig();
|
||||||
|
|
||||||
|
// On game start, see if we need to fix issues from previous versions
|
||||||
|
// Note: We do this as a method replacement so we can run _before_ SPT's gameStart
|
||||||
|
container.afterResolution("GameController", (_, result: GameController) => {
|
||||||
|
const originalGameStart = result.gameStart;
|
||||||
|
|
||||||
|
result.gameStart = (url: string, info: IEmptyRequestData, sessionID: string, startTimeStampMS: number) => {
|
||||||
|
// If there's a profile ID passed in, call our fixer method
|
||||||
|
if (sessionID)
|
||||||
|
{
|
||||||
|
this.fixProfile(sessionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the original
|
||||||
|
originalGameStart.apply(result, [url, info, sessionID, startTimeStampMS]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup debugging if enabled
|
||||||
|
const debugUtil = new Debug(this.config.debug)
|
||||||
|
debugUtil.giveProfileAllKeysAndGildedCases(staticRouterModService, saveServer, logger)
|
||||||
|
debugUtil.removeAllDebugInstanceIdsFromProfile(staticRouterModService, saveServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
public postDBLoad(container: DependencyContainer): void {
|
||||||
|
this.logger = container.resolve<ILogger>("WinstonLogger");
|
||||||
|
this.logger.log(`[${this.modName}] : Mod loading`, LogTextColor.GREEN);
|
||||||
|
const debugUtil = new Debug(this.config.debug)
|
||||||
|
const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
|
||||||
|
const dbTables = databaseServer.getTables();
|
||||||
|
const restrInRaid = dbTables.globals.config.RestrictionsInRaid;
|
||||||
|
const dbTemplates = dbTables.templates
|
||||||
|
const dbTraders = dbTables.traders
|
||||||
|
const dbItems = dbTemplates.items
|
||||||
|
const dbLocales = dbTables.locales.global.en
|
||||||
|
|
||||||
|
debugUtil.logRareKeys(this.logger, this.itemHelper, dbItems, dbLocales);
|
||||||
|
this.combatibilityThings(dbItems)
|
||||||
|
|
||||||
|
for (const caseName of Object.keys(cases))
|
||||||
|
{
|
||||||
|
this.createCase(container, cases[caseName], dbTables);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pushSupportiveBarters(dbTraders)
|
||||||
|
this.adjustItemProperties(dbItems)
|
||||||
|
this.setLabsCardInRaidLimit(restrInRaid, 9)
|
||||||
|
|
||||||
|
debugUtil.logMissingKeys(this.logger, this.itemHelper, dbItems, dbLocales, this.ignoredKeyList)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfig(): void {
|
||||||
|
const userConfigPath = path.resolve(__dirname, "../config/config.json5");
|
||||||
|
const defaultConfigPath = path.resolve(__dirname, "../config/config.default.json5");
|
||||||
|
|
||||||
|
// Copy the default config if the user config doesn't exist yet
|
||||||
|
if (!existsSync(userConfigPath))
|
||||||
|
{
|
||||||
|
copyFileSync(defaultConfigPath, userConfigPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the config as a merge of the default and user configs, so we always
|
||||||
|
// have the default values available, even if missing in the user config
|
||||||
|
this.config = {
|
||||||
|
...JSON5.parse(this.fileSystemSync.read(defaultConfigPath)),
|
||||||
|
...JSON5.parse(this.fileSystemSync.read(userConfigPath))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pushSupportiveBarters(dbTraders: Record<string, ITrader>):void{
|
||||||
|
for (const barter of Object.keys(barters)){
|
||||||
|
this.pushToTrader(barters[barter], barters[barter].id, dbTraders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
|
setLabsCardInRaidLimit(restrInRaid:any, limitAmount:number):void{
|
||||||
|
if (restrInRaid === undefined) return
|
||||||
|
|
||||||
|
//restrInRaid type set to any to shut the linter up because the type doesn't include MaxIn... props
|
||||||
|
//set labs access card limit in raid to 9 so the keycard case can be filled while on pmc
|
||||||
|
for (const restr in restrInRaid){
|
||||||
|
const thisRestriction = restrInRaid[restr]
|
||||||
|
if (thisRestriction.TemplateId === ItemTpl.KEYCARD_TERRAGROUP_LABS_ACCESS){
|
||||||
|
thisRestriction.MaxInLobby = limitAmount;
|
||||||
|
thisRestriction.MaxInRaid = limitAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustItemProperties(dbItems: Record<string, ITemplateItem>){
|
||||||
|
for (const [_, item] of Object.entries(dbItems)){
|
||||||
|
// Skip anything that isn't specifically an Item type item
|
||||||
|
if (item._type !== "Item")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemProps = item._props
|
||||||
|
|
||||||
|
// Adjust key specific properties
|
||||||
|
if (this.itemHelper.isOfBaseclass(item._id, BaseClasses.KEY)){
|
||||||
|
|
||||||
|
if (this.config.weightless_keys){
|
||||||
|
itemProps.Weight = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemProps.InsuranceDisabled = !this.config.key_insurance_enabled;
|
||||||
|
|
||||||
|
// If keys are to be set to no limit, and we're either not using the finite keys list, or this key doesn't exist
|
||||||
|
// in it, set the key max usage to 0 (infinite)
|
||||||
|
if (this.config.no_key_use_limit &&
|
||||||
|
(!this.config.use_finite_keys_list || !this.config.finite_keys_list.includes(item._id)))
|
||||||
|
{
|
||||||
|
itemProps.MaximumNumberOfUsage = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.keys_are_discardable) {
|
||||||
|
// BSG uses DiscordLimit == 0 to flag as not insurable, so we need to swap to the flag
|
||||||
|
if (itemProps.DiscardLimit === 0)
|
||||||
|
{
|
||||||
|
itemProps.InsuranceDisabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemProps.DiscardLimit = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove keys from secure container exclude filter
|
||||||
|
if (this.config.all_keys_in_secure && this.itemHelper.isOfBaseclass(item._id, BaseClasses.MOB_CONTAINER) && itemProps?.Grids) {
|
||||||
|
// Theta container has multiple grids, so we need to loop through all grids
|
||||||
|
for (const grid of itemProps.Grids) {
|
||||||
|
const filter = grid?._props?.filters[0];
|
||||||
|
if (filter)
|
||||||
|
{
|
||||||
|
// Exclude items with a base class of KEY. Have to check that it's an "Item" type first because isOfBaseClass only accepts Items
|
||||||
|
filter.ExcludedFilter = filter.ExcludedFilter.filter(
|
||||||
|
itemTpl => this.itemHelper.getItem(itemTpl)[1]?._type !== "Item" || !this.itemHelper.isOfBaseclass(itemTpl, BaseClasses.KEY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
combatibilityThings(dbItems: Record<string, ITemplateItem>):void{
|
||||||
|
//do a compatibility correction to make this mod work with other mods with destructive code (cough, SVM, cough)
|
||||||
|
//basically just add the filters element back to backpacks and secure containers if they've been removed by other mods
|
||||||
|
const compatFiltersElement = [{ Filter: [BaseClasses.ITEM], ExcludedFilter: [] }];
|
||||||
|
|
||||||
|
for (const [_, item] of Object.entries(dbItems)){
|
||||||
|
// Skip non-items
|
||||||
|
if (item._type !== "Item") continue;
|
||||||
|
|
||||||
|
if (
|
||||||
|
item._parent === BaseClasses.BACKPACK ||
|
||||||
|
item._parent === BaseClasses.VEST ||
|
||||||
|
(this.itemHelper.isOfBaseclass(item._id, BaseClasses.MOB_CONTAINER) && item._id !== ItemTpl.SECURE_CONTAINER_BOSS)
|
||||||
|
) {
|
||||||
|
for (const grid of item._props.Grids)
|
||||||
|
{
|
||||||
|
if (grid._props.filters[0] === undefined) {
|
||||||
|
grid._props.filters = structuredClone(compatFiltersElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createCase(container, caseConfig, tables){
|
||||||
|
const handbook = tables.templates.handbook;
|
||||||
|
const locales = Object.values(tables.locales.global) as Record<string, string>[];
|
||||||
|
const itemID: string = caseConfig.id;
|
||||||
|
const itemPrefabPath = `CaseBundles/${itemID.toLocaleLowerCase()}.bundle`
|
||||||
|
const templateId = this.newIdMap[itemID];
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
|
let item: any;
|
||||||
|
|
||||||
|
//clone a case
|
||||||
|
if (caseConfig.case_type === "container"){
|
||||||
|
item = structuredClone(tables.templates.items[ItemTpl.CONTAINER_SICC]);
|
||||||
|
item._props.IsAlwaysAvailableForInsurance = true;
|
||||||
|
item._props.DiscardLimit = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caseConfig.case_type === "slots"){
|
||||||
|
item = structuredClone(tables.templates.items[ItemTpl.MOUNT_STRIKE_INDUSTRIES_KEYMOD_4_INCH_RAIL]);
|
||||||
|
item._props.IsAlwaysAvailableForInsurance = true;
|
||||||
|
item._props.DiscardLimit = -1;
|
||||||
|
item._props.ItemSound = caseConfig.sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
item._name = caseConfig.item_name;
|
||||||
|
item._id = templateId;
|
||||||
|
item._props.Prefab.path = itemPrefabPath;
|
||||||
|
|
||||||
|
//call methods to set the grid or slot cells up
|
||||||
|
if (caseConfig.case_type === "container"){
|
||||||
|
item._props.Grids = this.createGrid(container, templateId, caseConfig);
|
||||||
|
}
|
||||||
|
if (caseConfig.case_type === "slots"){
|
||||||
|
item._props.Slots = this.createSlot(container, templateId, caseConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
//set external size of the container:
|
||||||
|
item._props.Width = caseConfig.ExternalSize.width;
|
||||||
|
item._props.Height = caseConfig.ExternalSize.height;
|
||||||
|
|
||||||
|
tables.templates.items[templateId] = item;
|
||||||
|
|
||||||
|
//add locales
|
||||||
|
for (const locale of locales) {
|
||||||
|
locale[`${templateId} Name`] = caseConfig.item_name;
|
||||||
|
locale[`${templateId} ShortName`] = caseConfig.item_short_name;
|
||||||
|
locale[`${templateId} Description`] = caseConfig.item_description;
|
||||||
|
}
|
||||||
|
|
||||||
|
item._props.CanSellOnRagfair = !this.config.cases_flea_banned;
|
||||||
|
item._props.InsuranceDisabled = !this.config.cases_insurance_enabled;
|
||||||
|
const price = caseConfig.flea_price
|
||||||
|
|
||||||
|
handbook.Items.push(
|
||||||
|
{
|
||||||
|
Id: templateId,
|
||||||
|
ParentId: this.HANDBOOK_GEARCASES,
|
||||||
|
Price: price
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//allow or disallow in secure containers, backpacks, other specific items per the config
|
||||||
|
this.allowIntoContainers(
|
||||||
|
templateId,
|
||||||
|
tables.templates.items
|
||||||
|
);
|
||||||
|
|
||||||
|
this.pushToTrader(caseConfig, templateId, tables.traders);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushToTrader(caseConfig, itemID:string, dbTraders: Record<string, ITrader>){
|
||||||
|
const traderIDs = {
|
||||||
|
mechanic: Traders.MECHANIC,
|
||||||
|
skier: Traders.SKIER,
|
||||||
|
peacekeeper: Traders.PEACEKEEPER,
|
||||||
|
therapist: Traders.THERAPIST,
|
||||||
|
prapor: Traders.PRAPOR,
|
||||||
|
jaeger: Traders.JAEGER,
|
||||||
|
ragman: Traders.RAGMAN
|
||||||
|
};
|
||||||
|
|
||||||
|
//add to config trader's inventory
|
||||||
|
let traderToPush = caseConfig.trader;
|
||||||
|
for (const [key, val] of Object.entries(traderIDs))
|
||||||
|
{
|
||||||
|
if (key === caseConfig.trader){
|
||||||
|
traderToPush = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const trader = dbTraders[traderToPush];
|
||||||
|
|
||||||
|
trader.assort.items.push({
|
||||||
|
_id: itemID,
|
||||||
|
_tpl: itemID,
|
||||||
|
parentId: "hideout",
|
||||||
|
slotId: "hideout",
|
||||||
|
upd:
|
||||||
|
{
|
||||||
|
UnlimitedCount: caseConfig.unlimited_stock,
|
||||||
|
StackObjectsCount: caseConfig.stock_amount
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
|
const barterTrade: any = [];
|
||||||
|
const configBarters = caseConfig.barter;
|
||||||
|
|
||||||
|
for (const barter in configBarters){
|
||||||
|
barterTrade.push(configBarters[barter]);
|
||||||
|
}
|
||||||
|
|
||||||
|
trader.assort.barter_scheme[itemID] = [barterTrade];
|
||||||
|
trader.assort.loyal_level_items[itemID] = caseConfig.trader_loyalty_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
allowIntoContainers(itemID, items: Record<string, ITemplateItem>): void {
|
||||||
|
for (const [_, item] of Object.entries(items)){
|
||||||
|
// Skip non-items
|
||||||
|
if (item._type !== "Item") continue;
|
||||||
|
|
||||||
|
//disallow in backpacks
|
||||||
|
if (!this.config.allow_cases_in_backpacks){
|
||||||
|
this.allowOrDisallowIntoCaseByParent(itemID, "exclude", item, BaseClasses.BACKPACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
//allow in secure containers
|
||||||
|
if (this.config.allow_cases_in_secure){
|
||||||
|
this.allowOrDisallowIntoCaseByParent(itemID, "include", item, BaseClasses.MOB_CONTAINER);
|
||||||
|
}
|
||||||
|
|
||||||
|
//disallow in additional specific items
|
||||||
|
for (const configItem in this.config.cases_disallowed_in){
|
||||||
|
if (this.config.cases_disallowed_in[configItem] === item._id){
|
||||||
|
this.allowOrDisallowIntoCaseByID(itemID, "exclude", item);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//allow in additional specific items
|
||||||
|
for (const configItem in this.config.cases_allowed_in){
|
||||||
|
if (this.config.cases_allowed_in[configItem] === item._id){
|
||||||
|
this.allowOrDisallowIntoCaseByID(itemID, "include", item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow in special slots
|
||||||
|
if (this.config.allow_cases_in_special && (item._id === ItemTpl.POCKETS_1X4_SPECIAL || item._id === ItemTpl.POCKETS_1X4_TUE)){
|
||||||
|
this.allowInSpecialSlots(itemID, item);
|
||||||
|
this.allowInSpecialSlots(itemID, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowOrDisallowIntoCaseByParent(customItemID, includeOrExclude, currentItem, caseParent): void {
|
||||||
|
// Skip if the parent isn't our case parent
|
||||||
|
if (currentItem._parent !== caseParent || currentItem._id === ItemTpl.SECURE_CONTAINER_BOSS)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeOrExclude === "exclude") {
|
||||||
|
for (const grid of currentItem._props.Grids) {
|
||||||
|
if (grid._props.filters[0].ExcludedFilter === undefined) {
|
||||||
|
grid._props.filters[0].ExcludedFilter = [customItemID];
|
||||||
|
} else {
|
||||||
|
grid._props.filters[0].ExcludedFilter.push(customItemID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeOrExclude === "include") {
|
||||||
|
for (const grid of currentItem._props.Grids) {
|
||||||
|
if (grid._props.filters[0].Filter === undefined) {
|
||||||
|
grid._props.filters[0].Filter = [customItemID];
|
||||||
|
} else {
|
||||||
|
grid._props.filters[0].Filter.push(customItemID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowOrDisallowIntoCaseByID(customItemID, includeOrExclude, currentItem): void {
|
||||||
|
|
||||||
|
//exclude custom case in specific item of caseToApplyTo id
|
||||||
|
if (includeOrExclude === "exclude"){
|
||||||
|
for (const grid of currentItem._props.Grids) {
|
||||||
|
if (grid._props.filters[0].ExcludedFilter === undefined){
|
||||||
|
grid._props.filters[0].ExcludedFilter = [customItemID];
|
||||||
|
} else {
|
||||||
|
grid._props.filters[0].ExcludedFilter.push(customItemID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//include custom case in specific item of caseToApplyTo id
|
||||||
|
if (includeOrExclude === "include"){
|
||||||
|
for (const grid of currentItem._props.Grids) {
|
||||||
|
if (grid._props.filters[0].Filter === undefined){
|
||||||
|
grid._props.filters[0].Filter = [customItemID];
|
||||||
|
} else {
|
||||||
|
grid._props.filters[0].Filter.push(customItemID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowInSpecialSlots(customItemID, currentItem): void {
|
||||||
|
for (const slot of currentItem._props.Slots) {
|
||||||
|
slot._props.filters[0]?.Filter.push(customItemID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createGrid(container, itemID, config) {
|
||||||
|
const grids = [];
|
||||||
|
|
||||||
|
// Loop over all grids in the config
|
||||||
|
for (let i = 0; i < config.Grids.length; i++) {
|
||||||
|
const grid = config.Grids[i];
|
||||||
|
const inFilt = this.replaceOldIdWithNewId(grid.included_filter ?? []);
|
||||||
|
const exFilt = this.replaceOldIdWithNewId(grid.excluded_filter ?? []);
|
||||||
|
const cellWidth = grid.width;
|
||||||
|
const cellHeight = grid.height;
|
||||||
|
|
||||||
|
// If there's no include filter, add all items
|
||||||
|
if (inFilt.length === 0) {
|
||||||
|
inFilt.push(BaseClasses.ITEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
grids.push(this.generateGridColumn(container, itemID, `column${i}`, cellWidth, cellHeight, inFilt, exFilt));
|
||||||
|
}
|
||||||
|
|
||||||
|
return grids;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceOldIdWithNewId(entries)
|
||||||
|
{
|
||||||
|
const newIdKeys = Object.keys(this.newIdMap);
|
||||||
|
for (let i = 0; i < entries.length; i++)
|
||||||
|
{
|
||||||
|
if (newIdKeys.includes(entries[i]))
|
||||||
|
{
|
||||||
|
entries[i] = this.newIdMap[entries[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSlot(container, itemID, config) {
|
||||||
|
const slots = [];
|
||||||
|
const configSlots = config.slot_ids;
|
||||||
|
|
||||||
|
for (let i = 0; i < configSlots.length; i++){
|
||||||
|
slots.push(this.generateSlotColumn(container, itemID, `mod_mount_${i}`, configSlots[i]));
|
||||||
|
}
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateGridColumn(container: DependencyContainer, itemID, name, cellH, cellV, inFilt, exFilt) {
|
||||||
|
const hashUtil = container.resolve<HashUtil>("HashUtil")
|
||||||
|
return {
|
||||||
|
_name: name,
|
||||||
|
_id: hashUtil.generate(),
|
||||||
|
_parent: itemID,
|
||||||
|
_props: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
Filter: [...inFilt],
|
||||||
|
ExcludedFilter: [...exFilt]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
cellsH: cellH,
|
||||||
|
cellsV: cellV,
|
||||||
|
minCount: 0,
|
||||||
|
maxCount: 0,
|
||||||
|
maxWeight: 0,
|
||||||
|
isSortingTable: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSlotColumn(container: DependencyContainer, itemID, name, configSlot) {
|
||||||
|
const hashUtil = container.resolve<HashUtil>("HashUtil")
|
||||||
|
return {
|
||||||
|
_name: name,
|
||||||
|
_id: hashUtil.generate(),
|
||||||
|
_parent: itemID,
|
||||||
|
_props: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
Filter: [configSlot],
|
||||||
|
ExcludedFilter: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
_required: false,
|
||||||
|
_mergeSlotWithChildren: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle updating the user profile between versions:
|
||||||
|
// - Update the container IDs to the new MongoID format
|
||||||
|
// - Look for any key cases in the user's inventory, and properly update the child key locations if we've moved them
|
||||||
|
fixProfile(sessionId: string) {
|
||||||
|
const databaseServer = this.container.resolve<DatabaseServer>("DatabaseServer");
|
||||||
|
const dbTables = databaseServer.getTables();
|
||||||
|
const dbItems = dbTables.templates.items;
|
||||||
|
|
||||||
|
const pmcProfile = this.profileHelper.getFullProfile(sessionId)?.characters?.pmc;
|
||||||
|
|
||||||
|
// Do nothing if the profile isn't initialized
|
||||||
|
if (!pmcProfile?.Inventory?.items) return;
|
||||||
|
|
||||||
|
// Update the container IDs to the new MongoID format
|
||||||
|
for (const item of pmcProfile.Inventory.items)
|
||||||
|
{
|
||||||
|
if (this.newIdMap[item._tpl])
|
||||||
|
{
|
||||||
|
item._tpl = this.newIdMap[item._tpl];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup the PMC inventory
|
||||||
|
const pmcInventory = structuredClone(pmcProfile.Inventory.items);
|
||||||
|
|
||||||
|
// Look for any key cases in the user's inventory, and properly update the child key locations if we've moved them
|
||||||
|
for (const caseName of Object.keys(cases))
|
||||||
|
{
|
||||||
|
const caseConfig = cases[caseName];
|
||||||
|
|
||||||
|
if (caseConfig.case_type === "slots" && !this.fixSlotCase(caseConfig, dbItems, pmcProfile)) {
|
||||||
|
pmcProfile.Inventory.items = pmcInventory;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caseConfig.case_type === "container" && !this.fixContainerCase(caseConfig, dbItems, pmcProfile)) {
|
||||||
|
pmcProfile.Inventory.items = pmcInventory;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fixSlotCase(caseConfig, dbItems, pmcProfile) {
|
||||||
|
const templateId = this.newIdMap[caseConfig.id];
|
||||||
|
|
||||||
|
// Get the template for the case
|
||||||
|
const caseTemplate = dbItems[templateId];
|
||||||
|
|
||||||
|
// Try to find the case in the user's profile
|
||||||
|
const inventoryCases = pmcProfile.Inventory.items.filter(x => x._tpl === templateId);
|
||||||
|
|
||||||
|
for (const inventoryCase of inventoryCases)
|
||||||
|
{
|
||||||
|
const caseChildren = pmcProfile.Inventory.items.filter(x => x.parentId === inventoryCase._id);
|
||||||
|
|
||||||
|
for (const child of caseChildren)
|
||||||
|
{
|
||||||
|
// Skip if the current slot filter can hold the given item, and there aren't multiple items in it
|
||||||
|
const currentSlot = caseTemplate._props?.Slots?.find(x => x._name === child.slotId);
|
||||||
|
if (currentSlot._props?.filters[0]?.Filter[0] === child._tpl &&
|
||||||
|
// A release of GKS went out that may have stacked keycards, so check for any stacked items in one slot
|
||||||
|
caseChildren.filter(x => x.slotId === currentSlot._name).length === 1
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a new slot, if this is a labs access item, find the first empty compatible slot
|
||||||
|
const newSlot = caseTemplate._props?.Slots?.find(x =>
|
||||||
|
x._props?.filters[0]?.Filter[0] === child._tpl &&
|
||||||
|
// A release of GKS went out that may have stacked keycards, try to fix that
|
||||||
|
(
|
||||||
|
child._tpl !== ItemTpl.KEYCARD_TERRAGROUP_LABS_ACCESS ||
|
||||||
|
!caseChildren.find(y => y.slotId === x._name)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we couldn't find a new slot for this key, something has gone horribly wrong, restore the inventory and exit
|
||||||
|
if (!newSlot)
|
||||||
|
{
|
||||||
|
this.logger.error(`[${this.modName}] : ERROR: Unable to find new slot for ${child._tpl}. Restoring inventory and exiting`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSlot._name !== child.slotId)
|
||||||
|
{
|
||||||
|
this.logger.debug(`[${this.modName}] : Need to move ${child.slotId} to ${newSlot._name}`);
|
||||||
|
child.slotId = newSlot._name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixContainerCase(caseConfig, dbItems, pmcProfile) {
|
||||||
|
const templateId = this.newIdMap[caseConfig.id];
|
||||||
|
|
||||||
|
// Get the template for the case
|
||||||
|
const caseTemplate = dbItems[templateId];
|
||||||
|
|
||||||
|
// Try to find the case in the user's profile
|
||||||
|
const inventoryCases = pmcProfile.Inventory.items.filter(x => x._tpl === templateId);
|
||||||
|
|
||||||
|
for (const inventoryCase of inventoryCases)
|
||||||
|
{
|
||||||
|
const caseChildren = pmcProfile.Inventory.items.filter(x => x.parentId === inventoryCase._id);
|
||||||
|
|
||||||
|
for (const child of caseChildren)
|
||||||
|
{
|
||||||
|
// Skip if the item already has a location property
|
||||||
|
if (child.location) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find which grid the item should be in
|
||||||
|
const newGrid = caseTemplate._props?.Grids?.find(x =>
|
||||||
|
x._props?.filters[0]?.Filter?.includes(child._tpl)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!newGrid) {
|
||||||
|
this.logger.error(`[${this.modName}] : ERROR: Unable to find new grid for ${child._tpl}. Restoring inventory and exiting`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first free slot in that grid, assume everything is a 1x1 item
|
||||||
|
let newX = -1;
|
||||||
|
let newY = -1;
|
||||||
|
for (let y = 0; y < newGrid._props.cellsV && newY < 0; y++)
|
||||||
|
{
|
||||||
|
for (let x = 0; x < newGrid._props.cellsH && newX < 0; x++)
|
||||||
|
{
|
||||||
|
if (!caseChildren.find(item => item.location?.x == x && item.location?.y == y)) {
|
||||||
|
newX = x;
|
||||||
|
newY = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newX == -1 || newY == -1) {
|
||||||
|
this.logger.error(`[${this.modName}] : ERROR: Unable to find new location for ${child._tpl}. Restoring inventory and exiting`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`[${this.modName}] : Need to move ${child.slotId} to ${newGrid._name} X: ${newX} Y: ${newY}`);
|
||||||
|
|
||||||
|
// Update the child item to the new location
|
||||||
|
child.location = {
|
||||||
|
"x": newX,
|
||||||
|
"y": newY,
|
||||||
|
"r": "Horizontal"
|
||||||
|
};
|
||||||
|
child.slotId = newGrid._name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { mod: new Mod() }
|
||||||
Loading…
Add table
Add a link
Reference in a new issue