This commit is contained in:
Oscar 2024-10-30 19:01:58 +03:00
parent b4b5254950
commit 602b6f27ed
53 changed files with 4371 additions and 487 deletions

124
Assets/prefabs/Colt.prefab Normal file
View File

@ -0,0 +1,124 @@
{
"RootObject": {
"__guid": "58539025-1035-4ff4-956c-cd4168ac898f",
"Flags": 0,
"Name": "colt",
"Enabled": true,
"Components": [
{
"__type": "SWB.Base.Weapon",
"__guid": "30890fea-3650-49c6-9f60-00994a5cd464",
"AimAnimData": {},
"AimFOV": -1,
"AimInFOVSpeed": 1,
"AimOutFOVSpeed": 1,
"AimPlayerFOV": -1,
"AimSensitivity": 0.85,
"AnimSpeed": 1,
"BoltBack": false,
"BoltBackAnim": "boltback",
"BoltBackEjectDelay": 0,
"BoltBackTime": 0,
"BulletCocking": true,
"ClassName": "Pistol",
"CustomizeAnimData": {},
"DeploySound": "sounds/swb/animations/swb_deploy.sound",
"DisplayName": "Colt",
"DrawAnim": "deploy",
"DrawEmptyAnim": "",
"DrawEmptyTime": -1,
"DrawTime": 0.5,
"FOV": 70,
"HoldType": "Pistol",
"Icon": "motivations/photo_2024-08-30_19-41-21.jpg",
"Primary": {
"_type": "component",
"component_id": "71dbc9ea-6e68-4ebf-8bcd-d023111a935d",
"go": "58539025-1035-4ff4-956c-cd4168ac898f",
"component_type": "ShootInfo"
},
"ReloadAnim": "reload",
"ReloadEmptyAnim": "reload_empty",
"ReloadEmptyTime": -1,
"ReloadTime": 1,
"RunAnimData": {},
"ScopeInfo": {
"LensTexture": "/materials/swb/scopes/swb_lens_hunter.png",
"ScopeTexture": "/materials/swb/scopes/swb_scope_hunter.png",
"ScopeInDelay": 0.2,
"FOV": 8,
"AimSensitivity": 0.25
},
"Scoping": false,
"ShellEjectDelay": 0,
"ShellReloading": false,
"ShellReloadingShootCancel": true,
"ShellReloadInsertTime": 0,
"ShellReloadStartTime": 0,
"Slot": 1,
"TuckRange": 30,
"WorldModel": "weapons/swb/colt/w_colt.vmdl"
},
{
"__type": "SWB.Base.ShootInfo",
"__guid": "71dbc9ea-6e68-4ebf-8bcd-d023111a935d",
"Ammo": 10,
"AmmoType": "pistol",
"BarrelSmokeParticle": "particles/swb/muzzle/barrel_smoke.vpcf",
"BulletEjectParticle": "particles/swb/bullet_hulls/9mm_eject.vpcf",
"Bullets": 1,
"BulletSize": 0.1,
"BulletTracerChance": 0.33,
"ClipSize": 10,
"Damage": 5,
"DryShootSound": "sounds/swb/clip/swb_pistol.empty.sound",
"FiringType": "semi",
"Force": 0.1,
"HitFlinch": 1.25,
"InfiniteAmmo": "reserve",
"MuzzleFlashParticle": "particles/swb/muzzle/flash.vpcf",
"Recoil": 0,
"RPM": 1000,
"ScreenShake": {},
"ShootAnim": "fire",
"ShootEmptyAnim": "",
"ShootSound": "sounds/swb/attachments/silencer/swb_pistol.silenced.fire.sound",
"Spread": 0.02,
"VMParticleScale": 1,
"WMParticleScale": 2
}
],
"Children": [],
"__variables": [],
"__properties": {
"FixedUpdateFrequency": 50,
"MaxFixedUpdates": 5,
"NetworkFrequency": 30,
"NetworkInterpolation": true,
"PhysicsSubSteps": 1,
"ThreadedAnimation": true,
"TimeScale": 1,
"UseFixedUpdate": true,
"Metadata": {},
"NavMesh": {
"Enabled": false,
"IncludeStaticBodies": true,
"IncludeKeyframedBodies": true,
"EditorAutoUpdate": true,
"AgentHeight": 64,
"AgentRadius": 16,
"AgentStepSize": 18,
"AgentMaxSlope": 40,
"ExcludedBodies": "",
"IncludedBodies": ""
}
}
},
"ShowInMenu": false,
"MenuPath": null,
"MenuIcon": null,
"DontBreakAsTemplate": false,
"ResourceVersion": 1,
"__references": [],
"__version": 1
}

161
Assets/prefabs/ScarH.prefab Normal file
View File

@ -0,0 +1,161 @@
{
"RootObject": {
"__guid": "a3b668c3-acfb-4043-8f7c-93b7df84b5c8",
"Flags": 0,
"Name": "scarh",
"Tags": "weapon",
"Components": [
{
"__type": "SWB.Base.Weapon",
"__guid": "4d44110c-f7a1-4168-a9f3-dbeaa77a630a",
"AimAnimData": {
"Angle": "-0.1,0.34,3.9",
"Pos": "-2.163,0.36,0.233"
},
"AimFOV": 35,
"AimInFOVSpeed": 1,
"AimOutFOVSpeed": 1,
"AimPlayerFOV": -1,
"AimSensitivity": 0.85,
"AnimSpeed": 1,
"BoltBack": false,
"BoltBackAnim": "boltback",
"BoltBackEjectDelay": 0,
"BoltBackTime": 0,
"BulletCocking": true,
"ClassName": "swb_scarh",
"CustomizeAnimData": {
"Angle": "-0.71,44.52,10",
"Pos": "5.755,-1.2,1.592"
},
"DeploySound": "sounds/swb/animations/swb_deploy.sound",
"DisplayName": "FN Scar H",
"DrawAnim": "deploy",
"DrawEmptyAnim": "",
"DrawEmptyTime": -1,
"DrawTime": 0.6,
"FOV": 65,
"HoldType": "Rifle",
"Icon": "weapons/swb/scarh/scarh.png",
"Primary": {
"_type": "component",
"component_id": "ff1c8c67-10c8-4767-925a-0d606a3845ff",
"go": "a3b668c3-acfb-4043-8f7c-93b7df84b5c8",
"component_type": "ShootInfo"
},
"ReloadAnim": "reload",
"ReloadEmptyAnim": "reload_empty",
"ReloadEmptyTime": -1,
"ReloadTime": 4.38,
"RunAnimData": {
"Angle": "8.41,59.66,0",
"Pos": "4.29,-0.86,-1.1"
},
"ScopeInfo": {
"LensTexture": "/materials/swb/scopes/swb_lens_hunter.png",
"ScopeTexture": "/materials/swb/scopes/swb_scope_hunter.png",
"ScopeInDelay": 0.2,
"FOV": 8,
"AimSensitivity": 0.25
},
"Scoping": false,
"ShellEjectDelay": 0,
"ShellReloading": false,
"ShellReloadingShootCancel": true,
"ShellReloadInsertTime": 0,
"ShellReloadStartTime": 0,
"Slot": 5,
"TuckRange": 30,
"WorldModel": "weapons/swb/scarh/w_scarh.vmdl"
},
{
"__type": "SWB.Base.ShootInfo",
"__guid": "ff1c8c67-10c8-4767-925a-0d606a3845ff",
"Ammo": 30,
"AmmoType": "rifle",
"BarrelSmokeParticle": "particles/swb/muzzle/barrel_smoke.vpcf",
"BulletEjectParticle": "particles/swb/bullet_hulls/20mm_eject.vpcf",
"Bullets": 1,
"BulletSize": 2,
"BulletTracerChance": 0.33,
"BulletTracerParticle": "particles/swb/tracer/tracer.vpcf",
"ClipSize": 30,
"Damage": 25,
"DryShootSound": "sounds/swb/clip/swb_rifle.empty.sound",
"FiringType": "auto",
"Force": 3,
"HitFlinch": 2,
"InfiniteAmmo": "reserve",
"MuzzleFlashParticle": "particles/swb/muzzle/flash.vpcf",
"Recoil": 0.35,
"RPM": 600,
"ScreenShake": {
"Duration": 0.08,
"Delay": 0.02,
"Size": 0.2,
"Rotation": 0.1
},
"ShootAimedAnim": "fireAimed",
"ShootAnim": "fire",
"ShootEmptyAnim": "",
"ShootSound": "weapons/swb/scarh/sounds/scar.fire.sound",
"Spread": 0.08,
"VMParticleScale": 1.3,
"WMParticleScale": 1
},
{
"__type": "SWB.Demo.ReflexSightBG",
"__guid": "0db16e29-30e1-463f-8812-7df647458e7c",
"AimAnimData": {
"Angle": "-1.47,0.44,3.9",
"Pos": "-2.181,0.36,0.596"
},
"AimFOV": -1,
"AimInFOVSpeed": -1,
"AimOutFOVSpeed": -1,
"ViewModelScale": "1,1,1",
"WorldModelScale": "1,1,1"
},
{
"__type": "SWB.Demo.RifleSilencerBG",
"__guid": "6a2ee00f-5ba2-4b35-a74e-ceb5f3cda188",
"EffectAttachmentOrBone": "muzzle_silenced",
"ShootSound": "sounds/swb/attachments/silencer/swb_rifle.silenced.fire.sound",
"ViewModelScale": "1,1,1",
"WorldModelScale": "1,1,1"
}
],
"Children": [],
"__variables": [],
"__properties": {
"FixedUpdateFrequency": 50,
"MaxFixedUpdates": 5,
"NetworkFrequency": 30,
"NetworkInterpolation": true,
"PhysicsSubSteps": 1,
"ThreadedAnimation": true,
"TimeScale": 1,
"UseFixedUpdate": true,
"Metadata": {},
"NavMesh": {
"Enabled": false,
"IncludeStaticBodies": true,
"IncludeKeyframedBodies": true,
"EditorAutoUpdate": true,
"AgentHeight": 64,
"AgentRadius": 16,
"AgentStepSize": 18,
"AgentMaxSlope": 40,
"ExcludedBodies": "",
"IncludedBodies": ""
}
}
},
"ShowInMenu": false,
"MenuPath": null,
"MenuIcon": null,
"DontBreakAsTemplate": false,
"ResourceVersion": 1,
"__references": [],
"__version": 1
}

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,22 @@
}, },
"SpawnPoints": [], "SpawnPoints": [],
"StartServer": true "StartServer": true
},
{
"__type": "SWB.Base.WeaponRegistry",
"__guid": "b4cb546a-e8ca-420d-b243-629a63341d2f",
"WeaponPrefabs": [
{
"_type": "gameobject",
"prefab": "prefabs/colt.prefab"
}
]
},
{
"__type": "SWB.Base.WeaponSettings",
"__guid": "7a6906e5-8d3a-44b1-875e-bc0e0ba96a59",
"AutoReload": false,
"Customization": false
} }
] ]
}, },
@ -82,83 +98,142 @@
] ]
}, },
{ {
"__guid": "6ad70641-3c6c-4402-9c85-9a4969af4764", "__guid": "3ee1c9f4-07be-4e0b-8b23-67bee2d8ec8a",
"Flags": 0, "Flags": 0,
"Name": "Plane", "Name": "Camera",
"Scale": "5,5,5", "Enabled": true,
"Components": [ "Components": [
{ {
"__type": "Sandbox.ModelRenderer", "__type": "Sandbox.CameraComponent",
"__guid": "0b6a18bf-fdb8-4661-970e-ef635bfa9baa", "__guid": "cf3cbf96-22b6-4be4-a5d0-672a96c17f9f",
"BodyGroups": 18446744073709551615, "BackgroundColor": "0.33333,0.46275,0.52157,1",
"MaterialOverride": "materials/default.vmat", "ClearFlags": "All",
"Model": "models/dev/plane.vmdl", "FieldOfView": 60,
"RenderOptions": { "IsMainCamera": true,
"GameLayer": true, "Orthographic": false,
"OverlayLayer": false, "OrthographicHeight": 1204,
"BloomLayer": false, "Priority": 1,
"AfterUILayer": false "RenderExcludeTags": "",
}, "RenderTags": "",
"RenderType": "On", "TargetEye": "None",
"Tint": "0.39546,0.51163,0.27128,1" "Viewport": "0,0,1,1",
"ZFar": 10000,
"ZNear": 10
}, },
{ {
"__type": "Sandbox.BoxCollider", "__type": "Sandbox.Bloom",
"__guid": "0715cb55-1733-4f5e-8560-c288b8695631", "__guid": "d73ef723-c888-41d4-802e-f797c79318be",
"Center": "0,0,-5", "BloomColor": {
"IsTrigger": false, "color": [
"Scale": "100,100,10", {
"Static": false, "c": "1,1,1,1"
"SurfaceVelocity": "0,0,0" },
{
"t": 1,
"c": "1,1,1,1"
}
],
"alpha": []
},
"BloomCurve": [
{
"y": 0.5
},
{
"x": 1,
"y": 1
}
],
"Mode": "Additive",
"Strength": 0.5,
"Threshold": 0.5,
"ThresholdWidth": 0.5
},
{
"__type": "Sandbox.Tonemapping",
"__guid": "9d76f362-7227-40eb-a189-69353c780c46",
"__version": 1,
"AutoExposureEnabled": true,
"ExposureBias": 2,
"ExposureCompensation": 0,
"ExposureMethod": "RGB",
"MaximumExposure": 2,
"MinimumExposure": 1,
"Mode": "Legacy",
"Rate": 1
},
{
"__type": "Sandbox.Sharpen",
"__guid": "0bffee5e-19f2-41c4-88f9-faefbcce6bf4",
"Scale": 0.2
} }
] ]
}, },
{ {
"__guid": "3c2490ef-54a0-49bb-8f13-490e40aa51d1", "__guid": "cf125021-fa85-46d4-9ad8-6ea4ac3cc1e9",
"Flags": 0, "Flags": 0,
"Name": "Cube", "Name": "mmm",
"Position": "-18.53616,-147.7339,86.60748", "Position": "0,0,-300",
"Rotation": "0.00000001819328,-0.00000000000000008235059,0.3052325,0.952278",
"Scale": "0.5632889,0.5632889,0.5632889",
"Enabled": true, "Enabled": true,
"Components": [ "Components": [
{ {
"__type": "Sandbox.ModelRenderer", "__type": "Sandbox.MapInstance",
"__guid": "b9121ffa-617c-4ccc-a2aa-8acc98727590", "__guid": "6e911feb-4dd7-4be6-9729-e48480e2ed2a",
"__version": 1,
"EnableCollision": true,
"MapName": "maps/nunu.vmap",
"NoOrigin": false,
"OnMapLoaded": null,
"OnMapUnloaded": null,
"UseMapFromLaunch": false
}
],
"Children": []
},
{
"__guid": "a100c2ff-ab5a-42dd-8c8f-6681e93d0bed",
"Flags": 0,
"Name": "Props",
"Enabled": true,
"Children": [
{
"__guid": "05bf2234-dded-4460-9cf0-9fe560d5aef0",
"Flags": 0,
"Name": "marketstall",
"Position": "416.919,201.6388,-218.2547",
"Rotation": "0,0,0.7071067,0.7071068",
"Scale": "3,3,3",
"Enabled": true,
"Components": [
{
"__type": "Sandbox.Prop",
"__guid": "4a71d1a2-b037-4cf6-acce-33bdc73faf6f",
"BodyGroups": 18446744073709551615, "BodyGroups": 18446744073709551615,
"MaterialOverride": "materials/default.vmat", "Health": 0,
"Model": "models/dev/box.vmdl", "IsStatic": true,
"RenderOptions": { "Model": "models/3dmodelscc0/medievalpropspack/marketstall/marketstall.vmdl",
"GameLayer": true, "StartAsleep": false,
"OverlayLayer": false, "Tint": "1,1,1,1"
"BloomLayer": false, }
"AfterUILayer": false ]
},
"RenderType": "On",
"Tint": "1,0,0.93333,1"
}, },
{ {
"__type": "Sandbox.BoxCollider", "__guid": "06b026eb-cb28-4808-97c2-3a6db8294da6",
"__guid": "8bb3ebcf-1ec9-4b20-bf31-4aece0950008", "Flags": 0,
"Center": "0,0,0", "Name": "couch",
"IsTrigger": false, "Position": "388.4759,324.1619,-246.5044",
"Scale": "50,50,50", "Rotation": "0,0,0.7071067,0.7071068",
"Static": false, "Enabled": true,
"SurfaceVelocity": "0,0,0" "Components": [
},
{ {
"__type": "Sandbox.Rigidbody", "__type": "Sandbox.Prop",
"__guid": "4aaa0334-6785-4716-9bae-869559ea6e10", "__guid": "cb993131-4f9c-4a03-aa64-896183340491",
"AngularDamping": 0, "BodyGroups": 18446744073709551615,
"Gravity": true, "Health": 5,
"LinearDamping": 0, "IsStatic": true,
"Locking": {}, "Model": "models/props/couch/couch.vmdl",
"MassCenterOverride": "0,0,0", "StartAsleep": false,
"MassOverride": 0, "Tint": "1,1,1,1"
"MotionEnabled": true,
"OverrideMassCenter": false,
"RigidbodyFlags": 0,
"StartAsleep": false
} }
] ]
}, },
@ -261,97 +336,113 @@
] ]
}, },
{ {
"__guid": "3ee1c9f4-07be-4e0b-8b23-67bee2d8ec8a", "__guid": "3c2490ef-54a0-49bb-8f13-490e40aa51d1",
"Flags": 0, "Flags": 0,
"Name": "Camera", "Name": "Cube",
"Position": "-18.53616,-147.7339,86.60748",
"Rotation": "0.00000001819328,-0.00000000000000008235059,0.3052325,0.952278",
"Scale": "0.5632889,0.5632889,0.5632889",
"Enabled": true, "Enabled": true,
"Components": [ "Components": [
{ {
"__type": "Sandbox.CameraComponent", "__type": "Sandbox.ModelRenderer",
"__guid": "cf3cbf96-22b6-4be4-a5d0-672a96c17f9f", "__guid": "b9121ffa-617c-4ccc-a2aa-8acc98727590",
"BackgroundColor": "0.33333,0.46275,0.52157,1", "BodyGroups": 18446744073709551615,
"ClearFlags": "All", "MaterialOverride": "materials/default.vmat",
"FieldOfView": 60, "Model": "models/dev/box.vmdl",
"IsMainCamera": true, "RenderOptions": {
"Orthographic": false, "GameLayer": true,
"OrthographicHeight": 1204, "OverlayLayer": false,
"Priority": 1, "BloomLayer": false,
"RenderExcludeTags": "", "AfterUILayer": false
"RenderTags": "", },
"TargetEye": "None", "RenderType": "On",
"Viewport": "0,0,1,1", "Tint": "1,0,0.93333,1"
"ZFar": 10000,
"ZNear": 10
}, },
{ {
"__type": "Sandbox.Bloom", "__type": "Sandbox.BoxCollider",
"__guid": "d73ef723-c888-41d4-802e-f797c79318be", "__guid": "8bb3ebcf-1ec9-4b20-bf31-4aece0950008",
"BloomColor": { "Center": "0,0,0",
"color": [ "IsTrigger": false,
{ "Scale": "50,50,50",
"c": "1,1,1,1" "Static": false,
"SurfaceVelocity": "0,0,0"
}, },
{ {
"t": 1, "__type": "Sandbox.Rigidbody",
"c": "1,1,1,1" "__guid": "4aaa0334-6785-4716-9bae-869559ea6e10",
} "AngularDamping": 0,
], "Gravity": true,
"alpha": [] "LinearDamping": 0,
}, "Locking": {},
"BloomCurve": [ "MassCenterOverride": "0,0,0",
{ "MassOverride": 0,
"y": 0.5 "MotionEnabled": true,
}, "OverrideMassCenter": false,
{ "RigidbodyFlags": 0,
"x": 1, "StartAsleep": false
"y": 1
}
],
"Mode": "Additive",
"Strength": 0.5,
"Threshold": 0.5,
"ThresholdWidth": 0.5
},
{
"__type": "Sandbox.Tonemapping",
"__guid": "9d76f362-7227-40eb-a189-69353c780c46",
"__version": 1,
"AutoExposureEnabled": true,
"ExposureBias": 2,
"ExposureCompensation": 0,
"ExposureMethod": "RGB",
"MaximumExposure": 2,
"MinimumExposure": 1,
"Mode": "Legacy",
"Rate": 1
},
{
"__type": "Sandbox.Sharpen",
"__guid": "0bffee5e-19f2-41c4-88f9-faefbcce6bf4",
"Scale": 0.2
} }
] ]
}, },
{ {
"__guid": "cf125021-fa85-46d4-9ad8-6ea4ac3cc1e9", "__guid": "9b58c908-7d46-4c7c-95b8-d43a0b374d6e",
"Flags": 0, "Flags": 0,
"Name": "mmm", "Name": "sboxspongehouse",
"Position": "0,0,-300", "Position": "-859.0681,986.7744,-244.0724",
"Enabled": true, "Enabled": true,
"Components": [ "Components": [
{ {
"__type": "Sandbox.MapInstance", "__type": "Sandbox.Prop",
"__guid": "6e911feb-4dd7-4be6-9729-e48480e2ed2a", "__guid": "7d1d85b7-df0a-42c5-b77a-9e47e802119c",
"__version": 1, "BodyGroups": 18446744073709551615,
"EnableCollision": true, "Health": 0,
"MapName": "maps/nunu.vmap", "IsStatic": true,
"NoOrigin": false, "Model": "spongebobshouse/sboxspongehouse.vmdl",
"OnMapLoaded": null, "StartAsleep": false,
"OnMapUnloaded": null, "Tint": "1,1,1,1"
"UseMapFromLaunch": false
} }
], ]
"Children": [] }
]
},
{
"__guid": "9d92bb31-0aae-4bd5-ba98-a4943b011fe6",
"Flags": 0,
"Name": "ceramicarch_m",
"Position": "-246.0864,1310.813,-157.7126",
"Scale": "1.520212,1.520212,1.520212",
"Enabled": true,
"Components": [
{
"__type": "Sandbox.Prop",
"__guid": "2514ccb2-a612-4fb5-869b-658a9b9319a5",
"BodyGroups": 18446744073709551615,
"Health": 0,
"IsStatic": true,
"Model": "models/ceramicarch/ceramicarch_m.vmdl",
"StartAsleep": false,
"Tint": "1,1,1,1"
}
]
},
{
"__guid": "cacdea24-9b76-4081-839a-5d377a34012f",
"Flags": 0,
"Name": "street_tree01",
"Position": "-609.5272,760.2201,-247.2903",
"Enabled": true,
"Components": [
{
"__type": "Sandbox.Prop",
"__guid": "e1c15a0e-34f9-43f8-b7a8-3ee4f658196c",
"BodyGroups": 18446744073709551615,
"Health": 0,
"IsStatic": true,
"Model": "models/tree/tree01/street_tree01.vmdl",
"StartAsleep": false,
"Tint": "1,1,1,1"
}
]
} }
], ],
"SceneProperties": { "SceneProperties": {
@ -382,6 +473,12 @@
"ResourceVersion": 2, "ResourceVersion": 2,
"Title": "minimal", "Title": "minimal",
"Description": null, "Description": null,
"__references": [], "__references": [
"facepunch.couch#33708",
"progressgames.ceramicarch_m#54712",
"spongeis.spongehouse#65599",
"tiowevolve.street_tree_01#15733",
"titanovsky.marketstall#53840"
],
"__version": 2 "__version": 2
} }

