Rework message sending to support gift system (!106)
Co-authored-by: Kaeno <e> Co-authored-by: Dev <dev@dev.sp-tarkov.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/106
This commit is contained in:
parent
a9e6d5d0fe
commit
a7b4ebe316
570
project/assets/configs/gifts.json
Normal file
570
project/assets/configs/gifts.json
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
{
|
||||||
|
"gifts": {
|
||||||
|
"NewYear2021": {
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "<ch>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</ch><cz>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</cz><en>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</en><fr>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</fr><ge>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</ge><hu>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</hu><it>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</it><jp>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</jp><kr>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</kr><pl>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</pl><po>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</po><sk>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</sk><es>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</es><es-mx>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</es-mx><tu>Hello there, friend. Happy New Year to you! You've probably forgotten that holidays exist, but I think it's important to remain human in any situation. Wishing you lots of working graphics cards this year and that your Hideout never lets you down. Take this gift and be happy!</tu><ru>Ну, здравствуй, друг! С Новым годом тебя! Ты наверное забыл уже, что есть праздники, но я считаю, важно оставаться людьми в любой ситуации. Работающих видеокарт тебе в новом году и убежище чтобы не подводило. Держи и радуйся!</ru>",
|
||||||
|
"collectionTimeHours": 20,
|
||||||
|
"associatedEvent": "NewYears",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8ed8cb019004",
|
||||||
|
"_tpl": "5c1a1e3f2e221602b66cc4c2",
|
||||||
|
"parentId": "62b997444agc8eb4cb013004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b397573a8c8ed8cb019002",
|
||||||
|
"_tpl": "5aafbde786f774389d0cbc0f",
|
||||||
|
"parentId": "62b997444agc8eb4cb013004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b397573a8c8ed8cb019007",
|
||||||
|
"_tpl": "58dd3ad986f77403051cba8f",
|
||||||
|
"parentId": "62b997444agc8eb4cb013004",
|
||||||
|
"slotId": "main",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b397573a8c8ed8cb019008",
|
||||||
|
"_tpl": "58dd3ad986f77403051cba8f",
|
||||||
|
"parentId": "62b997444agc8eb4cb013004",
|
||||||
|
"slotId": "main",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b397573a8c8ed8cb019009",
|
||||||
|
"_tpl": "58dd3ad986f77403051cba8f",
|
||||||
|
"parentId": "62b997444agc8eb4cb013004",
|
||||||
|
"slotId": "main",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b497573b8c8ee8cb019009",
|
||||||
|
"_tpl": "619cbf9e0a7c3a1a2731940a",
|
||||||
|
"parentId": "62b997444agc8eb4cb013004",
|
||||||
|
"slotId": "main"
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Halloween 2023": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "1274650827982e40930946f4",
|
||||||
|
"_tpl": "6389c6463485cf0eeb260715",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "Halloween Event Text",
|
||||||
|
"timestampToSend": 42069,
|
||||||
|
"collectionTimeHours": 48,
|
||||||
|
"associatedEvent": "Halloween"
|
||||||
|
},
|
||||||
|
"Christmas 2022": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "a89075c1a18874dd6404a6d7",
|
||||||
|
"_tpl": "5aafbde786f774389d0cbc0f",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "Christmas Event Text",
|
||||||
|
"timestampToSend": 42069,
|
||||||
|
"associatedEvent": "Christmas"
|
||||||
|
},
|
||||||
|
"1CLICKDRESSUP": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359b7",
|
||||||
|
"_tpl": "5dcbd56fdbd3d91b3e5468d5",
|
||||||
|
"upd": {
|
||||||
|
"FireMode": {
|
||||||
|
"FireMode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parentId": "64b996c9d0de4697180359b6",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359b8",
|
||||||
|
"_tpl": "5c48a2c22e221602b313fb6c",
|
||||||
|
"parentId": "64b996c9d0de4697180359b7",
|
||||||
|
"slotId": "mod_pistol_grip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359b9",
|
||||||
|
"_tpl": "5a3501acc4a282000d72293a",
|
||||||
|
"parentId": "64b996c9d0de4697180359b7",
|
||||||
|
"slotId": "mod_magazine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359ba",
|
||||||
|
"_tpl": "5dcbd6b46ec07c0c4347a564",
|
||||||
|
"parentId": "64b996c9d0de4697180359b7",
|
||||||
|
"slotId": "mod_handguard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359bb",
|
||||||
|
"_tpl": "5b7be4895acfc400170e2dd5",
|
||||||
|
"parentId": "64b996c9d0de4697180359ba",
|
||||||
|
"slotId": "mod_mount_000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359bc",
|
||||||
|
"_tpl": "58c157be86f77403c74b2bb6",
|
||||||
|
"parentId": "64b996c9d0de4697180359bb",
|
||||||
|
"slotId": "mod_foregrip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359bd",
|
||||||
|
"_tpl": "5c06595c0db834001a66af6c",
|
||||||
|
"parentId": "64b996c9d0de4697180359ba",
|
||||||
|
"slotId": "mod_tactical"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359be",
|
||||||
|
"_tpl": "5dcbe9431e1f4616d354987e",
|
||||||
|
"parentId": "64b996c9d0de4697180359b7",
|
||||||
|
"slotId": "mod_barrel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359bf",
|
||||||
|
"_tpl": "5a34fd2bc4a282329a73b4c5",
|
||||||
|
"parentId": "64b996c9d0de4697180359be",
|
||||||
|
"slotId": "mod_muzzle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c0",
|
||||||
|
"_tpl": "59db7eed86f77461f8380365",
|
||||||
|
"parentId": "64b996c9d0de4697180359b7",
|
||||||
|
"slotId": "mod_scope"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c1",
|
||||||
|
"_tpl": "5c052a900db834001a66acbd",
|
||||||
|
"parentId": "64b996c9d0de4697180359c0",
|
||||||
|
"slotId": "mod_scope"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c2",
|
||||||
|
"_tpl": "5a33bab6c4a28200741e22f8",
|
||||||
|
"parentId": "64b996c9d0de4697180359c1",
|
||||||
|
"slotId": "mod_mount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c3",
|
||||||
|
"_tpl": "5a32aa8bc4a2826c6e06d737",
|
||||||
|
"parentId": "64b996c9d0de4697180359c2",
|
||||||
|
"slotId": "mod_scope"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c4",
|
||||||
|
"_tpl": "5dfa3d7ac41b2312ea33362a",
|
||||||
|
"parentId": "64b996c9d0de4697180359b7",
|
||||||
|
"slotId": "mod_sight_rear"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c5",
|
||||||
|
"_tpl": "628b9c7d45122232a872358f",
|
||||||
|
"parentId": "64b996c9d0de4697180359b6",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c6",
|
||||||
|
"_tpl": "62963c18dbc8ab5f0d382d0b",
|
||||||
|
"parentId": "64b996c9d0de4697180359b6",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b996c9d0de4697180359c7",
|
||||||
|
"_tpl": "59fb023c86f7746d0d4b423c",
|
||||||
|
"parentId": "64b996c9d0de4697180359b6",
|
||||||
|
"slotId": "main"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "1CLICKDRESSUP",
|
||||||
|
"timestampToSend": 42069,
|
||||||
|
"collectionTimeHours": 48,
|
||||||
|
"associatedEvent": "Promo"
|
||||||
|
},
|
||||||
|
"BARMALEY": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa55",
|
||||||
|
"_tpl": "5cadf6eeae921500134b2799",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa56",
|
||||||
|
"_tpl": "5cadf6eeae921500134b2799",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa57",
|
||||||
|
"_tpl": "5cadf6eeae921500134b2799",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa58",
|
||||||
|
"_tpl": "5cadf6eeae921500134b2799",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa59",
|
||||||
|
"_tpl": "62330bfadc5883093563729b",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa5a",
|
||||||
|
"_tpl": "62330bfadc5883093563729b",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa5b",
|
||||||
|
"_tpl": "62330bfadc5883093563729b",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa5c",
|
||||||
|
"_tpl": "62330bfadc5883093563729b",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 30
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa5d",
|
||||||
|
"_tpl": "5d1b376e86f774252519444e",
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa5e",
|
||||||
|
"_tpl": "5aafbde786f774389d0cbc0f",
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa5f",
|
||||||
|
"_tpl": "5aafbde786f774389d0cbc0f",
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa60",
|
||||||
|
"_tpl": "633ec7c2a6918cb895019c6c",
|
||||||
|
"upd": {
|
||||||
|
"Repairable": {
|
||||||
|
"MaxDurability": 100,
|
||||||
|
"Durability": 100
|
||||||
|
},
|
||||||
|
"FireMode": {
|
||||||
|
"FireMode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa61",
|
||||||
|
"_tpl": "633ec6ee025b096d320a3b15",
|
||||||
|
"parentId": "64b99751053fc8a45106fa60",
|
||||||
|
"slotId": "mod_magazine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa62",
|
||||||
|
"_tpl": "633ec8e4025b096d320a3b1e",
|
||||||
|
"parentId": "64b99751053fc8a45106fa60",
|
||||||
|
"slotId": "mod_pistol_grip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa63",
|
||||||
|
"_tpl": "61a4c8884f95bc3b2c5dc96f",
|
||||||
|
"upd": {
|
||||||
|
"FireMode": {
|
||||||
|
"FireMode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parentId": "64b99751053fc8a45106fa54",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa64",
|
||||||
|
"_tpl": "619f54a1d25cbd424731fb99",
|
||||||
|
"parentId": "64b99751053fc8a45106fa63",
|
||||||
|
"slotId": "mod_magazine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa65",
|
||||||
|
"_tpl": "619f4cee4c58466fe1228435",
|
||||||
|
"parentId": "64b99751053fc8a45106fa63",
|
||||||
|
"slotId": "mod_sight_rear"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa66",
|
||||||
|
"_tpl": "619f4d304c58466fe1228437",
|
||||||
|
"parentId": "64b99751053fc8a45106fa63",
|
||||||
|
"slotId": "mod_sight_front"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b99751053fc8a45106fa67",
|
||||||
|
"_tpl": "619f4bffd25cbd424731fb97",
|
||||||
|
"parentId": "64b99751053fc8a45106fa63",
|
||||||
|
"slotId": "mod_pistol_grip"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "BARMALEY",
|
||||||
|
"timestampToSend": 42069,
|
||||||
|
"collectionTimeHours": 48,
|
||||||
|
"associatedEvent": "Promo"
|
||||||
|
},
|
||||||
|
"S00NS00N": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb019005",
|
||||||
|
"_tpl": "619cbf9e0a7c3a1a2731940a",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb019006",
|
||||||
|
"_tpl": "5c94bbff86f7747ee735c08f",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb019007",
|
||||||
|
"_tpl": "5c94bbff86f7747ee735c08f",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb019008",
|
||||||
|
"_tpl": "5c94bbff86f7747ee735c08f",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb019009",
|
||||||
|
"_tpl": "5c94bbff86f7747ee735c08f",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb01900a",
|
||||||
|
"_tpl": "5c94bbff86f7747ee735c08f",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb01900b",
|
||||||
|
"_tpl": "5aafbcd986f7745e590fff23",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb01900c",
|
||||||
|
"_tpl": "5c0e530286f7747fa1419862",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb01900d",
|
||||||
|
"_tpl": "5c0e534186f7747fa1419867",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb01900e",
|
||||||
|
"_tpl": "5c0e533786f7747fa23f4d47",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb01900f",
|
||||||
|
"_tpl": "544fb3f34bdc2d03748b456a",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b997574a8c8eb8cb019010",
|
||||||
|
"_tpl": "5c0e531d86f7747fa23f4d42",
|
||||||
|
"parentId": "64b997574a8c8eb8cb019004",
|
||||||
|
"slotId": "main"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "S00NS00N",
|
||||||
|
"timestampToSend": 42069,
|
||||||
|
"collectionTimeHours": 48,
|
||||||
|
"associatedEvent": "Promo"
|
||||||
|
},
|
||||||
|
"TRAMBON": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "64b9975c0863630f260c592a",
|
||||||
|
"_tpl": "591094e086f7747caa7bb2ef",
|
||||||
|
"parentId": "64b9975c0863630f260c5929",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b9975c0863630f260c592b",
|
||||||
|
"_tpl": "5e4abb5086f77406975c9342",
|
||||||
|
"parentId": "64b9975c0863630f260c5929",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b9975c0863630f260c592c",
|
||||||
|
"_tpl": "5ca2151486f774244a3b8d30",
|
||||||
|
"parentId": "64b9975c0863630f260c5929",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b9975c0863630f260c592d",
|
||||||
|
"_tpl": "60a3c68c37ea821725773ef5",
|
||||||
|
"parentId": "64b9975c0863630f260c5929",
|
||||||
|
"slotId": "main"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "TRAMBON",
|
||||||
|
"timestampToSend": 42069,
|
||||||
|
"collectionTimeHours": 48,
|
||||||
|
"associatedEvent": "Promo"
|
||||||
|
},
|
||||||
|
"PINEWOOD": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecc3",
|
||||||
|
"_tpl": "63a39fc0af870e651d58e6ae",
|
||||||
|
"parentId": "64b998310a0c2d62990eecc2",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecc4",
|
||||||
|
"_tpl": "5fc382a9d724d907e2077dab",
|
||||||
|
"upd": {
|
||||||
|
"StackObjectsCount": 20
|
||||||
|
},
|
||||||
|
"parentId": "64b998310a0c2d62990eecc2",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecc5",
|
||||||
|
"_tpl": "5fc22d7c187fea44d52eda44",
|
||||||
|
"upd": {
|
||||||
|
"FireMode": {
|
||||||
|
"FireMode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parentId": "64b998310a0c2d62990eecc2",
|
||||||
|
"slotId": "main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecc6",
|
||||||
|
"_tpl": "57c55efc2459772d2c6271e7",
|
||||||
|
"parentId": "64b998310a0c2d62990eecc5",
|
||||||
|
"slotId": "mod_pistol_grip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecc7",
|
||||||
|
"_tpl": "5fc23426900b1d5091531e15",
|
||||||
|
"parentId": "64b998310a0c2d62990eecc5",
|
||||||
|
"slotId": "mod_magazine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecc8",
|
||||||
|
"_tpl": "5649be884bdc2d79388b4577",
|
||||||
|
"parentId": "64b998310a0c2d62990eecc5",
|
||||||
|
"slotId": "mod_stock_001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecc9",
|
||||||
|
"_tpl": "5fc2369685fd526b824a5713",
|
||||||
|
"parentId": "64b998310a0c2d62990eecc8",
|
||||||
|
"slotId": "mod_stock_000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecca",
|
||||||
|
"_tpl": "5fc278107283c4046c581489",
|
||||||
|
"parentId": "64b998310a0c2d62990eecc5",
|
||||||
|
"slotId": "mod_reciever"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eeccb",
|
||||||
|
"_tpl": "5fc23678ab884124df0cd590",
|
||||||
|
"parentId": "64b998310a0c2d62990eecca",
|
||||||
|
"slotId": "mod_barrel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eeccc",
|
||||||
|
"_tpl": "5fc23636016cce60e8341b05",
|
||||||
|
"parentId": "64b998310a0c2d62990eeccb",
|
||||||
|
"slotId": "mod_muzzle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eeccd",
|
||||||
|
"_tpl": "5fc2360f900b1d5091531e19",
|
||||||
|
"parentId": "64b998310a0c2d62990eeccb",
|
||||||
|
"slotId": "mod_gas_block"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "64b998310a0c2d62990eecce",
|
||||||
|
"_tpl": "5fc235db2770a0045c59c683",
|
||||||
|
"parentId": "64b998310a0c2d62990eecca",
|
||||||
|
"slotId": "mod_handguard"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sender": "System",
|
||||||
|
"messageText": "PINEWOOD",
|
||||||
|
"timestampToSend": 42069,
|
||||||
|
"collectionTimeHours": 48,
|
||||||
|
"associatedEvent": "Promo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
|
|
||||||
|
import { GiftSentResult } from "@spt-aki/models/enums/GiftSentResult";
|
||||||
import { DialogueHelper } from "../helpers/DialogueHelper";
|
import { DialogueHelper } from "../helpers/DialogueHelper";
|
||||||
import { IGetAllAttachmentsResponse } from "../models/eft/dialog/IGetAllAttachmentsResponse";
|
import { IGetAllAttachmentsResponse } from "../models/eft/dialog/IGetAllAttachmentsResponse";
|
||||||
import { IGetFriendListDataResponse } from "../models/eft/dialog/IGetFriendListDataResponse";
|
import { IGetFriendListDataResponse } from "../models/eft/dialog/IGetFriendListDataResponse";
|
||||||
@ -11,7 +12,10 @@ import { ISendMessageRequest } from "../models/eft/dialog/ISendMessageRequest";
|
|||||||
import { Dialogue, DialogueInfo, IAkiProfile, IUserDialogInfo, Message } from "../models/eft/profile/IAkiProfile";
|
import { Dialogue, DialogueInfo, IAkiProfile, IUserDialogInfo, Message } from "../models/eft/profile/IAkiProfile";
|
||||||
import { MemberCategory } from "../models/enums/MemberCategory";
|
import { MemberCategory } from "../models/enums/MemberCategory";
|
||||||
import { MessageType } from "../models/enums/MessageType";
|
import { MessageType } from "../models/enums/MessageType";
|
||||||
|
import { ILogger } from "../models/spt/utils/ILogger";
|
||||||
import { SaveServer } from "../servers/SaveServer";
|
import { SaveServer } from "../servers/SaveServer";
|
||||||
|
import { GiftService } from "../services/GiftService";
|
||||||
|
import { MailSendService } from "../services/MailSendService";
|
||||||
import { HashUtil } from "../utils/HashUtil";
|
import { HashUtil } from "../utils/HashUtil";
|
||||||
import { TimeUtil } from "../utils/TimeUtil";
|
import { TimeUtil } from "../utils/TimeUtil";
|
||||||
|
|
||||||
@ -19,9 +23,12 @@ import { TimeUtil } from "../utils/TimeUtil";
|
|||||||
export class DialogueController
|
export class DialogueController
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
@inject("SaveServer") protected saveServer: SaveServer,
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
||||||
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
|
@inject("GiftService") protected giftService: GiftService,
|
||||||
@inject("HashUtil") protected hashUtil: HashUtil
|
@inject("HashUtil") protected hashUtil: HashUtil
|
||||||
)
|
)
|
||||||
{}
|
{}
|
||||||
@ -46,7 +53,7 @@ export class DialogueController
|
|||||||
return {
|
return {
|
||||||
"Friends": [
|
"Friends": [
|
||||||
{
|
{
|
||||||
_id: "sptfriend",
|
_id: "sptFriend",
|
||||||
Info: {
|
Info: {
|
||||||
Level: 1,
|
Level: 1,
|
||||||
MemberCategory: MemberCategory.DEVELOPER,
|
MemberCategory: MemberCategory.DEVELOPER,
|
||||||
@ -89,25 +96,36 @@ export class DialogueController
|
|||||||
const dialogue = this.saveServer.getProfile(sessionID).dialogues[dialogueID];
|
const dialogue = this.saveServer.getProfile(sessionID).dialogues[dialogueID];
|
||||||
|
|
||||||
const result: DialogueInfo = {
|
const result: DialogueInfo = {
|
||||||
"_id": dialogueID,
|
_id: dialogueID,
|
||||||
"type": dialogue.type ? dialogue.type : MessageType.NPC_TRADER,
|
type: dialogue.type ? dialogue.type : MessageType.NPC_TRADER,
|
||||||
"message": this.dialogueHelper.getMessagePreview(dialogue),
|
message: this.dialogueHelper.getMessagePreview(dialogue),
|
||||||
"new": dialogue.new,
|
new: dialogue.new,
|
||||||
"attachmentsNew": dialogue.attachmentsNew,
|
attachmentsNew: dialogue.attachmentsNew,
|
||||||
"pinned": dialogue.pinned,
|
pinned: dialogue.pinned,
|
||||||
Users: this.getDialogueUsers(dialogue.Users, dialogue.type, sessionID)
|
Users: this.getDialogueUsers(dialogue, dialogue.type, sessionID)
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
public getDialogueUsers(users: IUserDialogInfo[], messageType: MessageType, sessionID: string): IUserDialogInfo[]
|
* Todo
|
||||||
|
* @param users
|
||||||
|
* @param messageType
|
||||||
|
* @param sessionID
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getDialogueUsers(dialog: Dialogue, messageType: MessageType, sessionID: string): IUserDialogInfo[]
|
||||||
{
|
{
|
||||||
const profile = this.saveServer.getProfile(sessionID);
|
const profile = this.saveServer.getProfile(sessionID);
|
||||||
|
|
||||||
if (messageType === MessageType.USER_MESSAGE && !users.find(x => x._id === profile.characters.pmc._id))
|
if (messageType === MessageType.USER_MESSAGE && !dialog.Users?.find(x => x._id === profile.characters.pmc._id))
|
||||||
{
|
{
|
||||||
users.push({
|
if (!dialog.Users)
|
||||||
|
{
|
||||||
|
dialog.Users = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.Users.push({
|
||||||
_id: profile.characters.pmc._id,
|
_id: profile.characters.pmc._id,
|
||||||
info: {
|
info: {
|
||||||
Level: profile.characters.pmc.Info.Level,
|
Level: profile.characters.pmc.Info.Level,
|
||||||
@ -118,7 +136,7 @@ export class DialogueController
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return users ? users : undefined;
|
return dialog.Users ? dialog.Users : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,7 +202,12 @@ export class DialogueController
|
|||||||
|
|
||||||
return profile.dialogues[request.dialogId];
|
return profile.dialogues[request.dialogId];
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* @param pmcProfile
|
||||||
|
* @param dialogUsers
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
protected getProfilesForMail(pmcProfile: IAkiProfile, dialogUsers: IUserDialogInfo[]): IUserDialogInfo[]
|
protected getProfilesForMail(pmcProfile: IAkiProfile, dialogUsers: IUserDialogInfo[]): IUserDialogInfo[]
|
||||||
{
|
{
|
||||||
const result: IUserDialogInfo[] = [];
|
const result: IUserDialogInfo[] = [];
|
||||||
@ -285,37 +308,51 @@ export class DialogueController
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
public sendMessage(sessionId: string, request: ISendMessageRequest): string
|
public sendMessage(sessionId: string, request: ISendMessageRequest): string
|
||||||
{
|
{
|
||||||
const profile = this.saveServer.getProfile(sessionId);
|
this.mailSendService.sendPlayerMessageToNpc(sessionId, request.dialogId, request.text);
|
||||||
const dialog = profile.dialogues[request.dialogId];
|
|
||||||
dialog.messages.push({
|
|
||||||
_id: sessionId,
|
|
||||||
dt: this.timeUtil.getTimestamp(),
|
|
||||||
hasRewards: false,
|
|
||||||
items: {},
|
|
||||||
uid: profile.characters.pmc._id,
|
|
||||||
type: MessageType.USER_MESSAGE,
|
|
||||||
rewardCollected: false,
|
|
||||||
text: request.text
|
|
||||||
});
|
|
||||||
|
|
||||||
if (request.dialogId.includes("sptfriend") && request.text.includes("love you"))
|
// Handle when player types a keyword to sptfriend user
|
||||||
|
if (request.dialogId.includes("sptFriend"))
|
||||||
{
|
{
|
||||||
dialog.messages.push({
|
this.handleChatWithSPTFriend(sessionId, request);
|
||||||
_id: "sptfriend",
|
|
||||||
dt: this.timeUtil.getTimestamp()+1,
|
|
||||||
hasRewards: false,
|
|
||||||
items: {},
|
|
||||||
uid: "sptfriend",
|
|
||||||
type: MessageType.USER_MESSAGE,
|
|
||||||
rewardCollected: false,
|
|
||||||
text: "i love you too buddy :3"
|
|
||||||
});
|
|
||||||
dialog.new = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return request.dialogId;
|
return request.dialogId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected handleChatWithSPTFriend(sessionId: string, request: ISendMessageRequest): void
|
||||||
|
{
|
||||||
|
const sptFriendUser: IUserDialogInfo = {
|
||||||
|
_id: "sptFriend",
|
||||||
|
info: {
|
||||||
|
Level: 1,
|
||||||
|
MemberCategory: MemberCategory.DEVELOPER,
|
||||||
|
Nickname: "SPT",
|
||||||
|
Side: "Usec"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const giftSent = this.giftService.sendGiftToPlayer(sessionId, request.text);
|
||||||
|
|
||||||
|
if (giftSent === GiftSentResult.SUCCESS)
|
||||||
|
{
|
||||||
|
this.mailSendService.sendUserMessageToPlayer(sessionId, sptFriendUser, "hey! you got the right code!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (giftSent === GiftSentResult.FAILED_GIFT_ALREADY_RECEIVED)
|
||||||
|
{
|
||||||
|
this.mailSendService.sendUserMessageToPlayer(sessionId, sptFriendUser, "You already have that!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.text.toLowerCase().includes("love you"))
|
||||||
|
{
|
||||||
|
this.mailSendService.sendUserMessageToPlayer(sessionId, sptFriendUser, "I love you too buddy :3!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.text.toLowerCase() === "spt")
|
||||||
|
{
|
||||||
|
this.mailSendService.sendUserMessageToPlayer(sessionId, sptFriendUser, "its me!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get messages from a specific dialog that have items not expired
|
* Get messages from a specific dialog that have items not expired
|
||||||
* @param sessionId Session id
|
* @param sessionId Session id
|
||||||
|
@ -584,7 +584,7 @@ export class GameController
|
|||||||
if (!traderAssorts.loyal_level_items[assortKey])
|
if (!traderAssorts.loyal_level_items[assortKey])
|
||||||
{
|
{
|
||||||
// reverse lookup of enum key by value
|
// reverse lookup of enum key by value
|
||||||
this.logger.warning(this.localisationService.getText("assort-missing_quest_assort_unlock", {traderName: Object.keys(Traders)[Object.values(Traders).indexOf(traderId)], questName: quests[questKey].QuestName}));
|
this.logger.debug(this.localisationService.getText("assort-missing_quest_assort_unlock", {traderName: Object.keys(Traders)[Object.values(Traders).indexOf(traderId)], questName: quests[questKey].QuestName}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { ItemHelper } from "../helpers/ItemHelper";
|
|||||||
import { ProfileHelper } from "../helpers/ProfileHelper";
|
import { ProfileHelper } from "../helpers/ProfileHelper";
|
||||||
import { QuestConditionHelper } from "../helpers/QuestConditionHelper";
|
import { QuestConditionHelper } from "../helpers/QuestConditionHelper";
|
||||||
import { QuestHelper } from "../helpers/QuestHelper";
|
import { QuestHelper } from "../helpers/QuestHelper";
|
||||||
|
import { TraderHelper } from "../helpers/TraderHelper";
|
||||||
import { IPmcData } from "../models/eft/common/IPmcData";
|
import { IPmcData } from "../models/eft/common/IPmcData";
|
||||||
import { Quest } from "../models/eft/common/tables/IBotBase";
|
import { Quest } from "../models/eft/common/tables/IBotBase";
|
||||||
import { Item } from "../models/eft/common/tables/IItem";
|
import { Item } from "../models/eft/common/tables/IItem";
|
||||||
@ -26,6 +27,7 @@ import { ConfigServer } from "../servers/ConfigServer";
|
|||||||
import { DatabaseServer } from "../servers/DatabaseServer";
|
import { DatabaseServer } from "../servers/DatabaseServer";
|
||||||
import { LocaleService } from "../services/LocaleService";
|
import { LocaleService } from "../services/LocaleService";
|
||||||
import { LocalisationService } from "../services/LocalisationService";
|
import { LocalisationService } from "../services/LocalisationService";
|
||||||
|
import { MailSendService } from "../services/MailSendService";
|
||||||
import { PlayerService } from "../services/PlayerService";
|
import { PlayerService } from "../services/PlayerService";
|
||||||
import { SeasonalEventService } from "../services/SeasonalEventService";
|
import { SeasonalEventService } from "../services/SeasonalEventService";
|
||||||
import { HttpResponseUtil } from "../utils/HttpResponseUtil";
|
import { HttpResponseUtil } from "../utils/HttpResponseUtil";
|
||||||
@ -44,7 +46,9 @@ export class QuestController
|
|||||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
||||||
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||||
@inject("QuestHelper") protected questHelper: QuestHelper,
|
@inject("QuestHelper") protected questHelper: QuestHelper,
|
||||||
@inject("QuestConditionHelper") protected questConditionHelper: QuestConditionHelper,
|
@inject("QuestConditionHelper") protected questConditionHelper: QuestConditionHelper,
|
||||||
@inject("PlayerService") protected playerService: PlayerService,
|
@inject("PlayerService") protected playerService: PlayerService,
|
||||||
@ -427,9 +431,14 @@ export class QuestController
|
|||||||
protected sendSuccessDialogMessageOnQuestComplete(sessionID: string, pmcData: IPmcData, completedQuestId: string, questRewards: Reward[]): void
|
protected sendSuccessDialogMessageOnQuestComplete(sessionID: string, pmcData: IPmcData, completedQuestId: string, questRewards: Reward[]): void
|
||||||
{
|
{
|
||||||
const quest = this.questHelper.getQuestFromDb(completedQuestId, pmcData);
|
const quest = this.questHelper.getQuestFromDb(completedQuestId, pmcData);
|
||||||
const messageContent = this.dialogueHelper.createMessageContext(quest.successMessageText, MessageType.QUEST_SUCCESS, this.questConfig.redeemTime);
|
|
||||||
|
|
||||||
this.dialogueHelper.addDialogueMessage(quest.traderId, messageContent, sessionID, questRewards);
|
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
||||||
|
sessionID,
|
||||||
|
this.traderHelper.getTraderById(quest.traderId),
|
||||||
|
MessageType.QUEST_SUCCESS,
|
||||||
|
quest.successMessageText,
|
||||||
|
questRewards,
|
||||||
|
this.timeUtil.getHoursAsSeconds(this.questConfig.redeemTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,6 +201,7 @@ import { ItemBaseClassService } from "../services/ItemBaseClassService";
|
|||||||
import { ItemFilterService } from "../services/ItemFilterService";
|
import { ItemFilterService } from "../services/ItemFilterService";
|
||||||
import { LocaleService } from "../services/LocaleService";
|
import { LocaleService } from "../services/LocaleService";
|
||||||
import { LocalisationService } from "../services/LocalisationService";
|
import { LocalisationService } from "../services/LocalisationService";
|
||||||
|
import { MailSendService } from "../services/MailSendService";
|
||||||
import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService";
|
import { MatchBotDetailsCacheService } from "../services/MatchBotDetailsCacheService";
|
||||||
import { MatchLocationService } from "../services/MatchLocationService";
|
import { MatchLocationService } from "../services/MatchLocationService";
|
||||||
import { ModCompilerService } from "../services/ModCompilerService";
|
import { ModCompilerService } from "../services/ModCompilerService";
|
||||||
@ -610,6 +611,7 @@ export class Container
|
|||||||
depContainer.register<TraderPurchasePersisterService>("TraderPurchasePersisterService", TraderPurchasePersisterService);
|
depContainer.register<TraderPurchasePersisterService>("TraderPurchasePersisterService", TraderPurchasePersisterService);
|
||||||
depContainer.register<PmcChatResponseService>("PmcChatResponseService", PmcChatResponseService);
|
depContainer.register<PmcChatResponseService>("PmcChatResponseService", PmcChatResponseService);
|
||||||
depContainer.register<GiftService>("GiftService", GiftService);
|
depContainer.register<GiftService>("GiftService", GiftService);
|
||||||
|
depContainer.register<MailSendService>("MailSendService", MailSendService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static registerServers(depContainer: DependencyContainer): void
|
private static registerServers(depContainer: DependencyContainer): void
|
||||||
|
@ -429,13 +429,13 @@ export class BotGenerator
|
|||||||
if (botInfo.Nickname.toLowerCase() === "nikita")
|
if (botInfo.Nickname.toLowerCase() === "nikita")
|
||||||
{
|
{
|
||||||
botInfo.GameVersion = "edge_of_darkness";
|
botInfo.GameVersion = "edge_of_darkness";
|
||||||
botInfo.AccountType = MemberCategory.DEVELOPER;
|
botInfo.MemberCategory = MemberCategory.DEVELOPER;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
botInfo.GameVersion = this.weightedRandomHelper.getWeightedValue(this.botConfig.pmc.gameVersionWeight);
|
botInfo.GameVersion = this.weightedRandomHelper.getWeightedValue(this.botConfig.pmc.gameVersionWeight);
|
||||||
botInfo.AccountType = Number.parseInt(this.weightedRandomHelper.getWeightedValue(this.botConfig.pmc.accountTypeWeight));
|
botInfo.MemberCategory = Number.parseInt(this.weightedRandomHelper.getWeightedValue(this.botConfig.pmc.accountTypeWeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -356,7 +356,7 @@ export class BotWeaponGenerator
|
|||||||
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot([EquipmentSlots.SECURED_CONTAINER], id, ammoTpl, [{
|
this.botWeaponGeneratorHelper.addItemWithChildrenToEquipmentSlot([EquipmentSlots.SECURED_CONTAINER], id, ammoTpl, [{
|
||||||
_id: id,
|
_id: id,
|
||||||
_tpl: ammoTpl,
|
_tpl: ammoTpl,
|
||||||
upd: { "StackObjectsCount": stackSize }
|
upd: { StackObjectsCount: stackSize }
|
||||||
}],
|
}],
|
||||||
inventory);
|
inventory);
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,7 @@ export class BotWeaponGeneratorHelper
|
|||||||
if (!container)
|
if (!container)
|
||||||
{
|
{
|
||||||
// Desired equipment container (e.g. backpack) not found
|
// Desired equipment container (e.g. backpack) not found
|
||||||
|
this.logger.warning(`Unable to add items to bot slot: ${slot}, slot missing`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,27 +29,25 @@ export class DialogueHelper
|
|||||||
{ }
|
{ }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create basic message context template
|
* @deprecated Use MailSendService.sendMessage() or helpers
|
||||||
* @param templateId
|
|
||||||
* @param messageType
|
|
||||||
* @param maxStoreTime
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
public createMessageContext(templateId: string, messageType: MessageType, maxStoreTime: number): MessageContent
|
public createMessageContext(templateId: string, messageType: MessageType, maxStoreTime = null): MessageContent
|
||||||
{
|
{
|
||||||
return {
|
const result: MessageContent = {
|
||||||
templateId: templateId,
|
templateId: templateId,
|
||||||
type: messageType,
|
type: messageType
|
||||||
maxStorageTime: maxStoreTime * TimeUtil.oneHourAsSeconds
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (maxStoreTime)
|
||||||
|
{
|
||||||
|
result.maxStorageTime = maxStoreTime * TimeUtil.oneHourAsSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a templated message to the dialogue.
|
* @deprecated Use MailSendService.sendMessage() or helpers
|
||||||
* @param dialogueID
|
|
||||||
* @param messageContent
|
|
||||||
* @param sessionID
|
|
||||||
* @param rewards
|
|
||||||
*/
|
*/
|
||||||
public addDialogueMessage(dialogueID: string, messageContent: MessageContent, sessionID: string, rewards: Item[] = [], messageType = MessageType.NPC_TRADER): void
|
public addDialogueMessage(dialogueID: string, messageContent: MessageContent, sessionID: string, rewards: Item[] = [], messageType = MessageType.NPC_TRADER): void
|
||||||
{
|
{
|
||||||
@ -129,7 +127,7 @@ export class DialogueHelper
|
|||||||
dt: Math.round(Date.now() / 1000),
|
dt: Math.round(Date.now() / 1000),
|
||||||
text: messageContent.text ?? "",
|
text: messageContent.text ?? "",
|
||||||
templateId: messageContent.templateId,
|
templateId: messageContent.templateId,
|
||||||
hasRewards: rewards.length > 0,
|
hasRewards: items.data?.length > 0,
|
||||||
rewardCollected: false,
|
rewardCollected: false,
|
||||||
items: items,
|
items: items,
|
||||||
maxStorageTime: messageContent.maxStorageTime,
|
maxStorageTime: messageContent.maxStorageTime,
|
||||||
@ -137,6 +135,11 @@ export class DialogueHelper
|
|||||||
profileChangeEvents: (messageContent.profileChangeEvents?.length === 0) ? messageContent.profileChangeEvents : undefined
|
profileChangeEvents: (messageContent.profileChangeEvents?.length === 0) ? messageContent.profileChangeEvents : undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!message.templateId)
|
||||||
|
{
|
||||||
|
delete message.templateId;
|
||||||
|
}
|
||||||
|
|
||||||
dialogue.messages.push(message);
|
dialogue.messages.push(message);
|
||||||
|
|
||||||
// Offer Sold notifications are now separate from the main notification
|
// Offer Sold notifications are now separate from the main notification
|
||||||
|
@ -294,4 +294,39 @@ export class ProfileHelper
|
|||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag a profile as having received a gift
|
||||||
|
* Store giftid in profile aki object
|
||||||
|
* @param playerId Player to add gift flag to
|
||||||
|
* @param giftId Gift player received
|
||||||
|
*/
|
||||||
|
public addGiftReceivedFlagToProfile(playerId: string, giftId: string): void
|
||||||
|
{
|
||||||
|
const profileToUpdate = this.getFullProfile(playerId);
|
||||||
|
const giftHistory = profileToUpdate.aki.receivedGifts;
|
||||||
|
if (!giftHistory)
|
||||||
|
{
|
||||||
|
profileToUpdate.aki.receivedGifts = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
profileToUpdate.aki.receivedGifts.push({giftId: giftId, timestampAccepted: this.timeUtil.getTimestamp()});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if profile has recieved a gift by id
|
||||||
|
* @param playerId Player profile to check for gift
|
||||||
|
* @param giftId Gift to check for
|
||||||
|
* @returns True if player has recieved gift previously
|
||||||
|
*/
|
||||||
|
public playerHasRecievedGift(playerId: string, giftId: string): boolean
|
||||||
|
{
|
||||||
|
const profile = this.getFullProfile(playerId);
|
||||||
|
if (!profile.aki.receivedGifts)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!profile.aki.receivedGifts.find(x => x.giftId === giftId);
|
||||||
|
}
|
||||||
}
|
}
|
@ -386,4 +386,16 @@ export class TraderHelper
|
|||||||
|
|
||||||
return this.highestTraderBuyPriceItems[tpl];
|
return this.highestTraderBuyPriceItems[tpl];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a trader enum key by its value
|
||||||
|
* @param traderId Traders id
|
||||||
|
* @returns Traders key
|
||||||
|
*/
|
||||||
|
public getTraderById(traderId: string): Traders
|
||||||
|
{
|
||||||
|
const keys = Object.keys(Traders).filter(x => Traders[x] === traderId);
|
||||||
|
|
||||||
|
return keys.length > 0 ? keys[0] as Traders : null;
|
||||||
|
}
|
||||||
}
|
}
|
@ -81,7 +81,7 @@ export interface DialogueInfo
|
|||||||
_id: string
|
_id: string
|
||||||
type: MessageType
|
type: MessageType
|
||||||
pinned: boolean
|
pinned: boolean
|
||||||
Users?: any[]
|
Users?: IUserDialogInfo[]
|
||||||
message: MessagePreview
|
message: MessagePreview
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ export interface Message
|
|||||||
text?: string
|
text?: string
|
||||||
hasRewards: boolean
|
hasRewards: boolean
|
||||||
rewardCollected: boolean
|
rewardCollected: boolean
|
||||||
items: MessageItems
|
items?: MessageItems
|
||||||
maxStorageTime?: number
|
maxStorageTime?: number
|
||||||
systemData?: ISystemData
|
systemData?: ISystemData
|
||||||
profileChangeEvents?: any[]
|
profileChangeEvents?: any[]
|
||||||
@ -149,6 +149,7 @@ export interface Aki
|
|||||||
{
|
{
|
||||||
version: string
|
version: string
|
||||||
mods?: ModDetails[]
|
mods?: ModDetails[]
|
||||||
|
receivedGifts: ReceivedGift[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModDetails
|
export interface ModDetails
|
||||||
@ -159,6 +160,12 @@ export interface ModDetails
|
|||||||
dateAdded: number
|
dateAdded: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReceivedGift
|
||||||
|
{
|
||||||
|
giftId: string
|
||||||
|
timestampAccepted: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface Vitality
|
export interface Vitality
|
||||||
{
|
{
|
||||||
health: Health
|
health: Health
|
||||||
|
@ -22,5 +22,6 @@ export enum ConfigTypes
|
|||||||
TRADER = "aki-trader",
|
TRADER = "aki-trader",
|
||||||
WEATHER = "aki-weather",
|
WEATHER = "aki-weather",
|
||||||
SEASONAL_EVENT = "aki-seasonalevents",
|
SEASONAL_EVENT = "aki-seasonalevents",
|
||||||
LOST_ON_DEATH = "aki-lostondeath"
|
LOST_ON_DEATH = "aki-lostondeath",
|
||||||
|
GIFTS = "aki-gifts"
|
||||||
}
|
}
|
6
project/src/models/enums/GiftSenderType.ts
Normal file
6
project/src/models/enums/GiftSenderType.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum GiftSenderType
|
||||||
|
{
|
||||||
|
SYSTEM = "System",
|
||||||
|
TRADER = "Trader",
|
||||||
|
USER = "User"
|
||||||
|
}
|
7
project/src/models/enums/GiftSentResult.ts
Normal file
7
project/src/models/enums/GiftSentResult.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum GiftSentResult
|
||||||
|
{
|
||||||
|
FAILED_UNKNOWN = 1,
|
||||||
|
FAILED_GIFT_ALREADY_RECEIVED = 2,
|
||||||
|
FAILED_GIFT_DOESNT_EXIST = 3,
|
||||||
|
SUCCESS = 4
|
||||||
|
}
|
@ -2,5 +2,7 @@ export enum SeasonalEventType
|
|||||||
{
|
{
|
||||||
NONE = "None",
|
NONE = "None",
|
||||||
CHRISTMAS = "Christmas",
|
CHRISTMAS = "Christmas",
|
||||||
HALLOWEEN = "Halloween"
|
HALLOWEEN = "Halloween",
|
||||||
|
NEW_YEARS = "NewYears",
|
||||||
|
PROMO = "Promo"
|
||||||
}
|
}
|
30
project/src/models/spt/config/IGiftsConfig.ts
Normal file
30
project/src/models/spt/config/IGiftsConfig.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Item } from "../../../models/eft/common/tables/IItem";
|
||||||
|
import { IUserDialogInfo } from "../../../models/eft/profile/IAkiProfile";
|
||||||
|
import { GiftSenderType } from "../../../models/enums/GiftSenderType";
|
||||||
|
import { SeasonalEventType } from "../../../models/enums/SeasonalEventType";
|
||||||
|
import { Traders } from "../../../models/enums/Traders";
|
||||||
|
import { IBaseConfig } from "./IBaseConfig";
|
||||||
|
|
||||||
|
export interface IGiftsConfig extends IBaseConfig
|
||||||
|
{
|
||||||
|
kind: "aki-gifts"
|
||||||
|
gifts: Record<string, Gift>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Gift
|
||||||
|
{
|
||||||
|
/** Items to send to player */
|
||||||
|
items: Item[]
|
||||||
|
/** Who is sending the gift to player */
|
||||||
|
sender: GiftSenderType
|
||||||
|
/** Optinal - supply a users id to send from, not necessary when sending from SYSTEM or TRADER */
|
||||||
|
senderId?: string
|
||||||
|
senderDetails: IUserDialogInfo,
|
||||||
|
/** Optional - supply a trader type to send from, not necessary when sending from SYSTEM or USER */
|
||||||
|
trader?: Traders
|
||||||
|
messageText: string
|
||||||
|
/** Optional - Used by Seasonal events to send on specific day */
|
||||||
|
timestampToSend?: number
|
||||||
|
associatedEvent: SeasonalEventType
|
||||||
|
collectionTimeHours: number
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { IBaseConfig } from "./IBaseConfig";
|
|||||||
export interface IQuestConfig extends IBaseConfig
|
export interface IQuestConfig extends IBaseConfig
|
||||||
{
|
{
|
||||||
kind: "aki-quest"
|
kind: "aki-quest"
|
||||||
|
// Hours to get/redeem items from quest mail
|
||||||
redeemTime: number
|
redeemTime: number
|
||||||
questTemplateIds: IPlayerTypeQuestIds
|
questTemplateIds: IPlayerTypeQuestIds
|
||||||
/** Show non-seasonal quests be shown to player */
|
/** Show non-seasonal quests be shown to player */
|
||||||
|
32
project/src/models/spt/dialog/ISendMessageDetails.ts
Normal file
32
project/src/models/spt/dialog/ISendMessageDetails.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Item } from "../../../models/eft/common/tables/IItem";
|
||||||
|
import { ISystemData, IUserDialogInfo, MessageContentRagfair } from "../../../models/eft/profile/IAkiProfile";
|
||||||
|
import { MessageType } from "../../../models/enums/MessageType";
|
||||||
|
import { Traders } from "../../../models/enums/Traders";
|
||||||
|
|
||||||
|
export interface ISendMessageDetails
|
||||||
|
{
|
||||||
|
/** Player id */
|
||||||
|
recipientId: string
|
||||||
|
/** Who is sending this message */
|
||||||
|
sender: MessageType
|
||||||
|
/** Optional - leave blank to use sender value */
|
||||||
|
dialogType?: MessageType
|
||||||
|
/** Optional - if sender is USER these details are used */
|
||||||
|
senderDetails?: IUserDialogInfo
|
||||||
|
/** Optional - the trader sending the message */
|
||||||
|
trader?: Traders
|
||||||
|
/** Optional - used in player/system messages, otherwise templateId is used */
|
||||||
|
messageText?: string
|
||||||
|
/** Optinal - Items to send to player */
|
||||||
|
items?: Item[];
|
||||||
|
/** Optional - How long items will be stored in mail before expiry */
|
||||||
|
itemsMaxStorageLifetimeSeconds?: number
|
||||||
|
/** Optional - Used when sending messages from traders who send text from locale json */
|
||||||
|
templateId?: string
|
||||||
|
/** Optional - ragfair related */
|
||||||
|
systemData?: ISystemData
|
||||||
|
/** Optional - Used by ragfair messages */
|
||||||
|
ragfairDetails?: MessageContentRagfair
|
||||||
|
/** Optional - Usage not known, unsure of purpose, even dumps dont have it */
|
||||||
|
profileChangeEvents?: any[]
|
||||||
|
}
|
@ -1,37 +1,156 @@
|
|||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
import { DialogueHelper } from "../helpers/DialogueHelper";
|
import { ProfileHelper } from "../helpers/ProfileHelper";
|
||||||
import { ConfigTypes } from "../models/enums/ConfigTypes";
|
import { ConfigTypes } from "../models/enums/ConfigTypes";
|
||||||
|
import { GiftSenderType } from "../models/enums/GiftSenderType";
|
||||||
|
import { GiftSentResult } from "../models/enums/GiftSentResult";
|
||||||
import { MessageType } from "../models/enums/MessageType";
|
import { MessageType } from "../models/enums/MessageType";
|
||||||
|
import { Gift, IGiftsConfig } from "../models/spt/config/IGiftsConfig";
|
||||||
|
import { ISendMessageDetails } from "../models/spt/dialog/ISendMessageDetails";
|
||||||
import { ILogger } from "../models/spt/utils/ILogger";
|
import { ILogger } from "../models/spt/utils/ILogger";
|
||||||
import { ConfigServer } from "../servers/ConfigServer";
|
import { ConfigServer } from "../servers/ConfigServer";
|
||||||
|
import { HashUtil } from "../utils/HashUtil";
|
||||||
|
import { TimeUtil } from "../utils/TimeUtil";
|
||||||
|
import { MailSendService } from "./MailSendService";
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class GiftService
|
export class GiftService
|
||||||
{
|
{
|
||||||
protected giftConfig: any;
|
protected giftConfig: IGiftsConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject("WinstonLogger") protected logger: ILogger,
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||||
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
@inject("ConfigServer") protected configServer: ConfigServer
|
@inject("ConfigServer") protected configServer: ConfigServer
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.giftConfig = this.configServer.getConfig(ConfigTypes.QUEST);
|
this.giftConfig = this.configServer.getConfig(ConfigTypes.GIFTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a player a gift
|
* Does a gift with a specific ID exist in db
|
||||||
* @param playerId Player to send gift to
|
* @param giftId Gift id to check for
|
||||||
* @param giftId Id of gift to send player
|
* @returns True if it exists in db
|
||||||
*/
|
*/
|
||||||
public sendGiftToPlayer(playerId: string, giftId: string): void
|
public giftExists(giftId: string): boolean
|
||||||
{
|
{
|
||||||
//TODO: get gift items
|
return !!this.giftConfig.gifts[giftId];
|
||||||
const giftItems = [];
|
}
|
||||||
const maxStoreTime = 999999;
|
|
||||||
|
|
||||||
const messageContent = this.dialogueHelper.createMessageContext(null, MessageType.SYSTEM_MESSAGE, maxStoreTime);
|
/**
|
||||||
|
* Send player a gift from a range of sources
|
||||||
|
* @param playerId Player to send gift to / sessionId
|
||||||
|
* @param giftId Id of gift in configs/gifts.json to send player
|
||||||
|
* @returns outcome of sending gift to player
|
||||||
|
*/
|
||||||
|
public sendGiftToPlayer(playerId: string, giftId: string): GiftSentResult
|
||||||
|
{
|
||||||
|
const giftData = this.giftConfig.gifts[giftId];
|
||||||
|
if (!giftData)
|
||||||
|
{
|
||||||
|
return GiftSentResult.FAILED_GIFT_DOESNT_EXIST;
|
||||||
|
}
|
||||||
|
|
||||||
this.dialogueHelper.addDialogueMessage("traderId", messageContent, playerId, giftItems, MessageType.SYSTEM_MESSAGE);
|
if (this.profileHelper.playerHasRecievedGift(playerId, giftId))
|
||||||
|
{
|
||||||
|
this.logger.debug(`Player already recieved gift: ${giftId}`);
|
||||||
|
|
||||||
|
return GiftSentResult.FAILED_GIFT_ALREADY_RECEIVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle system messsages
|
||||||
|
if (giftData.sender === GiftSenderType.SYSTEM)
|
||||||
|
{
|
||||||
|
this.mailSendService.sendSystemMessageToPlayer(
|
||||||
|
playerId,
|
||||||
|
giftData.messageText,
|
||||||
|
giftData.items,
|
||||||
|
this.timeUtil.getHoursAsSeconds(giftData.collectionTimeHours));
|
||||||
|
}
|
||||||
|
// Handle user messages
|
||||||
|
else if (giftData.sender === GiftSenderType.USER)
|
||||||
|
{
|
||||||
|
this.mailSendService.sendUserMessageToPlayer(
|
||||||
|
playerId,
|
||||||
|
giftData.senderDetails,
|
||||||
|
giftData.messageText,
|
||||||
|
giftData.items,
|
||||||
|
this.timeUtil.getHoursAsSeconds(giftData.collectionTimeHours));
|
||||||
|
}
|
||||||
|
else if (giftData.sender === GiftSenderType.TRADER)
|
||||||
|
{
|
||||||
|
this.mailSendService.sendDirectNpcMessageToPlayer(
|
||||||
|
playerId,
|
||||||
|
giftData.trader,
|
||||||
|
MessageType.MESSAGE_WITH_ITEMS,
|
||||||
|
giftData.messageText,
|
||||||
|
giftData.items,
|
||||||
|
this.timeUtil.getHoursAsSeconds(giftData.collectionTimeHours));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: further split out into different message systems like above SYSTEM method
|
||||||
|
// Trader / ragfair
|
||||||
|
const details: ISendMessageDetails = {
|
||||||
|
recipientId: playerId,
|
||||||
|
sender: this.getMessageType(giftData),
|
||||||
|
senderDetails: { _id: this.getSenderId(giftData), info: null},
|
||||||
|
messageText: giftData.messageText,
|
||||||
|
items: giftData.items,
|
||||||
|
itemsMaxStorageLifetimeSeconds: this.timeUtil.getHoursAsSeconds(giftData.collectionTimeHours)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (giftData.trader)
|
||||||
|
{
|
||||||
|
details.trader = giftData.trader;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mailSendService.sendMessageToPlayer(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.profileHelper.addGiftReceivedFlagToProfile(playerId, giftId);
|
||||||
|
|
||||||
|
return GiftSentResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sender id based on gifts sender type enum
|
||||||
|
* @param giftData Gift to send player
|
||||||
|
* @returns trader/user/system id
|
||||||
|
*/
|
||||||
|
protected getSenderId(giftData: Gift): string
|
||||||
|
{
|
||||||
|
if (giftData.sender === GiftSenderType.TRADER)
|
||||||
|
{
|
||||||
|
return giftData.trader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (giftData.sender === GiftSenderType.USER)
|
||||||
|
{
|
||||||
|
return giftData.senderId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert GiftSenderType into a dialog MessageType
|
||||||
|
* @param giftData Gift to send player
|
||||||
|
* @returns MessageType enum value
|
||||||
|
*/
|
||||||
|
protected getMessageType(giftData: Gift): MessageType
|
||||||
|
{
|
||||||
|
switch (giftData.sender)
|
||||||
|
{
|
||||||
|
case GiftSenderType.SYSTEM:
|
||||||
|
return MessageType.SYSTEM_MESSAGE;
|
||||||
|
case GiftSenderType.TRADER:
|
||||||
|
return MessageType.NPC_TRADER;
|
||||||
|
case GiftSenderType.USER:
|
||||||
|
return MessageType.USER_MESSAGE;
|
||||||
|
default:
|
||||||
|
this.logger.error(`Gift message type: ${giftData.sender} not handled`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
408
project/src/services/MailSendService.ts
Normal file
408
project/src/services/MailSendService.ts
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
import { inject, injectable } from "tsyringe";
|
||||||
|
import { ItemHelper } from "../helpers/ItemHelper";
|
||||||
|
import { NotificationSendHelper } from "../helpers/NotificationSendHelper";
|
||||||
|
import { NotifierHelper } from "../helpers/NotifierHelper";
|
||||||
|
import { Item } from "../models/eft/common/tables/IItem";
|
||||||
|
import { Dialogue, IUserDialogInfo, Message, MessageItems } from "../models/eft/profile/IAkiProfile";
|
||||||
|
import { MessageType } from "../models/enums/MessageType";
|
||||||
|
import { Traders } from "../models/enums/Traders";
|
||||||
|
import { ISendMessageDetails } from "../models/spt/dialog/ISendMessageDetails";
|
||||||
|
import { ILogger } from "../models/spt/utils/ILogger";
|
||||||
|
import { DatabaseServer } from "../servers/DatabaseServer";
|
||||||
|
import { SaveServer } from "../servers/SaveServer";
|
||||||
|
import { HashUtil } from "../utils/HashUtil";
|
||||||
|
import { TimeUtil } from "../utils/TimeUtil";
|
||||||
|
import { LocalisationService } from "./LocalisationService";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MailSendService
|
||||||
|
{
|
||||||
|
protected readonly systemSenderId = "59e7125688a45068a6249071";
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("WinstonLogger") protected logger: ILogger,
|
||||||
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||||
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
|
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
||||||
|
@inject("NotifierHelper") protected notifierHelper: NotifierHelper,
|
||||||
|
@inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper,
|
||||||
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
|
@inject("ItemHelper") protected itemHelper: ItemHelper
|
||||||
|
)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message from an NPC (e.g. prapor) to the player with or without items using direct message text, do not look up any locale
|
||||||
|
* @param playerId Players id to send message to
|
||||||
|
* @param sender The trader sending the message
|
||||||
|
* @param messageType What type the message will assume (e.g. QUEST_SUCCESS)
|
||||||
|
* @param message Text to send to the player
|
||||||
|
* @param items Optional items to send to player
|
||||||
|
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||||
|
*/
|
||||||
|
public sendDirectNpcMessageToPlayer(playerId: string, sender: Traders, messageType: MessageType, message: string, items: Item[] = [], maxStorageTimeSeconds = null): void
|
||||||
|
{
|
||||||
|
const details: ISendMessageDetails = {
|
||||||
|
recipientId: playerId,
|
||||||
|
sender: messageType,
|
||||||
|
dialogType: MessageType.NPC_TRADER,
|
||||||
|
trader: sender,
|
||||||
|
messageText: message
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add items to message
|
||||||
|
if (items.length > 0)
|
||||||
|
{
|
||||||
|
details.items = items;
|
||||||
|
details.itemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMessageToPlayer(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message from an NPC (e.g. prapor) to the player with or without items
|
||||||
|
* @param playerId Players id to send message to
|
||||||
|
* @param sender The trader sending the message
|
||||||
|
* @param messageType What type the message will assume (e.g. QUEST_SUCCESS)
|
||||||
|
* @param messageLocaleId The localised text to send to player
|
||||||
|
* @param items Optional items to send to player
|
||||||
|
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||||
|
*/
|
||||||
|
public sendLocalisedNpcMessageToPlayer(playerId: string, sender: Traders, messageType: MessageType, messageLocaleId: string, items: Item[] = [], maxStorageTimeSeconds = null): void
|
||||||
|
{
|
||||||
|
const details: ISendMessageDetails = {
|
||||||
|
recipientId: playerId,
|
||||||
|
sender: messageType,
|
||||||
|
dialogType: MessageType.NPC_TRADER,
|
||||||
|
trader: sender,
|
||||||
|
templateId: messageLocaleId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add items to message
|
||||||
|
if (items.length > 0)
|
||||||
|
{
|
||||||
|
details.items = items;
|
||||||
|
details.itemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMessageToPlayer(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message from SYSTEM to the player with or without items
|
||||||
|
* @param playerId Players id to send message to
|
||||||
|
* @param message The text to send to player
|
||||||
|
* @param items Optional items to send to player
|
||||||
|
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||||
|
*/
|
||||||
|
public sendSystemMessageToPlayer(playerId: string, message: string, items: Item[] = [], maxStorageTimeSeconds = null): void
|
||||||
|
{
|
||||||
|
const details: ISendMessageDetails = {
|
||||||
|
recipientId: playerId,
|
||||||
|
sender: MessageType.SYSTEM_MESSAGE,
|
||||||
|
messageText: message
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add items to message
|
||||||
|
if (items.length > 0)
|
||||||
|
{
|
||||||
|
details.items = items;
|
||||||
|
details.itemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMessageToPlayer(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a USER message to a player with or without items
|
||||||
|
* @param playerId Players id to send message to
|
||||||
|
* @param senderId Who is sending the message
|
||||||
|
* @param message The text to send to player
|
||||||
|
* @param items Optional items to send to player
|
||||||
|
* @param maxStorageTimeSeconds Optional time to collect items before they expire
|
||||||
|
*/
|
||||||
|
public sendUserMessageToPlayer(playerId: string, senderDetails: IUserDialogInfo, message: string, items: Item[] = [], maxStorageTimeSeconds = null): void
|
||||||
|
{
|
||||||
|
const details: ISendMessageDetails = {
|
||||||
|
recipientId: playerId,
|
||||||
|
sender: MessageType.USER_MESSAGE,
|
||||||
|
senderDetails: senderDetails,
|
||||||
|
messageText: message
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add items to message
|
||||||
|
if (items.length > 0)
|
||||||
|
{
|
||||||
|
details.items = items;
|
||||||
|
details.itemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMessageToPlayer(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Large function to send messages to players from a variety of sources (SYSTEM/NPC/USER)
|
||||||
|
* Helper functions in this class are availble to simplify common actions
|
||||||
|
* @param messageDetails Details needed to send a message to the player
|
||||||
|
*/
|
||||||
|
public sendMessageToPlayer(messageDetails: ISendMessageDetails): void
|
||||||
|
{
|
||||||
|
// Get dialog, create if doesn't exist
|
||||||
|
const senderDialog = this.getDialog(messageDetails);
|
||||||
|
|
||||||
|
// Flag dialog as containing a new message to player
|
||||||
|
senderDialog.new++;
|
||||||
|
|
||||||
|
// Craft message
|
||||||
|
const message = this.createDialogMessage(senderDialog._id, messageDetails);
|
||||||
|
|
||||||
|
// Create items array
|
||||||
|
// Generate item stash if we have rewards.
|
||||||
|
const itemsToSendToPlayer = this.processItemsBeforeAddingToMail(senderDialog.type, messageDetails);
|
||||||
|
|
||||||
|
// If there's items to send to player, flag dialog as containing attachments
|
||||||
|
if (itemsToSendToPlayer.data?.length > 0)
|
||||||
|
{
|
||||||
|
senderDialog.attachmentsNew += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store reward items inside message and set appropriate flags inside message
|
||||||
|
this.addRewardItemsToMessage(message, itemsToSendToPlayer, messageDetails.itemsMaxStorageLifetimeSeconds);
|
||||||
|
|
||||||
|
// Add message to dialog
|
||||||
|
senderDialog.messages.push(message);
|
||||||
|
|
||||||
|
// TODO: clean up old code here
|
||||||
|
// Offer Sold notifications are now separate from the main notification
|
||||||
|
if (senderDialog.type === MessageType.FLEAMARKET_MESSAGE && messageDetails.ragfairDetails)
|
||||||
|
{
|
||||||
|
const offerSoldMessage = this.notifierHelper.createRagfairOfferSoldNotification(message, messageDetails.ragfairDetails);
|
||||||
|
this.notificationSendHelper.sendMessage(messageDetails.recipientId, offerSoldMessage);
|
||||||
|
message.type = MessageType.MESSAGE_WITH_ITEMS; // Should prevent getting the same notification popup twice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send message off to player so they get it in client
|
||||||
|
const notificationMessage = this.notifierHelper.createNewMessageNotification(message);
|
||||||
|
this.notificationSendHelper.sendMessage(messageDetails.recipientId, notificationMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message from the player to an NPC
|
||||||
|
* @param sessionId Player id
|
||||||
|
* @param targetNpcId NPC message is sent to
|
||||||
|
* @param message Text to send to NPC
|
||||||
|
*/
|
||||||
|
public sendPlayerMessageToNpc(sessionId: string, targetNpcId: string, message: string): void
|
||||||
|
{
|
||||||
|
const playerProfile = this.saveServer.getProfile(sessionId);
|
||||||
|
const dialogWithNpc = playerProfile.dialogues[targetNpcId];
|
||||||
|
if (!dialogWithNpc)
|
||||||
|
{
|
||||||
|
this.logger.error(`Dialog for: ${targetNpcId} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogWithNpc.messages.push({
|
||||||
|
_id: sessionId, // players id
|
||||||
|
dt: this.timeUtil.getTimestamp(),
|
||||||
|
hasRewards: false,
|
||||||
|
items: {},
|
||||||
|
uid: playerProfile.characters.pmc._id,
|
||||||
|
type: MessageType.USER_MESSAGE,
|
||||||
|
rewardCollected: false,
|
||||||
|
text: message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a message for storage inside a dialog in the player profile
|
||||||
|
* @param senderDialog Id of dialog that will hold the message
|
||||||
|
* @param messageDetails Various details on what the message must contain/do
|
||||||
|
* @returns Message
|
||||||
|
*/
|
||||||
|
protected createDialogMessage(dialogId: string, messageDetails: ISendMessageDetails): Message
|
||||||
|
{
|
||||||
|
const message: Message = {
|
||||||
|
_id: this.hashUtil.generate(),
|
||||||
|
uid: dialogId, // must match the dialog id
|
||||||
|
type: messageDetails.sender, // Same enum is used for defining dialog type + message type, thanks bsg
|
||||||
|
dt: Math.round(Date.now() / 1000),
|
||||||
|
text: messageDetails.templateId ? "" : messageDetails.messageText, // store empty string if template id has value, otherwise store raw message text
|
||||||
|
templateId: messageDetails.templateId, // used by traders to send localised text from database\locales\global
|
||||||
|
hasRewards: false, // The default dialog message has no rewards, can be added later via addRewardItemsToMessage()
|
||||||
|
rewardCollected: false, // The default dialog message has no rewards, can be added later via addRewardItemsToMessage()
|
||||||
|
systemData: messageDetails.systemData ? messageDetails.systemData : undefined, // Used by ragfair
|
||||||
|
profileChangeEvents: (messageDetails.profileChangeEvents?.length === 0) ? messageDetails.profileChangeEvents : undefined // no one knows, its never been used in any dumps
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clean up empty system data
|
||||||
|
if (!message.systemData)
|
||||||
|
{
|
||||||
|
delete message.systemData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up empty template id
|
||||||
|
if (!message.templateId)
|
||||||
|
{
|
||||||
|
delete message.templateId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add items to message and adjust various properties to reflect the items being added
|
||||||
|
* @param message Message to add items to
|
||||||
|
* @param itemsToSendToPlayer Items to add to message
|
||||||
|
* @param maxStorageTimeSeconds total time items are stored in mail before being deleted
|
||||||
|
*/
|
||||||
|
protected addRewardItemsToMessage(message: Message, itemsToSendToPlayer: MessageItems, maxStorageTimeSeconds: number): void
|
||||||
|
{
|
||||||
|
if (itemsToSendToPlayer?.data?.length > 0)
|
||||||
|
{
|
||||||
|
message.items = itemsToSendToPlayer;
|
||||||
|
message.hasRewards = true;
|
||||||
|
message.maxStorageTime = maxStorageTimeSeconds;
|
||||||
|
message.rewardCollected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* perform various sanitising actions on the items before they're considered ready for insertion into message
|
||||||
|
* @param dialogType The type of the dialog that will hold the reward items being processed
|
||||||
|
* @param messageDetails
|
||||||
|
* @returns Sanitised items
|
||||||
|
*/
|
||||||
|
protected processItemsBeforeAddingToMail(dialogType: MessageType, messageDetails: ISendMessageDetails): MessageItems
|
||||||
|
{
|
||||||
|
const db = this.databaseServer.getTables().templates.items;
|
||||||
|
|
||||||
|
let itemsToSendToPlayer: MessageItems = {};
|
||||||
|
if (messageDetails.items?.length > 0)
|
||||||
|
{
|
||||||
|
// No parent id, generate random id and add (doesnt need to be actual parentId from db, only unique)
|
||||||
|
if (!messageDetails.items[0]?.parentId)
|
||||||
|
{
|
||||||
|
messageDetails.items[0].parentId = this.hashUtil.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsToSendToPlayer = {
|
||||||
|
stash: messageDetails.items[0].parentId,
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure Ids are unique and cont collide with items in player invenory later
|
||||||
|
messageDetails.items = this.itemHelper.replaceIDs(null, messageDetails.items);
|
||||||
|
|
||||||
|
for (const reward of messageDetails.items)
|
||||||
|
{
|
||||||
|
// Ensure item exists in items db
|
||||||
|
const itemTemplate = db[reward._tpl];
|
||||||
|
if (!itemTemplate)
|
||||||
|
{
|
||||||
|
// Can happen when modded items are insured + mod is removed
|
||||||
|
this.logger.error(this.localisationService.getText("dialog-missing_item_template", {tpl: reward._tpl, type: dialogType}));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure every 'base/root' item has the same parentId + has a slotid of 'main'
|
||||||
|
if (!("slotId" in reward) || reward.slotId === "hideout")
|
||||||
|
{
|
||||||
|
// Reward items NEED a parent id + slotid
|
||||||
|
reward.parentId = messageDetails.items[0].parentId;
|
||||||
|
reward.slotId = "main";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item is sanitised and ready to be put into holding array
|
||||||
|
itemsToSendToPlayer.data.push(reward);
|
||||||
|
|
||||||
|
// Item can contain sub-items, add those to array e.g. ammo boxes
|
||||||
|
if ("StackSlots" in itemTemplate._props)
|
||||||
|
{
|
||||||
|
const stackSlotItems = this.itemHelper.generateItemsFromStackSlot(itemTemplate, reward._id);
|
||||||
|
for (const itemToAdd of stackSlotItems)
|
||||||
|
{
|
||||||
|
itemsToSendToPlayer.data.push(itemToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty data property if no rewards
|
||||||
|
if (itemsToSendToPlayer.data.length === 0)
|
||||||
|
{
|
||||||
|
delete itemsToSendToPlayer.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemsToSendToPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a dialog with a specified entity (user/trader)
|
||||||
|
* Create and store empty dialog if none exists in profile
|
||||||
|
* @param messageDetails Data on what message should do
|
||||||
|
* @returns Relevant Dialogue
|
||||||
|
*/
|
||||||
|
protected getDialog(messageDetails: ISendMessageDetails): Dialogue
|
||||||
|
{
|
||||||
|
const dialogsInProfile = this.saveServer.getProfile(messageDetails.recipientId).dialogues;
|
||||||
|
const senderId = this.getMessageSenderIdByType(messageDetails);
|
||||||
|
|
||||||
|
// Does dialog exist
|
||||||
|
let senderDialog = dialogsInProfile[senderId];
|
||||||
|
if (!senderDialog)
|
||||||
|
{
|
||||||
|
// Create if doesnt
|
||||||
|
dialogsInProfile[senderId] = {
|
||||||
|
_id: senderId,
|
||||||
|
type: messageDetails.dialogType ? messageDetails.dialogType : messageDetails.sender,
|
||||||
|
messages: [],
|
||||||
|
pinned: false,
|
||||||
|
new: 0,
|
||||||
|
attachmentsNew: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
senderDialog = dialogsInProfile[senderId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return senderDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the appropriate sender id by the sender enum type
|
||||||
|
* @param messageDetails
|
||||||
|
* @returns gets an id of the individual sending it
|
||||||
|
*/
|
||||||
|
protected getMessageSenderIdByType(messageDetails: ISendMessageDetails): string
|
||||||
|
{
|
||||||
|
if (messageDetails.sender === MessageType.SYSTEM_MESSAGE)
|
||||||
|
{
|
||||||
|
return this.systemSenderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageDetails.sender === MessageType.NPC_TRADER)
|
||||||
|
{
|
||||||
|
return messageDetails.trader;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageDetails.sender === MessageType.USER_MESSAGE)
|
||||||
|
{
|
||||||
|
return messageDetails.senderDetails?._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageDetails.senderDetails?._id)
|
||||||
|
{
|
||||||
|
return messageDetails.senderDetails._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageDetails.trader)
|
||||||
|
{
|
||||||
|
return Traders[messageDetails.trader];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.warning(`Unable to handle message of type: ${messageDetails.sender}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -105,7 +105,8 @@ export class ProfileFixerService
|
|||||||
{
|
{
|
||||||
this.logger.debug("Adding aki object to profile");
|
this.logger.debug("Adding aki object to profile");
|
||||||
fullProfile.aki = {
|
fullProfile.aki = {
|
||||||
version: this.watermark.getVersionTag()
|
version: this.watermark.getVersionTag(),
|
||||||
|
receivedGifts: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user