AI захватит мир
This commit is contained in:
parent
793165bb03
commit
875d594038
1
2025-06-26-13-04-01.zip.json
Normal file
1
2025-06-26-13-04-01.zip.json
Normal file
File diff suppressed because one or more lines are too long
24
Assets/Items/pistol.ammo
Normal file
24
Assets/Items/pistol.ammo
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Slot": "RightHand",
|
"Slot": "RightHand",
|
||||||
"HoldType": "Pistol",
|
"HoldType": "Pistol",
|
||||||
|
"AmmoType": "Pistol",
|
||||||
"WeaponDefinition": {
|
"WeaponDefinition": {
|
||||||
"Position": "-1.108,0.38,-2.367",
|
"Position": "-1.108,0.38,-2.367",
|
||||||
"Rotation": "0.002790701,0.01173679,0.006057173,0.9999089"
|
"Rotation": "0.002790701,0.01173679,0.006057173,0.9999089"
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
"OnComponentUpdate": null
|
"OnComponentUpdate": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type": "Sandbox.UI.PickupItem",
|
"__type": "Sasalka.PickupItem",
|
||||||
"__guid": "a90f059e-5c0a-4122-b742-9dd12e5d8494",
|
"__guid": "a90f059e-5c0a-4122-b742-9dd12e5d8494",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Label": "E",
|
"Label": "E",
|
||||||
|
|||||||
@ -61,7 +61,7 @@
|
|||||||
"SurfaceVelocity": "0,0,0"
|
"SurfaceVelocity": "0,0,0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type": "Sandbox.UI.PickupItem",
|
"__type": "Sasalka.PickupItem",
|
||||||
"__guid": "8546ae27-6c60-4e32-b613-760bc20bd651",
|
"__guid": "8546ae27-6c60-4e32-b613-760bc20bd651",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Label": "E",
|
"Label": "E",
|
||||||
@ -106,8 +106,7 @@
|
|||||||
"__guid": "beaec0c2-ccc3-48b0-8459-5a3f3fe679f1",
|
"__guid": "beaec0c2-ccc3-48b0-8459-5a3f3fe679f1",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Count": 120,
|
"Count": 120,
|
||||||
"Definition": "items/pistol_ammo.inv",
|
"Definition": "items/pistol.ammo",
|
||||||
"Equipped": false,
|
|
||||||
"OnComponentDestroy": null,
|
"OnComponentDestroy": null,
|
||||||
"OnComponentDisabled": null,
|
"OnComponentDisabled": null,
|
||||||
"OnComponentEnabled": null,
|
"OnComponentEnabled": null,
|
||||||
@ -179,7 +178,7 @@
|
|||||||
"SurfaceVelocity": "0,0,0"
|
"SurfaceVelocity": "0,0,0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type": "Sandbox.UI.PickupItem",
|
"__type": "Sasalka.PickupItem",
|
||||||
"__guid": "edaf7116-38ed-4e2f-91d6-b20aa9a1a009",
|
"__guid": "edaf7116-38ed-4e2f-91d6-b20aa9a1a009",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Label": "E",
|
"Label": "E",
|
||||||
@ -223,9 +222,8 @@
|
|||||||
"__type": "Sasalka.InventoryItem",
|
"__type": "Sasalka.InventoryItem",
|
||||||
"__guid": "0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c",
|
"__guid": "0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Count": 120,
|
"Count": 1,
|
||||||
"Definition": "items/pistol_ammo.inv",
|
"Definition": "items/pistol_ammo.inv",
|
||||||
"Equipped": false,
|
|
||||||
"OnComponentDestroy": null,
|
"OnComponentDestroy": null,
|
||||||
"OnComponentDisabled": null,
|
"OnComponentDisabled": null,
|
||||||
"OnComponentEnabled": null,
|
"OnComponentEnabled": null,
|
||||||
@ -274,7 +272,7 @@
|
|||||||
"SurfaceVelocity": "0,0,0"
|
"SurfaceVelocity": "0,0,0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type": "Sandbox.UI.PickupItem",
|
"__type": "Sasalka.PickupItem",
|
||||||
"__guid": "a8bc6a6d-dc7b-441a-8191-79e83a998981",
|
"__guid": "a8bc6a6d-dc7b-441a-8191-79e83a998981",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Label": "E",
|
"Label": "E",
|
||||||
@ -318,9 +316,8 @@
|
|||||||
"__type": "Sasalka.InventoryItem",
|
"__type": "Sasalka.InventoryItem",
|
||||||
"__guid": "50b368f3-d27b-4408-a66b-a9d852e6fbae",
|
"__guid": "50b368f3-d27b-4408-a66b-a9d852e6fbae",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Count": 120,
|
"Count": 1,
|
||||||
"Definition": "items/pistol_ammo.inv",
|
"Definition": "items/pistol_ammo.inv",
|
||||||
"Equipped": false,
|
|
||||||
"OnComponentDestroy": null,
|
"OnComponentDestroy": null,
|
||||||
"OnComponentDisabled": null,
|
"OnComponentDisabled": null,
|
||||||
"OnComponentEnabled": null,
|
"OnComponentEnabled": null,
|
||||||
|
|||||||
@ -136,10 +136,11 @@
|
|||||||
"particlePrefab": {
|
"particlePrefab": {
|
||||||
"_type": "gameobject",
|
"_type": "gameobject",
|
||||||
"prefab": "prefabs/impacts/impact.metal.prefab"
|
"prefab": "prefabs/impacts/impact.metal.prefab"
|
||||||
}
|
},
|
||||||
|
"WeaponDefinition": "items/pistol_test.weapon"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__type": "Sandbox.UI.PickupItem",
|
"__type": "Sasalka.PickupItem",
|
||||||
"__guid": "7350112e-dea3-4c22-b9fc-76eaed3d2e57",
|
"__guid": "7350112e-dea3-4c22-b9fc-76eaed3d2e57",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"Label": "E",
|
"Label": "E",
|
||||||
|
|||||||
@ -483,18 +483,18 @@
|
|||||||
"e5e018f2-9cad-4ece-b160-ab2aa3aeb7ec": "7cdbb78c-19c3-4f0a-9662-91609da57955",
|
"e5e018f2-9cad-4ece-b160-ab2aa3aeb7ec": "7cdbb78c-19c3-4f0a-9662-91609da57955",
|
||||||
"ee9d15a8-ffe9-43f1-b6b4-2f04ce55c908": "5fbe67ec-6b54-4560-87ef-8515fce7d33d",
|
"ee9d15a8-ffe9-43f1-b6b4-2f04ce55c908": "5fbe67ec-6b54-4560-87ef-8515fce7d33d",
|
||||||
"5830c995-d747-4c8c-abd7-b0366fa2a2aa": "81bd6292-744c-4e33-8c6a-3904a13ee646",
|
"5830c995-d747-4c8c-abd7-b0366fa2a2aa": "81bd6292-744c-4e33-8c6a-3904a13ee646",
|
||||||
"8546ae27-6c60-4e32-b613-760bc20bd651": "9e0bc531-e476-492f-912c-53859daf1dea",
|
"eeca4029-990c-43e6-aa16-1df8fc69fa1c": "cd79147f-c09a-4b76-94a0-b46b730a9ff1",
|
||||||
"4d97a077-f187-44f4-967b-d346530c38d9": "7fb3b2db-8dd8-4611-b79b-18529c4a2081",
|
"4d97a077-f187-44f4-967b-d346530c38d9": "7fb3b2db-8dd8-4611-b79b-18529c4a2081",
|
||||||
"beaec0c2-ccc3-48b0-8459-5a3f3fe679f1": "823f8251-0a51-4513-9995-178c44e63469",
|
"beaec0c2-ccc3-48b0-8459-5a3f3fe679f1": "823f8251-0a51-4513-9995-178c44e63469",
|
||||||
"e428ecbc-40fd-4ec5-b1af-373bce265f3e": "766cbde5-8f5c-4660-8608-100ffefbfa29",
|
"e428ecbc-40fd-4ec5-b1af-373bce265f3e": "766cbde5-8f5c-4660-8608-100ffefbfa29",
|
||||||
"ce6aaed8-3ae5-41a4-a21c-1386b86a158a": "a8918404-1c91-48c8-ba49-f878516a2565",
|
"ce6aaed8-3ae5-41a4-a21c-1386b86a158a": "a8918404-1c91-48c8-ba49-f878516a2565",
|
||||||
"1ef0f21d-1147-4a46-97e8-97c587a99040": "6151c1b8-ff4e-4cc8-99dd-1eff15bb641d",
|
"1ef0f21d-1147-4a46-97e8-97c587a99040": "6151c1b8-ff4e-4cc8-99dd-1eff15bb641d",
|
||||||
"edaf7116-38ed-4e2f-91d6-b20aa9a1a009": "8ea1e4c3-5543-4367-b3e2-7ed11559b437",
|
"038bd275-df09-422f-9f02-8448b02706f8": "bc2008a9-f206-4008-89d1-beee5d1051bd",
|
||||||
"1bb4075f-849c-4761-93bc-38f31cd11650": "811e4d93-ba7e-4edd-9ac4-ccb89bc87fb3",
|
"1bb4075f-849c-4761-93bc-38f31cd11650": "811e4d93-ba7e-4edd-9ac4-ccb89bc87fb3",
|
||||||
"0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c": "90d493ea-1b5c-4a93-ba8b-a8b538fb4ade",
|
"0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c": "90d493ea-1b5c-4a93-ba8b-a8b538fb4ade",
|
||||||
"3cd5251b-38a8-4f0e-8b33-4b2e5f7041ab": "2ccacb25-f30f-42ea-982d-bc521e4aefac",
|
"3cd5251b-38a8-4f0e-8b33-4b2e5f7041ab": "2ccacb25-f30f-42ea-982d-bc521e4aefac",
|
||||||
"8b94182b-9ac1-4646-8f8c-d3188a804946": "15f06e09-733f-4885-9a95-f7dd23d93657",
|
"8b94182b-9ac1-4646-8f8c-d3188a804946": "15f06e09-733f-4885-9a95-f7dd23d93657",
|
||||||
"a8bc6a6d-dc7b-441a-8191-79e83a998981": "fea348c1-39df-4974-9b74-857163bd976c",
|
"c4a3df64-7ff1-483a-9f9e-e181152d7645": "f5ed5bad-5418-4b0d-a6f6-3c0cdfbcef5f",
|
||||||
"064ba569-c0b4-48ac-8ab4-a5c4d63b1a30": "73756613-cabc-413b-8fec-024b6e6c0246",
|
"064ba569-c0b4-48ac-8ab4-a5c4d63b1a30": "73756613-cabc-413b-8fec-024b6e6c0246",
|
||||||
"50b368f3-d27b-4408-a66b-a9d852e6fbae": "dd02755a-000b-440f-9e55-7b4b0b7bac75"
|
"50b368f3-d27b-4408-a66b-a9d852e6fbae": "dd02755a-000b-440f-9e55-7b4b0b7bac75"
|
||||||
}
|
}
|
||||||
@ -530,18 +530,18 @@
|
|||||||
"e5e018f2-9cad-4ece-b160-ab2aa3aeb7ec": "2fe45086-5076-4fdc-a2c7-29ec92c23810",
|
"e5e018f2-9cad-4ece-b160-ab2aa3aeb7ec": "2fe45086-5076-4fdc-a2c7-29ec92c23810",
|
||||||
"ee9d15a8-ffe9-43f1-b6b4-2f04ce55c908": "c7e24a6f-de41-4d1e-be96-0348b347119b",
|
"ee9d15a8-ffe9-43f1-b6b4-2f04ce55c908": "c7e24a6f-de41-4d1e-be96-0348b347119b",
|
||||||
"5830c995-d747-4c8c-abd7-b0366fa2a2aa": "d7c8e6d4-4f33-4929-9600-91bab058201d",
|
"5830c995-d747-4c8c-abd7-b0366fa2a2aa": "d7c8e6d4-4f33-4929-9600-91bab058201d",
|
||||||
"8546ae27-6c60-4e32-b613-760bc20bd651": "cd546fdd-9e2c-4e21-b068-6802c17eac4b",
|
"eeca4029-990c-43e6-aa16-1df8fc69fa1c": "30727755-3261-4208-b2cd-604ea5f3110e",
|
||||||
"4d97a077-f187-44f4-967b-d346530c38d9": "6d89a94c-3924-4534-822a-8199fcc273c6",
|
"4d97a077-f187-44f4-967b-d346530c38d9": "6d89a94c-3924-4534-822a-8199fcc273c6",
|
||||||
"beaec0c2-ccc3-48b0-8459-5a3f3fe679f1": "7f1321e3-4e18-431a-ac36-4c006ff42ca2",
|
"beaec0c2-ccc3-48b0-8459-5a3f3fe679f1": "7f1321e3-4e18-431a-ac36-4c006ff42ca2",
|
||||||
"e428ecbc-40fd-4ec5-b1af-373bce265f3e": "dd9de019-e850-4b70-9f23-9c7606b71258",
|
"e428ecbc-40fd-4ec5-b1af-373bce265f3e": "dd9de019-e850-4b70-9f23-9c7606b71258",
|
||||||
"ce6aaed8-3ae5-41a4-a21c-1386b86a158a": "6835aaa4-31e7-44d3-bcf7-21435bfc30a8",
|
"ce6aaed8-3ae5-41a4-a21c-1386b86a158a": "6835aaa4-31e7-44d3-bcf7-21435bfc30a8",
|
||||||
"1ef0f21d-1147-4a46-97e8-97c587a99040": "24907222-1cef-4d26-aae2-55cde0ce8506",
|
"1ef0f21d-1147-4a46-97e8-97c587a99040": "24907222-1cef-4d26-aae2-55cde0ce8506",
|
||||||
"edaf7116-38ed-4e2f-91d6-b20aa9a1a009": "9a64d9dd-30d7-49d1-b1f8-f06602183892",
|
"038bd275-df09-422f-9f02-8448b02706f8": "f23c909a-e080-418f-8bf4-4812459b17b4",
|
||||||
"1bb4075f-849c-4761-93bc-38f31cd11650": "60d7794d-069e-4533-a002-5a0596f4a52d",
|
"1bb4075f-849c-4761-93bc-38f31cd11650": "60d7794d-069e-4533-a002-5a0596f4a52d",
|
||||||
"0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c": "f87dd0c6-58c1-4d94-9799-7ad2e8a247f8",
|
"0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c": "f87dd0c6-58c1-4d94-9799-7ad2e8a247f8",
|
||||||
"3cd5251b-38a8-4f0e-8b33-4b2e5f7041ab": "abb38bd9-332e-45fc-8929-165de4a4cbde",
|
"3cd5251b-38a8-4f0e-8b33-4b2e5f7041ab": "abb38bd9-332e-45fc-8929-165de4a4cbde",
|
||||||
"8b94182b-9ac1-4646-8f8c-d3188a804946": "2f496a29-ba4e-4878-abdb-85a9b2af2748",
|
"8b94182b-9ac1-4646-8f8c-d3188a804946": "2f496a29-ba4e-4878-abdb-85a9b2af2748",
|
||||||
"a8bc6a6d-dc7b-441a-8191-79e83a998981": "8252f8f4-c89c-4f50-b3d1-09912957e3de",
|
"c4a3df64-7ff1-483a-9f9e-e181152d7645": "89cb0618-4fb3-4ccf-b9d3-63abe5bd67ed",
|
||||||
"064ba569-c0b4-48ac-8ab4-a5c4d63b1a30": "f291bdc2-16d4-496b-9cfc-2c1900649bf0",
|
"064ba569-c0b4-48ac-8ab4-a5c4d63b1a30": "f291bdc2-16d4-496b-9cfc-2c1900649bf0",
|
||||||
"50b368f3-d27b-4408-a66b-a9d852e6fbae": "a2165c8e-f32c-4bba-a482-c0a1c52bfe1f"
|
"50b368f3-d27b-4408-a66b-a9d852e6fbae": "a2165c8e-f32c-4bba-a482-c0a1c52bfe1f"
|
||||||
}
|
}
|
||||||
@ -577,18 +577,18 @@
|
|||||||
"e5e018f2-9cad-4ece-b160-ab2aa3aeb7ec": "ef158b83-fa9f-4774-9eb1-36651c8b08a9",
|
"e5e018f2-9cad-4ece-b160-ab2aa3aeb7ec": "ef158b83-fa9f-4774-9eb1-36651c8b08a9",
|
||||||
"ee9d15a8-ffe9-43f1-b6b4-2f04ce55c908": "c1b4d1db-34b2-4dd2-986c-0ca47ee367c6",
|
"ee9d15a8-ffe9-43f1-b6b4-2f04ce55c908": "c1b4d1db-34b2-4dd2-986c-0ca47ee367c6",
|
||||||
"5830c995-d747-4c8c-abd7-b0366fa2a2aa": "688d5b7a-3628-411c-bce4-7e71c36b65c8",
|
"5830c995-d747-4c8c-abd7-b0366fa2a2aa": "688d5b7a-3628-411c-bce4-7e71c36b65c8",
|
||||||
"8546ae27-6c60-4e32-b613-760bc20bd651": "e6b39fb1-4f30-45a7-8ba4-fb6d40910258",
|
"eeca4029-990c-43e6-aa16-1df8fc69fa1c": "32cb01e1-fc77-431b-ae65-96f807c4580e",
|
||||||
"4d97a077-f187-44f4-967b-d346530c38d9": "621b7ca2-7ae7-4867-9251-30e27db49025",
|
"4d97a077-f187-44f4-967b-d346530c38d9": "621b7ca2-7ae7-4867-9251-30e27db49025",
|
||||||
"beaec0c2-ccc3-48b0-8459-5a3f3fe679f1": "57855c6f-9f2c-45a8-a493-b4144dabbdae",
|
"beaec0c2-ccc3-48b0-8459-5a3f3fe679f1": "57855c6f-9f2c-45a8-a493-b4144dabbdae",
|
||||||
"e428ecbc-40fd-4ec5-b1af-373bce265f3e": "f421713f-ba53-49ef-9299-e43dfbafcb96",
|
"e428ecbc-40fd-4ec5-b1af-373bce265f3e": "f421713f-ba53-49ef-9299-e43dfbafcb96",
|
||||||
"ce6aaed8-3ae5-41a4-a21c-1386b86a158a": "3d8d56a5-3400-4f27-bfd6-ea9bdca3d1fc",
|
"ce6aaed8-3ae5-41a4-a21c-1386b86a158a": "3d8d56a5-3400-4f27-bfd6-ea9bdca3d1fc",
|
||||||
"1ef0f21d-1147-4a46-97e8-97c587a99040": "75e8850c-7f68-42f8-b708-5cee771c0712",
|
"1ef0f21d-1147-4a46-97e8-97c587a99040": "75e8850c-7f68-42f8-b708-5cee771c0712",
|
||||||
"edaf7116-38ed-4e2f-91d6-b20aa9a1a009": "adc87480-58e1-4d52-ba22-ec0ba98e72d7",
|
"038bd275-df09-422f-9f02-8448b02706f8": "248d9927-b022-4daa-aa13-222b1e5a4a96",
|
||||||
"1bb4075f-849c-4761-93bc-38f31cd11650": "72218084-be9b-4bf0-986a-166ca09da287",
|
"1bb4075f-849c-4761-93bc-38f31cd11650": "72218084-be9b-4bf0-986a-166ca09da287",
|
||||||
"0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c": "7f607022-8e99-4761-9e9b-72a71ae4d5a8",
|
"0b67d1c5-594a-49fa-8c23-e7a3ed9edb2c": "7f607022-8e99-4761-9e9b-72a71ae4d5a8",
|
||||||
"3cd5251b-38a8-4f0e-8b33-4b2e5f7041ab": "a766afdb-5b02-4407-96f7-0a0e345ba3c1",
|
"3cd5251b-38a8-4f0e-8b33-4b2e5f7041ab": "a766afdb-5b02-4407-96f7-0a0e345ba3c1",
|
||||||
"8b94182b-9ac1-4646-8f8c-d3188a804946": "67c84159-9ad0-43fb-880c-3ad680d09cd3",
|
"8b94182b-9ac1-4646-8f8c-d3188a804946": "67c84159-9ad0-43fb-880c-3ad680d09cd3",
|
||||||
"a8bc6a6d-dc7b-441a-8191-79e83a998981": "b2510e96-331a-4ab3-9734-69bb46501c1f",
|
"c4a3df64-7ff1-483a-9f9e-e181152d7645": "deaf34f3-b848-4556-ab94-f9c93cadf1bc",
|
||||||
"064ba569-c0b4-48ac-8ab4-a5c4d63b1a30": "bd311d99-63b5-4a80-8fc6-7debe7012e8d",
|
"064ba569-c0b4-48ac-8ab4-a5c4d63b1a30": "bd311d99-63b5-4a80-8fc6-7debe7012e8d",
|
||||||
"50b368f3-d27b-4408-a66b-a9d852e6fbae": "a5a33b69-7390-4492-8dfd-a52766b99b88"
|
"50b368f3-d27b-4408-a66b-a9d852e6fbae": "a5a33b69-7390-4492-8dfd-a52766b99b88"
|
||||||
}
|
}
|
||||||
@ -625,7 +625,7 @@
|
|||||||
"6017d24d-39d0-4acf-bf9f-010c345fd13d": "4a6feb0c-3798-41fd-b68f-9f43df293833",
|
"6017d24d-39d0-4acf-bf9f-010c345fd13d": "4a6feb0c-3798-41fd-b68f-9f43df293833",
|
||||||
"be95c906-5f2f-4239-8fd6-a42a148dea6e": "7844d416-a403-43e6-b473-c337d1be6fc5",
|
"be95c906-5f2f-4239-8fd6-a42a148dea6e": "7844d416-a403-43e6-b473-c337d1be6fc5",
|
||||||
"fc38d078-995b-443a-b409-e877618fcf09": "428482a2-31e0-4f7b-a587-eb91a6027627",
|
"fc38d078-995b-443a-b409-e877618fcf09": "428482a2-31e0-4f7b-a587-eb91a6027627",
|
||||||
"7350112e-dea3-4c22-b9fc-76eaed3d2e57": "d900697b-2fde-4916-b9ac-14ebedc2797b",
|
"f5968c60-ed2e-47d1-a2bf-353e0398f234": "257dce97-2921-4cf9-95de-e06525bd0492",
|
||||||
"ce60c131-01f0-4aa3-a84c-c93eac1d1c2d": "cb13417e-2527-40b7-9ddf-2c017f9a8a49",
|
"ce60c131-01f0-4aa3-a84c-c93eac1d1c2d": "cb13417e-2527-40b7-9ddf-2c017f9a8a49",
|
||||||
"8a705640-2966-489e-a41a-2bf6f2cc62f0": "a5f8a313-cccc-4c11-98c0-e82b09badcd3",
|
"8a705640-2966-489e-a41a-2bf6f2cc62f0": "a5f8a313-cccc-4c11-98c0-e82b09badcd3",
|
||||||
"8b4ca11f-8cd7-4bb5-bb63-df86dac15ab9": "43445fd1-090e-49c9-8ed7-80ee31b7e783",
|
"8b4ca11f-8cd7-4bb5-bb63-df86dac15ab9": "43445fd1-090e-49c9-8ed7-80ee31b7e783",
|
||||||
@ -680,7 +680,7 @@
|
|||||||
"6017d24d-39d0-4acf-bf9f-010c345fd13d": "a04677d0-af09-4eb5-bda6-9019c17389d2",
|
"6017d24d-39d0-4acf-bf9f-010c345fd13d": "a04677d0-af09-4eb5-bda6-9019c17389d2",
|
||||||
"be95c906-5f2f-4239-8fd6-a42a148dea6e": "204255d7-7330-4b3d-b0fd-e648a4b8c728",
|
"be95c906-5f2f-4239-8fd6-a42a148dea6e": "204255d7-7330-4b3d-b0fd-e648a4b8c728",
|
||||||
"fc38d078-995b-443a-b409-e877618fcf09": "ff3f86ae-660f-4a43-a0b0-1c7e87d1e642",
|
"fc38d078-995b-443a-b409-e877618fcf09": "ff3f86ae-660f-4a43-a0b0-1c7e87d1e642",
|
||||||
"7350112e-dea3-4c22-b9fc-76eaed3d2e57": "804bd67d-499b-4fbb-89ac-b59184fd434f",
|
"f5968c60-ed2e-47d1-a2bf-353e0398f234": "2c56a4c8-eb8d-44aa-95fa-6c27d6391e4d",
|
||||||
"ce60c131-01f0-4aa3-a84c-c93eac1d1c2d": "4157870e-5b90-40cb-a0ba-3c61a7805d06",
|
"ce60c131-01f0-4aa3-a84c-c93eac1d1c2d": "4157870e-5b90-40cb-a0ba-3c61a7805d06",
|
||||||
"8a705640-2966-489e-a41a-2bf6f2cc62f0": "1eb22b1c-ab75-4553-9470-f5e3e4eef369",
|
"8a705640-2966-489e-a41a-2bf6f2cc62f0": "1eb22b1c-ab75-4553-9470-f5e3e4eef369",
|
||||||
"8b4ca11f-8cd7-4bb5-bb63-df86dac15ab9": "f9cec441-fa67-4932-a804-41c9beebf07e",
|
"8b4ca11f-8cd7-4bb5-bb63-df86dac15ab9": "f9cec441-fa67-4932-a804-41c9beebf07e",
|
||||||
@ -735,7 +735,7 @@
|
|||||||
"6017d24d-39d0-4acf-bf9f-010c345fd13d": "b2d698cd-6aee-497b-bfa9-163d21d2e0b5",
|
"6017d24d-39d0-4acf-bf9f-010c345fd13d": "b2d698cd-6aee-497b-bfa9-163d21d2e0b5",
|
||||||
"be95c906-5f2f-4239-8fd6-a42a148dea6e": "fa129559-6f5f-40ee-87f0-9ab69ee70743",
|
"be95c906-5f2f-4239-8fd6-a42a148dea6e": "fa129559-6f5f-40ee-87f0-9ab69ee70743",
|
||||||
"fc38d078-995b-443a-b409-e877618fcf09": "16b190ad-d52f-403d-882b-bff9261232ae",
|
"fc38d078-995b-443a-b409-e877618fcf09": "16b190ad-d52f-403d-882b-bff9261232ae",
|
||||||
"7350112e-dea3-4c22-b9fc-76eaed3d2e57": "ef492981-3649-401a-aa0f-7b12d77873bc",
|
"f5968c60-ed2e-47d1-a2bf-353e0398f234": "7f88a632-e787-4609-8a8b-ecd3f555872a",
|
||||||
"ce60c131-01f0-4aa3-a84c-c93eac1d1c2d": "e59c9852-cd4c-4475-9385-4fd32ad4b876",
|
"ce60c131-01f0-4aa3-a84c-c93eac1d1c2d": "e59c9852-cd4c-4475-9385-4fd32ad4b876",
|
||||||
"8a705640-2966-489e-a41a-2bf6f2cc62f0": "aa7217af-d2b3-440f-9852-c8c047c096c6",
|
"8a705640-2966-489e-a41a-2bf6f2cc62f0": "aa7217af-d2b3-440f-9852-c8c047c096c6",
|
||||||
"8b4ca11f-8cd7-4bb5-bb63-df86dac15ab9": "1ad59142-d2a2-47c6-88ee-4ed5817cb5c6",
|
"8b4ca11f-8cd7-4bb5-bb63-df86dac15ab9": "1ad59142-d2a2-47c6-88ee-4ed5817cb5c6",
|
||||||
@ -845,7 +845,6 @@
|
|||||||
"__type": "WorlModelClothSpawner",
|
"__type": "WorlModelClothSpawner",
|
||||||
"__guid": "a6297e3c-aaa4-4051-8850-d577d90f7640",
|
"__guid": "a6297e3c-aaa4-4051-8850-d577d90f7640",
|
||||||
"__enabled": true,
|
"__enabled": true,
|
||||||
"CenterPosition": "0,0,0",
|
|
||||||
"Height": 5,
|
"Height": 5,
|
||||||
"OnComponentDestroy": null,
|
"OnComponentDestroy": null,
|
||||||
"OnComponentDisabled": null,
|
"OnComponentDisabled": null,
|
||||||
@ -5095,6 +5094,32 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Children": []
|
"Children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__guid": "b593d08e-1bf7-4ebb-a2ed-bd8ee901d09e",
|
||||||
|
"__version": 1,
|
||||||
|
"__Prefab": "prefabs/item_parcel.prefab",
|
||||||
|
"__PrefabInstancePatch": {
|
||||||
|
"AddedObjects": [],
|
||||||
|
"RemovedObjects": [],
|
||||||
|
"PropertyOverrides": [
|
||||||
|
{
|
||||||
|
"Target": {
|
||||||
|
"Type": "GameObject",
|
||||||
|
"IdValue": "0b13f26c-c198-4efc-88df-260d896af36a"
|
||||||
|
},
|
||||||
|
"Property": "Position",
|
||||||
|
"Value": "160,16,4.069437"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"MovedObjects": []
|
||||||
|
},
|
||||||
|
"__PrefabIdToInstanceId": {
|
||||||
|
"0b13f26c-c198-4efc-88df-260d896af36a": "b593d08e-1bf7-4ebb-a2ed-bd8ee901d09e",
|
||||||
|
"80b52707-c81d-47fa-bab7-4be48f2d75a4": "2150cd49-a27c-462f-a7ea-ead17e42f6d6",
|
||||||
|
"c888eaab-cb05-4469-bf5f-7c23ede5c25f": "799f4a0c-f42b-46e6-ba2b-44de0a85255e",
|
||||||
|
"a90f059e-5c0a-4122-b742-9dd12e5d8494": "d14d3f67-52e9-4ff1-9348-66990b170c0f"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"SceneProperties": {
|
"SceneProperties": {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
public class AttachmentSlotResolver
|
public class AttachmentSlotResolver
|
||||||
{
|
{
|
||||||
private readonly Func<string, GameObject> _attachmentGetter;
|
private readonly Func<string, GameObject> _attachmentGetter;
|
||||||
|
private readonly Dictionary<Inventar.InventorySlot, GameObject> _slotCache = new();
|
||||||
|
|
||||||
public AttachmentSlotResolver( Func<string, GameObject> attachmentGetter )
|
public AttachmentSlotResolver( Func<string, GameObject> attachmentGetter )
|
||||||
{
|
{
|
||||||
@ -11,12 +12,27 @@ public class AttachmentSlotResolver
|
|||||||
|
|
||||||
public GameObject GetSlotObject( Inventar.InventorySlot slot )
|
public GameObject GetSlotObject( Inventar.InventorySlot slot )
|
||||||
{
|
{
|
||||||
return slot switch
|
// Проверяем кэш
|
||||||
|
if (_slotCache.TryGetValue(slot, out var cachedObject))
|
||||||
|
{
|
||||||
|
return cachedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем объект и кэшируем его
|
||||||
|
var slotObject = slot switch
|
||||||
{
|
{
|
||||||
Inventar.InventorySlot.LeftHand => _attachmentGetter.Invoke( "hold_L" ),
|
Inventar.InventorySlot.LeftHand => _attachmentGetter.Invoke( "hold_L" ),
|
||||||
Inventar.InventorySlot.RightHand => _attachmentGetter.Invoke( "hold_R" ),
|
Inventar.InventorySlot.RightHand => _attachmentGetter.Invoke( "hold_R" ),
|
||||||
Inventar.InventorySlot.Body => _attachmentGetter.Invoke( "forward_reference_modelspace" ),
|
Inventar.InventorySlot.Body => _attachmentGetter.Invoke( "forward_reference_modelspace" ),
|
||||||
_ => _attachmentGetter.Invoke( "forward_reference_modelspace" )
|
_ => _attachmentGetter.Invoke( "forward_reference_modelspace" )
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_slotCache[slot] = slotObject;
|
||||||
|
return slotObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearCache()
|
||||||
|
{
|
||||||
|
_slotCache.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
Code/Inventory/Definitions/AmmoItemDefinition.cs
Normal file
32
Code/Inventory/Definitions/AmmoItemDefinition.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
namespace Sasalka;
|
||||||
|
|
||||||
|
[GameResource( "Ammo Item Definition", "ammo", "", Category = "Sasalka", Icon = "inventory_2" )]
|
||||||
|
public class AmmoItemDefinition : BaseItemDefinition
|
||||||
|
{
|
||||||
|
[Property, Category( "Ammo Properties" )]
|
||||||
|
public string AmmoType { get; set; } = "Pistol";
|
||||||
|
|
||||||
|
[Property, Category( "Ammo Properties" )]
|
||||||
|
public float Damage { get; set; } = 10f;
|
||||||
|
|
||||||
|
[Property, Category( "Ammo Properties" )]
|
||||||
|
public float Penetration { get; set; } = 0f;
|
||||||
|
|
||||||
|
[Property, Category( "Ammo Properties" )]
|
||||||
|
public bool IsExplosive { get; set; } = false;
|
||||||
|
|
||||||
|
[Property, Category( "Ammo Properties" )]
|
||||||
|
public float ExplosionRadius { get; set; } = 0f;
|
||||||
|
|
||||||
|
[Property, Category( "Ammo Properties" )]
|
||||||
|
public string CompatibleWeapons { get; set; } = "";
|
||||||
|
|
||||||
|
public override ItemCategory Category => ItemCategory.Ammo;
|
||||||
|
|
||||||
|
public override bool CanUse() => false; // Патроны нельзя использовать напрямую
|
||||||
|
|
||||||
|
public bool IsCompatibleWith( string ammoType )
|
||||||
|
{
|
||||||
|
return AmmoType == ammoType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,92 @@
|
|||||||
namespace Sasalka;
|
namespace Sasalka;
|
||||||
|
|
||||||
[GameResource( "Base Item Definition", "inv", "", Category = "Sasalka", Icon = "inventory_2" )]
|
public enum ItemCategory
|
||||||
|
{
|
||||||
|
Weapon,
|
||||||
|
Clothing,
|
||||||
|
Ammo,
|
||||||
|
Consumable,
|
||||||
|
Tool,
|
||||||
|
Misc
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ItemRarity
|
||||||
|
{
|
||||||
|
Common,
|
||||||
|
Uncommon,
|
||||||
|
Rare,
|
||||||
|
Epic,
|
||||||
|
Legendary
|
||||||
|
}
|
||||||
|
|
||||||
|
[GameResource("Base Item Definition", "inv", "", Category = "Sasalka", Icon = "inventory_2")]
|
||||||
public class BaseItemDefinition : GameResource
|
public class BaseItemDefinition : GameResource
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
[Property, Title("Basic Info")]
|
||||||
|
public string Name { get; set; } = "Unknown Item";
|
||||||
|
|
||||||
public string Description { get; set; }
|
[Property]
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
|
||||||
[ResourceType( "prefab" )]
|
[Property, Category("Visual")]
|
||||||
public GameObject Prefab { get; set; } = GameObject.GetPrefab( "prefabs/item_parcel.prefab" );
|
[ResourceType("prefab")]
|
||||||
|
public GameObject Prefab { get; set; } = GameObject.GetPrefab("prefabs/item_parcel.prefab");
|
||||||
|
|
||||||
|
[Property, Category("Visual")]
|
||||||
public Texture ImageTexture { get; set; }
|
public Texture ImageTexture { get; set; }
|
||||||
|
|
||||||
|
[Property, Category("Visual")]
|
||||||
public string ImageUrl { get; set; }
|
public string ImageUrl { get; set; }
|
||||||
|
|
||||||
|
[Property, Category("Properties")]
|
||||||
|
[Range(1, 1000)]
|
||||||
public int MaxCount { get; set; } = 1;
|
public int MaxCount { get; set; } = 1;
|
||||||
|
|
||||||
|
[Property, Category("Properties")]
|
||||||
|
public virtual ItemCategory Category { get; set; } = ItemCategory.Misc;
|
||||||
|
|
||||||
|
[Property, Category("Properties")]
|
||||||
|
public ItemRarity Rarity { get; set; } = ItemRarity.Common;
|
||||||
|
|
||||||
|
[Property, Category("Properties")]
|
||||||
|
public float Weight { get; set; } = 1.0f;
|
||||||
|
|
||||||
|
[Property, Category("Properties")]
|
||||||
|
public bool IsStackable => MaxCount > 1;
|
||||||
|
|
||||||
|
[Property, Category("Properties")]
|
||||||
|
public bool IsEquipable => this is IEquipable;
|
||||||
|
|
||||||
public virtual Inventar.InventorySlot? GetSlot() => null;
|
public virtual Inventar.InventorySlot? GetSlot() => null;
|
||||||
|
|
||||||
|
public virtual bool CanUse() => false;
|
||||||
|
|
||||||
|
public virtual void OnUse(InventoryItem item) { }
|
||||||
|
|
||||||
|
public string GetRarityColor()
|
||||||
|
{
|
||||||
|
return Rarity switch
|
||||||
|
{
|
||||||
|
ItemRarity.Common => "#ffffff",
|
||||||
|
ItemRarity.Uncommon => "#1eff00",
|
||||||
|
ItemRarity.Rare => "#0070dd",
|
||||||
|
ItemRarity.Epic => "#a335ee",
|
||||||
|
ItemRarity.Legendary => "#ff8000",
|
||||||
|
_ => "#ffffff"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetCategoryIcon()
|
||||||
|
{
|
||||||
|
return Category switch
|
||||||
|
{
|
||||||
|
ItemCategory.Weapon => "🔫",
|
||||||
|
ItemCategory.Clothing => "👕",
|
||||||
|
ItemCategory.Ammo => "💥",
|
||||||
|
ItemCategory.Consumable => "🍖",
|
||||||
|
ItemCategory.Tool => "🔧",
|
||||||
|
ItemCategory.Misc => "📦",
|
||||||
|
_ => "📦"
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,29 @@
|
|||||||
[GameResource( "Clothing Item Definition", "clitem", "", Category = "Sasalka", Icon = "inventory_2" )]
|
[GameResource( "Clothing Item Definition", "clitem", "", Category = "Sasalka", Icon = "inventory_2" )]
|
||||||
public class ClothingItemDefinition : BaseItemDefinition, IEquipable
|
public class ClothingItemDefinition : BaseItemDefinition, IEquipable
|
||||||
{
|
{
|
||||||
[Property] public string ClothUrl { get; set; }
|
[Property, Category( "Clothing Properties" )]
|
||||||
|
public string ClothUrl { get; set; }
|
||||||
|
|
||||||
|
[Property, Category( "Clothing Properties" )]
|
||||||
public Inventar.InventorySlot Slot { get; set; }
|
public Inventar.InventorySlot Slot { get; set; }
|
||||||
|
|
||||||
|
[Property, Category( "Clothing Properties" )]
|
||||||
|
public float ArmorValue { get; set; } = 0f;
|
||||||
|
|
||||||
|
[Property, Category( "Clothing Properties" )]
|
||||||
|
public bool IsVisible { get; set; } = true;
|
||||||
|
|
||||||
|
[Property, Category( "Clothing Properties" )]
|
||||||
|
public string BodyPart { get; set; } = "";
|
||||||
|
|
||||||
public override Inventar.InventorySlot? GetSlot() => Slot;
|
public override Inventar.InventorySlot? GetSlot() => Slot;
|
||||||
|
|
||||||
|
public override ItemCategory Category => ItemCategory.Clothing;
|
||||||
|
|
||||||
|
public override bool CanUse() => true;
|
||||||
|
|
||||||
|
public override void OnUse( InventoryItem item )
|
||||||
|
{
|
||||||
|
// Логика экипировки одежды
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,16 +5,55 @@ namespace Sasalka;
|
|||||||
[GameResource( "Weapon Item Definition", "weapon", "", Category = "Sasalka", Icon = "inventory_2" )]
|
[GameResource( "Weapon Item Definition", "weapon", "", Category = "Sasalka", Icon = "inventory_2" )]
|
||||||
public class WeaponItemDefinition : BaseItemDefinition, IEquipable
|
public class WeaponItemDefinition : BaseItemDefinition, IEquipable
|
||||||
{
|
{
|
||||||
|
[Property, Category( "Weapon Properties" )]
|
||||||
public Inventar.InventorySlot Slot { get; set; }
|
public Inventar.InventorySlot Slot { get; set; }
|
||||||
|
|
||||||
public override Inventar.InventorySlot? GetSlot() => Slot;
|
[Property, Category( "Weapon Properties" )]
|
||||||
public CitizenAnimationHelper.HoldTypes HoldType { get; set; } = CitizenAnimationHelper.HoldTypes.None;
|
public CitizenAnimationHelper.HoldTypes HoldType { get; set; } = CitizenAnimationHelper.HoldTypes.None;
|
||||||
[InlineEditor, Space] public WeaponDefinition WeaponDefinition { get; set; }
|
|
||||||
|
[Property, Category( "Weapon Properties" )]
|
||||||
|
[InlineEditor, Space]
|
||||||
|
public WeaponDefinition WeaponDefinition { get; set; }
|
||||||
|
|
||||||
|
[Property, Category( "Weapon Properties" )]
|
||||||
|
public float Damage { get; set; } = 10f;
|
||||||
|
|
||||||
|
[Property, Category( "Weapon Properties" )]
|
||||||
|
public float FireRate { get; set; } = 1f;
|
||||||
|
|
||||||
|
[Property, Category( "Weapon Properties" )]
|
||||||
|
public float Range { get; set; } = 1000f;
|
||||||
|
|
||||||
|
[Property, Category( "Weapon Properties" )]
|
||||||
|
public int MagazineSize { get; set; } = 30;
|
||||||
|
|
||||||
|
[Property, Category( "Weapon Properties" )]
|
||||||
|
public string AmmoType { get; set; } = "Pistol";
|
||||||
|
|
||||||
|
public override Inventar.InventorySlot? GetSlot() => Slot;
|
||||||
|
|
||||||
|
public override ItemCategory Category => ItemCategory.Weapon;
|
||||||
|
|
||||||
|
public override bool CanUse() => true;
|
||||||
|
|
||||||
|
public override void OnUse( InventoryItem item )
|
||||||
|
{
|
||||||
|
// Логика использования оружия будет в компоненте Weapon
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct WeaponDefinition
|
public struct WeaponDefinition
|
||||||
{
|
{
|
||||||
// public CitizenAnimationHelper.Hand Hand { get; set; }
|
[Property] public Vector3 Position { get; set; }
|
||||||
public Vector3 Position { get; set; }
|
|
||||||
public Rotation Rotation { get; set; }
|
[Property] public Rotation Rotation { get; set; }
|
||||||
|
|
||||||
|
[Property] public Vector3 Scale { get; set; }
|
||||||
|
|
||||||
|
public WeaponDefinition()
|
||||||
|
{
|
||||||
|
Position = Vector3.Zero;
|
||||||
|
Rotation = Rotation.Identity;
|
||||||
|
Scale = Vector3.One;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
namespace Sasalka;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
public class Inventar
|
namespace Sasalka;
|
||||||
|
|
||||||
|
public class Inventar : Component
|
||||||
{
|
{
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum InventorySlot
|
public enum InventorySlot
|
||||||
@ -15,84 +20,243 @@ public class Inventar
|
|||||||
Feet = 1 << 6 // 64
|
Feet = 1 << 6 // 64
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<InventoryItem> Items { get; private set; } = new();
|
[Sync] public IList<InventoryItem> Items { get; set; } = new List<InventoryItem>();
|
||||||
|
|
||||||
|
[Sync]
|
||||||
|
public IDictionary<InventorySlot, InventoryItem> EquippedItems { get; set; } =
|
||||||
|
new Dictionary<InventorySlot, InventoryItem>();
|
||||||
|
|
||||||
|
// Настройка вместимости инвентаря
|
||||||
|
[Property] public int MaxInventorySlots { get; set; } = 20; // Максимальное количество слотов
|
||||||
|
[Property] public bool UnlimitedSlots { get; set; } = false; // Безлимитный инвентарь
|
||||||
|
|
||||||
public static bool IsInventoryOpen = false;
|
public static bool IsInventoryOpen = false;
|
||||||
public Dictionary<InventorySlot, InventoryItem> EquippedItems { get; private set; } = new();
|
|
||||||
|
|
||||||
public event Action OnChanged;
|
public event Action OnChanged;
|
||||||
public event Action<InventoryItem> OnEquipped;
|
public event Action<InventoryItem> OnEquipped;
|
||||||
public event Action<InventoryItem> OnUnEquipped;
|
public event Action<InventoryItem> OnUnEquipped;
|
||||||
|
public event Action<InventoryItem> OnItemAdded;
|
||||||
|
public event Action<InventoryItem> OnItemRemoved;
|
||||||
|
|
||||||
public void AddItem( InventoryItem item )
|
public bool CanAddItem( InventoryItem item )
|
||||||
{
|
{
|
||||||
Items.Add( item );
|
if ( item == null || item.Definition == null )
|
||||||
OnChanged?.Invoke();
|
return false;
|
||||||
|
|
||||||
|
// Проверяем, есть ли уже такой предмет в инвентаре
|
||||||
|
var existingItem = Items.FirstOrDefault( x => x.Definition == item.Definition );
|
||||||
|
|
||||||
|
if ( existingItem != null )
|
||||||
|
{
|
||||||
|
// Если предмет уже есть, проверяем, можно ли добавить к нему количество
|
||||||
|
return existingItem.Count + item.Count <= item.Definition.MaxCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveItem( InventoryItem item )
|
// Если предмета нет, проверяем вместимость инвентаря
|
||||||
|
if ( !UnlimitedSlots && Items.Count >= MaxInventorySlots )
|
||||||
{
|
{
|
||||||
|
return false; // Инвентарь полон
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем только MaxCount для нового предмета
|
||||||
|
return item.Count <= item.Definition.MaxCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Добавляет предмет в инвентарь, распределяя по существующим и новым стекам. Возвращает остаток, который не удалось добавить (или 0, если всё добавлено).
|
||||||
|
/// </summary>
|
||||||
|
public int AddItem(InventoryItem item)
|
||||||
|
{
|
||||||
|
if (item == null || item.Definition == null || item.Count <= 0)
|
||||||
|
return item.Count;
|
||||||
|
|
||||||
|
int toAdd = item.Count;
|
||||||
|
|
||||||
|
// 1. Заполняем существующие стаки
|
||||||
|
foreach (var stack in Items.Where(x => x.Definition == item.Definition && x.Count < x.Definition.MaxCount))
|
||||||
|
{
|
||||||
|
int canAdd = Math.Min(toAdd, stack.Definition.MaxCount - stack.Count);
|
||||||
|
if (canAdd > 0)
|
||||||
|
{
|
||||||
|
stack.Count += canAdd;
|
||||||
|
toAdd -= canAdd;
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
OnItemAdded?.Invoke(stack);
|
||||||
|
}
|
||||||
|
if (toAdd <= 0) return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Добавляем новые стаки, если есть место
|
||||||
|
while (toAdd > 0 && (UnlimitedSlots || Items.Count < MaxInventorySlots))
|
||||||
|
{
|
||||||
|
int stackCount = Math.Min(toAdd, item.Definition.MaxCount);
|
||||||
|
var newStack = new InventoryItem { Definition = item.Definition, Count = stackCount };
|
||||||
|
Items.Add(newStack);
|
||||||
|
toAdd -= stackCount;
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
OnItemAdded?.Invoke(newStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Возвращаем остаток, если не всё удалось добавить
|
||||||
|
return toAdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveItem( InventoryItem item, int count = 1 )
|
||||||
|
{
|
||||||
|
if ( item == null || !Items.Contains( item ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( count >= item.Count )
|
||||||
|
{
|
||||||
|
// Удаляем весь предмет
|
||||||
UnEquipItem( item );
|
UnEquipItem( item );
|
||||||
Items.Remove( item );
|
Items.Remove( item );
|
||||||
OnChanged?.Invoke();
|
OnChanged?.Invoke();
|
||||||
|
OnItemRemoved?.Invoke( item );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Уменьшаем количество
|
||||||
|
item.Count -= count;
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EquipItem( InventoryItem item )
|
public bool EquipItem( InventoryItem item )
|
||||||
{
|
{
|
||||||
if ( item.Definition is not IEquipable equipable )
|
if ( item?.Definition is not IEquipable equipable )
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
|
var slot = item.Definition.GetSlot();
|
||||||
|
if ( slot == null )
|
||||||
|
return false;
|
||||||
|
|
||||||
// Если уже экипирован этот же предмет — снять его
|
// Если уже экипирован этот же предмет — снять его
|
||||||
if ( EquippedItems.ContainsValue( item ) )
|
if ( EquippedItems.Values.Contains( item ) )
|
||||||
{
|
{
|
||||||
UnEquipItem( item );
|
UnEquipItem( item );
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если на этом слоте уже что-то есть — снять старый предмет
|
// Если на этом слоте уже что-то есть — снять старый предмет
|
||||||
if ( EquippedItems.TryGetValue( equipable.Slot, out var oldItem ) )
|
if ( EquippedItems.TryGetValue( slot.Value, out var oldItem ) )
|
||||||
{
|
{
|
||||||
UnEquipItem( oldItem );
|
UnEquipItem( oldItem );
|
||||||
|
|
||||||
// Вернуть снятый предмет обратно в инвентарь, если его там нет
|
|
||||||
if ( !Items.Contains( oldItem ) )
|
|
||||||
Items.Add( oldItem );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Экипировать новый предмет
|
// Экипировать новый предмет
|
||||||
EquippedItems[equipable.Slot] = item;
|
EquippedItems[slot.Value] = item;
|
||||||
item.Equipped = true;
|
item.Equipped = true;
|
||||||
OnEquipped?.Invoke( item );
|
OnEquipped?.Invoke( item );
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DropItem( InventoryItem item, Vector3 position )
|
public void DropItem( InventoryItem item, Vector3 position )
|
||||||
{
|
{
|
||||||
|
if ( item == null || !Items.Contains( item ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Создаем копию предмета для выбрасывания
|
||||||
|
var droppedItem = new InventoryItem
|
||||||
|
{
|
||||||
|
Definition = item.Definition,
|
||||||
|
Count = item.Count // Выбрасываем всю стопку
|
||||||
|
};
|
||||||
|
|
||||||
GameObject gO = item.Definition.Prefab.Clone( position );
|
GameObject gO = item.Definition.Prefab.Clone( position );
|
||||||
|
|
||||||
if ( gO.Components.TryGet<InventoryItem>( out var inventoryItem ) )
|
if ( gO.Components.TryGet<InventoryItem>( out var inventoryItem ) )
|
||||||
{
|
{
|
||||||
inventoryItem.Count = item.Count;
|
inventoryItem.Count = droppedItem.Count;
|
||||||
|
inventoryItem.Definition = droppedItem.Definition;
|
||||||
if ( inventoryItem.Definition == null )
|
|
||||||
{
|
|
||||||
inventoryItem.Definition = item.Definition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gO.NetworkSpawn( null );
|
gO.NetworkSpawn();
|
||||||
RemoveItem( item );
|
|
||||||
Items.Remove( item );
|
|
||||||
OnChanged?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Удаляем весь предмет из инвентаря
|
||||||
|
RemoveItem( item, item.Count );
|
||||||
|
}
|
||||||
|
|
||||||
public void UnEquipItem( InventoryItem item )
|
public void UnEquipItem( InventoryItem item )
|
||||||
{
|
{
|
||||||
foreach ( var kvp in EquippedItems.Where( kvp => kvp.Value == item ).ToList() )
|
if ( item == null )
|
||||||
{
|
return;
|
||||||
EquippedItems.Remove( kvp.Key );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var slotToRemove = EquippedItems.FirstOrDefault( kvp => kvp.Value == item ).Key;
|
||||||
|
|
||||||
|
if ( EquippedItems.ContainsKey( slotToRemove ) )
|
||||||
|
{
|
||||||
|
EquippedItems.Remove( slotToRemove );
|
||||||
item.Equipped = false;
|
item.Equipped = false;
|
||||||
OnUnEquipped?.Invoke( item );
|
OnUnEquipped?.Invoke( item );
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InventoryItem GetEquippedItem( InventorySlot slot )
|
||||||
|
{
|
||||||
|
return EquippedItems.TryGetValue( slot, out var item ) ? item : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSlotOccupied( InventorySlot slot )
|
||||||
|
{
|
||||||
|
return EquippedItems.ContainsKey( slot );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearInventory()
|
||||||
|
{
|
||||||
|
// Снимаем все экипированные предметы
|
||||||
|
foreach ( var item in EquippedItems.Values.ToList() )
|
||||||
|
{
|
||||||
|
UnEquipItem( item );
|
||||||
|
}
|
||||||
|
|
||||||
|
Items.Clear();
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Публичный метод для уведомления об изменениях извне класса
|
||||||
|
public void NotifyChanged()
|
||||||
|
{
|
||||||
|
OnChanged?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Публичный метод для уведомления о добавлении предмета извне класса
|
||||||
|
public void NotifyItemAdded( InventoryItem item )
|
||||||
|
{
|
||||||
|
OnItemAdded?.Invoke( item );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Методы для получения информации о вместимости
|
||||||
|
public int GetUsedSlots()
|
||||||
|
{
|
||||||
|
return Items.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetAvailableSlots()
|
||||||
|
{
|
||||||
|
if ( UnlimitedSlots )
|
||||||
|
return int.MaxValue;
|
||||||
|
|
||||||
|
return Math.Max( 0, MaxInventorySlots - Items.Count );
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInventoryFull()
|
||||||
|
{
|
||||||
|
if ( UnlimitedSlots )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Items.Count >= MaxInventorySlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetInventoryUsagePercentage()
|
||||||
|
{
|
||||||
|
if ( UnlimitedSlots )
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
return (float)Items.Count / MaxInventorySlots * 100f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,14 +6,60 @@ namespace Sasalka;
|
|||||||
public class InventoryItem : Component
|
public class InventoryItem : Component
|
||||||
{
|
{
|
||||||
[Property] public BaseItemDefinition Definition { get; set; }
|
[Property] public BaseItemDefinition Definition { get; set; }
|
||||||
[Property] public int Count { get; set; } = 1;
|
[Sync, Property] public int Count { get; set; } = 1;
|
||||||
[Property] public bool Equipped { get; set; } = false;
|
[Sync] public bool Equipped { get; set; } = false;
|
||||||
|
|
||||||
protected override void OnStart()
|
protected override void OnStart()
|
||||||
{
|
{
|
||||||
if ( GameObject.Components.TryGet<PickupItem>( out var item ) ) //FindMode.EverythingInSelf
|
if ( GameObject.Components.TryGet<PickupItem>( out var item ) )
|
||||||
{
|
{
|
||||||
item.Label = Definition.Name;
|
item.Label = Definition?.Name ?? "Unknown Item";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanStackWith( InventoryItem other )
|
||||||
|
{
|
||||||
|
if ( other == null || Definition == null || other.Definition == null )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Definition == other.Definition && Definition.MaxCount > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanAddCount( int amount )
|
||||||
|
{
|
||||||
|
if ( Definition == null )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Count + amount <= Definition.MaxCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryAddCount( int amount )
|
||||||
|
{
|
||||||
|
if ( !CanAddCount( amount ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Count += amount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryRemoveCount( int amount )
|
||||||
|
{
|
||||||
|
if ( Count < amount )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Count -= amount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InventoryItem Clone()
|
||||||
|
{
|
||||||
|
var clone = new InventoryItem { Definition = Definition, Count = Count, Equipped = false };
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Definition?.Name ?? "Unknown"} x{Count}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
using Sandbox;
|
|
||||||
|
|
||||||
namespace Sasalka;
|
|
||||||
|
|
||||||
public class InventoryItemCountable : InventoryItem
|
|
||||||
{
|
|
||||||
// public int Count { get; set; } = 1;
|
|
||||||
// public int MaxCount { get; set; } = 1;
|
|
||||||
}
|
|
||||||
55
Code/Inventory/ItemSpawner.cs
Normal file
55
Code/Inventory/ItemSpawner.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace Sasalka;
|
||||||
|
|
||||||
|
public class ItemSpawner : Component
|
||||||
|
{
|
||||||
|
[Property] public bool SpawnTestItems { get; set; } = false;
|
||||||
|
[Property] public GameObject ItemPrefab { get; set; }
|
||||||
|
|
||||||
|
protected override void OnStart()
|
||||||
|
{
|
||||||
|
if (!Network.IsOwner || !SpawnTestItems) return;
|
||||||
|
|
||||||
|
GameTask.DelaySeconds(2f).ContinueWith(_ => SpawnItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnItems()
|
||||||
|
{
|
||||||
|
if (ItemPrefab == null)
|
||||||
|
{
|
||||||
|
// Log.Warning("ItemPrefab не установлен!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log.Info("Спавним тестовые предметы...");
|
||||||
|
|
||||||
|
var player = Dedugan.Local;
|
||||||
|
if (player?.Transform == null) return;
|
||||||
|
|
||||||
|
var playerPos = player.WorldPosition;
|
||||||
|
var spawnRadius = 5f;
|
||||||
|
var itemCount = 5;
|
||||||
|
|
||||||
|
for (int i = 0; i < itemCount; i++)
|
||||||
|
{
|
||||||
|
var angle = (float)i / itemCount * 360f * MathF.PI / 180f;
|
||||||
|
var offset = new Vector3(
|
||||||
|
MathF.Cos(angle) * spawnRadius,
|
||||||
|
0,
|
||||||
|
MathF.Sin(angle) * spawnRadius
|
||||||
|
);
|
||||||
|
|
||||||
|
var position = playerPos + offset;
|
||||||
|
var item = ItemPrefab.Clone(position);
|
||||||
|
|
||||||
|
// Добавляем случайный поворот
|
||||||
|
item.WorldRotation = Rotation.Random;
|
||||||
|
|
||||||
|
// Спавним в сети
|
||||||
|
item.NetworkSpawn();
|
||||||
|
|
||||||
|
// Log.Info($"Предмет создан в позиции {position}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
Code/Inventory/README.md
Normal file
172
Code/Inventory/README.md
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
# Система инвентаря Sasalka
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Новая система инвентаря была полностью переработана для улучшения производительности, сетевой синхронизации и пользовательского опыта.
|
||||||
|
|
||||||
|
## Основные изменения
|
||||||
|
|
||||||
|
### 1. Inventar (Основной класс инвентаря)
|
||||||
|
- Теперь наследуется от `Component` для лучшей интеграции с S&box
|
||||||
|
- Добавлена сетевая синхронизация с атрибутами `[Sync]`
|
||||||
|
- Улучшена валидация при добавлении предметов
|
||||||
|
- Добавлена поддержка стакания предметов
|
||||||
|
- Новые события: `OnItemAdded`, `OnItemRemoved`
|
||||||
|
|
||||||
|
### 2. InventoryItem
|
||||||
|
- Добавлена сетевая синхронизация с `[Sync]`
|
||||||
|
- Улучшена валидация количества предметов
|
||||||
|
- Новые методы: `CanStackWith`, `CanAddCount`, `TryAddCount`, `TryRemoveCount`, `Clone`
|
||||||
|
|
||||||
|
### 3. Определения предметов
|
||||||
|
|
||||||
|
#### BaseItemDefinition
|
||||||
|
- Добавлены категории предметов (`ItemCategory`)
|
||||||
|
- Добавлена система редкости (`ItemRarity`)
|
||||||
|
- Добавлен вес предметов
|
||||||
|
- Улучшен редактор с группировкой свойств
|
||||||
|
|
||||||
|
#### WeaponItemDefinition
|
||||||
|
- Добавлены свойства оружия (урон, скорострельность, дальность)
|
||||||
|
- Улучшена структура `WeaponDefinition`
|
||||||
|
|
||||||
|
#### ClothingItemDefinition
|
||||||
|
- Добавлены свойства брони и видимости
|
||||||
|
- Улучшена интеграция с системой одежды
|
||||||
|
|
||||||
|
#### AmmoItemDefinition (новый)
|
||||||
|
- Специализированное определение для патронов
|
||||||
|
- Система совместимости с оружием
|
||||||
|
- Свойства патронов (урон, пробивная способность)
|
||||||
|
|
||||||
|
### 4. UI инвентаря
|
||||||
|
- Добавлена фильтрация по категориям
|
||||||
|
- Улучшен дизайн с поддержкой редкости
|
||||||
|
- Отображение экипированных предметов
|
||||||
|
- Адаптивный интерфейс
|
||||||
|
|
||||||
|
## Исправления API
|
||||||
|
|
||||||
|
### Сетевая синхронизация
|
||||||
|
- Заменены атрибуты `[Net]` на `[Sync]` для совместимости с S&box
|
||||||
|
- Все изменения инвентаря автоматически синхронизируются между клиентами
|
||||||
|
|
||||||
|
### Методы коллекций
|
||||||
|
- Исправлен метод `ContainsValue()` на `Values.Contains()` для `Dictionary`
|
||||||
|
- Обновлены методы работы с коллекциями для совместимости
|
||||||
|
|
||||||
|
### Using директивы
|
||||||
|
- Добавлены необходимые `using` директивы в UI файлы
|
||||||
|
- Исправлены namespace для компонентов
|
||||||
|
|
||||||
|
### Наследование
|
||||||
|
- Исправлено наследование `PickupItem` от `Sandbox.UI.InteractionButton`
|
||||||
|
- Обновлены интерфейсы и базовые классы
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Создание предметов
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Создание предмета оружия
|
||||||
|
var weaponItem = new InventoryItem
|
||||||
|
{
|
||||||
|
Definition = ResourceLibrary.Get<WeaponItemDefinition>("Items/pistol_test.weapon")
|
||||||
|
};
|
||||||
|
inventory.AddItem(weaponItem);
|
||||||
|
|
||||||
|
// Создание патронов
|
||||||
|
var ammoItem = new InventoryItem
|
||||||
|
{
|
||||||
|
Definition = ResourceLibrary.Get<AmmoItemDefinition>("Items/pistol_ammo.ammo")
|
||||||
|
};
|
||||||
|
ammoItem.Count = 30;
|
||||||
|
inventory.AddItem(ammoItem);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Экипировка предметов
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Экипировка предмета
|
||||||
|
inventory.EquipItem(item);
|
||||||
|
|
||||||
|
// Проверка экипированного предмета
|
||||||
|
var equippedWeapon = inventory.GetEquippedItem(Inventar.InventorySlot.RightHand);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Валидация
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Проверка возможности добавления
|
||||||
|
if (inventory.CanAddItem(item))
|
||||||
|
{
|
||||||
|
inventory.AddItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка стакания
|
||||||
|
if (item1.CanStackWith(item2))
|
||||||
|
{
|
||||||
|
// Предметы можно объединить
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Создание определений предметов
|
||||||
|
|
||||||
|
### Оружие
|
||||||
|
1. Создайте ресурс типа "Weapon Item Definition"
|
||||||
|
2. Укажите слот экипировки
|
||||||
|
3. Настройте параметры оружия (урон, дальность и т.д.)
|
||||||
|
4. Укажите тип патронов
|
||||||
|
|
||||||
|
### Патроны
|
||||||
|
1. Создайте ресурс типа "Ammo Item Definition"
|
||||||
|
2. Укажите тип патронов
|
||||||
|
3. Настройте совместимость с оружием
|
||||||
|
|
||||||
|
### Одежда
|
||||||
|
1. Создайте ресурс типа "Clothing Item Definition"
|
||||||
|
2. Укажите слот экипировки
|
||||||
|
3. Добавьте URL одежды из Workshop
|
||||||
|
|
||||||
|
## Сетевая синхронизация
|
||||||
|
|
||||||
|
Все изменения инвентаря автоматически синхронизируются между клиентами благодаря атрибутам `[Sync]` на свойствах `Items` и `EquippedItems`.
|
||||||
|
|
||||||
|
## Производительность
|
||||||
|
|
||||||
|
- Улучшена производительность UI с оптимизированным `BuildHash()`
|
||||||
|
- Добавлено кэширование для `GetUsables()`
|
||||||
|
- Оптимизирована работа с коллекциями
|
||||||
|
|
||||||
|
## Миграция
|
||||||
|
|
||||||
|
Для миграции существующих предметов:
|
||||||
|
1. Обновите определения предметов с новыми свойствами
|
||||||
|
2. Измените код создания предметов на новый API
|
||||||
|
3. Обновите UI для использования новых компонентов
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
### InventoryTest
|
||||||
|
Используйте компонент `InventoryTest` для добавления тестовых предметов:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Добавьте компонент к игроку и установите AddTestItems = true
|
||||||
|
var testComponent = player.GameObject.Components.GetOrCreate<InventoryTest>();
|
||||||
|
testComponent.AddTestItems = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
### InventoryCompilationTest
|
||||||
|
Используйте компонент `InventoryCompilationTest` для проверки компиляции:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Добавьте компонент к игроку и установите RunTest = true
|
||||||
|
var testComponent = player.GameObject.Components.GetOrCreate<InventoryCompilationTest>();
|
||||||
|
testComponent.RunTest = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Известные проблемы
|
||||||
|
|
||||||
|
- Убедитесь, что все необходимые ресурсы (префабы, текстуры) существуют
|
||||||
|
- Проверьте, что определения предметов правильно настроены в редакторе
|
||||||
|
- При использовании в сети убедитесь, что все компоненты имеют правильные атрибуты `[Sync]`
|
||||||
@ -4,6 +4,21 @@
|
|||||||
|
|
||||||
<root class="inventory @( Inventar.IsInventoryOpen ? "" : "hidden" )">
|
<root class="inventory @( Inventar.IsInventoryOpen ? "" : "hidden" )">
|
||||||
<div class="inventory-panel">
|
<div class="inventory-panel">
|
||||||
|
@if ( PlayerInventory != null )
|
||||||
|
{
|
||||||
|
<div class="inventory-header">
|
||||||
|
<div class="inventory-title">Инвентарь</div>
|
||||||
|
@if ( !PlayerInventory.UnlimitedSlots )
|
||||||
|
{
|
||||||
|
<div class="inventory-slots">
|
||||||
|
@PlayerInventory.GetUsedSlots() / @PlayerInventory.MaxInventorySlots слотов
|
||||||
|
<div class="inventory-usage-bar">
|
||||||
|
<div class="inventory-usage-fill" style="width: @(PlayerInventory.GetInventoryUsagePercentage())%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
@if ( PlayerInventory.Items.Count > 0 )
|
@if ( PlayerInventory.Items.Count > 0 )
|
||||||
{
|
{
|
||||||
@foreach ( var item in PlayerInventory.Items )
|
@foreach ( var item in PlayerInventory.Items )
|
||||||
@ -11,6 +26,11 @@
|
|||||||
<Sasalka.Ui.InventoryItem Item="@item" OnItemClick="@( UseItem )" OnItemRightClick="@( DropItem )"/>
|
<Sasalka.Ui.InventoryItem Item="@item" OnItemClick="@( UseItem )" OnItemRightClick="@( DropItem )"/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="inventory-empty">Инвентарь пуст</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,59 @@ Inventory {
|
|||||||
//background-color: rgba(255, 0, 0, 0.1);
|
//background-color: rgba(255, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inventory-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 2px solid #2a3d54;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-slots {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-slots {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #a0b4c8;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-usage-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(42, 61, 84, 0.5);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #2a3d54;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-usage-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #4CAF50 0%, #8BC34A 100%);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #a0b4c8;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 40px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@ -12,25 +12,50 @@
|
|||||||
: !string.IsNullOrWhiteSpace( definition?.ImageUrl )
|
: !string.IsNullOrWhiteSpace( definition?.ImageUrl )
|
||||||
? definition.ImageUrl
|
? definition.ImageUrl
|
||||||
: null;
|
: null;
|
||||||
|
var rarityColor = definition?.GetRarityColor() ?? "#ffffff";
|
||||||
|
var categoryIcon = definition?.GetCategoryIcon() ?? "📦";
|
||||||
}
|
}
|
||||||
|
|
||||||
<root class="inventory-item @( Item.Equipped ? "equipped" : "" )" @onclick="@(() => OnItemClick?.Invoke( Item ))" @onrightclick=@( () => OnItemRightClick?.Invoke( Item ) )>
|
<root class="inventory-item @( Item.Equipped ? "equipped" : "" )"
|
||||||
@if ( slot is not null )
|
@onclick="@(() => OnItemClick?.Invoke( Item ))"
|
||||||
{
|
@onrightclick="@( () => OnItemRightClick?.Invoke( Item ) )"
|
||||||
<div class="inventory-item__slot">@slot</div>
|
style="border-left-color: @rarityColor;">
|
||||||
}
|
|
||||||
|
|
||||||
|
<div class="inventory-item__icon">
|
||||||
@if ( !string.IsNullOrEmpty( imageUrl ) )
|
@if ( !string.IsNullOrEmpty( imageUrl ) )
|
||||||
{
|
{
|
||||||
<img src="@imageUrl" alt="@name"/>
|
<img src="@imageUrl" alt="@name"/>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="category-icon">@categoryIcon</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="inventory-item__name">@name</div>
|
<div class="inventory-item__info">
|
||||||
|
<div class="inventory-item__name" style="color: @rarityColor;">@name</div>
|
||||||
|
@if ( definition?.Category != ItemCategory.Misc )
|
||||||
|
{
|
||||||
|
<div class="inventory-item__category">@definition?.Category</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inventory-item__meta">
|
||||||
|
@if ( slot is not null )
|
||||||
|
{
|
||||||
|
<div class="inventory-item__slot">@GetSlotName( slot.Value )</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if ( definition?.MaxCount > 1 )
|
@if ( definition?.MaxCount > 1 )
|
||||||
{
|
{
|
||||||
<div class="inventory-item__count">@Item?.Count / @definition.MaxCount</div>
|
<div class="inventory-item__count">@Item?.Count / @definition.MaxCount</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@* @if ( Item?.Equipped == true ) *@
|
||||||
|
@* { *@
|
||||||
|
@* <div class="inventory-item__equipped">✓</div> *@
|
||||||
|
@* } *@
|
||||||
|
</div>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@ -38,11 +63,28 @@
|
|||||||
public Action<Sasalka.InventoryItem> OnItemClick { get; set; }
|
public Action<Sasalka.InventoryItem> OnItemClick { get; set; }
|
||||||
public Action<Sasalka.InventoryItem> OnItemRightClick { get; set; }
|
public Action<Sasalka.InventoryItem> OnItemRightClick { get; set; }
|
||||||
|
|
||||||
|
string GetSlotName( Inventar.InventorySlot slot )
|
||||||
|
{
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
Inventar.InventorySlot.LeftHand => "Л.Рука",
|
||||||
|
Inventar.InventorySlot.RightHand => "П.Рука",
|
||||||
|
Inventar.InventorySlot.Head => "Голова",
|
||||||
|
Inventar.InventorySlot.Body => "Тело",
|
||||||
|
Inventar.InventorySlot.Hands => "Руки",
|
||||||
|
Inventar.InventorySlot.Bottom => "Ноги",
|
||||||
|
Inventar.InventorySlot.Feet => "Обувь",
|
||||||
|
_ => "Неизвестно"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected override int BuildHash()
|
protected override int BuildHash()
|
||||||
{
|
{
|
||||||
base.BuildHash();
|
base.BuildHash();
|
||||||
var hash = new HashCode();
|
var hash = new HashCode();
|
||||||
hash.Add( Item?.Count );
|
hash.Add( Item?.Count );
|
||||||
|
hash.Add( Item?.Equipped );
|
||||||
|
hash.Add( Item?.Definition?.Name );
|
||||||
return hash.ToHashCode();
|
return hash.ToHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,49 +1,154 @@
|
|||||||
InventoryItem {
|
.inventory-item {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #2a3d53;
|
background: linear-gradient(135deg, #2a3d53 0%, #1f2d3f 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
border: 1px solid #666;
|
border: 1px solid #666;
|
||||||
|
border-left: 4px solid #ffffff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 12px 24px;
|
padding: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s ease;
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
//box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #444;
|
background: linear-gradient(135deg, #3a4d63 0%, #2f3d4f 100%);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
//box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
border-color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
//box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.equipped {
|
||||||
|
border: 2px solid #4caf50;
|
||||||
|
background: linear-gradient(135deg, #2e3e2e 0%, #1f2d1f 100%);
|
||||||
|
//box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(135deg, #3e4e3e 0%, #2f3d2f 100%);
|
||||||
|
//box-shadow: 0 6px 16px rgba(76, 175, 80, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item__name {
|
||||||
|
color: #4caf50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item__icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #1a2a3a 0%, #0f1a2a 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #3a4a5a;
|
||||||
|
//box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
//filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.category-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
//filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item__info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
.inventory-item__name {
|
.inventory-item__name {
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
//text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item__description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #cccccc;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item__category {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #8fc98f;
|
||||||
|
background: linear-gradient(135deg, rgba(143, 201, 143, 0.2) 0%, rgba(143, 201, 143, 0.1) 100%);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid rgba(143, 201, 143, 0.3);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory-item__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.inventory-item__slot {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #8fc98f;
|
||||||
|
background: linear-gradient(135deg, rgba(143, 201, 143, 0.3) 0%, rgba(143, 201, 143, 0.2) 100%);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid rgba(143, 201, 143, 0.4);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
.inventory-item__count {
|
.inventory-item__count {
|
||||||
margin-left: auto;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inventory-item__slot {
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #8fc98f;
|
color: #cccccc;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
font-weight: 500;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.equipped {
|
.inventory-item__equipped {
|
||||||
border: 2px solid #4caf50;
|
font-size: 14px;
|
||||||
background: #2e3e2e;
|
color: #4caf50;
|
||||||
|
font-weight: bold;
|
||||||
|
//text-shadow: 0 1px 2px rgba(76, 175, 80, 0.5);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,35 +2,83 @@
|
|||||||
|
|
||||||
public abstract class AmmoUseableBase : UseableBase
|
public abstract class AmmoUseableBase : UseableBase
|
||||||
{
|
{
|
||||||
|
private WeaponItemDefinition _cachedWeaponDef;
|
||||||
|
private InventoryItem _cachedAmmoItem;
|
||||||
|
|
||||||
protected InventoryItem AmmoItem => FindAmmoItem();
|
protected InventoryItem AmmoItem => FindAmmoItem();
|
||||||
|
|
||||||
private InventoryItem FindAmmoItem()
|
private InventoryItem FindAmmoItem()
|
||||||
{
|
{
|
||||||
//По типу патрон поиск + енум типа патрон
|
var player = Dedugan.Local;
|
||||||
return Dedugan.Local.Inventory.Items.FirstOrDefault( i => i.Definition.Name == "Pistol Ammo" );
|
if (player?.Inventory == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Кэшируем WeaponDefinition для избежания повторных вызовов
|
||||||
|
if (_cachedWeaponDef == null)
|
||||||
|
{
|
||||||
|
_cachedWeaponDef = GetWeaponDefinition();
|
||||||
|
if (_cachedWeaponDef == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем кэшированный результат
|
||||||
|
if (_cachedAmmoItem != null && _cachedAmmoItem.Count > 0)
|
||||||
|
{
|
||||||
|
return _cachedAmmoItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ищем патроны
|
||||||
|
foreach (var item in player.Inventory.Items)
|
||||||
|
{
|
||||||
|
if (item.Definition is AmmoItemDefinition ammoDef && ammoDef.IsCompatibleWith(_cachedWeaponDef.AmmoType))
|
||||||
|
{
|
||||||
|
_cachedAmmoItem = item;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_cachedAmmoItem = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual WeaponItemDefinition GetWeaponDefinition()
|
||||||
|
{
|
||||||
|
// Переопределите в наследниках для возврата определения оружия
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanUse()
|
public override bool CanUse()
|
||||||
{
|
{
|
||||||
var ammo = AmmoItem;
|
var ammo = AmmoItem;
|
||||||
return base.CanUse() && ammo != null && ammo.Count > 0;
|
var baseCanUse = base.CanUse();
|
||||||
|
var hasAmmo = ammo != null && ammo.Count > 0;
|
||||||
|
|
||||||
|
return baseCanUse && hasAmmo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Use()
|
public override void Use()
|
||||||
{
|
{
|
||||||
if ( !CanUse() )
|
if (!CanUse())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OnUse();
|
OnUse();
|
||||||
|
|
||||||
var ammo = AmmoItem;
|
var ammo = AmmoItem;
|
||||||
if ( ammo != null )
|
if (ammo != null)
|
||||||
{
|
{
|
||||||
ammo.Count--;
|
// Уменьшаем количество патронов
|
||||||
|
if (ammo.TryRemoveCount(1))
|
||||||
if ( ammo.Count <= 0 )
|
|
||||||
{
|
{
|
||||||
Dedugan.Local.Inventory.RemoveItem( ammo );
|
// Если патроны закончились, удаляем предмет из инвентаря
|
||||||
|
if (ammo.Count <= 0)
|
||||||
|
{
|
||||||
|
Dedugan.Local.Inventory.RemoveItem(ammo);
|
||||||
|
_cachedAmmoItem = null; // Очищаем кэш
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,54 @@
|
|||||||
using Sandbox.Gravity;
|
using Sandbox.Gravity;
|
||||||
|
using Sandbox.UI;
|
||||||
using Sasalka;
|
using Sasalka;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Sandbox.UI;
|
namespace Sasalka;
|
||||||
|
|
||||||
[Icon( "skip_next" )]
|
[Icon( "skip_next" )]
|
||||||
public sealed class PickupItem : InteractionButton
|
public sealed class PickupItem : InteractionButton
|
||||||
{
|
{
|
||||||
[Property] public override string Label { get; set; } = "E";
|
[Property] public override string Label { get; set; } = "E";
|
||||||
|
|
||||||
|
protected override void OnStart()
|
||||||
|
{
|
||||||
|
base.OnStart();
|
||||||
|
|
||||||
|
// Устанавливаем правильную метку для предмета
|
||||||
|
if ( GameObject.Components.TryGet<InventoryItem>( out var inventoryItem ) )
|
||||||
|
{
|
||||||
|
Label = inventoryItem.Definition?.Name ?? "Подобрать";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override bool Press( IPressable.Event e )
|
public override bool Press( IPressable.Event e )
|
||||||
{
|
{
|
||||||
base.Press( e );
|
base.Press( e );
|
||||||
|
|
||||||
if ( e.Source.Components.TryGet<Dedugan>( out var dedugan ) )
|
if ( e.Source.Components.TryGet<Dedugan>( out var dedugan ) )
|
||||||
{
|
{
|
||||||
dedugan.Inventory.AddItem( Components.Get<InventoryItem>() );
|
var inventoryItem = Components.Get<InventoryItem>();
|
||||||
|
|
||||||
|
if ( inventoryItem != null && dedugan.Inventory != null )
|
||||||
|
{
|
||||||
|
// Пытаемся добавить предмет в инвентарь, остаток остаётся на земле
|
||||||
|
int left = dedugan.Inventory.AddItem( inventoryItem );
|
||||||
|
if ( left <= 0 )
|
||||||
|
{
|
||||||
RpcDestroy();
|
RpcDestroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inventoryItem.Count = left;
|
||||||
|
// Оставляем предмет с новым количеством на земле
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Rpc.Broadcast]
|
[Rpc.Broadcast]
|
||||||
|
|||||||
@ -4,8 +4,13 @@ public static class UseSystem
|
|||||||
{
|
{
|
||||||
public static bool TryUse( IUseContext context )
|
public static bool TryUse( IUseContext context )
|
||||||
{
|
{
|
||||||
foreach ( var useable in context.GetUsables() )
|
// Получаем все доступные предметы
|
||||||
|
var usables = context.GetUsables();
|
||||||
|
|
||||||
|
// Проверяем каждый предмет на возможность использования
|
||||||
|
foreach ( var useable in usables )
|
||||||
{
|
{
|
||||||
|
// Раннее прерывание если предмет может быть использован
|
||||||
if ( useable.CanUse() )
|
if ( useable.CanUse() )
|
||||||
{
|
{
|
||||||
useable.Use();
|
useable.Use();
|
||||||
|
|||||||
@ -37,7 +37,8 @@ public abstract class UseableBase : Component, IUseable
|
|||||||
|
|
||||||
public virtual void OnEquipped()
|
public virtual void OnEquipped()
|
||||||
{
|
{
|
||||||
Log.Info( $"OnEquip {this}" );
|
Equipped = true;
|
||||||
|
// Log.Info( $"OnEquip {this}" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool CanUse()
|
public virtual bool CanUse()
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using Sasalka;
|
|||||||
|
|
||||||
public sealed partial class Dedugan : Component
|
public sealed partial class Dedugan : Component
|
||||||
{
|
{
|
||||||
[Property, InlineEditor] public Inventar Inventory { get; private set; } = new();
|
[Property] public Inventar Inventory { get; private set; }
|
||||||
|
|
||||||
private Dictionary<Inventar.InventorySlot, (GameObject obj, IUseable useable)> _useableCache = new();
|
private Dictionary<Inventar.InventorySlot, (GameObject obj, IUseable useable)> _useableCache = new();
|
||||||
[Sync] private bool InAds { get; set; } = false;
|
[Sync] private bool InAds { get; set; } = false;
|
||||||
@ -12,65 +12,72 @@ public sealed partial class Dedugan : Component
|
|||||||
|
|
||||||
void InventoryStart()
|
void InventoryStart()
|
||||||
{
|
{
|
||||||
if ( !Network.IsOwner ) return;
|
if (!Network.IsOwner) return;
|
||||||
|
|
||||||
_resolver = new AttachmentSlotResolver( Renderer.GetAttachmentObject );
|
// Создаем инвентарь как компонент
|
||||||
|
Inventory = GameObject.Components.GetOrCreate<Inventar>();
|
||||||
|
_resolver = new AttachmentSlotResolver(Renderer.GetAttachmentObject);
|
||||||
|
|
||||||
// Inventory.AddItem( new InventoryItem
|
// Добавляем тестовые предметы (раскомментируйте для тестирования)
|
||||||
// {
|
var clothingItem = new InventoryItem
|
||||||
// Definition = ResourceLibrary.Get<ClothingItemDefinition>( "Items/Cloth/cloth_pijama.clitem" )
|
{
|
||||||
// } );
|
Definition = ResourceLibrary.Get<ClothingItemDefinition>("Items/Cloth/cloth_pijama.clitem")
|
||||||
//
|
};
|
||||||
// Inventory.AddItem( new InventoryItem
|
Inventory.AddItem(clothingItem);
|
||||||
// {
|
|
||||||
// Definition = ResourceLibrary.Get<ClothingItemDefinition>( "Items/Cloth/cloth_pijama_bottom.clitem" )
|
var weaponItem = new InventoryItem
|
||||||
// } );
|
{
|
||||||
//
|
Definition = ResourceLibrary.Get<WeaponItemDefinition>("Items/pistol_test.weapon")
|
||||||
// Inventory.AddItem( new InventoryItem
|
};
|
||||||
// {
|
Inventory.AddItem(weaponItem);
|
||||||
// Definition = ResourceLibrary.Get<WeaponItemDefinition>( "Items/pistol_test.weapon" )
|
|
||||||
// } );
|
var ammoItem = new InventoryItem
|
||||||
//
|
{
|
||||||
// var ammo = new InventoryItem
|
Definition = ResourceLibrary.Get<AmmoItemDefinition>("Items/pistol_ammo.inv")
|
||||||
// {
|
};
|
||||||
// Definition = ResourceLibrary.Get<BaseItemDefinition>( "Items/pistol_ammo.inv" )
|
ammoItem.Count = 30;
|
||||||
// };
|
Inventory.AddItem(ammoItem);
|
||||||
// ammo.Count = 30;
|
|
||||||
// ammo.MaxCount = 130;
|
|
||||||
//
|
|
||||||
// Inventory.AddItem( ammo );
|
|
||||||
|
|
||||||
Inventory.OnEquipped += OnItemEquipped;
|
Inventory.OnEquipped += OnItemEquipped;
|
||||||
Inventory.OnUnEquipped += OnItemUnEquipped;
|
Inventory.OnUnEquipped += OnItemUnEquipped;
|
||||||
|
Inventory.OnItemAdded += OnItemAdded;
|
||||||
|
Inventory.OnItemRemoved += OnItemRemoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnItemEquipped( InventoryItem item )
|
private void OnItemEquipped(InventoryItem item)
|
||||||
{
|
{
|
||||||
// Если это оружие
|
// Очищаем кэши при экипировке предмета
|
||||||
if ( item.Definition is WeaponItemDefinition weaponDef && weaponDef.Prefab.IsValid() )
|
_useableCache.Clear();
|
||||||
|
_resolver?.ClearCache();
|
||||||
|
|
||||||
|
if (item?.Definition is WeaponItemDefinition weaponDef && weaponDef.Prefab.IsValid())
|
||||||
{
|
{
|
||||||
var go = weaponDef.Prefab.Clone();
|
var go = weaponDef.Prefab.Clone();
|
||||||
|
|
||||||
AnimationHelper.HoldType = weaponDef.HoldType;
|
AnimationHelper.HoldType = weaponDef.HoldType;
|
||||||
|
|
||||||
switch ( weaponDef.Slot )
|
switch (weaponDef.Slot)
|
||||||
{
|
{
|
||||||
case Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand:
|
case Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand:
|
||||||
case Inventar.InventorySlot.RightHand:
|
case Inventar.InventorySlot.RightHand:
|
||||||
go.Parent = Renderer.GetAttachmentObject( "hold_R" );
|
go.Parent = Renderer.GetAttachmentObject("hold_R");
|
||||||
break;
|
break;
|
||||||
case Inventar.InventorySlot.LeftHand:
|
case Inventar.InventorySlot.LeftHand:
|
||||||
go.Parent = Renderer.GetAttachmentObject( "hold_L" );
|
go.Parent = Renderer.GetAttachmentObject("hold_L");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
go.Parent = Renderer.GetAttachmentObject( "forward_reference_modelspace" );
|
go.Parent = Renderer.GetAttachmentObject("forward_reference_modelspace");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
go.LocalPosition = weaponDef.WeaponDefinition.Position;
|
go.LocalPosition = weaponDef.WeaponDefinition.Position;
|
||||||
go.LocalRotation = weaponDef.WeaponDefinition.Rotation;
|
go.LocalRotation = weaponDef.WeaponDefinition.Rotation;
|
||||||
|
go.LocalScale = weaponDef.WeaponDefinition.Scale;
|
||||||
|
|
||||||
go.Components.Get<UseableBase>().Equipped = true;
|
if (go.Components.TryGet<UseableBase>(out var useable))
|
||||||
|
{
|
||||||
|
useable.Equipped = true;
|
||||||
|
}
|
||||||
|
|
||||||
go.NetworkSpawn();
|
go.NetworkSpawn();
|
||||||
|
|
||||||
@ -83,94 +90,108 @@ public sealed partial class Dedugan : Component
|
|||||||
};
|
};
|
||||||
|
|
||||||
AnimationHelper.Handedness = hand;
|
AnimationHelper.Handedness = hand;
|
||||||
|
RpcSetHoldAnimation(weaponDef.HoldType, hand);
|
||||||
RpcSetHoldAnimation( weaponDef.HoldType, hand );
|
|
||||||
|
|
||||||
InAds = true;
|
InAds = true;
|
||||||
}
|
}
|
||||||
// Если это одежда
|
else if (item?.Definition is ClothingItemDefinition clothingDef)
|
||||||
else if ( item.Definition is ClothingItemDefinition clothingDef )
|
|
||||||
{
|
{
|
||||||
WearWorkshop( new List<string>() { clothingDef.ClothUrl } );
|
WearWorkshop(new List<string>() { clothingDef.ClothUrl });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnItemUnEquipped(InventoryItem item)
|
||||||
|
{
|
||||||
|
// Очищаем кэши при снятии предмета
|
||||||
|
_useableCache.Clear();
|
||||||
|
_resolver?.ClearCache();
|
||||||
|
|
||||||
private void OnItemUnEquipped( InventoryItem item )
|
if (item?.Definition is WeaponItemDefinition weaponDef && weaponDef.Prefab.IsValid())
|
||||||
{
|
{
|
||||||
if ( item.Definition is WeaponItemDefinition weaponDef && weaponDef.Prefab.IsValid() )
|
switch (weaponDef.Slot)
|
||||||
{
|
|
||||||
switch ( weaponDef.Slot )
|
|
||||||
{
|
{
|
||||||
case Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand:
|
case Inventar.InventorySlot.LeftHand | Inventar.InventorySlot.RightHand:
|
||||||
case Inventar.InventorySlot.RightHand:
|
case Inventar.InventorySlot.RightHand:
|
||||||
case Inventar.InventorySlot.LeftHand:
|
case Inventar.InventorySlot.LeftHand:
|
||||||
var attachmentName = !weaponDef.Slot.HasFlag( Inventar.InventorySlot.RightHand )
|
var attachmentName = !weaponDef.Slot.HasFlag(Inventar.InventorySlot.RightHand)
|
||||||
? "hold_L"
|
? "hold_L"
|
||||||
: "hold_R";
|
: "hold_R";
|
||||||
|
|
||||||
Renderer.GetAttachmentObject( attachmentName ).Children.ForEach( child => child.Destroy() );
|
Renderer.GetAttachmentObject(attachmentName).Children.ForEach(child => child.Destroy());
|
||||||
RpcSetHoldAnimation( CitizenAnimationHelper.HoldTypes.None, CitizenAnimationHelper.Hand.Both );
|
RpcSetHoldAnimation(CitizenAnimationHelper.HoldTypes.None, CitizenAnimationHelper.Hand.Both);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Renderer.GetAttachmentObject( "forward_reference_modelspace" ).Children
|
Renderer.GetAttachmentObject("forward_reference_modelspace").Children
|
||||||
.ForEach( child => child.Destroy() );
|
.ForEach(child => child.Destroy());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Destroy();
|
|
||||||
InAds = false;
|
InAds = false;
|
||||||
}
|
}
|
||||||
else if ( item.Definition is ClothingItemDefinition clothingDef )
|
else if (item?.Definition is ClothingItemDefinition clothingDef)
|
||||||
{
|
{
|
||||||
StripByName( clothingDef.Description );
|
StripByName(clothingDef.Description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnItemAdded(InventoryItem item)
|
||||||
|
{
|
||||||
|
// Очищаем кэши при добавлении предмета
|
||||||
|
_useableCache.Clear();
|
||||||
|
_resolver?.ClearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnItemRemoved(InventoryItem item)
|
||||||
|
{
|
||||||
|
// Очищаем кэши при удалении предмета
|
||||||
|
_useableCache.Clear();
|
||||||
|
_resolver?.ClearCache();
|
||||||
|
}
|
||||||
|
|
||||||
[Rpc.Broadcast]
|
[Rpc.Broadcast]
|
||||||
public void RpcSetHoldAnimation( CitizenAnimationHelper.HoldTypes HoldType, CitizenAnimationHelper.Hand hand )
|
public void RpcSetHoldAnimation(CitizenAnimationHelper.HoldTypes HoldType, CitizenAnimationHelper.Hand hand)
|
||||||
{
|
{
|
||||||
AnimationHelper.HoldType = HoldType;
|
AnimationHelper.HoldType = HoldType;
|
||||||
AnimationHelper.Handedness = hand;
|
AnimationHelper.Handedness = hand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// AnimationHelper.HoldType = CitizenAnimationHelper.HoldTypes.None;
|
|
||||||
// AnimationHelper.Handedness = CitizenAnimationHelper.Hand.Both;
|
|
||||||
|
|
||||||
|
|
||||||
void InventoryUpdate()
|
void InventoryUpdate()
|
||||||
{
|
{
|
||||||
if ( !Network.IsOwner ) return;
|
if (!Network.IsOwner) return;
|
||||||
|
|
||||||
// InAds = Input.Down( "Attack2" );
|
if (Input.Pressed("Attack1"))
|
||||||
|
|
||||||
if ( Input.Pressed( "Attack1" ) )
|
|
||||||
{
|
{
|
||||||
if ( UseSystem.TryUse( this ) )
|
if (UseSystem.TryUse(this))
|
||||||
{
|
{
|
||||||
Attack();
|
Attack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IUseable>
|
public IEnumerable<IUseable> GetUsables()
|
||||||
GetUsables() //Допустим, у джетпака слот Body. Просто дописываешь в GetUsables() Inventar.InventorySlot.Body:
|
|
||||||
{
|
{
|
||||||
foreach ( var slot in new[] { Inventar.InventorySlot.LeftHand, Inventar.InventorySlot.RightHand } )
|
// Кэшируем слоты для избежания повторного создания массива
|
||||||
|
var slots = new[] { Inventar.InventorySlot.LeftHand, Inventar.InventorySlot.RightHand };
|
||||||
|
|
||||||
|
foreach (var slot in slots)
|
||||||
|
{
|
||||||
|
if (!Inventory.EquippedItems.TryGetValue(slot, out var item))
|
||||||
{
|
{
|
||||||
if ( !Inventory.EquippedItems.TryGetValue( slot, out var item ) )
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var holder = _resolver.GetSlotObject( slot );
|
var holder = _resolver.GetSlotObject(slot);
|
||||||
var heldObject = holder?.Children.FirstOrDefault();
|
if (holder == null) continue;
|
||||||
|
|
||||||
if ( heldObject == null )
|
var heldObject = holder.Children.FirstOrDefault();
|
||||||
continue;
|
if (heldObject == null)
|
||||||
|
|
||||||
if ( _useableCache.TryGetValue( slot, out var cached ) && cached.obj == heldObject )
|
|
||||||
{
|
{
|
||||||
if ( cached.useable != null )
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем кэш
|
||||||
|
if (_useableCache.TryGetValue(slot, out var cached) && cached.obj == heldObject)
|
||||||
|
{
|
||||||
|
if (cached.useable != null)
|
||||||
yield return cached.useable;
|
yield return cached.useable;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -178,7 +199,7 @@ public sealed partial class Dedugan : Component
|
|||||||
var useable = heldObject.Components.Get<IUseable>();
|
var useable = heldObject.Components.Get<IUseable>();
|
||||||
_useableCache[slot] = (heldObject, useable);
|
_useableCache[slot] = (heldObject, useable);
|
||||||
|
|
||||||
if ( useable != null )
|
if (useable != null)
|
||||||
yield return useable;
|
yield return useable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,7 +208,7 @@ public sealed partial class Dedugan : Component
|
|||||||
[Rpc.Broadcast]
|
[Rpc.Broadcast]
|
||||||
void Attack()
|
void Attack()
|
||||||
{
|
{
|
||||||
Renderer.Set( "b_attack", true );
|
Renderer.Set("b_attack", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ public sealed class Weapon : AmmoUseableBase
|
|||||||
[Property] public GameObject MuzzleLight { get; private set; }
|
[Property] public GameObject MuzzleLight { get; private set; }
|
||||||
[Property] public GameObject particlePrefab { get; set; }
|
[Property] public GameObject particlePrefab { get; set; }
|
||||||
[Property] public GameObject bloodParticle { get; set; }
|
[Property] public GameObject bloodParticle { get; set; }
|
||||||
|
[Property] public WeaponItemDefinition WeaponDefinition { get; set; }
|
||||||
|
|
||||||
private SoundPointComponent _sound;
|
private SoundPointComponent _sound;
|
||||||
private Rigidbody _rigidbody;
|
private Rigidbody _rigidbody;
|
||||||
@ -17,7 +18,7 @@ public sealed class Weapon : AmmoUseableBase
|
|||||||
protected override void OnStart()
|
protected override void OnStart()
|
||||||
{
|
{
|
||||||
base.OnStart();
|
base.OnStart();
|
||||||
_sound = GameObject.GetComponent<SoundPointComponent>( true );
|
_sound = GameObject.GetComponent<SoundPointComponent>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnEquipped()
|
public override void OnEquipped()
|
||||||
@ -27,66 +28,73 @@ public sealed class Weapon : AmmoUseableBase
|
|||||||
GameObject.Components.Get<PickupItem>().Enabled = false;
|
GameObject.Components.Get<PickupItem>().Enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override WeaponItemDefinition GetWeaponDefinition()
|
||||||
|
{
|
||||||
|
return WeaponDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
public void Attack()
|
public void Attack()
|
||||||
{
|
{
|
||||||
AttackEffects();
|
AttackEffects();
|
||||||
|
|
||||||
Vector3 startPos = Scene.Camera.WorldPosition;
|
Vector3 startPos = Scene.Camera.WorldPosition;
|
||||||
Vector3 dir = Scene.Camera.WorldRotation.Forward;
|
Vector3 dir = Scene.Camera.WorldRotation.Forward;
|
||||||
float maxDistance = 1000f;
|
float maxDistance = WeaponDefinition?.Range ?? 1000f;
|
||||||
|
|
||||||
var tr = Scene.Trace
|
var tr = Scene.Trace
|
||||||
.Ray( startPos, startPos + dir * maxDistance )
|
.Ray(startPos, startPos + dir * maxDistance)
|
||||||
.IgnoreGameObjectHierarchy( Dedugan.Local.GameObject )
|
.IgnoreGameObjectHierarchy(Dedugan.Local.GameObject)
|
||||||
.WithoutTags( "weapon" )
|
.WithoutTags("weapon")
|
||||||
.UseHitboxes()
|
.UseHitboxes()
|
||||||
.Run();
|
.Run();
|
||||||
|
|
||||||
if ( tr.Hit && tr.Hitbox != null )
|
if (tr.Hit)
|
||||||
|
{
|
||||||
|
if (tr.Hitbox != null)
|
||||||
{
|
{
|
||||||
var components = tr.GameObject.Components;
|
|
||||||
var boneIndex = tr.Hitbox.Bone.Index;
|
var boneIndex = tr.Hitbox.Bone.Index;
|
||||||
|
var components = tr.GameObject.Components;
|
||||||
|
|
||||||
// Log.Info($"{tr.GameObject.Name} attacked");
|
// Кэшируем компоненты для избежания повторных поисков
|
||||||
|
|
||||||
var dedugan = components.Get<Dedugan>();
|
var dedugan = components.Get<Dedugan>();
|
||||||
var enemy = components.Get<Enemy>();
|
var enemy = components.Get<Enemy>();
|
||||||
|
var hasTarget = dedugan.IsValid() || enemy.IsValid();
|
||||||
|
|
||||||
if ( dedugan.IsValid() || enemy.IsValid() )
|
if (hasTarget)
|
||||||
{
|
{
|
||||||
CreateHitEffects( tr.EndPosition, tr.Normal, true );
|
CreateHitEffects(tr.EndPosition, tr.Normal, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( dedugan.IsValid() )
|
if (dedugan.IsValid())
|
||||||
{
|
{
|
||||||
dedugan.ReportHit( dir, boneIndex );
|
dedugan.ReportHit(dir, boneIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( enemy.IsValid() )
|
if (enemy.IsValid())
|
||||||
{
|
{
|
||||||
enemy.ReportHit( dir, boneIndex );
|
enemy.ReportHit(dir, boneIndex);
|
||||||
Log.Info( boneIndex );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( tr.Hitbox == null )
|
else
|
||||||
{
|
{
|
||||||
CreateHitEffects( tr.EndPosition, tr.Normal );
|
CreateHitEffects(tr.EndPosition, tr.Normal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Rpc.Broadcast]
|
[Rpc.Broadcast]
|
||||||
private void CreateHitEffects( Vector3 position, Vector3 normal, bool blood = false )
|
private void CreateHitEffects(Vector3 position, Vector3 normal, bool blood = false)
|
||||||
{
|
{
|
||||||
var rot = Rotation.LookAt( normal );
|
var rot = Rotation.LookAt(normal);
|
||||||
DestroyAsync(
|
DestroyAsync(
|
||||||
blood
|
blood
|
||||||
? bloodParticle.Clone( position, rot )
|
? bloodParticle.Clone(position, rot)
|
||||||
: particlePrefab.Clone( position, rot ), 0.5f );
|
: particlePrefab.Clone(position, rot), 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
async void DestroyAsync( GameObject go, float delay )
|
async void DestroyAsync(GameObject go, float delay)
|
||||||
{
|
{
|
||||||
await GameTask.DelaySeconds( delay );
|
await GameTask.DelaySeconds(delay);
|
||||||
go.Destroy();
|
go.Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,12 +103,12 @@ public sealed class Weapon : AmmoUseableBase
|
|||||||
{
|
{
|
||||||
_sound?.StartSound();
|
_sound?.StartSound();
|
||||||
MuzzleLight.Enabled = true;
|
MuzzleLight.Enabled = true;
|
||||||
GunRenderer.Set( "Fire", true );
|
GunRenderer.Set("Fire", true);
|
||||||
|
|
||||||
GameTask.DelaySeconds( 0.05f ).ContinueWith( ( _ ) =>
|
GameTask.DelaySeconds(0.05f).ContinueWith((_) =>
|
||||||
{
|
{
|
||||||
MuzzleLight.Enabled = false;
|
MuzzleLight.Enabled = false;
|
||||||
} );
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUse()
|
protected override void OnUse()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user