88
Code/Inventory.cs Normal file
View File

@ -0,0 +1,88 @@
using SWB.Shared;
public class Inventory : Component, IInventory
{
[Sync] public NetList<GameObject> Items { get; set; } = new();
[Sync] public new GameObject Active { get; set; }
Kal player;
protected override void OnAwake()
{
player = Components.Get<Kal>();
}
public void Add( GameObject gameObject, bool makeActive = false )
{
if ( !Has( gameObject ) )
{
Items.Add( gameObject );
}
if ( makeActive )
SetActive( gameObject );
else
{
if ( gameObject.Components.TryGet<IInventoryItem>( out var item ) )
{
item.OnCarryStop();
}
}
}
public GameObject AddClone( GameObject gamePrefab, bool makeActive = true )
{
var gameObject = gamePrefab.Clone( player.GameObject, player.WorldPosition, player.WorldRotation, Vector3.One );
gameObject.Name = gamePrefab.Name;
gameObject.NetworkSpawn( player.Network.Owner );
Add( gameObject, makeActive );
return gameObject;
}
public bool Has( GameObject gameObject )
{
return Items.Contains( gameObject );
}
public void SetActive( GameObject gameObject )
{
if ( !Has( gameObject ) || Active == gameObject ) return;
if ( Active is not null && Active.Components.TryGet<IInventoryItem>( out var oldActive ) )
{
if ( !oldActive.CanCarryStop() ) return;
oldActive.OnCarryStop();
}
if ( gameObject.Components.TryGet<IInventoryItem>( out var newActive, FindMode.EverythingInSelf ) )
{
newActive.OnCarryStart();
}
Active = gameObject;
}
public void SetActive( string name )
{
foreach ( var item in Items )
{
if ( item.Name == name )
{
SetActive( item );
break;
}
}
}
public void Clear()
{
foreach ( var item in Items )
{
item.Destroy();
}
Items.Clear();
Active = null;
}
}

View File

@ -2,16 +2,51 @@ using System;
using Sandbox.Citizen; using Sandbox.Citizen;
using Sandbox.Network; using Sandbox.Network;
using ShrimpleCharacterController; using ShrimpleCharacterController;
using SWB.Base;
using SWB.Shared;
using DamageInfo = SWB.Shared.DamageInfo;
public sealed class Kal : Component public sealed class Kal : Component, IPlayerBase
{ {
[RequireComponent] private ShrimpleCharacterController.ShrimpleCharacterController Controller { get; set; } public ShrimpleCharacterController.ShrimpleCharacterController CharacterController { get; set; }
[RequireComponent] private AnimationHelper AnimationHelper { get; set; } public AnimationHelper AnimationHelper { get; set; }
[RequireComponent] private RagdollController RagdollController { get; set; } private RagdollController RagdollController { get; set; }
public SkinnedModelRenderer Renderer { get; set; } public SkinnedModelRenderer BodyRenderer { get; set; }
[Property] public GameObject Body { get; set; } [Property] public GameObject Body { get; set; }
[Property] public GameObject CameraPivot { get; set; } [Property] public GameObject CameraPivot { get; set; }
public GameObject Camera { get; set; } public CameraComponent Camera { get; set; }
public IInventory Inventory { get; set; }
public bool IsAlive { get; set; } = true;
[Property] public int MaxHealth { get; set; }
public int Health { get; set; }
public int Kills { get; set; }
public int Deaths { get; set; }
[Sync] public NetDictionary<string, int> Ammo { get; set; } = new();
public int AmmoCount( string ammoType )
{
if ( Ammo.TryGetValue( ammoType, out var amount ) )
{
return amount;
}
return 0;
}
public int TakeAmmo( string type, int amount )
{
throw new NotImplementedException();
}
public void TakeDamage( DamageInfo info )
{
Health -= (int)info.Damage;
}
public void ShakeScreen( ScreenShake screenShake )
{
return;
}
[Property] [Range(1f, 200f, 1f)] public float CamOffsetX { get; set; } [Property] [Range(1f, 200f, 1f)] public float CamOffsetX { get; set; }
[Property] [Range(1f, 200f, 1f)] public float CamOffsetZ { get; set; } [Property] [Range(1f, 200f, 1f)] public float CamOffsetZ { get; set; }
@ -26,18 +61,31 @@ public sealed class Kal : Component
{ {
base.OnStart(); base.OnStart();
Controller = Components.Get<ShrimpleCharacterController.ShrimpleCharacterController>(); CharacterController = Components.Get<ShrimpleCharacterController.ShrimpleCharacterController>();
AnimationHelper = Components.Get<AnimationHelper>(); AnimationHelper = Components.Get<AnimationHelper>();
RagdollController = Components.Get<RagdollController>(); RagdollController = Components.Get<RagdollController>();
if ( !Network.IsOwner ) return; if ( !Network.IsOwner ) return;
Renderer = Components.Get<SkinnedModelRenderer>(FindMode.EverythingInSelfAndDescendants); BodyRenderer = Components.Get<SkinnedModelRenderer>(FindMode.EverythingInSelfAndDescendants);
Camera = Scene.Camera.GameObject; Camera = Scene.Camera;
Camera.SetParent(GameObject); Camera.GameObject.SetParent(GameObject);
var cameraComponent = Camera.Components.Create<CameraComponent>(); var cameraComponent = Camera.Components.Create<CameraComponent>();
cameraComponent.ZFar = 32768f; cameraComponent.ZFar = 32768f;
var weaponRegistery = Scene.Components.GetInChildren<WeaponRegistry>();
var weaponGO = weaponRegistery.Get( "Pistol" );
var weapon = weaponGO.Components.Get<Weapon>( true );
weaponGO.SetParent(GameObject);
// weaponGO.Enabled = true;
Inventory = Components.Create<Inventory>();
Inventory.Add(weaponGO);
Inventory.SetActive("Pistol");
Log.Info(Inventory.Items[0].Name);
} }
protected override void OnFixedUpdate() protected override void OnFixedUpdate()
@ -53,13 +101,13 @@ public sealed class Kal : Component
var wishSpeed = isDucking ? DuckSpeed : var wishSpeed = isDucking ? DuckSpeed :
isRunning ? RunSpeed : WalkSpeed; isRunning ? RunSpeed : WalkSpeed;
Controller.WishVelocity = wishDirection * wishSpeed; CharacterController.WishVelocity = wishDirection * wishSpeed;
Controller.Move(); CharacterController.Move();
if ( Input.Pressed( "Jump" ) && Controller.IsOnGround ) if ( Input.Pressed( "Jump" ) && CharacterController.IsOnGround )
{ {
Controller.Punch( Vector3.Up * JumpStrength ); CharacterController.Punch( Vector3.Up * JumpStrength );
AnimationHelper.TriggerJump(); AnimationHelper.TriggerJump();
} }
@ -68,9 +116,9 @@ public sealed class Kal : Component
AnimationHelper.DuckLevel = isDucking ? 1f : 0f; AnimationHelper.DuckLevel = isDucking ? 1f : 0f;
} }
AnimationHelper.WithWishVelocity(Controller.WishVelocity); AnimationHelper.WithWishVelocity(CharacterController.WishVelocity);
AnimationHelper.WithVelocity(Controller.Velocity); AnimationHelper.WithVelocity(CharacterController.Velocity);
AnimationHelper.IsGrounded = Controller.IsOnGround; AnimationHelper.IsGrounded = CharacterController.IsOnGround;
} }
protected override void OnUpdate() protected override void OnUpdate()
@ -102,7 +150,7 @@ public sealed class Kal : Component
float rotateDifference = Body.WorldRotation.Distance(targetAngle); float rotateDifference = Body.WorldRotation.Distance(targetAngle);
if(rotateDifference > 30f || Controller.Velocity.Length > 10f) if(rotateDifference > 30f || CharacterController.Velocity.Length > 10f)
{ {
Body.WorldRotation = Rotation.Lerp(Body.WorldRotation, targetAngle, Time.Delta * 2f); Body.WorldRotation = Rotation.Lerp(Body.WorldRotation, targetAngle, Time.Delta * 2f);
} }

View File

@ -6,7 +6,7 @@ public sealed class RagdollController : Component
[Group("Config"), Order(0), Property] public bool isLocked { get; set; } [Group("Config"), Order(0), Property] public bool isLocked { get; set; }
[Sync] [Sync]
public bool Enabled public new bool Enabled
{ {
get => bodyPhysics.Enabled; get => bodyPhysics.Enabled;
private set private set

View File

@ -0,0 +1,71 @@
using SWB.Shared;
using System;
using System.Linq;
namespace SWB.Base;
public class HitScanBullet : IBulletBase
{
public void Shoot( Weapon weapon, ShootInfo shootInfo, Vector3 spreadOffset )
{
if ( !weapon.IsValid ) return;
var player = weapon.Owner;
if ( player is null ) return;
var forward = player.Camera.WorldRotation.Forward + spreadOffset;
forward = forward.Normal;
var endPos = player.Camera.WorldPosition + forward * 999999;
var bulletTr = weapon.TraceBullet( player.Camera.WorldPosition, endPos );
var hitObj = bulletTr.GameObject;
if ( SurfaceUtil.IsSkybox( bulletTr.Surface ) || bulletTr.HitPosition == Vector3.Zero ) return;
// Impact
weapon.CreateBulletImpact( bulletTr );
// Tracer
if ( shootInfo.BulletTracerParticle is not null )
{
var random = new Random();
var randVal = random.NextDouble();
if ( randVal < shootInfo.BulletTracerChance )
TracerEffects( weapon, shootInfo, bulletTr.HitPosition );
}
// Damage
if ( !weapon.IsProxy && hitObj is not null && hitObj.Tags.Has( TagsHelper.Player ) )
{
var target = hitObj.Components.GetInAncestorsOrSelf<IPlayerBase>();
if ( !target.IsAlive ) return;
var hitTags = Array.Empty<string>();
if ( bulletTr.Hitbox is not null )
hitTags = bulletTr.Hitbox.Tags.TryGetAll().ToArray();
var dmgInfo = Shared.DamageInfo.FromBullet( weapon.Owner.Id, weapon.ClassName, shootInfo.Damage, bulletTr.HitPosition, forward * 100 * shootInfo.Force, hitTags );
target?.TakeDamage( dmgInfo );
}
}
public Vector3 GetRandomSpread( float spread )
{
return (Vector3.Random + Vector3.Random + Vector3.Random + Vector3.Random) * spread * 0.1f;
}
void TracerEffects( Weapon weapon, ShootInfo shootInfo, Vector3 endPos )
{
var scale = shootInfo.WMParticleScale;
var muzzleTransform = weapon.GetMuzzleTransform();
if ( !muzzleTransform.HasValue ) return;
SceneParticles particles = new( weapon.Scene.SceneWorld, shootInfo.BulletTracerParticle );
particles?.SetControlPoint( 1, muzzleTransform.Value );
particles?.SetControlPoint( 2, endPos );
particles?.SetNamedValue( "scale", scale );
particles?.PlayUntilFinished( TaskSource.Create() );
}
}

View File

@ -0,0 +1,8 @@
namespace SWB.Base;
public interface IBulletBase
{
public void Shoot( Weapon weapon, ShootInfo shootInfo, Vector3 spreadOffset );
public Vector3 GetRandomSpread( float spread );
}

24
Code/swb_base/Commands.cs Normal file
View File

@ -0,0 +1,24 @@
// using SWB.Player;
//
// namespace SWB.Base;
//
// public class Commands
// {
// [ConCmd( "swb_host_customization", Help = "Enable the weapon customization menu (Q)" )]
// public static void SetWeaponCustomization( int enable )
// {
// var player = PlayerBase.GetLocal();
// if ( !player.IsHost ) return;
//
// WeaponSettings.Instance.Customization = enable != 0;
// }
//
// [ConCmd( "swb_host_autoreload", Help = "Reload weapons automatically while shooting if clip is empty" )]
// public static void SetWeaponAutoReload( int enable )
// {
// var player = PlayerBase.GetLocal();
// if ( !player.IsHost ) return;
//
// WeaponSettings.Instance.AutoReload = enable != 0;
// }
// }

View File

@ -0,0 +1,17 @@
using SWB.Base.Attachments;
namespace SWB.Base;
public partial class Weapon
{
public Attachment GetActiveAttachmentForCategory( AttachmentCategory category )
{
foreach ( var attachment in Attachments )
{
if ( attachment.Category == category && attachment.Equipped )
return attachment;
}
return null;
}
}

View File

@ -0,0 +1,53 @@
namespace SWB.Base;
public partial class Weapon
{
// Burst Fire
public void ResetBurstFireCount( ShootInfo shootInfo, string inputButton )
{
if ( shootInfo is null || shootInfo.FiringType != FiringType.burst ) return;
if ( Input.Released( inputButton ) )
{
burstCount = 0;
}
}
// Barrel heat
public void BarrelHeatCheck()
{
if ( TimeSincePrimaryShoot > 3 && TimeSinceSecondaryShoot > 0 )
{
barrelHeat = 0;
}
}
// Tucking
public virtual float GetTuckDist()
{
if ( TuckRange == -1 )
return -1;
if ( !Owner.IsValid ) return -1;
var pos = Owner.Camera.GameObject.WorldPosition;
var forward = Owner.Camera.GameObject.WorldRotation.Forward;
var trace = TraceBullet( Owner.Camera.GameObject.WorldPosition, pos + forward * TuckRange );
if ( !trace.Hit )
return -1;
return trace.Distance;
}
// public bool ShouldTuck()
// {
// return GetTuckDist() != -1;
// }
//
// public bool ShouldTuck( out float dist )
// {
// dist = GetTuckDist();
// return dist != -1;
// }
}

View File

@ -0,0 +1,129 @@
using SWB.Base.Attachments;
namespace SWB.Base;
public partial class Weapon
{
public virtual SkinnedModelRenderer GetEffectRenderer()
{
SkinnedModelRenderer effectModel = WorldModelRenderer;
return effectModel;
}
/// <summary>
/// Gets the info on where to show the muzzle effect
/// </summary>
public virtual Transform? GetMuzzleTransform()
{
var activeAttachment = GetActiveAttachmentForCategory( AttachmentCategory.Muzzle );
var effectRenderer = GetEffectRenderer();
var effectAttachment = "muzzle";
if ( activeAttachment is not null )
{
effectAttachment = activeAttachment.EffectAttachmentOrBone;
Transform? effectBoneTransform = null;
// Custom models will not use attachments but bones instead to position effects
if (activeAttachment.WorldModelRenderer is not null )
{
effectBoneTransform = activeAttachment.WorldModelRenderer.SceneModel.GetBoneWorldTransform( effectAttachment );
}
if ( effectBoneTransform.HasValue )
return effectBoneTransform.Value;
}
return effectRenderer?.GetAttachment( effectAttachment );
}
/// <summary>
/// Gets the correct shoot animation
/// </summary>
/// <param name="shootInfo">Info used for the current attack</param>
/// <returns></returns>
public virtual string GetShootAnimation( ShootInfo shootInfo )
{
if ( IsAiming && (!string.IsNullOrEmpty( shootInfo.ShootAimedAnim )) )
{
return shootInfo.ShootAimedAnim;
}
else if ( shootInfo.Ammo == 0 && !string.IsNullOrEmpty( shootInfo.ShootEmptyAnim ) )
{
return shootInfo.ShootEmptyAnim;
}
return shootInfo.ShootAnim;
}
/// <summary>
/// If there is usable ammo left
/// </summary>
public bool HasAmmo()
{
if ( Primary.InfiniteAmmo == InfiniteAmmoType.clip )
return true;
if ( Primary.ClipSize == -1 )
{
return Owner.AmmoCount( Primary.AmmoType ) > 0;
}
if ( Primary.Ammo == 0 )
return false;
return true;
}
public ShootInfo GetShootInfo( bool isPrimary )
{
return isPrimary ? Primary : Secondary;
}
public bool IsShooting()
{
if ( Secondary is null )
return GetRealRPM( Primary.RPM ) > TimeSincePrimaryShoot;
return GetRealRPM( Primary.RPM ) > TimeSincePrimaryShoot || GetRealRPM( Secondary.RPM ) > TimeSinceSecondaryShoot;
}
public static float GetRealRPM( int rpm )
{
return 60f / rpm;
}
public virtual float GetRealSpread( float baseSpread = -1 )
{
if ( !Owner.IsValid() ) return 0;
float spread = baseSpread != -1 ? baseSpread : Primary.Spread;
float floatMod = 1f;
// Aiming
if ( IsAiming && Primary.Bullets == 1 )
floatMod /= 4;
// if ( !Owner.IsOnGround )
// {
// // Jumping
// floatMod += 0.75f;
// }
// else if ( Owner.Velocity.Length > 100 )
// {
// // Moving
// floatMod += 0.25f;
// }
return spread * floatMod;
}
public virtual Angles GetRecoilAngles( ShootInfo shootInfo )
{
var recoilX = IsAiming ? -shootInfo.Recoil * 0.4f : -shootInfo.Recoil;
var recoilY = Game.Random.NextFloat( -0.2f, 0.2f ) * recoilX;
var recoilAngles = new Angles( recoilX, recoilY, 0 );
return recoilAngles;
}
}

View File

@ -0,0 +1,125 @@
namespace SWB.Base;
public partial class Weapon
{
public virtual void Reload()
{
if ( IsReloading || InBoltBack || IsShooting() )
return;
var maxClipSize = BulletCocking ? Primary.ClipSize + 1 : Primary.ClipSize;
if ( Primary.Ammo >= maxClipSize || Primary.ClipSize == -1 )
return;
var isEmptyReload = ReloadEmptyTime > 0 && Primary.Ammo == 0;
TimeSinceReload = -(isEmptyReload ? ReloadEmptyTime : ReloadTime);
if ( Owner.AmmoCount( Primary.AmmoType ) <= 0 && Primary.InfiniteAmmo != InfiniteAmmoType.reserve )
return;
if ( IsScoping )
OnScopeEnd();
IsReloading = true;
// Anim
var reloadAnim = ReloadAnim;
if ( isEmptyReload && !string.IsNullOrEmpty( ReloadEmptyAnim ) )
{
reloadAnim = ReloadEmptyAnim;
}
WorldModelRenderer?.Set( reloadAnim, true );
// Player anim
HandleReloadEffects();
//Boltback
if ( !isEmptyReload && Primary.Ammo == 0 && BoltBack )
{
TimeSinceReload -= BoltBackTime;
AsyncBoltBack( ReloadTime );
}
}
public virtual void OnReloadFinish()
{
IsReloading = false;
var maxClipSize = BulletCocking && Primary.Ammo > 0 ? Primary.ClipSize + 1 : Primary.ClipSize;
if ( Primary.InfiniteAmmo == InfiniteAmmoType.reserve )
{
Primary.Ammo = maxClipSize;
return;
}
var ammo = Owner.TakeAmmo( Primary.AmmoType, maxClipSize - Primary.Ammo );
if ( ammo == 0 )
return;
Primary.Ammo += ammo;
}
public virtual void CancelShellReload()
{
IsReloading = false;
WorldModelRenderer.Set( ReloadAnim, false );
}
public virtual void OnShellReload()
{
ReloadTime = ShellReloadStartTime + ShellReloadInsertTime;
Reload();
}
public virtual void OnShellReloadFinish()
{
IsReloading = false;
var hasInfiniteReserve = Primary.InfiniteAmmo == InfiniteAmmoType.reserve;
var ammo = hasInfiniteReserve ? 1 : Owner.TakeAmmo( Primary.AmmoType, 1 );
Primary.Ammo += 1;
if ( ammo != 0 && Primary.Ammo < Primary.ClipSize )
{
ReloadTime = ShellReloadInsertTime;
Reload();
}
else
{
CancelShellReload();
}
}
async void AsyncBoltBack( float boltBackDelay )
{
InBoltBack = true;
// Start boltback
await GameTask.DelaySeconds( boltBackDelay );
if ( !IsValid ) return;
if ( !IsProxy )
WorldModelRenderer?.Set( BoltBackAnim, true );
// Eject shell
await GameTask.DelaySeconds( BoltBackEjectDelay );
if ( !IsValid ) return;
var scale = Primary.WMParticleScale;
CreateParticle( Primary.BulletEjectParticle, "ejection_point", scale );
// Finished
await GameTask.DelaySeconds( BoltBackTime - BoltBackEjectDelay );
if ( !IsValid ) return;
InBoltBack = false;
}
[Broadcast]
public virtual void HandleReloadEffects()
{
// Player
Owner?.BodyRenderer?.Set( "b_reload", true );
}
}

View File

@ -0,0 +1,27 @@
namespace SWB.Base;
public partial class Weapon
{
public async void OnScopeStart()
{
await GameTask.DelaySeconds( ScopeInfo.ScopeInDelay );
if ( !IsAiming || IsScoping || IsReloading ) return;
IsScoping = true;
// ViewModelHandler.ShouldDraw = false;
if ( ScopeInfo.ScopeInSound is not null )
PlaySound( ScopeInfo.ScopeInSound.ResourceId );
}
public void OnScopeEnd()
{
if ( !IsScoping ) return;
IsScoping = false;
// ViewModelHandler.ShouldDraw = true;
if ( ScopeInfo.ScopeOutSound is not null )
PlaySound( ScopeInfo.ScopeOutSound.ResourceId );
}
}

View File

@ -0,0 +1,293 @@
using SWB.Shared;
using System;
using System.Collections.Generic;
namespace SWB.Base;
public partial class Weapon
{
/// <summary>
/// Checks if the weapon can do the provided attack
/// </summary>
/// <param name="shootInfo">Attack information</param>
/// <param name="lastAttackTime">Time since this attack</param>
/// <param name="inputButton">The input button for this attack</param>
/// <returns></returns>
public virtual bool CanShoot( ShootInfo shootInfo, TimeSince lastAttackTime, string inputButton )
{
if ( (IsReloading && !ShellReloading) || (IsReloading && ShellReloading && !ShellReloadingShootCancel) || InBoltBack ) return false;
if ( !Owner.IsValid() || !Input.Down( inputButton ) ) return false; // || (Secondary is null) shootInfo is null ||
if ( !HasAmmo() )
{
if ( Input.Pressed( inputButton ) )
{
// Check for auto reloading
if ( Settings.AutoReload && lastAttackTime > GetRealRPM( shootInfo.RPM ) )
{
TimeSincePrimaryShoot = 999;
TimeSinceSecondaryShoot = 999;
if ( ShellReloading )
OnShellReload();
else
Reload();
return false;
}
// Dry fire
if ( shootInfo.DryShootSound is not null )
PlaySound( shootInfo.DryShootSound.ResourceId );
}
return false;
}
if ( shootInfo.FiringType == FiringType.semi && !Input.Pressed( inputButton ) ) return false;
if ( shootInfo.FiringType == FiringType.burst )
{
if ( burstCount > 2 ) return false;
if ( Input.Down( inputButton ) && lastAttackTime > GetRealRPM( shootInfo.RPM ) )
{
burstCount++;
return true;
}
return false;
};
if ( shootInfo.RPM <= 0 ) return true;
return lastAttackTime > GetRealRPM( shootInfo.RPM );
}
/// <summary>
/// Checks if weapon can do the primary attack
/// </summary>
public virtual bool CanPrimaryShoot()
{
return CanShoot( Primary, TimeSincePrimaryShoot, InputButtonHelper.PrimaryAttack );
}
/// <summary>
/// Checks if weapon can do the secondary attack
/// </summary>
public virtual bool CanSecondaryShoot()
{
return false; // CanShoot( Secondary, TimeSinceSecondaryShoot, InputButtonHelper.SecondaryAttack );
}
public virtual void Shoot( ShootInfo shootInfo, bool isPrimary )
{
Log.Info(shootInfo);
// Ammo
shootInfo.Ammo -= 1;
// Animations
var shootAnim = GetShootAnimation( shootInfo );
if ( !string.IsNullOrEmpty( shootAnim ) )
WorldModelRenderer.Set( shootAnim, true );
// Sound
if ( shootInfo.ShootSound is not null )
PlaySound( shootInfo.ShootSound.ResourceId );
// Particles
HandleShootEffects( isPrimary );
// Barrel smoke
barrelHeat += 1;
// Recoil
// Owner.EyeAnglesOffset += GetRecoilAngles( shootInfo );
// Screenshake
// if ( shootInfo.ScreenShake is not null )
// Owner.ShakeScreen( shootInfo.ScreenShake );
// UI
BroadcastUIEvent( "shoot", GetRealRPM( shootInfo.RPM ) );
// Bullet
for ( int i = 0; i < shootInfo.Bullets; i++ )
{
var realSpread = IsScoping ? 0 : GetRealSpread( shootInfo.Spread );
var spreadOffset = shootInfo.BulletType.GetRandomSpread( realSpread );
ShootBullet( isPrimary, spreadOffset );
}
}
[Broadcast]
public virtual void ShootBullet( bool isPrimary, Vector3 spreadOffset )
{
if ( !IsValid ) return;
var shootInfo = GetShootInfo( isPrimary );
shootInfo?.BulletType?.Shoot( this, shootInfo, spreadOffset );
}
/// <summary> A single bullet trace from start to end with a certain radius.</summary>
public virtual SceneTraceResult TraceBullet( Vector3 start, Vector3 end, float radius = 2.0f )
{
var startsInWater = SurfaceUtil.IsPointWater( start );
List<string> withoutTags = new() { TagsHelper.Trigger, TagsHelper.PlayerClip, TagsHelper.PassBullets, TagsHelper.ViewModel };
if ( startsInWater )
withoutTags.Add( TagsHelper.Water );
var tr = Scene.Trace.Ray( start, end )
.UseHitboxes()
.WithoutTags( withoutTags.ToArray() )
.Size( radius )
.IgnoreGameObjectHierarchy( Owner.GameObject )
.Run();
// Log.Info( tr.GameObject );
return tr;
}
[Broadcast]
public virtual void HandleShootEffects( bool isPrimary )
{
if ( !IsValid || Owner is null ) return;
// Player
Owner.BodyRenderer.Set( "b_attack", true );
// Weapon
var shootInfo = GetShootInfo( isPrimary );
if ( shootInfo is null ) return;
var scale = shootInfo.WMParticleScale;
var muzzleTransform = GetMuzzleTransform();
// Bullet eject
if ( shootInfo.BulletEjectParticle is not null )
{
if ( !BoltBack )
{
if ( !ShellReloading || (ShellReloading && ShellEjectDelay == 0) )
{
CreateParticle( shootInfo.BulletEjectParticle, "ejection_point", scale );
}
else
{
var delayedEject = async () =>
{
await GameTask.DelaySeconds( ShellEjectDelay );
if ( !IsValid ) return;
CreateParticle( shootInfo.BulletEjectParticle, "ejection_point", scale );
};
delayedEject();
}
}
else if ( shootInfo.Ammo > 0 )
{
AsyncBoltBack( GetRealRPM( shootInfo.RPM ) );
}
}
if ( !muzzleTransform.HasValue ) return;
// Muzzle flash
if ( shootInfo.MuzzleFlashParticle is not null )
CreateParticle( shootInfo.MuzzleFlashParticle, muzzleTransform.Value, scale, ( particles ) => ParticleToMuzzlePos( particles ) );
// Barrel smoke
if ( !IsProxy && shootInfo.BarrelSmokeParticle is not null && barrelHeat >= shootInfo.ClipSize * 0.75 )
CreateParticle( shootInfo.BarrelSmokeParticle, muzzleTransform.Value, shootInfo.VMParticleScale, ( particles ) => ParticleToMuzzlePos( particles ) );
}
void ParticleToMuzzlePos( SceneParticles particles )
{
var transform = GetMuzzleTransform();
if ( transform.HasValue )
{
// Apply velocity to prevent muzzle shift when moving fast
particles?.SetControlPoint( 0, transform.Value.Position ); //+ Owner.Velocity * 0.03f
particles?.SetControlPoint( 0, transform.Value.Rotation );
}
else
{
particles?.Delete();
}
}
/// <summary>Create a bullet impact effect</summary>
public virtual void CreateBulletImpact( SceneTraceResult tr )
{
// Sound
tr.Surface.PlayCollisionSound( tr.HitPosition );
// Particles
if ( tr.Surface.ImpactEffects.Bullet is not null )
{
var effectPath = Game.Random.FromList( tr.Surface.ImpactEffects.Bullet, "particles/impact.generic.smokepuff.vpcf" );
if ( effectPath is not null )
{
// Surface def for flesh has wrong blood particle linked
if ( effectPath.Contains( "impact.flesh" ) )
{
effectPath = "particles/impact.flesh.bloodpuff.vpcf";
}
else if ( effectPath.Contains( "impact.wood" ) )
{
effectPath = "particles/impact.generic.smokepuff.vpcf";
}
var p = new SceneParticles( Scene.SceneWorld, effectPath );
p.SetControlPoint( 0, tr.HitPosition );
p.SetControlPoint( 0, Rotation.LookAt( tr.Normal ) );
p.PlayUntilFinished( TaskSource.Create() );
}
}
// Decal
if ( tr.Surface.ImpactEffects.BulletDecal is not null )
{
var decalPath = Game.Random.FromList( tr.Surface.ImpactEffects.BulletDecal, "decals/bullethole.decal" );
if ( ResourceLibrary.TryGet<DecalDefinition>( decalPath, out var decalDef ) )
{
var decalEntry = Game.Random.FromList( decalDef.Decals );
var gameObject = Scene.CreateObject();
//gameObject.SetParent( tr.GameObject, false );
gameObject.WorldPosition = tr.HitPosition;
gameObject.WorldRotation = Rotation.LookAt( -tr.Normal );
var decalRenderer = gameObject.Components.Create<DecalRenderer>();
decalRenderer.Material = decalEntry.Material;
decalRenderer.Size = new( decalEntry.Height.GetValue(), decalEntry.Height.GetValue(), decalEntry.Depth.GetValue() );
gameObject.DestroyAsync( 30f );
}
}
}
/// <summary>Create a weapon particle</summary>
public virtual void CreateParticle( ParticleSystem particle, string attachment, float scale, Action<SceneParticles> OnFrame = null )
{
var effectRenderer = GetEffectRenderer();
if ( effectRenderer is null || effectRenderer.SceneModel is null ) return;
var transform = effectRenderer.SceneModel.GetAttachment( attachment );
if ( !transform.HasValue ) return;
CreateParticle( particle, transform.Value, scale, OnFrame );
}
public virtual void CreateParticle( ParticleSystem particle, Transform transform, float scale, Action<SceneParticles> OnFrame = null )
{
SceneParticles particles = new( Scene.SceneWorld, particle );
particles?.SetControlPoint( 0, transform.Position );
particles?.SetControlPoint( 0, transform.Rotation );
particles?.SetNamedValue( "scale", scale );
particles?.PlayUntilFinished( Task, OnFrame );
}
}

View File

@ -0,0 +1,50 @@
using SWB.Base.UI;
namespace SWB.Base;
public partial class Weapon
{
public ScreenPanel ScreenPanel { get; set; }
public PanelComponent RootPanel { get; set; }
private CustomizationMenu customizationMenu;
/// <summary>Override this if you want custom UI elements</summary>
public virtual void CreateUI()
{
ScreenPanel = Components.Create<ScreenPanel>();
ScreenPanel.Opacity = 1;
ScreenPanel.ZIndex = 1;
var rootPanel = Components.Create<RootWeaponDisplay>();
rootPanel.Weapon = this;
RootPanel = rootPanel;
}
public virtual void DestroyUI()
{
ScreenPanel?.Destroy();
RootPanel?.Destroy();
}
void BroadcastUIEvent( string name, object value )
{
if ( RootPanel is null ) return;
foreach ( var panel in RootPanel.Panel.Children )
{
panel.CreateEvent( name, value );
}
}
void OpenCustomizationMenu()
{
customizationMenu = new CustomizationMenu( this );
RootPanel.Panel.AddChild( customizationMenu );
}
void CloseCustomizationMenu()
{
customizationMenu?.Delete( true );
}
}

164
Code/swb_base/Weapon.Var.cs Normal file
View File

@ -0,0 +1,164 @@
using Sandbox.Citizen;
namespace SWB.Base;
public partial class Weapon
{
/// <summary>Thirdperson Model</summary>
[Property, Group( "Models" )] public Model WorldModel { get; set; }
/// <summary>Unique name that identifies the weapon</summary>
[Property, Group( "General" )] public string ClassName { get; set; }
[Property, Group( "General" )] public string DisplayName { get; set; }
[Property, Group( "General" ), ImageAssetPath] public string Icon { get; set; }
/// <summary>How the player holds the weapon in thirdperson</summary>
[Property, Group( "General" )] public AnimationHelper.HoldTypes HoldType { get; set; } = AnimationHelper.HoldTypes.Pistol;
/// <summary>Mouse sensitivity while aiming (lower is slower, 0 to disable)</summary>
[Property, Group( "General" )] public float AimSensitivity { get; set; } = 0.85f;
/// <summary>Can bullets be cocked in the barrel? (clip ammo + 1)</summary>
[Property, Group( "General" )] public bool BulletCocking { get; set; } = true;
/// <summary>Range that tucking should be enabled (-1 to disable tucking)</summary>
[Property, Group( "General" )] public float TuckRange { get; set; } = 30f;
[Property, Group( "General" )] public int Slot { get; set; } = 0;
/// <summary>Firing sound when clip is empty</summary>
[Property, Group( "Sounds" )] public SoundEvent DeploySound { get; set; }
/// <summary>Default weapon field of view</summary>
[Property, Group( "FOV" )] public float FOV { get; set; } = 70f;
/// <summary>Weapon FOV while aiming (-1 to use default weapon fov)</summary>
[Property, Group( "FOV" )] public float AimFOV { get; set; } = -1f;
/// <summary>Player FOV while aiming (-1 to use default player fov)</summary>
[Property, Group( "FOV" )] public float AimPlayerFOV { get; set; } = -1f;
/// <summary>FOV aim in speed</summary>
[Property, Group( "FOV" ), Title( "Aim in FOV speed" )] public float AimInFOVSpeed { get; set; } = 1f;
/// <summary>FOV aim out speed</summary>
[Property, Group( "FOV" ), Title( "Aim out FOV speed" )] public float AimOutFOVSpeed { get; set; } = 1f;
/// <summary>Procedural animation speed (lower is slower)</summary>
[Property, Group( "Animations" )] public float AnimSpeed { get; set; } = 1;
/// <summary>Offset used for setting the weapon to its aim position</summary>
[Property, Group( "Animations" ), Title( "Aim Offset (swb_editor_offsets)" )] public AngPos AimAnimData { get; set; }
/// <summary>Offset used for setting the weapon to its run position</summary>
[Property, Group( "Animations" ), Title( "Run Offset (swb_editor_offsets)" )] public AngPos RunAnimData { get; set; }
/// <summary>Offset used for setting the weapon to its run position</summary>
[Property, Group( "Animations" ), Title( "Customizing Offset (swb_editor_offsets)" )] public AngPos CustomizeAnimData { get; set; }
/// <summary>Duration of the reload animation</summary>
[Property, Group( "Animations" )] public float ReloadTime { get; set; } = 1f;
/// <summary>Reloading animation</summary>
[Property, Group( "Animations" )] public string ReloadAnim { get; set; } = "reload";
/// <summary>Duration of the empty reload animation (-1 to disable)</summary>
[Property, Group( "Animations" )] public float ReloadEmptyTime { get; set; } = -1f;
/// <summary>Reloading animation when clip is empty</summary>
[Property, Group( "Animations" )] public string ReloadEmptyAnim { get; set; } = "reload_empty";
/// <summary>Duration of the draw animation</summary>
[Property, Group( "Animations" )] public float DrawTime { get; set; } = 0.5f;
/// <summary>Draw animation</summary>
[Property, Group( "Animations" )] public string DrawAnim { get; set; } = "deploy";
/// <summary>Duration of the empty draw animation (-1 to disable)</summary>
[Property, Group( "Animations" )] public float DrawEmptyTime { get; set; } = -1f;
/// <summary>Draw animation when there is no ammo</summary>
[Property, Group( "Animations" )] public string DrawEmptyAnim { get; set; } = "";
/// <summary>Is the weapon reloading shells instead of a magazine?</summary>
[Property, Group( "Shell Reloading" )] public bool ShellReloading { get; set; } = false;
/// <summary>Can the weapon shoot while reloading to cancel the reload?</summary>
[Property, Group( "Shell Reloading" )] public bool ShellReloadingShootCancel { get; set; } = true;
/// <summary>Delay in fire animation to eject the shell</summary>
[Property, Group( "Shell Reloading" )] public float ShellEjectDelay { get; set; } = 0;
/// <summary>Duration of the shell reload start animation (animation is set with ReloadAnim)</summary>
[Property, Group( "Shell Reloading" )] public float ShellReloadStartTime { get; set; } = 0;
/// <summary>Duration of the shell reload insert animation (animation is set in animgraph)</summary>
[Property, Group( "Shell Reloading" )] public float ShellReloadInsertTime { get; set; } = 0;
/// <summary>Is this a bolt action weapon?</summary>
[Property, Group( "Bolt Action Reloading" ), Title( "Bolt Action" )] public bool BoltBack { get; set; } = false;
/// <summary>Duration of the boltback animation</summary>
[Property, Group( "Bolt Action Reloading" )] public float BoltBackTime { get; set; } = 0f;
/// <summary>Boltback animation</summary>
[Property, Group( "Bolt Action Reloading" )] public string BoltBackAnim { get; set; } = "boltback";
/// <summary>Bullet eject delay during the boltback animation (-1 to disable)</summary>
[Property, Group( "Bolt Action Reloading" )] public float BoltBackEjectDelay { get; set; } = 0f;
/// <summary>Enable scoping, renders a 2D scope on ADS</summary>
[Property, Group( "Scoping" )] public bool Scoping { get; set; } = false;
/// <summary>Scope Information</summary>
[Property, Group( "Scoping" )] public ScopeInfo ScopeInfo { get; set; } = new();
/// <summary>Primary attack data</summary>
[Property, Group( "Firing" ), Title( "Primary ShootInfo (component)" )] public ShootInfo Primary { get; set; } = new();
/// <summary>Secondary attack data (setting this will disable weapon aiming)</summary>
[Property, Group( "Firing" ), Title( "Secondary ShootInfo (component)" )] public ShootInfo Secondary { get; set; }
/// <summary>Time since the last primary attack</summary>
public TimeSince TimeSincePrimaryShoot { get; set; }
/// <summary>Time since the last secondary attack</summary>
public TimeSince TimeSinceSecondaryShoot { get; set; }
/// <summary>Time since deployment</summary>
public TimeSince TimeSinceDeployed { get; set; }
/// <summary>Time since the last reload</summary>
public TimeSince TimeSinceReload { get; set; }
public bool IsCustomizing { get; set; }
/// <summary>If the weapon is being reloaded</summary>
[Sync] public bool IsReloading { get; set; }
/// <summary>If the weapon is being aimed</summary>
[Sync] public bool IsAiming { get; set; }
/// <summary>If the weapon is being scoped</summary>
[Sync] public bool IsScoping { get; set; }
/// <summary>If the weapon is being bolt back reloaded</summary>
[Sync] public bool InBoltBack { get; set; }
public StatsModifier InitialPrimaryStats { get; private set; }
public StatsModifier InitialSecondaryStats { get; private set; }
public bool IsDeploying => TimeSinceDeployed < 0;
// Private
int burstCount = 0;
int barrelHeat = 0;
}

225
Code/swb_base/Weapon.cs Normal file
View File

@ -0,0 +1,225 @@
using SWB.Base.Attachments;
using SWB.Shared;
using System.Collections.Generic;
using System.Linq;
namespace SWB.Base;
[Group( "SWB" )]
[Title( "Weapon" )]
public partial class Weapon : Component, IInventoryItem
{
public IPlayerBase Owner { get; private set; }
public SkinnedModelRenderer WorldModelRenderer { get; private set; }
public WeaponSettings Settings { get; private set; }
public List<Attachment> Attachments = new();
protected override void OnAwake()
{
Tags.Add( TagsHelper.Weapon );
Attachments = Components.GetAll<Attachment>( FindMode.EverythingInSelf ).OrderBy( att => att.Name ).ToList();
Settings = WeaponSettings.Instance;
InitialPrimaryStats = StatsModifier.FromShootInfo( Primary );
InitialSecondaryStats = StatsModifier.FromShootInfo( Primary );
}
protected override void OnDestroy()
{
}
protected override void OnEnabled()
{
if ( IsProxy ) return;
CreateUI();
}
protected override void OnDisabled()
{
if ( IsProxy ) return;
IsReloading = false;
IsScoping = false;
IsAiming = false;
IsCustomizing = false;
DestroyUI();
}
[Broadcast]
public void OnCarryStart()
{
if ( !IsValid ) return;
GameObject.Enabled = true;
}
[Broadcast]
public void OnCarryStop()
{
if ( !IsValid ) return;
GameObject.Enabled = false;
}
public bool CanCarryStop()
{
return TimeSinceDeployed > 0;
}
public void OnDeploy()
{
var delay = 0f;
if ( Primary.Ammo == 0 && !string.IsNullOrEmpty( DrawEmptyAnim ) )
{
delay = DrawEmptyTime;
}
else if ( !string.IsNullOrEmpty( DrawAnim ) )
{
delay = DrawTime;
}
TimeSinceDeployed = -delay;
// Sound
if ( DeploySound is not null )
PlaySound( DeploySound.ResourceId );
// Start drawing
// Boltback
if ( InBoltBack )
AsyncBoltBack( delay );
}
protected override void OnStart()
{
Owner = Components.GetInAncestors<IPlayerBase>();
CreateModels();
// Attachments (load for clients joining late)
if ( IsProxy )
{
// Log.Info( "Checking -> " + Network.Owner.DisplayName + "'s " + DisplayName + " for attachments" );
Attachments.ForEach( att =>
{
// Log.Info( "[" + att.Name + "] equipped ->" + att.Equipped );
if ( att is not null && att.Equipped )
att.Equip();
} );
}
}
protected override void OnUpdate()
{
if ( Owner is null ) return;
// UpdateModels();
Owner.AnimationHelper.HoldType = HoldType;
if ( !IsProxy )
{
// if ( IsDeploying ) return;
// Customization
if ( WeaponSettings.Instance.Customization && !IsScoping && !IsAiming && Input.Pressed( InputButtonHelper.Menu ) && Attachments.Count > 0 )
{
if ( !IsCustomizing )
OpenCustomizationMenu();
else
CloseCustomizationMenu();
IsCustomizing = !IsCustomizing;
}
// Don't cancel reload when customizing
if ( IsCustomizing && !IsReloading ) return;
if ( Scoping )
{
if ( IsAiming && !IsScoping )
OnScopeStart();
else if ( !IsAiming && IsScoping )
OnScopeEnd();
}
ResetBurstFireCount( Primary, InputButtonHelper.PrimaryAttack );
ResetBurstFireCount( Secondary, InputButtonHelper.SecondaryAttack );
BarrelHeatCheck();
// var shouldTuck = ShouldTuck();
if ( CanPrimaryShoot() )
{
if ( IsReloading && ShellReloading && ShellReloadingShootCancel )
CancelShellReload();
TimeSincePrimaryShoot = 0;
Shoot( Primary, true );
}
else if ( CanSecondaryShoot())
{
TimeSinceSecondaryShoot = 0;
Shoot( Secondary, false );
}
else if ( Input.Down( InputButtonHelper.Reload ) )
{
if ( ShellReloading )
OnShellReload();
else
Reload();
}
if ( IsReloading && TimeSinceReload >= 0 )
{
if ( ShellReloading )
OnShellReloadFinish();
else
OnReloadFinish();
}
}
}
void CreateModels()
{
WorldModelRenderer = Components.Create<SkinnedModelRenderer>();
WorldModelRenderer.Model = WorldModel;
WorldModelRenderer.AnimationGraph = WorldModel.AnimGraph;
WorldModelRenderer.CreateBoneObjects = true;
ModelUtil.ParentToBone( GameObject, Owner.BodyRenderer, "hold_R" );
Network.ClearInterpolation();
Log.Info("START WEAPON");
}
// Temp fix until https://github.com/Facepunch/sbox-issues/issues/5247 is fixed
// void ResetViewModelAnimations()
// {
// ViewModelRenderer?.Set( Primary.ShootAnim, false );
// ViewModelRenderer?.Set( Primary.ShootEmptyAnim, false );
// ViewModelRenderer?.Set( Primary.ShootAimedAnim, false );
//
// if ( Secondary is not null )
// {
// ViewModelRenderer?.Set( Secondary.ShootAnim, false );
// ViewModelRenderer?.Set( Secondary.ShootEmptyAnim, false );
// ViewModelRenderer?.Set( Secondary.ShootAimedAnim, false );
// }
//
// ViewModelRenderer?.Set( ReloadAnim, false );
// ViewModelRenderer?.Set( ReloadEmptyAnim, false );
// ViewModelRenderer?.Set( DrawAnim, false );
// ViewModelRenderer?.Set( DrawEmptyAnim, false );
// }
[Broadcast]
void PlaySound( int resourceID )
{
if ( !IsValid ) return;
var sound = ResourceLibrary.Get<SoundEvent>( resourceID );
if ( sound is null ) return;
Sound.Play( sound, WorldPosition );
}
}

View File

@ -0,0 +1,55 @@
using System.Collections.Generic;
namespace SWB.Base;
/*
* Attach this component somewhere in the root of your scene.
* Register all weapons that you want to use in here
*/
[Group( "SWB" )]
[Title( "Weapon Registry" )]
public class WeaponRegistry : Component
{
[Property] public List<PrefabScene> WeaponPrefabs { get; set; } = new();
public Dictionary<string, GameObject> Weapons { get; set; } = new();
static public WeaponRegistry Instance
{
get
{
return Game.ActiveScene.Components.GetInChildren<WeaponRegistry>();
}
}
protected override void OnAwake()
{
WeaponPrefabs.ForEach( weaponPrefab =>
{
var weaponGO = weaponPrefab.Clone();
weaponGO.SetParent( this.GameObject );
weaponGO.Enabled = false;
var weapon = weaponGO.Components.Get<Weapon>( true );
Weapons.TryAdd( weapon.ClassName, weaponGO );
weaponGO.Name = weapon.ClassName;
} );
}
public GameObject Get( string className )
{
if ( className is null ) return null;
Weapons.TryGetValue( className, out var weaponGO );
return weaponGO;
}
public Weapon GetWeapon( string className )
{
var weaponGO = Get( className );
if ( weaponGO is null ) return null;
return weaponGO.Components.Get<Weapon>( true );
}
}

View File

@ -0,0 +1,30 @@
namespace SWB.Base;
/*
* Attach this component somewhere in the root of your scene.
* Gives control over weapon settings (host only)
*/
[Group( "SWB" )]
[Title( "Weapon Settings" )]
public class WeaponSettings : Component
{
/// <summary>Enable the weapon customization menu (Q)</summary>
[HostSync, Property] public bool Customization { get; set; } = true;
/// <summary>Reload weapons automatically when trying to shoot if clip is empty</summary>
[HostSync, Property] public bool AutoReload { get; set; } = true;
protected override void OnAwake()
{
GameObject.NetworkMode = NetworkMode.Object;
}
static public WeaponSettings Instance
{
get
{
return Game.ActiveScene.Components.GetInChildren<WeaponSettings>();
}
}
}

View File

@ -0,0 +1,243 @@
using SWB.Shared;
using System;
namespace SWB.Base.Attachments;
public enum AttachmentCategory
{
Barrel,
Sight,
Grip,
Rail,
Magazine,
Muzzle,
Stock,
Other,
Special,
Tactical,
Laser,
None,
}
/*
* Attachment base that allows for weapon bone parenting as well as bodygroup changes simultaneously
*/
[Group( "SWB Attachments" )]
public abstract class Attachment : Component, IComparable<Attachment>
{
/// <summary>Display name (needs to be unique)</summary>
public virtual string Name => "";
/// <summary>Display description</summary>
public virtual string Description => "";
/// <summary>Only 1 active attachment per category, this is also used to determine what effect attachment to overide</summary>
public virtual AttachmentCategory Category => AttachmentCategory.None;
/// <summary>List of positive attributes</summary>
public virtual string[] Positives => Array.Empty<string>();
/// <summary>List of negative attributes</summary>
public virtual string[] Negatives => Array.Empty<string>();
/// <summary>Weapon stats changer</summary>
public virtual StatsModifier StatsModifier { get; set; }
/// <summary>Path to an image that represent the attachment on the HUD</summary>
public virtual string IconPath => "";
/// <summary>Path to the attachment model</summary>
public virtual string ModelPath => "";
/// <summary>Name of the model attachment used for new effect origins</summary>
public virtual string EffectAttachmentOrBone { get; set; } = "";
/// <summary>Hide this attachment in menus</summary>
public virtual bool Hide { get; set; } = false;
/// <summary>Depends on another attachment (e.g. rail/mount)</summary>
[Property] public Attachment RequiresAttachment { get; set; }
/// <summary>Name of the bone you want to attach the model to</summary>
[Property, Group( "Model Parenting" )] public virtual string Bone { get; set; }
/// <summary>Viewmodel scale</summary>
[Property, Group( "Model Parenting" )] public virtual Vector3 ViewModelScale { get; set; } = Vector3.One;
/// <summary>Worldmodel scale</summary>
[Property, Group( "Model Parenting" )] public virtual Vector3 WorldModelScale { get; set; } = Vector3.One;
/// <summary>The name of the body group</summary>
[Property, Group( "BodyGroup" )] public virtual string BodyGroup { get; set; }
/// <summary>The name of the body group choice</summary>
[Property, Group( "BodyGroup" )] public virtual int BodyGroupChoice { get; set; } = 0;
/// <summary>The default target body group value</summary>
[Property, Group( "BodyGroup" )] public virtual int BodyGroupDefault { get; set; } = 0;
/// <summary>If already equipped</summary>
[Sync] public bool Equipped { get; private set; }
public Weapon Weapon { get; private set; }
public SkinnedModelRenderer ViewModelRenderer { get; private set; }
public SkinnedModelRenderer WorldModelRenderer { get; private set; }
private int equipTries = 0;
private bool equippedOnClient = false;
protected override void OnAwake()
{
Weapon = Components.Get<Weapon>();
}
private void SetBodyGroup( int choice )
{
if ( string.IsNullOrEmpty( BodyGroup ) ) return;
Weapon.WorldModelRenderer.SetBodyGroup( BodyGroup, choice );
}
private void CreateModel( bool isViewModel = false )
{
if ( string.IsNullOrEmpty( ModelPath ) || string.IsNullOrEmpty( Bone ) ) return;
var attachmentGo = new GameObject( true, "Attachment" );
attachmentGo.Tags.Add( TagsHelper.Attachment );
var attachmentRenderer = attachmentGo.Components.Create<SkinnedModelRenderer>();
attachmentRenderer.Model = Model.Load( ModelPath );
attachmentRenderer.Enabled = true;
attachmentRenderer.WorldScale = WorldModelScale;
WorldModelRenderer = attachmentRenderer;
ModelUtil.ParentToBone( attachmentGo, Weapon.WorldModelRenderer, Bone );
}
private void CreateModels()
{
if ( !IsProxy )
CreateModel( true );
CreateModel();
}
/// <summary>Equips the attachment for everyone</summary>
[Broadcast]
public virtual void EquipBroadCast()
{
if ( !IsValid ) return;
Equip();
}
/// <summary>Equips the attachment</summary>
public virtual void Equip()
{
// Log.Info( "Trying to equip -> " + Name + ", info -> equippedOnClient: " + equippedOnClient + " equipTries: " + equipTries );
if ( equippedOnClient || !IsValid || Weapon is null ) return;
if ( !IsProxy || Weapon.WorldModelRenderer is null )
{
if ( equipTries > 10 ) return;
equipTries += 1;
async void retry()
{
await GameTask.Delay( 1 );
Equip();
}
retry();
return;
}
// Make sure there is no other active attachment in same category
foreach ( var att in Weapon.Attachments )
{
if ( att.Category == Category && att.Equipped )
{
att.Unequip();
break;
}
}
equippedOnClient = true;
if ( !IsProxy )
Equipped = true;
// Equip dependent attachment
RequiresAttachment?.Equip();
// BodyGroup
SetBodyGroup( BodyGroupChoice );
// Models
CreateModels();
// Stats
StatsModifier?.Apply( Weapon );
if ( !IsProxy )
CreateHudElements();
OnEquip();
}
/// <summary>Unequips the attachment for everyone</summary>
[Broadcast]
public virtual void UnEquipBroadCast()
{
if ( !IsValid ) return;
Unequip();
}
/// <summary>Unequips the attachment</summary>
public virtual void Unequip()
{
if ( !equippedOnClient ) return;
equippedOnClient = false;
if ( !IsProxy )
Equipped = false;
// Unequip dependent attachment
RequiresAttachment?.Unequip();
// BodyGroup
SetBodyGroup( BodyGroupDefault );
// Model
WorldModelRenderer?.GameObject.Destroy();
// Stats
StatsModifier?.Remove( Weapon );
if ( !IsProxy )
DestroyHudElements();
OnUnequip();
}
/// <summary>Gets called after the attachment is equipped</summary>
public abstract void OnEquip();
/// <summary>Gets called after the attachment is unequipped</summary>
public abstract void OnUnequip();
/// <summary>Gets called when the weapon is creating its HUD elements</summary>
public virtual void CreateHudElements() { }
/// <summary>Gets called when the weapon is destroying its HUD elements</summary>
public virtual void DestroyHudElements() { }
public int CompareTo( Attachment obj )
{
if ( obj == null )
return 1;
else
return Name.CompareTo( obj.Name );
}
}

View File

@ -0,0 +1,119 @@
using SWB.Shared;
namespace SWB.Base.Attachments;
public abstract class LaserAttachment : Attachment
{
public override string Name => "Laser";
public override AttachmentCategory Category => AttachmentCategory.Laser;
public override string Description => "Aids target acquisition by projecting a beam onto the target that provides a visual reference point.";
// Not easily possible atm, attachments and bones lag behind.
// Waiting for: https://github.com/Facepunch/sbox-issues/issues/5200
public override bool Hide => true;
public override string[] Positives => new string[]
{
"Increases accuracy by 5%"
};
public override string[] Negatives => new string[]
{
"Visibible to enemies"
};
public override StatsModifier StatsModifier { get; set; } = new()
{
Spread = -0.05f,
};
/// <summary>New muzzle flash effect point</summary>
[Property, Group( "Laser" )] public override string EffectAttachmentOrBone { get; set; } = "laser_start";
/// <summary>Laser beam particle</summary>
[Property, Group( "Laser" )] public virtual ParticleSystem BeamParticle { get; set; } = ParticleSystem.Load( "particles/swb/laser/laser_small.vpcf" );
/// <summary>Laser dot particle</summary>
[Property, Group( "Laser" )] public virtual ParticleSystem DotParticle { get; set; } = ParticleSystem.Load( "particles/swb/laser/laser_dot.vpcf" );
[Property, Group( "Laser" )] public virtual Color Color { get; set; } = Color.Red;
SceneParticles beamParticles;
SceneParticles dotParticles;
public override void OnEquip()
{
CreateParticles();
}
public override void OnUnequip()
{
DestroyParticles();
}
private void CreateParticles()
{
DestroyParticles();
beamParticles = new( Weapon.Scene.SceneWorld, BeamParticle );
beamParticles.Tags.Add( TagsHelper.ViewModel );
//beamParticles.
//beamParticles?.SetControlPoint( 1, muzzleTransform.Value );
//beamParticles?.SetControlPoint( 2, endPos );
beamParticles?.SetNamedValue( "color", Color );
// beamParticles?.SetControlPoint( 3, Color );
beamParticles?.PlayUntilFinished( TaskSource.Create(), ( particles ) =>
{
//var startAttachment = Weapon.ViewModelRenderer.SceneModel.GetAttachment( EffectAttachmentOrBone );
//var startPos = startAttachment.Value.Position;
//var endPos = startAttachment.Value.Position + startAttachment.Value.Rotation.Forward * 9999;
//var tr = Scene.Trace.Ray( startPos, endPos )
//.UseHitboxes()
//.WithoutTags( "trigger" )
//.Size( 1.0f )
//.IgnoreGameObjectHierarchy( Weapon.Owner.GameObject )
//.Run();
//var testStartPos = Weapon.GetMuzzleTransform();
////particles?.SetControlPoint( 0, startPos );
//particles?.SetControlPoint( 0, startPos );
//particles?.SetControlPoint( 1, tr.EndPosition );
} );
//laserParticle = Particles.Create( Particle );
//laserParticle?.SetPosition( 3, Color );
//laserDotParticle = Particles.Create( DotParticle );
//laserDotParticle?.SetPosition( 1, Color );
}
private void DestroyParticles()
{
beamParticles?.Delete();
beamParticles = null;
dotParticles?.Delete();
dotParticles = null;
}
protected override void OnUpdate()
{
if ( !Equipped ) return;
var startAttachment = Weapon.WorldModelRenderer.SceneModel.GetAttachment( EffectAttachmentOrBone );
var startPos = startAttachment.Value.Position;
var endPos = startAttachment.Value.Position + startAttachment.Value.Rotation.Forward * 9999;
var tr = Scene.Trace.Ray( startPos, endPos )
.UseHitboxes()
.WithoutTags( TagsHelper.Trigger )
.Size( 1.0f )
.IgnoreGameObjectHierarchy( Weapon.Owner.GameObject )
.Run();
beamParticles?.SetControlPoint( 0, startPos );
beamParticles?.SetControlPoint( 1, tr.EndPosition );
}
}

View File

@ -0,0 +1,17 @@
namespace SWB.Base.Attachments;
public abstract class RailAttachment : Attachment
{
public override string Name => "Rail";
public override AttachmentCategory Category => AttachmentCategory.Rail;
public override string Description => "Used by other attachments to be able to attach to the weapon.";
public override bool Hide { get; set; } = true;
public override void OnEquip()
{
}
public override void OnUnequip()
{
}
}

View File

@ -0,0 +1,57 @@
using SWB.Base.UI;
namespace SWB.Base.Attachments;
public abstract class Scope2DAttachment : Attachment
{
public override string Name => "2D Scope";
public override AttachmentCategory Category => AttachmentCategory.Sight;
public override string Description => "A high magnification scope that is utilized for firing at long ranges.";
public override string[] Positives => new string[]
{
"x12 magnification",
"100% accurate while scoped in"
};
public override string[] Negatives => new string[]
{
};
/// <summary>The new aim position offset</summary>
[Property, Group( "Scope" )] public AngPos AimAnimData { get; set; }
AngPos oldAimAnimData;
/// <summary>Scope Information</summary>
[Property, Group( "Scope" )] public virtual ScopeInfo ScopeInfo { get; set; } = new();
ScopeInfo oldScopeInfo = new();
SniperScope sniperScope;
public override void OnEquip()
{
oldAimAnimData = Weapon.AimAnimData;
oldScopeInfo = Weapon.ScopeInfo;
Weapon.Scoping = true;
Weapon.AimAnimData = AimAnimData;
Weapon.ScopeInfo = ScopeInfo;
}
public override void OnUnequip()
{
Weapon.Scoping = false;
Weapon.AimAnimData = oldAimAnimData;
Weapon.ScopeInfo = oldScopeInfo;
}
public override void CreateHudElements()
{
sniperScope = new SniperScope( Weapon, Weapon.ScopeInfo.LensTexture, Weapon.ScopeInfo.ScopeTexture );
Weapon.RootPanel.Panel.AddChild( sniperScope );
}
public override void DestroyHudElements()
{
sniperScope?.Delete();
}
}

View File

@ -0,0 +1,75 @@
namespace SWB.Base.Attachments;
public abstract class SightAttachment : Attachment
{
public override string Name => "Sight";
public override AttachmentCategory Category => AttachmentCategory.Sight;
public override string Description => "An optical sight that allows the user to look through a partially reflecting glass element and see an illuminated projection of an aiming point or some other image superimposed on the field of view.";
public override string[] Positives => new string[]
{
"Precision sight picture",
"Increases accuracy by 5%"
};
public override string[] Negatives => new string[]
{
};
public override StatsModifier StatsModifier { get; set; } = new()
{
Spread = -0.05f,
};
/// <summary>The new aim position offset</summary>
[Property, Group( "Sight" )] public AngPos AimAnimData { get; set; }
AngPos oldAimAnimData;
/// <summary>Weapon FOV while aiming (-1 to use default)</summary>
[Property, Group( "Sight" )] public virtual float AimFOV { get; set; } = -1f;
float oldAimFOV;
/// <summary>Player FOV while aiming (-1 to use default)</summary>
[Property, Group( "Sight" )] public virtual float AimPlayerFOV { get; set; } = -1f;
float oldAimPlayerFOV;
/// <summary>FOV aim in speed (-1 to use default)</summary>
[Property, Group( "Sight" ), Title( "Aim in FOV speed" )] public virtual float AimInFOVSpeed { get; set; } = -1f;
float oldAimInFOVSpeed;
/// <summary>FOV aim out speed (-1 to use default)</summary>
[Property, Group( "Sight" ), Title( "Aim out FOV speed" )] public virtual float AimOutFOVSpeed { get; set; } = -1f;
private float oldAimOutFOVSpeed;
/// <summary>Mouse sensitivity while aiming (-1 to use default)</summary>
[Property, Group( "Sight" )] public virtual float AimSensitivity { get; set; } = -1;
float oldAimSensitivity;
public override void OnEquip()
{
oldAimAnimData = Weapon.AimAnimData;
oldAimFOV = Weapon.AimFOV;
oldAimPlayerFOV = Weapon.AimPlayerFOV;
oldAimInFOVSpeed = Weapon.AimInFOVSpeed;
oldAimOutFOVSpeed = Weapon.AimOutFOVSpeed;
oldAimSensitivity = Weapon.AimSensitivity;
Weapon.AimAnimData = AimAnimData;
if ( AimFOV > -1 ) Weapon.AimFOV = AimFOV;
if ( AimPlayerFOV > -1 ) Weapon.AimPlayerFOV = AimPlayerFOV;
if ( AimInFOVSpeed > -1 ) Weapon.AimInFOVSpeed = AimInFOVSpeed;
if ( AimOutFOVSpeed > -1 ) Weapon.AimOutFOVSpeed = AimOutFOVSpeed;
if ( AimSensitivity > -1 ) Weapon.AimSensitivity = AimSensitivity;
}
public override void OnUnequip()
{
Weapon.AimAnimData = oldAimAnimData;
Weapon.AimFOV = oldAimFOV;
Weapon.AimPlayerFOV = oldAimPlayerFOV;
Weapon.AimInFOVSpeed = oldAimInFOVSpeed;
Weapon.AimOutFOVSpeed = oldAimOutFOVSpeed;
Weapon.AimSensitivity = oldAimSensitivity;
}
}

View File

@ -0,0 +1,52 @@
namespace SWB.Base.Attachments;
public abstract class SilencerAttachment : Attachment
{
public override string Name => "Silencer";
public override AttachmentCategory Category => AttachmentCategory.Muzzle;
public override string Description => "Reduces the acoustic intensity of the muzzle report and the recoil when a gun is discharged by modulating the speed and pressure of the propellant gas from the muzzle.";
public override string[] Positives => new string[]
{
"Reduce sound",
"Reduce muzzle flash",
"Increases accuracy by 5%"
};
public override string[] Negatives => new string[]
{
};
public override StatsModifier StatsModifier { get; set; } = new()
{
Spread = -0.05f,
};
/// <summary>New muzzle flash effect point</summary>
[Property, Group( "Silencer" )] public override string EffectAttachmentOrBone { get; set; } = "muzzle_silenced";
/// <summary>New particle used for the muzzle flash</summary>
[Property, Group( "Silencer" )] public virtual ParticleSystem MuzzleFlashParticle { get; set; }
ParticleSystem oldMuzzleFlashParticle;
/// <summary>New sound used for firing</summary>
[Property, Group( "Silencer" )] public virtual SoundEvent ShootSound { get; set; }
SoundEvent oldShootSound;
public override void OnEquip()
{
oldMuzzleFlashParticle = Weapon.Primary.MuzzleFlashParticle;
oldShootSound = Weapon.Primary.ShootSound;
if ( MuzzleFlashParticle is not null )
Weapon.Primary.MuzzleFlashParticle = MuzzleFlashParticle;
if ( ShootSound is not null )
Weapon.Primary.ShootSound = ShootSound;
}
public override void OnUnequip()
{
Weapon.Primary.MuzzleFlashParticle = oldMuzzleFlashParticle;
Weapon.Primary.ShootSound = oldShootSound;
}
}

View File

@ -0,0 +1,36 @@
namespace SWB.Base;
public sealed class TimedDestroyComponent : Component
{
/// <summary>
/// How long until we destroy the GameObject.
/// </summary>
[Property] public float Time { get; set; } = 1f;
/// <summary>
/// The real time until we destroy the GameObject.
/// </summary>
[Property, ReadOnly] TimeUntil TimeUntilDestroy { get; set; } = 0;
protected override void OnStart()
{
TimeUntilDestroy = Time;
}
protected override void OnUpdate()
{
if ( TimeUntilDestroy )
{
GameObject.Destroy();
}
}
}
public static class GameObjectExtensions
{
public static void DestroyAsync( this GameObject self, float seconds = 1.0f )
{
var component = self.Components.Create<TimedDestroyComponent>();
component.Time = seconds;
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
namespace SWB.Base;
public static class ParticleExtensions
{
public static async void PlayUntilFinished( this SceneParticles particles, TaskSource source, Action<SceneParticles> OnFrame = null )
{
try
{
while ( particles.IsValid() && !particles.Finished )
{
await source.Frame();
if ( OnFrame is not null )
OnFrame( particles );
particles?.Simulate( Time.Delta );
}
}
catch ( TaskCanceledException )
{
// Do nothing.
}
particles.Delete();
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace SWB.Base;
public static class RandomExtensions
{
public static float NextFloat(
this Random random,
float minValue,
float maxValue )
{
return random.Float() * (maxValue - minValue) + minValue;
}
}

View File

@ -0,0 +1,62 @@
namespace SWB.Base;
public struct AngPos
{
[KeyProperty] public Angles Angle { get; set; } = Angles.Zero;
[KeyProperty] public Vector3 Pos { get; set; } = Vector3.Zero;
public static readonly AngPos Zero = new();
public AngPos()
{
}
public AngPos( Angles angle, Vector3 pos )
{
Angle = angle;
Pos = pos;
}
public bool Equals( AngPos angPos )
{
return Angle == angPos.Angle && Pos == angPos.Pos;
}
public static AngPos operator +( AngPos x, AngPos y )
{
return new AngPos( x.Angle + y.Angle, x.Pos + y.Pos );
}
public static AngPos operator -( AngPos x, AngPos y )
{
return new AngPos( x.Angle - y.Angle, x.Pos - y.Pos );
}
public static AngPos operator -( AngPos x )
{
return new AngPos( x.Angle * -1, -x.Pos );
}
public static bool operator ==( AngPos x, AngPos y )
{
return x.Equals( y );
}
public static bool operator !=( AngPos x, AngPos y )
{
return !x.Equals( y );
}
public override bool Equals( object obj )
{
if ( obj is AngPos angPos )
return Equals( angPos );
return false;
}
public override int GetHashCode()
{
return Angle.GetHashCode() + Pos.GetHashCode();
}
}

View File

@ -0,0 +1,25 @@
namespace SWB.Base;
public class ScopeInfo
{
/// <summary>2D lens texture</summary>
[Property, ImageAssetPathAttribute] public string LensTexture { get; set; } = "/materials/swb/scopes/swb_lens_hunter.png";
/// <summary>2D scope texture</summary>
[Property, ImageAssetPathAttribute] public string ScopeTexture { get; set; } = "/materials/swb/scopes/swb_scope_hunter.png";
/// <summary>Delay between ADS and scoping in ms</summary>
[Property] public float ScopeInDelay { get; set; } = 0.2f;
/// <summary>Sound that plays when scoping starts</summary>
[Property] public SoundEvent ScopeInSound { get; set; }
/// <summary>Sound that plays when scoping ends</summary>
[Property] public SoundEvent ScopeOutSound { get; set; }
/// <summary>Player FOV while scoping</summary>
[Property] public float FOV { get; set; } = 8f;
/// <summary>Mouse sensitivity while scoping (lower is slower, 0 to use AimSensitivity while scoped)</summary>
[Property] public float AimSensitivity { get; set; } = 0.25f;
}

View File

@ -0,0 +1,117 @@
using SWB.Shared;
namespace SWB.Base;
public enum FiringType
{
/// <summary>Single fire</summary>
semi,
/// <summary>Automatic fire</summary>
auto,
/// <summary>3-Burst fire</summary>
burst
}
public enum InfiniteAmmoType
{
/// <summary>No infinite ammo</summary>
disabled = 0,
/// <summary>Infinite clip ammo, no need to reload</summary>
clip = 1,
/// <summary>Infinite reserve ammo, can always reload</summary>
reserve = 2
}
[Group( "SWB" )]
[Title( "ShootInfo" )]
public class ShootInfo : Component
{
/// <summary>Type of ammo</summary>
[Property, Group( "Ammo" )] public string AmmoType { get; set; } = "pistol";
/// <summary>Amount of ammo in the clip</summary>
[Property, Group( "Ammo" ), Sync] public int Ammo { get; set; } = 10;
/// <summary>Size of the clip</summary>
[Property, Group( "Ammo" )] public int ClipSize { get; set; } = 10;
/// <summary>If the weapon should have infinite ammo</summary>
[Property, Group( "Ammo" )] public InfiniteAmmoType InfiniteAmmo { get; set; } = InfiniteAmmoType.disabled;
// Shooting //
/// <summary>Amount of bullets per shot</summary>
[Property, Group( "Bullets" )] public int Bullets { get; set; } = 1;
/// <summary>Bullet size</summary>
[Property, Group( "Bullets" )] public float BulletSize { get; set; } = 0.1f;
/// <summary>Bullet type (Hitscan/Physical)</summary>
public IBulletBase BulletType { get; set; } = new HitScanBullet();
/// <summary>Chance the BulletTracerParticle is created (0-1)</summary>
[Property, Group( "Bullets" )] public float BulletTracerChance { get; set; } = 0.33f;
/// <summary>Damage per bullet</summary>
[Property, Group( "Bullets" )] public float Damage { get; set; } = 5;
/// <summary>Bullet impact force</summary>
[Property, Group( "Bullets" )] public float Force { get; set; } = 0.1f;
/// <summary>Bullet hit flinch</summary>
[Property, Group( "Bullets" )] public float HitFlinch { get; set; } = 1.25f;
/// <summary>Weapon spread</summary>
[Property, Group( "Bullets" )] public float Spread { get; set; } = 0.1f;
/// <summary>Weapon recoil</summary>
[Property, Group( "Bullets" )] public float Recoil { get; set; } = 0.1f;
/// <summary>Rate Per Minute, firing speed (higher is faster)</summary>
[Property, Group( "Bullets" )] public int RPM { get; set; } = 200;
/// <summary>Screenshake per shot</summary>
[Property, Group( "Bullets" )] public ScreenShake ScreenShake { get; set; }
/// <summary>Weapon firing type</summary>
[Property, Group( "Bullets" )] public FiringType FiringType { get; set; } = FiringType.semi;
// Animations //
/// <summary>Animation used for shooting</summary>
[Property, Group( "Animations" )] public string ShootAnim { get; set; } = "fire";
/// <summary>Animation used for shooting the last bullet</summary>
[Property, Group( "Animations" )] public string ShootEmptyAnim { get; set; } = "";
/// <summary>Animation used for shooting while aiming</summary>
[Property, Group( "Animations" )] public string ShootAimedAnim { get; set; }
// Sounds //
/// <summary>Firing sound when clip is empty</summary>
[Property, Group( "Sounds" )] public SoundEvent DryShootSound { get; set; }
/// <summary>Firing sound</summary>
[Property, Group( "Sounds" )] public SoundEvent ShootSound { get; set; }
// Particles //
/// <summary> View Model particle scale</summary>
[Property, Title( "View Model Scale" ), Group( "Particles" )] public float VMParticleScale { get; set; } = 1f;
/// <summary> World Model particle scale</summary>
[Property, Title( "World Model Scale" ), Group( "Particles" )] public float WMParticleScale { get; set; } = 1f;
/// <summary>Particle used for bullet ejection</summary>
[Property, Group( "Particles" )] public ParticleSystem BulletEjectParticle { get; set; }
/// <summary>Particle used for the muzzle flash</summary>
[Property, Group( "Particles" )] public ParticleSystem MuzzleFlashParticle { get; set; }
/// <summary>Particle used for the barrel smoke</summary>
[Property, Group( "Particles" )] public ParticleSystem BarrelSmokeParticle { get; set; }
/// <summary>Particle used for the barrel smoke</summary>
[Property, Group( "Particles" )] public ParticleSystem BulletTracerParticle { get; set; }
}

View File

@ -0,0 +1,76 @@
namespace SWB.Base;
public class StatsModifier
{
public float Damage { get; set; }
public float Recoil { get; set; }
public float Spread { get; set; }
public float RPM { get; set; }
public float Force { get; set; }
// After phys bullets have been recreated
//public float BulletVelocity { get; set; } = 0;
public static readonly StatsModifier Zero = new();
private bool applied;
public static StatsModifier FromShootInfo( ShootInfo shootInfo )
{
return new()
{
Damage = shootInfo.Damage,
Recoil = shootInfo.Recoil,
Spread = shootInfo.Spread,
RPM = shootInfo.RPM,
Force = shootInfo.Force,
};
}
public void Apply( Weapon weapon, bool onPrimary = true )
{
if ( applied ) return;
if ( onPrimary )
Apply( weapon.Primary, weapon.InitialPrimaryStats );
else
Apply( weapon.Secondary, weapon.InitialSecondaryStats );
applied = true;
}
private void Apply( ShootInfo shootInfo, StatsModifier initialStats )
{
if ( shootInfo is null || initialStats is null ) return;
shootInfo.Damage += initialStats.Damage * Damage;
shootInfo.Recoil += initialStats.Recoil * Recoil;
shootInfo.Spread += initialStats.Spread * Spread;
shootInfo.RPM += (int)(initialStats.RPM * RPM);
//weapon.BulletVelocityMod += BulletVelocity;
}
public void Remove( Weapon weapon, bool onPrimary = true )
{
if ( !applied ) return;
if ( onPrimary )
Remove( weapon.Primary, weapon.InitialPrimaryStats );
else
Remove( weapon.Secondary, weapon.InitialSecondaryStats );
//weapon.BulletVelocityMod -= BulletVelocity;
applied = false;
}
private void Remove( ShootInfo shootInfo, StatsModifier initialStats )
{
if ( shootInfo is null || initialStats is null ) return;
shootInfo.Damage -= initialStats.Damage * Damage;
shootInfo.Recoil -= initialStats.Recoil * Recoil;
shootInfo.Spread -= initialStats.Spread * Spread;
shootInfo.RPM -= (int)(initialStats.RPM * RPM);
}
}

View File

@ -0,0 +1,146 @@
using Sandbox.UI;
using SWB.Shared;
using System.Threading.Tasks;
namespace SWB.Base.UI;
public class Crosshair : Panel
{
IPlayerBase player => weapon.Owner;
Weapon weapon;
Panel centerDot;
Panel leftBar;
Panel rightBar;
Panel topBar;
Panel bottomBar;
int spreadOffset = 400;
int sprintOffset = 100;
int fireOffset = 50;
bool wasAiming = false;
public Crosshair( Weapon weapon )
{
this.weapon = weapon;
StyleSheet.Load( "/swb_base/ui/Crosshair.cs.scss" );
centerDot = Add.Panel( "centerDot" );
leftBar = Add.Panel( "leftBar" );
rightBar = Add.Panel( "rightBar" );
topBar = Add.Panel( "topBar" );
bottomBar = Add.Panel( "bottomBar" );
leftBar.AddClass( "sharedBarStyling" );
rightBar.AddClass( "sharedBarStyling" );
topBar.AddClass( "sharedBarStyling" );
bottomBar.AddClass( "sharedBarStyling" );
}
private void UpdateCrosshair()
{
centerDot.Style.Dirty();
leftBar.Style.Dirty();
rightBar.Style.Dirty();
topBar.Style.Dirty();
bottomBar.Style.Dirty();
}
private void RestoreBarPositions()
{
leftBar.Style.Left = -16;
rightBar.Style.Left = 5;
topBar.Style.Top = -16;
bottomBar.Style.Top = 5;
}
private void RestoreCrosshairOpacity()
{
centerDot.Style.Opacity = 1;
leftBar.Style.Opacity = 1;
rightBar.Style.Opacity = 1;
topBar.Style.Opacity = 1;
bottomBar.Style.Opacity = 1;
}
private void HideBarLines()
{
leftBar.Style.Opacity = 0;
rightBar.Style.Opacity = 0;
topBar.Style.Opacity = 0;
bottomBar.Style.Opacity = 0;
}
public override void Tick()
{
bool isValidWeapon = weapon is not null;
var shouldHide = !isValidWeapon || weapon.IsScoping || weapon.IsCustomizing;
SetClass( "hideCrosshair", shouldHide );
//var hideCrosshairDot = shouldHide /*|| !weapon.UISettings.ShowCrosshairDot*/;
//centerDot.SetClass( "hideCrosshair", hideCrosshairDot );
//var hideCrosshairLines = shouldHide /*|| !weapon.UISettings.ShowCrosshairLines*/;
//leftBar.SetClass( "hideCrosshair", hideCrosshairLines );
//rightBar.SetClass( "hideCrosshair", hideCrosshairLines );
//topBar.SetClass( "hideCrosshair", hideCrosshairLines );
//bottomBar.SetClass( "hideCrosshair", hideCrosshairLines );
if ( shouldHide ) return;
// Crosshair spread offset
var screenOffset = spreadOffset * weapon.GetRealSpread();
leftBar.Style.MarginLeft = -screenOffset;
rightBar.Style.MarginLeft = screenOffset;
topBar.Style.MarginTop = -screenOffset;
bottomBar.Style.MarginTop = screenOffset;
// Sprint spread offsets
if (weapon.IsReloading || weapon.IsDeploying || (weapon.InBoltBack && !weapon.IsAiming) )
{
leftBar.Style.Left = -sprintOffset;
rightBar.Style.Left = sprintOffset - 5;
topBar.Style.Top = -sprintOffset;
bottomBar.Style.Top = sprintOffset - 5;
HideBarLines();
}
else if ( weapon.IsAiming )
{
wasAiming = true;
// centerDot.Style.Opacity = 0;
// HideBarLines();
}
else if ( leftBar.Style.Left == -sprintOffset || wasAiming )
{
wasAiming = false;
RestoreBarPositions();
RestoreCrosshairOpacity();
}
UpdateCrosshair();
}
[PanelEvent( "shoot" )]
public void ShootEvent( float fireDelay )
{
// Fire spread offsets
leftBar.Style.Left = -fireOffset;
rightBar.Style.Left = fireOffset - 5;
topBar.Style.Top = -fireOffset;
bottomBar.Style.Top = fireOffset - 5;
_ = FireDelay( fireDelay / 2 );
}
private async Task FireDelay( float delay )
{
await GameTask.DelaySeconds( delay );
RestoreBarPositions();
RestoreCrosshairOpacity();
}
}

View File

@ -0,0 +1,51 @@
Crosshair {
position: absolute;
left: 50%;
top: 50%;
z-index: -1;
.centerDot {
background-color: white;
top: 0px;
left: 0px;
width: 2px;
height: 2px;
box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.4);
transition: all 0.1s ease-out 0s;
}
.leftBar {
left: -16px;
width: 12px;
height: 1px;
}
.rightBar {
left: 5px;
width: 12px;
height: 1px;
}
.topBar {
top: -16px;
width: 1px;
height: 12px;
}
.bottomBar {
top: 5px;
width: 1px;
height: 12px;
}
.sharedBarStyling {
position: absolute;
background-color: white;
box-shadow: 0px 0px 1px 1px rgba(0,0,0,0.4);
transition: all 0.1s ease-out 0s;
}
&.hideCrosshair {
display: none;
}
}

View File

@ -0,0 +1,186 @@
using Sandbox.UI;
using Sandbox.UI.Construct;
using SWB.Base.Attachments;
using System.Collections.Generic;
namespace SWB.Base.UI;
public class CustomizationMenu : Panel
{
Weapon weapon;
Panel categoryWrapper;
Panel attachmentWrapper;
Panel descriptionWrapper;
Panel activeCategoryP;
Panel activeAttachmentP;
Attachment selectedAttachment;
Attachment hoveredAttachment;
Dictionary<AttachmentCategory, List<Attachment>> attachmentsPerCategory = new();
public CustomizationMenu( Weapon weapon )
{
this.weapon = weapon;
FillAttachmentsPerCategory();
StyleSheet.Load( "/swb_base/ui/CustomizationMenu.cs.scss" );
categoryWrapper = Add.Panel( "categoryWrapper" );
categoryWrapper.Add.Label( weapon.DisplayName, "weaponName" );
attachmentWrapper = Add.Panel( "attachmentWrapper" );
descriptionWrapper = Add.Panel( "descriptionWrapper" );
CreateCategoryPanels();
}
void FillAttachmentsPerCategory()
{
weapon.Attachments.ForEach( attachment =>
{
if ( attachment.Hide ) return;
if ( attachmentsPerCategory.TryGetValue( attachment.Category, out var attachments ) )
attachments.Add( attachment );
else
attachmentsPerCategory.Add( attachment.Category, new List<Attachment>() { attachment } );
} );
}
void CreateCategoryPanels()
{
foreach ( var entry in attachmentsPerCategory )
{
var categoryP = categoryWrapper.Add.Panel( "category" );
categoryP.Add.Label( entry.Key.ToString(), "name" );
categoryP.Add.Label( "", "attName" );
var catActiveAttachP = categoryP.Add.Panel( "activeAttachment" );
catActiveAttachP.Add.Label( "", "name" );
var iconWrapperP = catActiveAttachP.Add.Panel( "iconWrapper" );
iconWrapperP.Add.Image( "", "icon" );
var activeCatAttachment = weapon.GetActiveAttachmentForCategory( entry.Key );
if ( activeCatAttachment is not null )
SetAttachmentOnCategoryPanel( categoryP, activeCatAttachment );
categoryP.AddEventListener( "onmousedown", () =>
{
if ( activeCategoryP != categoryP )
{
PlaySound( "swb_click" );
activeCategoryP?.SetClass( "active", false );
categoryP.SetClass( "active", true );
activeCategoryP = categoryP;
selectedAttachment = null;
descriptionWrapper.DeleteChildren();
CreateAttachmentPanels( entry.Key );
}
} );
categoryP.AddEventListener( "onmouseover", () =>
{
PlaySound( "ui.button.over" );
} );
}
}
void SetAttachmentOnCategoryPanel( Panel categoryP, Attachment attachment )
{
categoryP.SetClass( "hasAttachment", true );
var attachP = categoryP.GetChild( 2 );
var nameL = (Label)attachP.GetChild( 0 );
var iconWrapper = attachP.GetChild( 1 );
var iconP = (Image)iconWrapper.GetChild( 0 );
nameL.Text = attachment.Name;
iconP.SetTexture( attachment.IconPath );
}
void CreateAttachmentPanels( AttachmentCategory category )
{
attachmentWrapper.DeleteChildren();
activeAttachmentP = null;
if ( !attachmentsPerCategory.TryGetValue( category, out var attachments ) ) return;
var activeAttachment = weapon.GetActiveAttachmentForCategory( category );
attachments.ForEach( attachment =>
{
var attachmentP = attachmentWrapper.Add.Panel( "attachment" );
attachmentP.Add.Label( attachment.Name, "name" );
var iconWrapperP = attachmentP.Add.Panel( "iconWrapper" );
iconWrapperP.Add.Image( attachment.IconPath, "icon" );
var isActiveAttachment = attachment == activeAttachment;
attachmentP.SetClass( "active", isActiveAttachment );
if ( isActiveAttachment )
{
activeAttachmentP = attachmentP;
selectedAttachment = attachment;
CreateDescriptionPanel( attachment );
}
attachmentP.AddEventListener( "onmousedown", () =>
{
activeAttachmentP?.SetClass( "active", false );
if ( activeAttachmentP != attachmentP )
{
PlaySound( "swb_equip" );
attachment.EquipBroadCast();
attachmentP.SetClass( "active", true );
activeAttachmentP = attachmentP;
selectedAttachment = attachment;
SetAttachmentOnCategoryPanel( activeCategoryP, attachment );
}
else
{
PlaySound( "swb_unequip" );
attachment.UnEquipBroadCast();
activeCategoryP.SetClass( "hasAttachment", false );
activeAttachmentP = null;
selectedAttachment = null;
}
} );
attachmentP.AddEventListener( "onmouseover", () =>
{
hoveredAttachment = attachment;
PlaySound( "ui.button.over" );
CreateDescriptionPanel( attachment );
} );
attachmentP.AddEventListener( "onmouseout", () =>
{
if ( hoveredAttachment != attachment ) return;
if ( selectedAttachment is not null )
CreateDescriptionPanel( this.selectedAttachment );
else
descriptionWrapper.DeleteChildren();
} );
} );
}
private void CreateDescriptionPanel( Attachment attach )
{
descriptionWrapper.DeleteChildren();
descriptionWrapper.Add.Label( attach.Description, "description" );
var posWrapper = descriptionWrapper.Add.Panel( "posWrapper" );
foreach ( var pos in attach.Positives )
{
posWrapper.Add.Label( "> " + pos, "label" );
}
var negWrapper = descriptionWrapper.Add.Panel( "negWrapper" );
foreach ( var neg in attach.Negatives )
{
negWrapper.Add.Label( "> " + neg, "label" );
}
}
}

View File

@ -0,0 +1,303 @@
$item-width: 280px;
$item-bg-full-dark: rgb(33, 33, 33);
$item-bg-extra-dark: rgba(33, 33, 33,0.95);
$item-bg-dark: rgba(33, 33, 33,0.8);
$col-green: #2ecc71;
$col-green-trans: #2ecc7196;
$col-red: #e84118;
CustomizationMenu {
position: absolute;
top: 40px;
left: 60px;
pointer-events: all;
align-items: flex-start; // Enables columns with diff heights
transition: opacity 0.2s ease-out 0s;
text-shadow: 0px 1px 1px rgba(0,0,0,0.3);
gap: 4px;
&:intro {
opacity: 0.01;
}
.categoryWrapper {
flex-direction: column;
.weaponName {
min-width: $item-width;
align-items: center;
color: #eccc68;
text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
font-weight: 500;
font-size: 28px;
font-family: FONTSPRING DEMO - Integral CF;
padding: 0 12px 2px 12px;
border-radius: 2px;
min-height: 60px;
background-color: rgba( #2222, 0.75 );
backdrop-filter: blur( 16px );
margin-bottom: 4px;
}
.category {
margin-bottom: 4px;
width: $item-width;
border-radius: 4px;
cursor: pointer;
align-items: center;
min-height: 44px;
transition: all 0.1s ease-out 0s;
background-color: rgba( #2222, 0.5 );
backdrop-filter: blur( 16px );
&.active {
border-left: 3px solid $col-green;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
.name {
color: $col-green;
}
}
&:hover {
.name {
color: $col-green;
}
&.hasAttachment {
.name {
color: white;
}
.activeAttachment {
.name {
color: $col-green;
transform: scale(1.04);
}
.iconWrapper {
filter: drop-shadow(0px 0px 6px $col-green);
.icon {
height: 60px;
width: 60px;
}
}
}
}
}
&.hasAttachment {
min-height: 60px;
overflow: hidden; // too long attach name
&.active {
.name {
color: white;
}
.activeAttachment {
.name {
color: $col-green;
}
}
}
.name {
font-size: 16px;
margin-bottom: auto;
margin-top: 4px;
color: rgb(200,200,200);
}
.activeAttachment {
opacity: 1;
}
}
.name {
color: white;
font-family: Poppins;
font-size: 22px;
margin-left: 10px;
border-radius: 2px;
transition: font-size 0.05s ease;
pointer-events: none;
text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
}
.activeAttachment {
position: absolute;
width: $item-width;
height: 60px;
border-radius: 2px;
opacity: 0;
transition: all 0.1s ease-out 0s;
pointer-events: none;
.name {
color: white;
font-family: Poppins;
font-size: 20px;
margin: 28px 0 0 10px;
transition: all 0.1s ease-out 0s;
}
.iconWrapper {
height: 60px;
width: 60px;
border-radius: 2px;
margin: 0 6px 0 auto;
//margin-right: -1px;
filter: drop-shadow(0px 0px 6px #eccc68);
justify-content: center;
align-items: center;
transition: all 0.1s ease-out 0s;
.icon {
height: 56px;
width: 56px;
background-repeat: no-repeat;
background-size: cover;
transition: all 0.1s ease-out 0s;
}
}
}
}
}
.attachmentWrapper {
flex-direction: column;
.attachment {
margin-bottom: 4px;
width: 260px;
border-radius: 4px;
cursor: pointer;
align-items: center;
min-height: 40px;
transition: all 0.1s ease-out 0s;
background-color: rgba( #2222, 0.5 );
backdrop-filter: blur( 16px );
&:hover {
.name {
color: $col-green;
}
.iconWrapper {
filter: drop-shadow(0px 0px 6px $col-green);
}
}
&.active {
min-height: 46px;
border-left: 3px solid $col-green;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
.name {
color: $col-green;
font-weight: bold;
}
.iconWrapper {
filter: drop-shadow(0px 0px 6px $col-green);
min-height: 46px;
width: 62px;
.icon {
height: 46px;
width: 46px;
}
}
}
.name {
color: white;
font-family: Poppins;
font-size: 18px;
padding-top: 2px;
margin-left: 10px;
border-radius: 2px;
transition: font-size 0.05s ease;
pointer-events: none;
text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
}
.iconWrapper {
// height: 100% -> broken somehow
min-height: 40px;
width: 52px;
border-radius: 2px;
margin-left: auto;
justify-content: center;
align-items: center;
transition: all 0.1s ease-out 0s;
pointer-events: none;
filter: drop-shadow(0px 0px 1px white);
.icon {
height: 42px;
width: 42px;
background-repeat: no-repeat;
background-size: cover;
transition: all 0.1s ease-out 0s;
}
}
}
}
.descriptionWrapper {
position: relative;
width: 400px;
margin-left: 4px;
flex-direction: column;
border-radius: 2px;
padding: 10px;
font-family: Poppins;
background-color: rgba( #2222, 0.5 );
backdrop-filter: blur( 16px );
text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
font-size: 16px;
&:empty {
opacity: 0;
}
.description {
color: white;
}
.posWrapper {
margin-top: 10px;
padding-left: 4px;
flex-direction: column;
border-left: 2px solid $col-green;
.label {
color: $col-green;
font-weight: 500;
}
}
.negWrapper {
margin-top: 10px;
padding-left: 4px;
flex-direction: column;
border-left: 2px solid $col-red;
&:empty {
margin-top: 0;
}
.label {
color: $col-red;
font-weight: 500;
}
}
}
}

View File

@ -0,0 +1,28 @@
using Sandbox.UI;
namespace SWB.Base.UI;
public class RootWeaponDisplay : PanelComponent
{
public Weapon Weapon { get; set; }
protected override void OnStart()
{
if ( IsProxy )
{
Enabled = false;
return;
}
Panel.StyleSheet.Load( "/swb_base/ui/RootWeaponDisplay.cs.scss" );
var crosshair = new Crosshair( Weapon );
Panel.AddChild( crosshair );
if ( Weapon.Scoping )
{
var sniperScope = new SniperScope( Weapon, Weapon.ScopeInfo.LensTexture, Weapon.ScopeInfo.ScopeTexture );
Panel.AddChild( sniperScope );
}
}
}

View File

@ -0,0 +1,5 @@
RootWeaponDisplay {
position: absolute;
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,84 @@
using Sandbox.UI;
using Sandbox.UI.Construct;
using SWB.Shared;
using System;
namespace SWB.Base.UI;
public class SniperScope : Panel
{
IPlayerBase player => weapon.Owner;
Weapon weapon;
Panel lensWrapper;
Panel scope;
float lensRotation;
public SniperScope( Weapon weapon, string lensTexture, string scopeTexture )
{
this.weapon = weapon;
StyleSheet.Load( "/swb_base/ui/SniperScope.cs.scss" );
AddClass( "hide" );
if ( scopeTexture != null )
Add.Panel( "leftBar" );
lensWrapper = Add.Panel( "lensWrapper" );
lensWrapper.Add.Image( lensTexture, "lens" );
if ( scopeTexture != null )
{
scope = lensWrapper.Add.Image( scopeTexture, "scope" );
Add.Panel( "rightBar" );
Add.Panel( "topBar" );
Add.Panel( "bottomBar" );
}
}
public override void Tick()
{
if ( weapon is null ) return;
// Scope size
var scopeSize = Screen.Height * ScaleFromScreen;
lensWrapper.Style.Width = Length.Pixels( scopeSize );
// Show when zooming
SetClass( "hide", !weapon.IsScoping );
// Check if ADS & firing
if ( weapon.IsAiming && weapon.TimeSincePrimaryShoot < 0.1f )
return;
// Movement impact
var velocityJump = 0.02f;
var velocityMove = 0.005f;
var lensBob = 0f;
if ( velocityJump != 0 )
{
lensBob += velocityJump;
}
else if ( velocityMove != 0 )
{
lensBob += MathF.Sin( RealTime.Now * 17f ) * velocityMove;
}
Style.MarginTop = Length.Percent( velocityJump + lensBob );
if ( scope == null ) return;
// Rotation impact
var rightVector = player.Camera.WorldRotation.Right * 0f;
var targetRotation = (rightVector.y + rightVector.x) * 0.015f;
var rotateTransform = new PanelTransform();
lensRotation = MathUtil.FILerp( lensRotation, targetRotation, 20 );
rotateTransform.AddRotation( 0, 0, lensRotation );
scope.Style.Transform = rotateTransform;
// Movement blur
scope.Style.FilterBlur = Math.Abs( lensRotation * 2 + velocityJump + lensBob );
}
}

View File

@ -0,0 +1,49 @@
SniperScope {
position: absolute;
width: 100%;
height: 100%;
overflow: visible;
&.hide {
display: none;
}
.lensWrapper {
position: relative;
width: 100%;
}
.lens {
height: 100%;
width: 100%;
}
.scope {
position: absolute;
height: 100%;
width: 100%;
}
.leftBar,
.rightBar {
background-color: black;
height: 100%;
flex-grow: 1;
}
.topBar {
position: absolute;
background-color: black;
top: -100%;
width: 100%;
height: 100%;
}
.bottomBar {
position: absolute;
background-color: black;
bottom: -100%;
width: 100%;
height: 100%;
}
}

View File

@ -0,0 +1,52 @@
using System;
/*
* Utility class to handle framerate independent + useful calculations
*/
namespace SWB.Base;
class MathUtil
{
public static float FILerp( float fromF, float toF, float amount )
{
return fromF.LerpTo( toF, amount * RealTime.Delta );
}
public static Vector3 FILerp( Vector3 fromVec, Vector3 toVec, float amount )
{
return fromVec.LerpTo( toVec, amount * RealTime.Delta );
}
public static Angles FILerp( Angles fromAng, Angles toAng, float amount )
{
return Angles.Lerp( fromAng, toAng, amount * RealTime.Delta );
}
public static Vector3 RelativeAdd( Vector3 vec1, Vector3 vec2, Rotation rot )
{
vec1 += vec2.x * rot.Right;
vec1 += vec2.y * rot.Up;
vec1 += vec2.z * rot.Forward;
return vec1;
}
// Helpful bezier function. Use this if you gotta: https://www.desmos.com/calculator/cahqdxeshd
public static float BezierY( float f, float a, float b, float c )
{
f *= 3.2258f;
return MathF.Pow( (1.0f - f), 2.0f ) * a + 2.0f * (1.0f - f) * f * b + MathF.Pow( f, 2.0f ) * c;
}
public static Vector3 ToVector3( Angles angles )
{
return new Vector3( angles.pitch, angles.yaw, angles.roll );
}
public static Angles ToAngles( Vector3 vector )
{
return new Angles( vector.x, vector.y, vector.z );
}
}

View File

@ -0,0 +1,22 @@
using System.Linq;
namespace SWB.Base;
public class ModelUtil
{
public static void ParentToBone( GameObject gameObject, SkinnedModelRenderer target, string bone )
{
var targetBone = target.Model.Bones.AllBones.FirstOrDefault( b => b.Name == bone );
if ( targetBone is null )
{
Log.Error( $"Could not find bone '{bone}' on {target}" );
return;
}
var holdBoneGo = target.GetBoneObject( targetBone );
Log.Info(holdBoneGo);
gameObject.SetParent( holdBoneGo );
gameObject.WorldPosition = holdBoneGo.WorldPosition;
gameObject.WorldRotation = holdBoneGo.WorldRotation;
}
}

View File

@ -0,0 +1,47 @@
using SWB.Shared;
using System.Collections.Generic;
/*
* Util class for checking surface properties
*/
namespace SWB.Base;
public static class SurfaceUtil
{
public static List<string> PenetratableSurfaces = new()
{
"water",
"glass",
"glass.pane"
};
public static List<string> RicochetSurfaces = new()
{
"wip",
};
public static bool CanPenetrate( Surface surface )
{
return PenetratableSurfaces.Contains( surface.ResourceName );
}
public static bool CanRicochet( Surface surface )
{
return RicochetSurfaces.Contains( surface.ResourceName );
}
public static bool IsPointWater( Vector3 pos )
{
var tr = Game.SceneTrace.Ray( pos, pos + Vector3.Forward )
.WithTag( TagsHelper.Water )
.Run();
return tr.Hit;
}
public static bool IsSkybox( Surface surface )
{
return surface.HasTag( TagsHelper.World ) && !surface.HasTag( TagsHelper.Solid );
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
namespace SWB.Base;
class TableUtil
{
public static T GetRandom<T>( List<T> list )
{
if ( list.Count == 0 ) return default;
var random = new Random();
var randI = random.Next( list.Count );
return list[randI];
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Linq;
namespace SWB.Shared;
public class DamageInfo
{
public Guid AttackerId { get; set; }
public string Inflictor { get; set; }
public float Damage { get; set; }
public Vector3 Origin { get; set; }
public Vector3 Force { get; set; }
public string[] Tags { get; set; }
public static DamageInfo FromBullet( Guid attackerId, string inflictor, float damage, Vector3 origin, Vector3 force, string[] tags )
{
return new()
{
AttackerId = attackerId,
Inflictor = inflictor,
Damage = damage,
Origin = origin,
Force = force,
Tags = new[] { "bullet" }.Concat( tags ).ToArray(),
};
}
}

View File

@ -0,0 +1,35 @@
namespace SWB.Shared;
public interface IInventory
{
public NetList<GameObject> Items { get; set; }
public GameObject Active { get; set; }
public void Add( GameObject gameObject, bool makeActive = false );
public GameObject AddClone( GameObject gamePrefab, bool makeActive = true );
public bool Has( GameObject gameObject );
public void SetActive( GameObject gameObject );
public void SetActive( string name );
public void Clear();
}
public interface IInventoryItem : IValid
{
/// <summary>Inventory slot</summary>
public int Slot { get; set; }
/// <summary>Image that represent the item on the HUD</summary>
public string Icon { get; set; }
/// <summary>Name that represent the item on the HUD</summary>
public string DisplayName { get; set; }
/// <summary>Called on the GameObject that will be the new active one (Broadcast for networked gameObjects!)</summary>
public void OnCarryStart();
/// <summary>Called when the GameObject stops being the active one (Broadcast for networked gameObjects!)</summary>
public void OnCarryStop();
/// <summary>Can the GameObject be switched out</summary>
public bool CanCarryStop();
}

View File

@ -0,0 +1,52 @@
using Sandbox.Citizen;
using System;
namespace SWB.Shared;
public interface IPlayerBase : IValid
{
// public CameraComponent ViewModelCamera { get; set; }
public CameraComponent Camera { get; set; }
public GameObject Body { get; set; }
public SkinnedModelRenderer BodyRenderer { get; set; }
public ShrimpleCharacterController.ShrimpleCharacterController CharacterController { get; set; }
public AnimationHelper AnimationHelper { get; set; }
public GameObject GameObject { get; }
public IInventory Inventory { get; set; }
public bool IsAlive { get; }
public int MaxHealth { get; set; }
public int Health { get; set; }
public int Kills { get; set; }
public int Deaths { get; set; }
public Guid Id { get; }
// /// <summary>Input sensitivity modifier</summary>
// public float InputSensitivity { get; set; }
/// <summary>
/// Called when the weapon wants to know how much ammo is available
/// </summary>
/// <param name="type">The type of ammo</param>
/// <returns>How much ammo is available</returns>
public int AmmoCount( string type );
/// <summary>
/// Called when the weapon is trying to take ammo.
/// </summary>
/// <param name="type">The type of ammo</param>
/// <param name="amount">The amount of ammo requested</param>
/// <returns>How much ammo was actually taken</returns>
public int TakeAmmo( string type, int amount );
/// <summary>
/// Called when the player takes damage
/// </summary>
/// <param name="info">Information about the damage</param>
public void TakeDamage( DamageInfo info );
/// <summary>
/// Shakes the camera
/// </summary>
/// <param name="screenShake">Information about the shake</param>
public void ShakeScreen( ScreenShake screenShake );
}

View File

@ -0,0 +1,36 @@
namespace SWB.Shared;
public partial class InputButtonHelper
{
public static string Forward => "Forward";
public static string Backward => "Backward";
public static string Left => "Left";
public static string Right => "Right";
public static string Jump => "Jump";
public static string Run => "Run";
public static string Walk => "Walk";
public static string Duck => "Duck";
public static string PrimaryAttack => "attack1";
public static string SecondaryAttack => "attack2";
public static string Reload => "reload";
public static string Use => "use";
public static string Slot1 => "Slot1";
public static string Slot2 => "Slot2";
public static string Slot3 => "Slot3";
public static string Slot4 => "Slot4";
public static string Slot5 => "Slot5";
public static string Slot6 => "Slot6";
public static string Slot7 => "Slot7";
public static string Slot8 => "Slot8";
public static string Slot9 => "Slot9";
public static string Slot0 => "Slot0";
public static string SlotPrev => "SlotPrev";
public static string SlotNext => "SlotNext";
public static string View => "View";
public static string Voice => "Voice";
public static string Drop => "Drop";
public static string Flashlight => "Flashlight";
public static string Score => "Score";
public static string Menu => "Menu";
public static string Chat => "Chat";
}

View File

@ -0,0 +1,16 @@
namespace SWB.Shared;
public class ScreenShake
{
/// <summary>Duration (s)</summary>
[KeyProperty] public float Duration { get; set; } = 0f;
/// <summary>Delay between shakes (s)</summary>
[KeyProperty] public float Delay { get; set; } = 0f;
/// <summary>Screen disposition amount</summary>
[KeyProperty] public float Size { get; set; } = 0f;
/// <summary>Screen rotation amount</summary>
[KeyProperty] public float Rotation { get; set; } = 0f;
}

View File

@ -0,0 +1,15 @@
namespace SWB.Shared;
public partial class TagsHelper
{
public static string Player => "player";
public static string Trigger => "trigger";
public static string Weapon => "weapon";
public static string ViewModel => "viewmodel";
public static string PlayerClip => "playerclip";
public static string PassBullets => "passbullets";
public static string Water => "water";
public static string World => "world";
public static string Solid => "solid";
public static string Attachment => "attachment";
}

View File

@ -1,8 +1,11 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=C_003A_005CUsers_005Chamit_005Fba31xcg_005CDocuments_005Cs_0026box_0020projects_005Ckakozuzo_005F2_005CLibraries_005Cfish_002Escc_005Ccode_005CAssembly_002Ecs/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/AddReferences/RecentPaths/=C_003A_005CUsers_005Chamit_005Fba31xcg_005CDocuments_005Cs_0026box_0020projects_005Ckakozuzo_005F2_005CLibraries_005Cfish_002Escc_005Ccode_005CAssembly_002Ecs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=C_003A_005CUsers_005Chamit_005Fba31xcg_005CDocuments_005Cs_0026box_0020projects_005Ckakozuzo_005F2_005CLibraries_005Cfish_002Escc_005Cscc_002Esbproj/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/AddReferences/RecentPaths/=C_003A_005CUsers_005Chamit_005Fba31xcg_005CDocuments_005Cs_0026box_0020projects_005Ckakozuzo_005F2_005CLibraries_005Cfish_002Escc_005Cscc_002Esbproj/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AComponentList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fda27136869dc28c32bc5fb3ea4841e055f0e290e45d4bde9530ad27d38be0db_003FComponentList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInputActionAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae28b94f898844b4bf21b8441ca0a34b66e00_003F4d_003F40860ce8_003FInputActionAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInputActionAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fae28b94f898844b4bf21b8441ca0a34b66e00_003F4d_003F40860ce8_003FInputActionAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInput_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcdc4720139e44c50ab0f14122fb7a0f2237a00_003Fe3_003Fe2d38eef_003FInput_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInput_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcdc4720139e44c50ab0f14122fb7a0f2237a00_003Fe3_003Fe2d38eef_003FInput_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIValid_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa8e4635c89e1456aac179819973df53b68400_003F14_003F50c24b3d_003FIValid_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetworking_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F81ecae4f6275493893a36d9c54b5776d233200_003F73_003F8bf7d187_003FNetworking_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetworking_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F81ecae4f6275493893a36d9c54b5776d233200_003F73_003F8bf7d187_003FNetworking_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASkinnedModelRenderer_002EParameters_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fed128fe9d254c953a46da1ac97997e29107b59c8f1369a7bff15f3d718978_003FSkinnedModelRenderer_002EParameters_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASkinnedModelRenderer_002EParameters_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fed128fe9d254c953a46da1ac97997e29107b59c8f1369a7bff15f3d718978_003FSkinnedModelRenderer_002EParameters_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AVoid_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb34296d7545f4a54bda041aecea156b4b1d878_003F_005Fe67e6_003FVoid_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
</wpf:ResourceDictionary> </wpf:ResourceDictionary>