Created
March 31, 2025 10:35
-
-
Save canoom/26c31d68ab7388a18a47f624e38d51f3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
└── code | |
├── Assembly.cs | |
├── BoardManager.cs | |
├── BugSegment.cs | |
├── CellComponent.cs | |
├── CoinComponent.cs | |
├── Components | |
├── DamageNumber.cs | |
├── FixTransparency.cs | |
├── RotateConstantly.cs | |
└── VoiceComponent.cs | |
├── Enums | |
├── BugBodyType.cs | |
└── GameState.cs | |
├── GameManager.cs | |
├── GameResource | |
├── BugResource.cs | |
└── WeaponResource.cs | |
├── Inputs | |
├── AttackingInput.cs | |
├── InspectInput.cs | |
└── PlacementInput.cs | |
├── PebbleComponent.cs | |
└── UI | |
├── Controls | |
├── InputGlyph.razor | |
└── InputGlyph.razor.scss | |
├── PanelComponents | |
├── BugHealthbar.razor | |
├── BugHealthbar.razor.scss | |
├── InspectorPanel.razor | |
├── InspectorPanel.razor.scss | |
├── MainHudPanel.razor | |
├── MainHudPanel.razor.scss | |
├── MainMenu.razor | |
├── MainMenu.razor.scss | |
├── PauseMenu.razor | |
└── PauseMenu.razor.scss | |
└── Panels | |
├── BugList | |
├── BugList.razor | |
├── BugList.razor.scss | |
├── BugListEntry.razor | |
├── BugListEntry.razor.scss | |
├── BugListSegment.razor | |
└── BugListSegment.razor.scss | |
├── GameHud | |
├── ControlsPanel.razor | |
├── ControlsPanel.razor.scss | |
├── GameHud.razor | |
├── GameHud.razor.scss | |
├── PlayerHud | |
│ ├── ChatEntry.razor | |
│ ├── PlayerHud.razor | |
│ └── PlayerHud.razor.scss | |
├── SidePanel.razor | |
├── SidePanel.razor.scss | |
└── WeaponHud | |
│ ├── WeaponButton.razor | |
│ ├── WeaponButton.razor.scss | |
│ ├── WeaponHud.razor | |
│ └── WeaponHud.razor.scss | |
├── HintPanel | |
├── HintPanel.razor | |
├── HintPanel.razor.scss | |
└── HintPanelEntry.razor | |
├── LobbyList.razor | |
├── LobbyList.razor.scss | |
├── PlacingHud.razor | |
├── PlacingHud.razor.scss | |
├── ResultsHud.razor | |
├── ResultsHud.razor.scss | |
├── ShopPanel | |
├── ShopPanel.razor | |
├── ShopPanel.razor.scss | |
├── ShopPanelEntry.razor | |
└── ShopPanelEntry.razor.scss | |
├── StatsPanel.razor | |
├── StatsPanel.razor.scss | |
├── WaitingHud.razor | |
└── WaitingHud.razor.scss | |
/code/Assembly.cs: | |
-------------------------------------------------------------------------------- | |
1 | global using Sandbox; | |
2 | global using System.Collections.Generic; | |
3 | global using System.Linq; | |
4 | | |
-------------------------------------------------------------------------------- | |
/code/BoardManager.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | using Sandbox; | |
3 | | |
4 | namespace Battlebugs; | |
5 | | |
6 | public sealed class BoardManager : Component | |
7 | { | |
8 | // Static Variables | |
9 | public static BoardManager Local | |
10 | { | |
11 | get | |
12 | { | |
13 | if ( !_local.IsValid() ) | |
14 | { | |
15 | _local = Game.ActiveScene.GetAllComponents<BoardManager>().FirstOrDefault( x => x.Network.IsOwner ); | |
16 | } | |
17 | return _local; | |
18 | } | |
19 | } | |
20 | private static BoardManager _local; | |
21 | public static BoardManager Opponent => Game.ActiveScene.GetAllComponents<BoardManager>().FirstOrDefault( x => x.Network.Owner != Connection.Local ); | |
22 | | |
23 | // Properties | |
24 | [Property] public int GridSize { get; set; } = 64; | |
25 | [Property] public int Width { get; set; } = 10; | |
26 | [Property] public int Height { get; set; } = 10; | |
27 | [Property, Group( "References" )] public GameObject CameraPosition { get; set; } | |
28 | | |
29 | // Networked Variables | |
30 | [Sync] public int Coins { get; set; } = 100; | |
31 | [Sync] public bool IsReady { get; set; } = false; | |
32 | [Sync] public NetList<BugReference> BugReferences { get; set; } = new(); | |
33 | [Sync] public int CoinsSpent { get; set; } = 0; | |
34 | [Sync] public int BugsKilled { get; set; } = 0; | |
35 | | |
36 | // Public Variables | |
37 | public WeaponResource SelectedWeapon = null; | |
38 | public Dictionary<BugResource, int> BugInventory = new(); | |
39 | public Dictionary<WeaponResource, int> WeaponInventory = new(); | |
40 | public int MaxPlaceableSegments => BugInventory.Where( x => x.Value > 0 ).OrderBy( x => x.Key.SegmentCount ).LastOrDefault().Key?.SegmentCount ?? 0; | |
41 | TimeSince timeSinceTurnStart = 0; | |
42 | | |
43 | protected override void OnStart() | |
44 | { | |
45 | if ( IsProxy ) return; | |
46 | | |
47 | InitBoard(); | |
48 | ResetBugInventory(); | |
49 | ResetWeaponInventory(); | |
50 | | |
51 | if ( Network.Owner is null ) | |
52 | { | |
53 | SetupBoardRandomly(); | |
54 | IsReady = true; | |
55 | } | |
56 | } | |
57 | | |
58 | protected override void OnFixedUpdate() | |
59 | { | |
60 | if ( GameManager.Instance.CurrentPlayer != this ) timeSinceTurnStart = 0; | |
61 | if ( Network.Owner is null && timeSinceTurnStart > 2.5f && GameManager.Instance.CurrentPlayer == this && GameManager.Instance.State == GameState.Playing && GameManager.Instance.IsFiring ) | |
62 | { | |
63 | AttackRandomly(); | |
64 | } | |
65 | } | |
66 | | |
67 | void InitBoard() | |
68 | { | |
69 | Vector3 startingPosition = WorldPosition + new Vector3( -(Width * GridSize) / 2f + GridSize / 2f, -(Height * GridSize) / 2f + GridSize / 2f, 0 ); | |
70 | for ( int x = 0; x < Width; x++ ) | |
71 | { | |
72 | for ( int y = 0; y < Height; y++ ) | |
73 | { | |
74 | var cellObj = GameManager.Instance.CellPrefab.Clone( startingPosition + new Vector3( x * GridSize, y * GridSize, 0 ) ); | |
75 | var cell = cellObj.Components.Get<CellComponent>(); | |
76 | var index = x + y * (Width + 1); | |
77 | cell.Init( this, new Vector2( x, y ), index ); | |
78 | cellObj.SetParent( GameObject ); | |
79 | cellObj.NetworkSpawn( Network.Owner ); | |
80 | } | |
81 | } | |
82 | } | |
83 | | |
84 | protected override void DrawGizmos() | |
85 | { | |
86 | base.DrawGizmos(); | |
87 | | |
88 | var size = new Vector3( Width * GridSize, Height * GridSize, 0 ); | |
89 | var bounds = new BBox( size / 2f, -size / 2f ); | |
90 | Gizmo.Draw.LineBBox( bounds ); | |
91 | } | |
92 | | |
93 | public void ClearAllBugs( bool playSound = true ) | |
94 | { | |
95 | if ( playSound ) Sound.Play( "clear-all-bugs" ); | |
96 | var segments = Scene.GetAllComponents<BugSegment>(); | |
97 | foreach ( var segment in segments ) | |
98 | { | |
99 | if ( segment.Network.OwnerId != Network.OwnerId ) continue; | |
100 | segment.Cell.IsOccupied = false; | |
101 | segment.GameObject.Destroy(); | |
102 | } | |
103 | var cells = Components.GetAll<CellComponent>( FindMode.InChildren ); | |
104 | foreach ( var cell in cells ) | |
105 | { | |
106 | cell.IsOccupied = false; | |
107 | cell.BroadcastUpdateHighlight(); | |
108 | } | |
109 | ResetBugInventory(); | |
110 | } | |
111 | | |
112 | public void ToggleReady() | |
113 | { | |
114 | if ( IsProxy ) return; | |
115 | IsReady = !IsReady; | |
116 | } | |
117 | | |
118 | void ResetBugInventory() | |
119 | { | |
120 | var allBugs = ResourceLibrary.GetAll<BugResource>(); | |
121 | BugInventory.Clear(); | |
122 | foreach ( var bug in allBugs ) | |
123 | { | |
124 | BugInventory[bug] = bug.StartingAmount; | |
125 | } | |
126 | } | |
127 | | |
128 | [Rpc.Owner] | |
129 | public void AttackRandomly() | |
130 | { | |
131 | var targetSegment = Scene.GetAllComponents<BugSegment>().OrderBy( x => Random.Shared.Float() ).FirstOrDefault( x => x.Network.OwnerId != Network.OwnerId ); | |
132 | var targetPosition = targetSegment.WorldPosition + (Vector3.Random.WithZ( 0 ) * (targetSegment.IsVisible ? 48 : 250)); | |
133 | var opponentPos = targetSegment.GameObject.Root.WorldPosition; | |
134 | targetPosition = new Vector3( | |
135 | Math.Clamp( targetPosition.x, opponentPos.x - (Width * GridSize) / 2f, opponentPos.x + (Width * GridSize) / 2f ), | |
136 | Math.Clamp( targetPosition.y, opponentPos.y - (Height * GridSize) / 2f, opponentPos.y + (Height * GridSize) / 2f ), | |
137 | 0 | |
138 | ); | |
139 | | |
140 | var weapon = WeaponInventory.OrderBy( x => Random.Shared.Float() ).FirstOrDefault( x => x.Value != 0 ).Key; | |
141 | SelectedWeapon = weapon; | |
142 | WeaponInventory[SelectedWeapon]--; | |
143 | GameManager.Instance.BroadcastFire( Id, SelectedWeapon.ResourceId, targetPosition ); | |
144 | } | |
145 | | |
146 | void ResetWeaponInventory() | |
147 | { | |
148 | var allWeapons = ResourceLibrary.GetAll<WeaponResource>(); | |
149 | WeaponInventory.Clear(); | |
150 | foreach ( var weapon in allWeapons ) | |
151 | { | |
152 | WeaponInventory[weapon] = weapon.StartingAmount; | |
153 | if ( weapon.StartingAmount < 0 ) SelectedWeapon = weapon; | |
154 | } | |
155 | } | |
156 | | |
157 | public float GetHealthPercent() | |
158 | { | |
159 | var segments = Scene.GetAllComponents<BugSegment>().Where( x => x.Network.OwnerId == Network.OwnerId ); | |
160 | var totalSegments = segments.Count(); | |
161 | var totalHealth = segments.Sum( x => x.Health ); | |
162 | var totalMaxHealth = BugReferences.Sum( x => ResourceLibrary.Get<BugResource>( x.ResourceId ).StartingHealth * x.ObjectIds.Count ); | |
163 | return (float)totalHealth / totalMaxHealth; | |
164 | } | |
165 | | |
166 | public float GetScorePercent() | |
167 | { | |
168 | if ( GameManager.Instance.State < GameState.Playing ) return 0.5f; | |
169 | var myScore = GetHealthPercent(); | |
170 | var opponentScore = Scene.GetAllComponents<BoardManager>().FirstOrDefault( x => x.Network.OwnerId != Network.OwnerId ).GetHealthPercent(); | |
171 | return myScore / (myScore + opponentScore); | |
172 | | |
173 | } | |
174 | | |
175 | [Rpc.Owner] | |
176 | public void SaveBugReferences() | |
177 | { | |
178 | BugReferences.Clear(); | |
179 | | |
180 | // Compose the list of bugs and their individual ids | |
181 | var references = new List<BugReference>(); | |
182 | var segments = Scene.GetAllComponents<BugSegment>(); | |
183 | foreach ( var segment in segments ) | |
184 | { | |
185 | if ( segment.Network.OwnerId != Network.OwnerId ) continue; | |
186 | var existingRef = references.FirstOrDefault( x => x.BugId == segment.GameObject.Name ); | |
187 | if ( existingRef.ResourceId != 0 ) existingRef.Add( segment.GameObject.Id ); | |
188 | else | |
189 | { | |
190 | var reference = new BugReference( segment.BugId, segment.GameObject.Name ); | |
191 | reference.Add( segment.GameObject.Id ); | |
192 | references.Add( reference ); | |
193 | } | |
194 | } | |
195 | | |
196 | // Add entries to the NetList | |
197 | foreach ( var reference in references ) | |
198 | { | |
199 | BugReferences.Add( reference ); | |
200 | } | |
201 | } | |
202 | | |
203 | public void PurchaseWeapon( WeaponResource weapon ) | |
204 | { | |
205 | Coins -= weapon.Cost; | |
206 | WeaponInventory[weapon]++; | |
207 | Sandbox.Services.Stats.Increment( "coins_spent", weapon.Cost ); | |
208 | } | |
209 | | |
210 | [Rpc.Owner] | |
211 | public void GiveCoins( int amount ) | |
212 | { | |
213 | Coins += amount; | |
214 | CoinsSpent += amount; | |
215 | Sandbox.Services.Stats.Increment( "coins_earned", amount ); | |
216 | } | |
217 | | |
218 | [Rpc.Owner] | |
219 | public void IncrementBugsKilled() | |
220 | { | |
221 | BugsKilled++; | |
222 | Sandbox.Services.Stats.Increment( "bugs_killed", 1 ); | |
223 | } | |
224 | | |
225 | public void IncrementDamageDealt( float damage ) | |
226 | { | |
227 | Sandbox.Services.Stats.Increment( "damage_dealt", (int)damage ); | |
228 | } | |
229 | | |
230 | [Rpc.Owner] | |
231 | public void GiveCellCoins( Vector3 position ) | |
232 | { | |
233 | GiveCoins( 5 ); | |
234 | GameManager.Instance.SpawnCoins( position, 1 ); | |
235 | HintPanel.Instance.CellCoinNotification(); | |
236 | } | |
237 | | |
238 | public void SetupBoardRandomly() | |
239 | { | |
240 | ClearAllBugs( false ); | |
241 | int attempts = 0; | |
242 | foreach ( var entry in BugInventory ) | |
243 | { | |
244 | while ( BugInventory[entry.Key] > 0 ) | |
245 | { | |
246 | while ( !TryPlaceRandomBug( entry.Key ) ) | |
247 | { | |
248 | attempts++; | |
249 | if ( attempts > 100 ) | |
250 | { | |
251 | SetupBoardRandomly(); | |
252 | return; | |
253 | } | |
254 | } | |
255 | } | |
256 | } | |
257 | } | |
258 | | |
259 | bool TryPlaceRandomBug( BugResource bug ) | |
260 | { | |
261 | int attempts = 0; | |
262 | | |
263 | var startingCell = Components.GetAll<CellComponent>().Where( x => !x.IsOccupied && x.GameObject.Root == GameObject ).OrderBy( x => Guid.NewGuid() ).FirstOrDefault(); | |
264 | if ( startingCell is null ) return false; | |
265 | | |
266 | var cells = new List<CellComponent> { startingCell }; | |
267 | | |
268 | while ( cells.Count < bug.SegmentCount ) | |
269 | { | |
270 | var cell = cells.Last(); | |
271 | var neighbors = cell.GetNeighbors().Where( x => !x.IsOccupied && !cells.Contains( x ) && x.GameObject.Root == GameObject ).ToList(); | |
272 | if ( neighbors.Count == 0 ) | |
273 | { | |
274 | attempts++; | |
275 | if ( attempts > 100 ) return false; | |
276 | cells = new List<CellComponent> { startingCell }; | |
277 | continue; | |
278 | } | |
279 | var nextCell = neighbors.OrderBy( x => Random.Shared.Float() ).First(); | |
280 | cells.Add( nextCell ); | |
281 | } | |
282 | | |
283 | GameManager.Instance.CreateBug( this, PlacementInput.Instance.GetPlacementData( cells, bug ), Network.Owner is null ); | |
284 | return true; | |
285 | } | |
286 | | |
287 | public struct BugReference | |
288 | { | |
289 | public int ResourceId { get; set; } | |
290 | public string BugId { get; set; } | |
291 | public List<string> ObjectIds { get; set; } | |
292 | | |
293 | private BugResource _bug; | |
294 | | |
295 | public BugReference( int resourceId, string bugId ) | |
296 | { | |
297 | ResourceId = resourceId; | |
298 | BugId = bugId; | |
299 | ObjectIds = new List<string>(); | |
300 | } | |
301 | | |
302 | public void Add( Guid objectId ) | |
303 | { | |
304 | ObjectIds.Add( objectId.ToString() ); | |
305 | } | |
306 | | |
307 | public BugResource GetBug() | |
308 | { | |
309 | if ( _bug is null ) _bug = ResourceLibrary.Get<BugResource>( ResourceId ); | |
310 | return _bug; | |
311 | } | |
312 | } | |
313 | | |
314 | } | |
315 | | |
-------------------------------------------------------------------------------- | |
/code/BugSegment.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | | |
3 | namespace Battlebugs; | |
4 | | |
5 | public class BugSegment : Component | |
6 | { | |
7 | [Property] public GameObject Body { get; set; } | |
8 | [Property] public ModelRenderer BodyRenderer { get; set; } | |
9 | [Property] BugHealthbar Healthbar { get; set; } | |
10 | [Property] bool Floating { get; set; } | |
11 | | |
12 | [Property, Group( "Prefabs" )] public GameObject BugSplatParticle { get; set; } | |
13 | | |
14 | [Sync] public int Index { get; set; } = 0; | |
15 | [Sync] public int BugId { get; set; } | |
16 | [Sync] public float Health { get; set; } = 10f; | |
17 | public BugResource Bug => ResourceLibrary.Get<BugResource>( BugId ); | |
18 | | |
19 | public CellComponent Cell { get; set; } | |
20 | | |
21 | bool _initialized { get; set; } = false; | |
22 | float _targetAlpha { get; set; } = 1f; | |
23 | TimeSince _timeSinceLastDamage { get; set; } = 0f; | |
24 | | |
25 | public bool IsVisible => _targetAlpha > 0f; | |
26 | | |
27 | public async void Init( BugResource bug, int index ) | |
28 | { | |
29 | Index = index; | |
30 | BugId = bug.ResourceId; | |
31 | Health = bug.StartingHealth; | |
32 | Body.LocalPosition = Vector3.Down * 250f; | |
33 | Body.LocalRotation = new Angles( 0, Random.Shared.Float( -180f, 180f ), 0 ); | |
34 | await GameTask.DelaySeconds( index * 0.05f ); | |
35 | _initialized = true; | |
36 | Sound.Play( "segment-drop" ); | |
37 | } | |
38 | | |
39 | protected override void OnStart() | |
40 | { | |
41 | SetAlpha( (Network.OwnerId == Connection.Local.Id) ? 1f : 0f, true ); | |
42 | } | |
43 | | |
44 | protected override void OnUpdate() | |
45 | { | |
46 | foreach ( var renderer in Components.GetAll<ModelRenderer>( FindMode.EverythingInSelfAndDescendants ) ) | |
47 | { | |
48 | renderer.Tint = renderer.Tint.WithAlpha( renderer.Tint.a.LerpTo( _targetAlpha, Time.Delta * 5f ) ); | |
49 | } | |
50 | Healthbar.Alpha = Healthbar.Alpha.LerpTo( (_targetAlpha == 1 && GameManager.Instance.State != GameState.Placing) ? 1 : 0, Time.Delta * 5f ); | |
51 | } | |
52 | | |
53 | protected override void OnFixedUpdate() | |
54 | { | |
55 | if ( _initialized || IsProxy ) | |
56 | { | |
57 | var targetPos = Vector3.Up * 2.5f; | |
58 | if ( Floating ) targetPos += Vector3.Up * (MathF.Sin( (Time.Now + (WorldPosition.x + (WorldPosition.y * 10f)) / 32f) * 2f ) * 0.25f) * 8f; | |
59 | Body.LocalPosition = Body.LocalPosition.LerpTo( targetPos, Time.Delta * 15f ); | |
60 | Body.LocalRotation = Rotation.Slerp( Body.LocalRotation, Rotation.Identity, Time.Delta * 15f ); | |
61 | } | |
62 | | |
63 | if ( !IsProxy ) | |
64 | { | |
65 | if ( _timeSinceLastDamage > 1f && Health <= 0 ) | |
66 | { | |
67 | Clear( true ); | |
68 | } | |
69 | } | |
70 | } | |
71 | | |
72 | public void SetAlpha( float alpha, bool instant = false ) | |
73 | { | |
74 | if ( instant ) | |
75 | { | |
76 | foreach ( var renderer in Components.GetAll<ModelRenderer>( FindMode.EverythingInSelfAndDescendants ) ) | |
77 | { | |
78 | renderer.Tint = renderer.Tint.WithAlpha( alpha ); | |
79 | } | |
80 | } | |
81 | _targetAlpha = alpha; | |
82 | } | |
83 | | |
84 | public void Clear( bool dropCoin = false ) | |
85 | { | |
86 | if ( IsProxy ) return; | |
87 | var otherBoard = Scene.GetAllComponents<BoardManager>().FirstOrDefault( x => x.Network.OwnerId != Network.OwnerId ); | |
88 | otherBoard.GiveCoins( 20 ); | |
89 | otherBoard.IncrementBugsKilled(); | |
90 | if ( GameManager.Instance.State == GameState.Playing ) Cell.BroadcastClear(); | |
91 | BroadcastDestroyFX( dropCoin ); | |
92 | GameObject.Destroy(); | |
93 | } | |
94 | | |
95 | | |
96 | public void AddHighlight( Color color = default ) | |
97 | { | |
98 | var outline = Components.GetOrCreate<HighlightOutline>(); | |
99 | outline.Color = color; | |
100 | outline.InsideColor = Color.Transparent; | |
101 | outline.ObscuredColor = Color.Transparent; | |
102 | outline.InsideObscuredColor = Color.Transparent; | |
103 | } | |
104 | | |
105 | public void RemoveHighlight() | |
106 | { | |
107 | if ( !IsValid ) return; | |
108 | Components.Get<HighlightOutline>()?.Destroy(); | |
109 | } | |
110 | | |
111 | CellComponent GetCell() | |
112 | { | |
113 | return Scene.GetAllComponents<CellComponent>().OrderBy( x => x.WorldPosition.DistanceSquared( WorldPosition ) ).FirstOrDefault(); | |
114 | } | |
115 | | |
116 | [Rpc.Broadcast] | |
117 | public void Damage( float damage ) | |
118 | { | |
119 | if ( Health <= 0 ) return; | |
120 | | |
121 | _targetAlpha = 1f; | |
122 | GetCell()?.BroadcastHit(); | |
123 | | |
124 | var otherBoard = Scene.GetAllComponents<BoardManager>().FirstOrDefault( x => x.Network.OwnerId != Network.OwnerId ); | |
125 | if ( otherBoard == BoardManager.Local ) | |
126 | { | |
127 | otherBoard.IncrementDamageDealt( damage ); | |
128 | } | |
129 | | |
130 | if ( IsProxy ) return; | |
131 | | |
132 | Health -= damage; | |
133 | _timeSinceLastDamage = 0f; | |
134 | } | |
135 | | |
136 | [Rpc.Broadcast] | |
137 | public void BroadcastDestroyFX( bool dropCoin = false ) | |
138 | { | |
139 | Sound.Play( "impact-bullet-flesh", WorldPosition ); | |
140 | if ( dropCoin ) GameManager.Instance.SpawnCoins( WorldPosition, 3 ); | |
141 | if ( BugSplatParticle is not null ) | |
142 | { | |
143 | var obj = BugSplatParticle.Clone( WorldPosition + Vector3.Up * 16f ); | |
144 | var part = obj.Components.Get<ParticleEffect>(); | |
145 | part.Tint = Bug.Color; | |
146 | } | |
147 | } | |
148 | } | |
-------------------------------------------------------------------------------- | |
/code/CellComponent.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | using Sandbox; | |
3 | | |
4 | namespace Battlebugs; | |
5 | | |
6 | public sealed class CellComponent : Component | |
7 | { | |
8 | [Property] ModelRenderer Renderer { get; set; } | |
9 | | |
10 | public BoardManager Board { get; set; } | |
11 | public Vector2 Position { get; set; } | |
12 | public bool IsHovering { get; private set; } = false; | |
13 | public bool IsSelected { get; set; } = false; | |
14 | | |
15 | [Sync] public bool WasOccupied { get; set; } = false; | |
16 | [Sync] public bool IsOccupied { get; set; } = false; | |
17 | [Sync] public bool IsHit { get; set; } = false; | |
18 | [Sync] bool IsOdd { get; set; } = false; | |
19 | Color BaseColor => IsOdd ? Color.White : Color.Gray; | |
20 | | |
21 | protected override void OnStart() | |
22 | { | |
23 | WorldRotation = new Angles( 0, Random.Shared.Int( 0, 3 ) * 90f + Random.Shared.Float( -3f, 3f ), 0 ); | |
24 | UpdateHighlight(); | |
25 | } | |
26 | | |
27 | public void Init( BoardManager board, Vector2 position, int index ) | |
28 | { | |
29 | Board = board; | |
30 | Position = position; | |
31 | IsOdd = index % 2 == 0; | |
32 | } | |
33 | | |
34 | public void MousePressed() | |
35 | { | |
36 | | |
37 | } | |
38 | | |
39 | public void MouseReleased() | |
40 | { | |
41 | | |
42 | } | |
43 | | |
44 | public void MouseEnter() | |
45 | { | |
46 | IsHovering = true; | |
47 | | |
48 | var bugs = Scene.GetAllComponents<BugSegment>(); | |
49 | var cellBug = bugs.FirstOrDefault( x => x.Cell == this ); | |
50 | if ( cellBug is not null ) | |
51 | { | |
52 | bugs = bugs.Where( x => x.GameObject.Name == cellBug.GameObject.Name ).ToList(); | |
53 | foreach ( var bug in bugs ) | |
54 | { | |
55 | bug.AddHighlight( Color.Red ); | |
56 | } | |
57 | } | |
58 | | |
59 | if ( IsSelected ) return; | |
60 | UpdateHighlight(); | |
61 | } | |
62 | | |
63 | public void MouseExit() | |
64 | { | |
65 | IsHovering = false; | |
66 | | |
67 | var bugs = Scene.GetAllComponents<BugSegment>(); | |
68 | var cellBug = bugs.FirstOrDefault( x => x.Cell == this ); | |
69 | if ( cellBug is not null ) | |
70 | { | |
71 | bugs = bugs.Where( x => x.GameObject.Name == cellBug.GameObject.Name ).ToList(); | |
72 | foreach ( var bug in bugs ) | |
73 | { | |
74 | bug.RemoveHighlight(); | |
75 | } | |
76 | } | |
77 | | |
78 | if ( IsSelected ) return; | |
79 | UpdateHighlight(); | |
80 | } | |
81 | | |
82 | public void Select() | |
83 | { | |
84 | IsSelected = true; | |
85 | PlacementInput.Instance.SelectedCells.Add( this ); | |
86 | | |
87 | Sound.Play( "ui-select" ); | |
88 | | |
89 | foreach ( var cell in PlacementInput.Instance.SelectedCells ) | |
90 | { | |
91 | cell.UpdateHighlight(); | |
92 | } | |
93 | } | |
94 | | |
95 | public void Deselect( bool remove = true ) | |
96 | { | |
97 | IsSelected = false; | |
98 | | |
99 | if ( remove ) | |
100 | { | |
101 | PlacementInput.Instance.SelectedCells.Remove( this ); | |
102 | Sound.Play( "ui-select-bug" ); | |
103 | UpdateHighlight(); | |
104 | } | |
105 | foreach ( var cell in PlacementInput.Instance.SelectedCells ) | |
106 | { | |
107 | cell.UpdateHighlight(); | |
108 | } | |
109 | } | |
110 | | |
111 | public bool IsAdjacent( CellComponent other ) | |
112 | { | |
113 | // Above/below | |
114 | if ( Position.x == other.Position.x && MathF.Abs( Position.y - other.Position.y ) == 1 ) | |
115 | return true; | |
116 | | |
117 | // Left/right | |
118 | if ( Position.y == other.Position.y && MathF.Abs( Position.x - other.Position.x ) == 1 ) | |
119 | return true; | |
120 | | |
121 | return false; | |
122 | } | |
123 | | |
124 | public List<CellComponent> GetNeighbors() | |
125 | { | |
126 | var neighbors = new List<CellComponent>(); | |
127 | | |
128 | var cells = Scene.GetAllComponents<CellComponent>(); | |
129 | foreach ( var cell in cells ) | |
130 | { | |
131 | if ( cell == this ) continue; | |
132 | if ( IsAdjacent( cell ) ) | |
133 | { | |
134 | neighbors.Add( cell ); | |
135 | } | |
136 | } | |
137 | | |
138 | return neighbors; | |
139 | } | |
140 | | |
141 | void UpdateHighlight() | |
142 | { | |
143 | if ( IsHit ) | |
144 | { | |
145 | Renderer.Tint = Color.Lerp( BaseColor, IsOccupied ? Color.Orange : (WasOccupied ? Color.Green : Color.Red), 0.95f ); | |
146 | } | |
147 | else if ( IsSelected ) | |
148 | { | |
149 | var color = Color.Yellow; | |
150 | var placing = PlacementInput.Instance.AttemptingToPlace; | |
151 | if ( placing is not null ) | |
152 | { | |
153 | if ( BoardManager.Local.BugInventory.FirstOrDefault( x => x.Key.ResourceId == placing.ResourceId ).Value <= 0 ) color = Color.Yellow; | |
154 | else color = placing.Color; | |
155 | } | |
156 | Renderer.Tint = Color.Lerp( BaseColor, color, 0.8f ); | |
157 | } | |
158 | else if ( IsHovering ) | |
159 | { | |
160 | Renderer.Tint = IsOccupied ? Color.Lerp( BaseColor, Color.Red, 0.95f ) : Color.Lerp( BaseColor, Color.Yellow, 0.95f ); | |
161 | } | |
162 | else | |
163 | { | |
164 | Renderer.Tint = BaseColor; | |
165 | } | |
166 | } | |
167 | | |
168 | [Rpc.Owner] | |
169 | public void BroadcastHit() | |
170 | { | |
171 | if ( !IsHit && Random.Shared.Float() < 0.3f ) | |
172 | { | |
173 | var otherBoard = Scene.GetAllComponents<BoardManager>().FirstOrDefault( x => x.Network.OwnerId != Network.OwnerId ); | |
174 | otherBoard.GiveCellCoins( WorldPosition ); | |
175 | } | |
176 | IsHit = true; | |
177 | BroadcastUpdateHighlight(); | |
178 | } | |
179 | | |
180 | [Rpc.Owner] | |
181 | public void BroadcastClear() | |
182 | { | |
183 | if ( IsProxy ) return; | |
184 | | |
185 | IsHit = true; | |
186 | WasOccupied = IsOccupied; | |
187 | IsOccupied = false; | |
188 | BroadcastUpdateHighlight(); | |
189 | } | |
190 | | |
191 | [Rpc.Broadcast] | |
192 | public void BroadcastUpdateHighlight() | |
193 | { | |
194 | UpdateHighlight(); | |
195 | | |
196 | if ( IsHit && !Network.IsOwner && BoardManager.Local.IsValid() ) | |
197 | { | |
198 | if ( IsOccupied ) | |
199 | HintPanel.Instance.YellowCellNotification(); | |
200 | else if ( !WasOccupied ) | |
201 | HintPanel.Instance.RedCellNotification(); | |
202 | else | |
203 | HintPanel.Instance.GreenCellNotification(); | |
204 | } | |
205 | } | |
206 | | |
207 | } | |
208 | | |
-------------------------------------------------------------------------------- | |
/code/CoinComponent.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | using Sandbox; | |
3 | | |
4 | public sealed class CoinComponent : Component | |
5 | { | |
6 | Vector3 Velocity = Vector3.Zero; | |
7 | TimeSince timeSinceStart = 0; | |
8 | | |
9 | protected override void OnStart() | |
10 | { | |
11 | Velocity = Vector3.Random.Normal; | |
12 | Velocity = Velocity.WithZ( MathF.Abs( Velocity.z ) ); | |
13 | Velocity = Velocity.Normal * Random.Shared.Float( 20, 100 ); | |
14 | timeSinceStart = Random.Shared.Float( 0.2f ); | |
15 | } | |
16 | | |
17 | protected override void OnFixedUpdate() | |
18 | { | |
19 | Velocity += Vector3.Down * 100 * Time.Delta; | |
20 | | |
21 | var tr = Scene.Trace.Ray( WorldPosition, WorldPosition + Velocity * Time.Delta ) | |
22 | .IgnoreGameObjectHierarchy( GameObject ) | |
23 | .Run(); | |
24 | | |
25 | if ( tr.Hit ) | |
26 | { | |
27 | if ( Velocity.z < -10 ) Sound.Play( "coin-bounce", WorldPosition ); | |
28 | Velocity = Vector3.Reflect( Velocity, tr.Normal ); | |
29 | Velocity /= 1.5f; | |
30 | } | |
31 | | |
32 | WorldPosition += Velocity * Time.Delta; | |
33 | | |
34 | if ( timeSinceStart > 1f ) | |
35 | { | |
36 | Sound.Play( "coin-collect", WorldPosition ); | |
37 | GameObject.Destroy(); | |
38 | } | |
39 | } | |
40 | } | |
41 | | |
-------------------------------------------------------------------------------- | |
/code/Components/DamageNumber.cs: | |
-------------------------------------------------------------------------------- | |
1 | using Sandbox; | |
2 | | |
3 | namespace Battlebugs; | |
4 | | |
5 | public sealed class DamageNumber : Component | |
6 | { | |
7 | [RequireComponent] TextRenderer Renderer { get; set; } | |
8 | | |
9 | TimeSince timeSinceCreated = 0; | |
10 | float speed = 100f; | |
11 | | |
12 | protected override void OnUpdate() | |
13 | { | |
14 | WorldPosition += Vector3.Up * speed * Time.Delta; | |
15 | speed = speed.LerpTo( 0, Time.Delta * 4f ); | |
16 | | |
17 | if ( timeSinceCreated > 1f ) | |
18 | { | |
19 | Renderer.Color = Renderer.Color.WithAlpha( Renderer.Color.a.LerpTo( 0, Time.Delta * 4f ) ); | |
20 | if ( Renderer.Color.a <= 0.01f ) | |
21 | { | |
22 | GameObject.Destroy(); | |
23 | } | |
24 | } | |
25 | } | |
26 | | |
27 | protected override void OnPreRender() | |
28 | { | |
29 | WorldRotation = Rotation.LookAt( WorldPosition - Scene.Camera.WorldPosition ); | |
30 | } | |
31 | } | |
32 | | |
-------------------------------------------------------------------------------- | |
/code/Components/FixTransparency.cs: | |
-------------------------------------------------------------------------------- | |
1 | using Sandbox; | |
2 | | |
3 | namespace Battlebugs; | |
4 | | |
5 | public sealed class FixTransparency : Component | |
6 | { | |
7 | [Property] ModelRenderer modelRenderer { get; set; } | |
8 | | |
9 | protected override void OnStart() | |
10 | { | |
11 | if ( !modelRenderer.IsValid() ) return; | |
12 | modelRenderer.SceneObject.Flags.WantsFrameBufferCopy = true; | |
13 | modelRenderer.SceneObject.Flags.IsTranslucent = true; | |
14 | modelRenderer.SceneObject.Flags.IsOpaque = false; | |
15 | } | |
16 | } | |
17 | | |
-------------------------------------------------------------------------------- | |
/code/Components/RotateConstantly.cs: | |
-------------------------------------------------------------------------------- | |
1 | using Sandbox; | |
2 | | |
3 | namespace Battlebugs; | |
4 | | |
5 | public sealed class RotateConstantly : Component | |
6 | { | |
7 | [Property] Angles RotationSpeed { get; set; } | |
8 | | |
9 | protected override void OnUpdate() | |
10 | { | |
11 | WorldRotation *= RotationSpeed * Time.Delta; | |
12 | } | |
13 | } | |
14 | | |
-------------------------------------------------------------------------------- | |
/code/Components/VoiceComponent.cs: | |
-------------------------------------------------------------------------------- | |
1 | namespace Battlebugs; | |
2 | | |
3 | public sealed class VoiceComponent : Voice | |
4 | { | |
5 | [Property] bool IsSpectator { get; set; } = false; | |
6 | | |
7 | protected override IEnumerable<Connection> ExcludeFilter() | |
8 | { | |
9 | if ( !IsSpectator ) return Enumerable.Empty<Connection>(); | |
10 | | |
11 | var connections = Connection.All.ToList(); | |
12 | var boards = Scene.GetAllComponents<BoardManager>(); | |
13 | for ( int i = connections.Count - 1; i >= 0; i-- ) | |
14 | { | |
15 | if ( !boards.Any( x => x.Network.OwnerId == connections[i].Id ) ) | |
16 | { | |
17 | connections.RemoveAt( i ); | |
18 | } | |
19 | } | |
20 | return connections; | |
21 | } | |
22 | } | |
-------------------------------------------------------------------------------- | |
/code/Enums/BugBodyType.cs: | |
-------------------------------------------------------------------------------- | |
1 | public enum BugBodyType | |
2 | { | |
3 | Head, | |
4 | Body, | |
5 | Corner, | |
6 | Tail | |
7 | } | |
-------------------------------------------------------------------------------- | |
/code/Enums/GameState.cs: | |
-------------------------------------------------------------------------------- | |
1 | public enum GameState | |
2 | { | |
3 | Waiting, | |
4 | Placing, | |
5 | Playing, | |
6 | Results | |
7 | } | |
-------------------------------------------------------------------------------- | |
/code/GameManager.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | using System.Threading.Tasks; | |
3 | using Sandbox; | |
4 | using Sandbox.Network; | |
5 | | |
6 | namespace Battlebugs; | |
7 | | |
8 | public sealed class GameManager : Component, Component.INetworkListener | |
9 | { | |
10 | public static GameManager Instance { get; private set; } | |
11 | | |
12 | [RequireComponent] PlacementInput _placementInput { get; set; } | |
13 | [RequireComponent] AttackingInput _attackingInput { get; set; } | |
14 | [RequireComponent] InspectInput _inspectInput { get; set; } | |
15 | | |
16 | // Properties | |
17 | [Property, Group( "Prefabs" )] public GameObject BoardPrefab { get; set; } | |
18 | [Property, Group( "Prefabs" )] public GameObject CellPrefab { get; set; } | |
19 | [Property, Group( "Prefabs" )] public GameObject DamageNumberPrefab { get; set; } | |
20 | [Property, Group( "Prefabs" )] public GameObject CoinPrefab { get; set; } | |
21 | | |
22 | // Networked Variables | |
23 | [Sync( SyncFlags.FromHost )] public GameState State { get; set; } | |
24 | [Sync( SyncFlags.FromHost )] public Guid CurrentPlayerId { get; set; } | |
25 | [Sync( SyncFlags.FromHost )] public bool IsFiring { get; set; } | |
26 | [Sync( SyncFlags.FromHost )] public TimeSince TimeSinceTurnStart { get; set; } | |
27 | [Sync( SyncFlags.FromHost )] public bool CpuMode { get; set; } | |
28 | | |
29 | // Local Variables | |
30 | public List<BoardManager> Boards; | |
31 | public BoardManager CurrentPlayer => Boards.FirstOrDefault( x => x.IsValid() && x.Network.OwnerId == CurrentPlayerId ); | |
32 | Vector3 LastPebblePosition; | |
33 | TimeSince TimeSincePebbleToss; | |
34 | | |
35 | protected override void OnAwake() | |
36 | { | |
37 | Instance = this; | |
38 | State = GameState.Waiting; | |
39 | Boards = new(); | |
40 | } | |
41 | | |
42 | protected override async Task OnLoad() | |
43 | { | |
44 | if ( Scene.IsEditor ) return; | |
45 | if ( Networking.IsActive ) return; | |
46 | CpuMode = MainMenu.IsCpuGame; | |
47 | if ( CpuMode ) return; | |
48 | | |
49 | LoadingScreen.Title = "Creating Lobby"; | |
50 | await Task.DelayRealtimeSeconds( 0.1f ); | |
51 | Networking.CreateLobby( new LobbyConfig() ); | |
52 | } | |
53 | | |
54 | protected override void OnStart() | |
55 | { | |
56 | // This is really just for late-joiners | |
57 | Boards = Scene.GetAllComponents<BoardManager>().ToList(); | |
58 | | |
59 | // Initialize both boards right away if in CPU mode | |
60 | if ( CpuMode && !IsProxy ) | |
61 | { | |
62 | CreateBoard( Connection.Local ); | |
63 | CreateBoard( null ); | |
64 | } | |
65 | } | |
66 | | |
67 | public void OnActive( Connection channel ) | |
68 | { | |
69 | // TODO: Create a spectator pawn or something | |
70 | if ( Boards.Count >= 2 ) return; | |
71 | | |
72 | CreateBoard( channel ); | |
73 | } | |
74 | | |
75 | void CreateBoard( Connection channel ) | |
76 | { | |
77 | var currentBoardCount = Scene.GetAllComponents<BoardManager>().Count(); | |
78 | var client = BoardPrefab.Clone( new CloneConfig() | |
79 | { | |
80 | Transform = new Transform( new Vector3( currentBoardCount * 1000f, 0, 2f ), new Angles( 0, currentBoardCount == 0 ? 0 : 180, 0 ) ), | |
81 | Name = channel?.DisplayName ?? "CPU" | |
82 | } ); | |
83 | client.Network.SetOrphanedMode( NetworkOrphaned.ClearOwner ); | |
84 | client.NetworkSpawn( channel ); | |
85 | | |
86 | Boards = Scene.GetAllComponents<BoardManager>().ToList(); | |
87 | } | |
88 | | |
89 | [Rpc.Broadcast] | |
90 | void StartGame() | |
91 | { | |
92 | Sound.Play( "player-join" ); | |
93 | | |
94 | if ( Networking.IsHost ) | |
95 | { | |
96 | State = GameState.Placing; | |
97 | } | |
98 | | |
99 | Boards = Scene.GetAllComponents<BoardManager>().ToList(); | |
100 | } | |
101 | | |
102 | [Rpc.Broadcast] | |
103 | void StartPlaying() | |
104 | { | |
105 | if ( Networking.IsHost ) | |
106 | { | |
107 | State = GameState.Playing; | |
108 | StartTurn(); | |
109 | | |
110 | foreach ( var board in Boards ) | |
111 | { | |
112 | board.SaveBugReferences(); | |
113 | } | |
114 | } | |
115 | | |
116 | foreach ( var segment in Scene.GetAllComponents<BugSegment>() ) | |
117 | { | |
118 | segment.SetAlpha( (segment.Network.OwnerId == Connection.Local.Id) ? 0.5f : 0f ); | |
119 | } | |
120 | } | |
121 | | |
122 | void StartTurn() | |
123 | { | |
124 | TimeSinceTurnStart = 0; | |
125 | CurrentPlayerId = Boards.FirstOrDefault( x => x.Network.OwnerId != CurrentPlayerId ).Network.OwnerId; | |
126 | IsFiring = true; | |
127 | } | |
128 | | |
129 | [Rpc.Broadcast] | |
130 | void EndGame() | |
131 | { | |
132 | if ( Networking.IsHost ) | |
133 | { | |
134 | State = GameState.Results; | |
135 | } | |
136 | | |
137 | if ( !(BoardManager.Local?.IsValid() ?? false) ) return; | |
138 | var didWin = BoardManager.Local.GetScorePercent() > 0.5f; | |
139 | Sandbox.Services.Stats.Increment( "games_played", 1 ); | |
140 | if ( didWin ) Sandbox.Services.Stats.Increment( "games_won", 1 ); | |
141 | else Sandbox.Services.Stats.Increment( "games_lost", 1 ); | |
142 | } | |
143 | | |
144 | protected override void OnUpdate() | |
145 | { | |
146 | switch ( State ) | |
147 | { | |
148 | case GameState.Waiting: UpdateWaiting(); break; | |
149 | case GameState.Placing: UpdatePlacing(); break; | |
150 | case GameState.Playing: UpdateGame(); break; | |
151 | case GameState.Results: UpdateResults(); break; | |
152 | } | |
153 | InspectInput.Instance.Enabled = State == GameState.Playing; | |
154 | } | |
155 | | |
156 | void UpdateWaiting() | |
157 | { | |
158 | if ( BoardManager.Local is not null ) | |
159 | { | |
160 | UpdateCamera( BoardManager.Local ); | |
161 | } | |
162 | | |
163 | if ( Networking.IsHost ) | |
164 | { | |
165 | if ( Boards.Count > 1 ) | |
166 | { | |
167 | StartGame(); | |
168 | } | |
169 | } | |
170 | } | |
171 | | |
172 | void UpdatePlacing() | |
173 | { | |
174 | PlacementInput.Instance.Enabled = true; | |
175 | | |
176 | if ( BoardManager.Local is not null ) | |
177 | { | |
178 | UpdateCamera( BoardManager.Local ); | |
179 | } | |
180 | | |
181 | if ( Networking.IsHost ) | |
182 | { | |
183 | if ( Boards.Any( x => x.Network.OwnerId == Guid.Empty ) && !CpuMode ) | |
184 | { | |
185 | EndGame(); | |
186 | } | |
187 | else if ( !Boards.Any( x => !x.IsReady ) ) | |
188 | { | |
189 | StartPlaying(); | |
190 | } | |
191 | } | |
192 | } | |
193 | | |
194 | void UpdateGame() | |
195 | { | |
196 | var currentPlayer = CurrentPlayer; | |
197 | PlacementInput.Instance.Enabled = false; | |
198 | AttackingInput.Instance.Enabled = IsFiring && (currentPlayer == BoardManager.Local); | |
199 | | |
200 | if ( currentPlayer is not null ) | |
201 | { | |
202 | var healthPercent = currentPlayer.GetHealthPercent(); | |
203 | if ( healthPercent == 0 ) | |
204 | { | |
205 | EndGame(); | |
206 | } | |
207 | | |
208 | var otherPlayer = Boards.FirstOrDefault( x => x.IsValid() && x.Network.OwnerId != CurrentPlayerId ); | |
209 | if ( otherPlayer is null ) return; | |
210 | if ( IsFiring ) | |
211 | { | |
212 | UpdateCamera( otherPlayer ); | |
213 | LastPebblePosition = Scene.Camera.WorldPosition + Scene.Camera.WorldRotation.Forward * 1000f; | |
214 | | |
215 | if ( TimeSinceTurnStart >= 15f ) | |
216 | { | |
217 | currentPlayer.AttackRandomly(); | |
218 | } | |
219 | } | |
220 | else | |
221 | { | |
222 | var pebbles = Scene.GetAllComponents<PebbleComponent>(); | |
223 | var pebble = pebbles.Where( x => x.TimeSinceCreated > 0.6f ).FirstOrDefault(); | |
224 | if ( pebble is not null ) | |
225 | { | |
226 | LastPebblePosition = pebble.WorldPosition; | |
227 | } | |
228 | if ( pebbles.Count() > 0 ) TimeSincePebbleToss = 0; | |
229 | UpdateCamera( otherPlayer, LastPebblePosition ); | |
230 | | |
231 | if ( pebbles.Count() == 0 && TimeSincePebbleToss > 3f ) | |
232 | { | |
233 | StartTurn(); | |
234 | } | |
235 | } | |
236 | } | |
237 | } | |
238 | | |
239 | void UpdateResults() | |
240 | { | |
241 | | |
242 | } | |
243 | | |
244 | void UpdateCamera( BoardManager board ) | |
245 | { | |
246 | Scene.Camera.WorldPosition = Scene.Camera.WorldPosition.LerpTo( board.CameraPosition.WorldPosition, Time.Delta * 5f ); | |
247 | Scene.Camera.WorldRotation = Rotation.Slerp( Scene.Camera.WorldRotation, board.CameraPosition.WorldRotation, Time.Delta * 5f ); | |
248 | } | |
249 | | |
250 | void UpdateCamera( BoardManager board, Vector3 lookAt ) | |
251 | { | |
252 | Scene.Camera.WorldPosition = Scene.Camera.WorldPosition.LerpTo( board.CameraPosition.WorldPosition, Time.Delta * 5f ); | |
253 | | |
254 | var rotation = Rotation.LookAt( lookAt - Scene.Camera.WorldPosition, Vector3.Up ); | |
255 | Scene.Camera.WorldRotation = Rotation.Slerp( Scene.Camera.WorldRotation, rotation, Time.Delta * 5f ); | |
256 | } | |
257 | | |
258 | public void CreateBug( BoardManager board, List<PlacementInput.PlacementData> cells, bool isCpu = false ) | |
259 | { | |
260 | var bug = BoardManager.Local.BugInventory.FirstOrDefault( x => x.Key.SegmentCount == cells.Count ); | |
261 | if ( bug.Value <= 0 ) return; | |
262 | var bugId = Guid.NewGuid(); | |
263 | | |
264 | for ( int i = 0; i < cells.Count; i++ ) | |
265 | { | |
266 | var segment = cells[i].Prefab.Clone( new Transform( | |
267 | cells[i].Cell.WorldPosition, | |
268 | cells[i].Rotation | |
269 | ) ); | |
270 | segment.Name = bugId.ToString(); | |
271 | var component = segment.Components.Get<BugSegment>(); | |
272 | component.Init( bug.Key, i ); | |
273 | component.Cell = cells[i].Cell; | |
274 | segment.NetworkSpawn( isCpu ? null : Connection.Local ); | |
275 | | |
276 | cells[i].Cell.IsOccupied = true; | |
277 | } | |
278 | | |
279 | board.BugInventory[bug.Key]--; | |
280 | } | |
281 | | |
282 | public void SpawnCoins( Vector3 position, int amount = 1 ) | |
283 | { | |
284 | for ( int i = 0; i < amount; i++ ) | |
285 | { | |
286 | CoinPrefab.Clone( position + Vector3.Up * 2f ); | |
287 | } | |
288 | | |
289 | } | |
290 | | |
291 | [Rpc.Owner] | |
292 | public void BroadcastFire( Guid boardId, int weaponId, Vector3 position ) | |
293 | { | |
294 | if ( !CpuMode && Rpc.CallerId != CurrentPlayerId ) return; | |
295 | if ( IsFiring == false ) return; | |
296 | | |
297 | var weapon = ResourceLibrary.Get<WeaponResource>( weaponId ); | |
298 | if ( weapon is null ) return; | |
299 | | |
300 | var board = Boards.FirstOrDefault( x => x.Id != boardId ); | |
301 | if ( board is null ) return; | |
302 | | |
303 | int count = (int)MathF.Round( weapon.AmountFired.GetValue() ); | |
304 | | |
305 | for ( int i = 0; i < count; i++ ) | |
306 | { | |
307 | var offset = Vector3.Random.WithZ( 0 ) * weapon.Spray; | |
308 | var pos = board.CameraPosition.WorldPosition.WithZ( 32f ) + (board.WorldRotation.Forward * 200f) + offset; | |
309 | var target = position + offset; | |
310 | var pebbleObj = weapon.Prefab.Clone( pos ); | |
311 | var pebble = pebbleObj.Components.Get<PebbleComponent>(); | |
312 | pebble.Damage = weapon.Damage.GetValue(); | |
313 | pebble.LaunchAt( target ); | |
314 | pebbleObj.NetworkSpawn( null ); | |
315 | } | |
316 | BroadcastFireSound(); | |
317 | IsFiring = false; | |
318 | } | |
319 | | |
320 | [Rpc.Broadcast] | |
321 | void BroadcastFireSound() | |
322 | { | |
323 | Sound.Play( "fling-rocks" ); | |
324 | } | |
325 | | |
326 | [Rpc.Broadcast] | |
327 | public void BroadcastDamageNumber( Vector3 position, float damage ) | |
328 | { | |
329 | if ( DamageNumberPrefab is not null ) | |
330 | { | |
331 | var damageNumber = DamageNumberPrefab.Clone( position ); | |
332 | var text = damageNumber.Components.Get<TextRenderer>(); | |
333 | text.Text = "-" + damage.ToString(); | |
334 | text.Color = Color.Red; | |
335 | } | |
336 | } | |
337 | | |
338 | [Rpc.Broadcast] | |
339 | public void SendChatMessage( string message ) | |
340 | { | |
341 | var playerHud = PlayerHud.Instances.FirstOrDefault( x => x.Board.Network.OwnerId == Rpc.CallerId ); | |
342 | if ( playerHud is null ) return; | |
343 | | |
344 | playerHud.AddChatMessage( message ); | |
345 | } | |
346 | | |
347 | } | |
-------------------------------------------------------------------------------- | |
/code/GameResource/BugResource.cs: | |
-------------------------------------------------------------------------------- | |
1 | namespace Battlebugs; | |
2 | | |
3 | [GameResource( "Battlebugs/Bug", "bug", "Describes a bug definition", Icon = "bug_report" )] | |
4 | public class BugResource : GameResource | |
5 | { | |
6 | [Group( "Information" )] public string Name { get; set; } = "Bug"; | |
7 | [Group( "Information" )] public Color Color { get; set; } = Color.White; | |
8 | [Group( "Information" )] public int SegmentCount { get; set; } = 3; | |
9 | [Group( "Stats" )] public int StartingAmount { get; set; } = 1; | |
10 | [Group( "Stats" )] public float StartingHealth { get; set; } = 8f; | |
11 | [Group( "Prefabs" )] public GameObject HeadPrefab { get; set; } | |
12 | [Group( "Prefabs" )] public Model HeadModel { get; set; } | |
13 | [Group( "Prefabs" )] public GameObject BodyPrefab { get; set; } | |
14 | [Group( "Prefabs" )] public Model BodyModel { get; set; } | |
15 | [Group( "Prefabs" )] public GameObject CornerPrefab { get; set; } | |
16 | [Group( "Prefabs" )] public Model CornerModel { get; set; } | |
17 | [Group( "Prefabs" )] public GameObject TailPrefab { get; set; } | |
18 | [Group( "Prefabs" )] public Model TailModel { get; set; } | |
19 | | |
20 | public string GetHeadIcon() => "ui/thumbnails/" + ResourceName + "_head.png"; | |
21 | public string GetBodyIcon() => "ui/thumbnails/" + ResourceName + "_body.png"; | |
22 | public string GetCornerIcon() => "ui/thumbnails/" + ResourceName + "_corner.png"; | |
23 | public string GetTailIcon() => "ui/thumbnails/" + ResourceName + "_tail.png"; | |
24 | | |
25 | | |
26 | | |
27 | } | |
-------------------------------------------------------------------------------- | |
/code/GameResource/WeaponResource.cs: | |
-------------------------------------------------------------------------------- | |
1 | namespace Battlebugs; | |
2 | | |
3 | [GameResource( "Battlebugs/Weapon", "weapon", "Describes a weapon definition", Icon = "whatshot" )] | |
4 | public class WeaponResource : GameResource | |
5 | { | |
6 | public string Name { get; set; } = "Rock"; | |
7 | [TextArea] public string Description { get; set; } = ""; | |
8 | [ImageAssetPath] public string Icon { get; set; } | |
9 | | |
10 | [Group( "Stats" )] public int StartingAmount { get; set; } = 1; | |
11 | [Group( "Stats" )] public int Cost { get; set; } = 20; | |
12 | [Group( "Attack" )] public RangedFloat AmountFired { get; set; } = new RangedFloat( 1 ); | |
13 | [Group( "Attack" )] public RangedFloat Damage { get; set; } = new RangedFloat( 8 ); | |
14 | [Group( "Attack" )] public float Spray { get; set; } = 1f; | |
15 | [Group( "Attack" )] public GameObject Prefab { get; set; } | |
16 | } | |
-------------------------------------------------------------------------------- | |
/code/Inputs/AttackingInput.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | using Sandbox; | |
3 | | |
4 | namespace Battlebugs; | |
5 | | |
6 | public sealed class AttackingInput : Component | |
7 | { | |
8 | public static AttackingInput Instance { get; private set; } | |
9 | | |
10 | [Property] GameObject ReticlePrefab { get; set; } | |
11 | | |
12 | GameObject Reticle = null; | |
13 | Vector3 ReticlePosition = Vector3.Zero; | |
14 | Vector3 ReticleOffset = Vector3.Zero; | |
15 | public int ReticleState = 0; | |
16 | SoundHandle AimingSound = null; | |
17 | | |
18 | protected override void OnAwake() | |
19 | { | |
20 | Instance = this; | |
21 | Enabled = false; | |
22 | } | |
23 | | |
24 | protected override void OnUpdate() | |
25 | { | |
26 | if ( !BoardManager.Local.IsValid() ) return; | |
27 | | |
28 | var tr = Scene.Trace.Ray( Scene.Camera.ScreenPixelToRay( Mouse.Position ), 8000f ) | |
29 | .WithoutTags( "bug" ) | |
30 | .Run(); | |
31 | | |
32 | if ( ReticleState == 0 ) | |
33 | { | |
34 | if ( !ShopPanel.Instance.IsOpen && !PauseMenu.Instance.IsOpen && tr.Hit && tr.GameObject.Components.TryGet<CellComponent>( out var cell ) ) | |
35 | { | |
36 | if ( cell.Board != BoardManager.Local ) | |
37 | { | |
38 | if ( !Reticle.IsValid() ) | |
39 | { | |
40 | if ( ReticlePrefab is not null ) | |
41 | { | |
42 | CreateReticle( tr.HitPosition ); | |
43 | } | |
44 | } | |
45 | else | |
46 | { | |
47 | Reticle.WorldPosition = tr.HitPosition.WithZ( BoardManager.Local.WorldPosition.z ) + Vector3.Up * 4f; | |
48 | } | |
49 | } | |
50 | } | |
51 | else if ( Reticle.IsValid() ) | |
52 | { | |
53 | DestroyReticle(); | |
54 | } | |
55 | } | |
56 | else if ( Reticle.IsValid() ) | |
57 | { | |
58 | if ( ReticleState == 1 ) | |
59 | { | |
60 | Reticle.WorldPosition = ReticlePosition + Vector3.Forward * MathF.Sin( Time.Now * 5f ) * 72f; | |
61 | } | |
62 | else if ( ReticleState == 2 ) | |
63 | { | |
64 | Reticle.WorldPosition = ReticlePosition + ReticleOffset + Vector3.Right * MathF.Sin( Time.Now * 5f ) * 72f; | |
65 | } | |
66 | } | |
67 | | |
68 | if ( Reticle.IsValid() && Input.Pressed( "Attack1" ) && BoardManager.Local.WeaponInventory[BoardManager.Local.SelectedWeapon] != 0 ) | |
69 | { | |
70 | if ( ReticleState == 0 ) | |
71 | { | |
72 | AimingSound?.Stop(); | |
73 | AimingSound = Sound.Play( "aiming-loop" ); | |
74 | } | |
75 | Sound.Play( "aiming-click" ); | |
76 | ReticleState++; | |
77 | ReticleOffset = Reticle.WorldPosition - ReticlePosition; | |
78 | if ( ReticleState < 2 ) ReticlePosition = Reticle.WorldPosition; | |
79 | if ( ReticleState == 3 ) | |
80 | { | |
81 | BoardManager.Local.WeaponInventory[BoardManager.Local.SelectedWeapon]--; | |
82 | GameManager.Instance.BroadcastFire( BoardManager.Local.Id, BoardManager.Local.SelectedWeapon.ResourceId, Reticle.WorldPosition ); | |
83 | DestroyReticle(); | |
84 | } | |
85 | } | |
86 | | |
87 | if ( Input.Pressed( "Attack2" ) && ReticleState == 1 ) | |
88 | { | |
89 | Sound.Play( "ui.navigate.back" ); | |
90 | ReticleState--; | |
91 | if ( ReticleState == 0 ) | |
92 | { | |
93 | AimingSound?.Stop(); | |
94 | AimingSound = null; | |
95 | } | |
96 | } | |
97 | } | |
98 | | |
99 | protected override void OnDisabled() | |
100 | { | |
101 | DestroyReticle(); | |
102 | } | |
103 | | |
104 | void CreateReticle( Vector3 position ) | |
105 | { | |
106 | Reticle = ReticlePrefab.Clone( position ); | |
107 | } | |
108 | | |
109 | void DestroyReticle() | |
110 | { | |
111 | AimingSound?.Stop(); | |
112 | AimingSound = null; | |
113 | Reticle?.Destroy(); | |
114 | Reticle = null; | |
115 | ReticleState = 0; | |
116 | } | |
117 | } | |
118 | | |
-------------------------------------------------------------------------------- | |
/code/Inputs/InspectInput.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | using Sandbox; | |
3 | | |
4 | namespace Battlebugs; | |
5 | | |
6 | public sealed class InspectInput : Component | |
7 | { | |
8 | public static InspectInput Instance { get; private set; } | |
9 | public BugSegment HighlightedSegment { get; private set; } = null; | |
10 | | |
11 | TimeSince timeSinceMouseMoved = 0; | |
12 | bool isPanelHovered = false; | |
13 | | |
14 | Vector2 lastMousePosition = Vector2.Zero; | |
15 | | |
16 | protected override void OnAwake() | |
17 | { | |
18 | Instance = this; | |
19 | Enabled = false; | |
20 | } | |
21 | | |
22 | protected override void OnUpdate() | |
23 | { | |
24 | if ( isPanelHovered ) return; | |
25 | if ( !BoardManager.Local.IsValid() ) return; | |
26 | if ( Mouse.Position != lastMousePosition ) | |
27 | { | |
28 | lastMousePosition = Mouse.Position; | |
29 | timeSinceMouseMoved = 0; | |
30 | } | |
31 | | |
32 | var tr = Scene.Trace.Ray( Scene.Camera.ScreenPixelToRay( Mouse.Position ), 8000f ) | |
33 | .WithAnyTags( "bug" ) | |
34 | .Run(); | |
35 | | |
36 | if ( tr.Hit ) | |
37 | { | |
38 | var bug = tr.GameObject.Components.Get<BugSegment>(); | |
39 | if ( bug is not null && bug.IsVisible ) | |
40 | { | |
41 | Deselect(); | |
42 | Select( bug ); | |
43 | } | |
44 | } | |
45 | else | |
46 | { | |
47 | Deselect(); | |
48 | } | |
49 | | |
50 | if ( HighlightedSegment is not null && timeSinceMouseMoved > 0.5f && InspectorPanel.Instance.Segment is null ) | |
51 | { | |
52 | InspectorPanel.Instance.Segment = HighlightedSegment; | |
53 | } | |
54 | } | |
55 | | |
56 | protected override void OnDisabled() | |
57 | { | |
58 | Deselect(); | |
59 | } | |
60 | | |
61 | public void Select( BugSegment bug, bool panelHovered = false ) | |
62 | { | |
63 | isPanelHovered = panelHovered; | |
64 | HighlightedSegment = bug; | |
65 | HighlightedSegment.AddHighlight( Color.White ); | |
66 | } | |
67 | | |
68 | public void Deselect() | |
69 | { | |
70 | isPanelHovered = false; | |
71 | if ( HighlightedSegment is null ) return; | |
72 | | |
73 | InspectorPanel.Instance.Segment = null; | |
74 | HighlightedSegment?.RemoveHighlight(); | |
75 | HighlightedSegment = null; | |
76 | } | |
77 | } | |
78 | | |
-------------------------------------------------------------------------------- | |
/code/Inputs/PlacementInput.cs: | |
-------------------------------------------------------------------------------- | |
1 | using Sandbox; | |
2 | | |
3 | namespace Battlebugs; | |
4 | | |
5 | public sealed class PlacementInput : Component | |
6 | { | |
7 | public static PlacementInput Instance { get; private set; } | |
8 | | |
9 | public CellComponent HighlightedCell { get; set; } = null; | |
10 | public List<CellComponent> SelectedCells { get; set; } = new(); | |
11 | public bool IsSelecting { get; private set; } = false; | |
12 | public BugResource AttemptingToPlace { get; private set; } = null; | |
13 | | |
14 | protected override void OnAwake() | |
15 | { | |
16 | Instance = this; | |
17 | Enabled = false; | |
18 | } | |
19 | | |
20 | protected override void OnUpdate() | |
21 | { | |
22 | if ( !BoardManager.Local.IsValid() ) return; | |
23 | | |
24 | var tr = Scene.Trace.Ray( Scene.Camera.ScreenPixelToRay( Mouse.Position ), 8000f ) | |
25 | .WithoutTags( "bug" ) | |
26 | .Run(); | |
27 | | |
28 | if ( !PauseMenu.Instance.IsOpen && tr.Hit ) | |
29 | { | |
30 | var newCell = tr.GameObject.Components.Get<CellComponent>(); | |
31 | if ( newCell?.Board != BoardManager.Local ) return; | |
32 | if ( newCell != HighlightedCell ) | |
33 | { | |
34 | HighlightedCell?.MouseExit(); | |
35 | HighlightedCell = newCell; | |
36 | HighlightedCell?.MouseEnter(); | |
37 | | |
38 | if ( IsSelecting ) | |
39 | { | |
40 | if ( SelectedCells.Count > 1 && HighlightedCell == SelectedCells.ElementAt( SelectedCells.Count - 2 ) ) | |
41 | { | |
42 | AttemptingToPlace = SelectedCells.Count > 0 ? SelectedCells.FirstOrDefault().Board.BugInventory.FirstOrDefault( x => x.Key.SegmentCount == SelectedCells.Count - 1 ).Key : null; | |
43 | SelectedCells.LastOrDefault()?.Deselect(); | |
44 | } | |
45 | | |
46 | if ( HighlightedCell is not null && !HighlightedCell.IsOccupied && !SelectedCells.Contains( HighlightedCell ) && (SelectedCells.Count == 0 || SelectedCells.LastOrDefault().IsAdjacent( HighlightedCell )) && SelectedCells.Count < (BoardManager.Local?.MaxPlaceableSegments ?? 0) ) | |
47 | { | |
48 | AttemptingToPlace = SelectedCells.Count > 0 ? SelectedCells.FirstOrDefault().Board.BugInventory.FirstOrDefault( x => x.Key.SegmentCount == SelectedCells.Count + 1 ).Key : null; | |
49 | HighlightedCell?.Select(); | |
50 | } | |
51 | } | |
52 | } | |
53 | } | |
54 | else | |
55 | { | |
56 | HighlightedCell?.MouseExit(); | |
57 | HighlightedCell = null; | |
58 | } | |
59 | | |
60 | if ( Input.Pressed( "Attack1" ) ) | |
61 | { | |
62 | HighlightedCell?.MousePressed(); | |
63 | | |
64 | IsSelecting = true; | |
65 | DeselectAll(); | |
66 | if ( HighlightedCell is not null && !HighlightedCell.IsOccupied ) | |
67 | { | |
68 | HighlightedCell?.Select(); | |
69 | } | |
70 | } | |
71 | | |
72 | if ( HighlightedCell is not null && Input.Pressed( "Attack2" ) ) | |
73 | { | |
74 | IsSelecting = false; | |
75 | DeselectAll(); | |
76 | | |
77 | var bugs = Scene.GetAllComponents<BugSegment>(); | |
78 | var cellBug = bugs.FirstOrDefault( x => x.Cell == HighlightedCell ); | |
79 | if ( cellBug is not null ) | |
80 | { | |
81 | BoardManager.Local.BugInventory[cellBug.Bug]++; | |
82 | bugs = bugs.Where( x => x.GameObject.Name == cellBug.GameObject.Name ).ToList(); | |
83 | foreach ( var bug in bugs ) | |
84 | { | |
85 | bug.Cell.IsOccupied = false; | |
86 | bug.BroadcastDestroyFX(); | |
87 | bug.GameObject.Destroy(); | |
88 | } | |
89 | } | |
90 | BoardManager.Local.IsReady = false; | |
91 | } | |
92 | | |
93 | if ( Input.Released( "Attack1" ) ) | |
94 | { | |
95 | HighlightedCell?.MouseReleased(); | |
96 | | |
97 | IsSelecting = false; | |
98 | | |
99 | if ( SelectedCells.Count > 1 ) | |
100 | { | |
101 | Sound.Play( "ui-select-complete" ); | |
102 | GameManager.Instance.CreateBug( BoardManager.Local, GetPlacementData() ); | |
103 | } | |
104 | else if ( SelectedCells.Count == 1 ) | |
105 | { | |
106 | Sound.Play( "ui-select-bug" ); | |
107 | } | |
108 | DeselectAll(); | |
109 | } | |
110 | } | |
111 | | |
112 | public List<PlacementData> GetPlacementData( List<CellComponent> cells = null, BugResource bug = null ) | |
113 | { | |
114 | if ( cells is null ) cells = SelectedCells.ToList(); | |
115 | if ( bug is null ) bug = AttemptingToPlace; | |
116 | var data = new List<PlacementData>(); | |
117 | | |
118 | var rotation = new Angles( 0, 0, 0 ); | |
119 | if ( cells.Count > 1 ) rotation = Rotation.LookAt( cells[1].WorldPosition - cells[0].WorldPosition, Vector3.Up ); | |
120 | | |
121 | for ( int i = 0; i < cells.Count; i++ ) | |
122 | { | |
123 | if ( i < cells.Count - 1 ) rotation = Rotation.LookAt( cells[i + 1].WorldPosition - cells[i].WorldPosition, Vector3.Up ); | |
124 | else if ( i > 0 ) rotation = Rotation.LookAt( cells[i].WorldPosition - cells[i - 1].WorldPosition, Vector3.Up ); | |
125 | | |
126 | // Get prefab based on rotation/position | |
127 | var prefab = bug.BodyPrefab; | |
128 | if ( i == 0 ) prefab = bug.TailPrefab; | |
129 | else if ( i == cells.Count - 1 ) prefab = bug.HeadPrefab; | |
130 | else | |
131 | { | |
132 | var last = cells[i - 1]; | |
133 | var next = cells[i + 1]; | |
134 | var current = cells[i]; | |
135 | if ( !(last.Position.x == next.Position.x || last.Position.y == next.Position.y) ) | |
136 | { | |
137 | prefab = bug.CornerPrefab; | |
138 | rotation = Rotation.LookAt( last.WorldPosition - next.WorldPosition, Vector3.Up ); | |
139 | var previousRotation = Rotation.LookAt( last.WorldPosition - current.WorldPosition, Vector3.Up ); | |
140 | | |
141 | // If previous piece is left and next piece is down | |
142 | if ( last.WorldPosition.x < current.WorldPosition.x && next.WorldPosition.y > current.WorldPosition.y ) | |
143 | { | |
144 | rotation -= new Angles( 0, 90 + 45, 0 ); | |
145 | } | |
146 | // If rotating left | |
147 | else if ( rotation.yaw - previousRotation.Yaw() > 0 ) | |
148 | { | |
149 | rotation -= new Angles( 0, 45 + 90, 0 ); | |
150 | } | |
151 | else | |
152 | { | |
153 | rotation += new Angles( 0, 45, 0 ); | |
154 | } | |
155 | } | |
156 | } | |
157 | | |
158 | data.Add( new PlacementData | |
159 | { | |
160 | Cell = cells[i], | |
161 | Rotation = rotation, | |
162 | Prefab = prefab | |
163 | } ); | |
164 | } | |
165 | | |
166 | return data; | |
167 | } | |
168 | | |
169 | protected override void OnDisabled() | |
170 | { | |
171 | if ( SelectedCells.Count > 0 ) | |
172 | { | |
173 | DeselectAll(); | |
174 | } | |
175 | if ( HighlightedCell.IsValid() ) | |
176 | { | |
177 | HighlightedCell.MouseExit(); | |
178 | } | |
179 | } | |
180 | | |
181 | void DeselectAll() | |
182 | { | |
183 | foreach ( var cell in SelectedCells ) | |
184 | { | |
185 | cell.Deselect( false ); | |
186 | } | |
187 | SelectedCells.Clear(); | |
188 | } | |
189 | | |
190 | public class PlacementData | |
191 | { | |
192 | public CellComponent Cell { get; set; } | |
193 | public Rotation Rotation { get; set; } | |
194 | public GameObject Prefab { get; set; } | |
195 | } | |
196 | } | |
197 | | |
-------------------------------------------------------------------------------- | |
/code/PebbleComponent.cs: | |
-------------------------------------------------------------------------------- | |
1 | using System; | |
2 | using Sandbox; | |
3 | | |
4 | namespace Battlebugs; | |
5 | | |
6 | public sealed class PebbleComponent : Component, Component.ICollisionListener | |
7 | { | |
8 | [RequireComponent] Rigidbody Rigidbody { get; set; } | |
9 | | |
10 | [Property] GameObject ParticlePrefab { get; set; } | |
11 | [Property] int HitCount { get; set; } = 1; | |
12 | | |
13 | [Sync] public float Damage { get; set; } = 4f; | |
14 | List<BugSegment> HitSegments = new(); | |
15 | List<CellComponent> HitCells = new(); | |
16 | | |
17 | public TimeSince TimeSinceCreated = 0; | |
18 | TimeSince timeSinceMoving = 0; | |
19 | | |
20 | public void LaunchAt( Vector3 target ) | |
21 | { | |
22 | var time = Random.Shared.Float( 1.8f, 2f ); | |
23 | var vector = target - WorldPosition.WithZ( target.z ); | |
24 | var direction = vector.Normal; | |
25 | var velocity = vector / time; | |
26 | var verticalForce = -Scene.PhysicsWorld.Gravity.z * time / 2f; | |
27 | velocity += Vector3.Up * verticalForce; | |
28 | | |
29 | Rigidbody.Velocity = velocity; | |
30 | Rigidbody.AngularVelocity = Vector3.Random * 10f; | |
31 | } | |
32 | | |
33 | protected override void OnFixedUpdate() | |
34 | { | |
35 | if ( IsProxy ) return; | |
36 | | |
37 | if ( TimeSinceCreated > 10f ) | |
38 | { | |
39 | GameObject.Destroy(); | |
40 | } | |
41 | | |
42 | if ( Rigidbody.Velocity.Length > 1f ) | |
43 | { | |
44 | timeSinceMoving = 0; | |
45 | } | |
46 | else if ( timeSinceMoving > 0.5f ) | |
47 | { | |
48 | HitCount = 0; | |
49 | Hit(); | |
50 | } | |
51 | } | |
52 | | |
53 | public void OnCollisionStart( Collision collision ) | |
54 | { | |
55 | if ( IsProxy ) return; | |
56 | if ( collision.Other.GameObject.Tags.Has( "pebble" ) ) return; | |
57 | | |
58 | // Enable friction after hitting something | |
59 | Rigidbody.LinearDamping = 2.5f; | |
60 | Rigidbody.AngularDamping = 2f; | |
61 | | |
62 | if ( collision.Other.GameObject.Components.TryGet<BugSegment>( out var segment ) ) | |
63 | { | |
64 | if ( HitSegments.Contains( segment ) ) return; | |
65 | HitSegments.Add( segment ); | |
66 | GameManager.Instance.BroadcastDamageNumber( WorldPosition, Damage ); | |
67 | segment.Damage( Damage ); | |
68 | Hit(); | |
69 | } | |
70 | else if ( collision.Other.GameObject.Components.TryGet<CellComponent>( out var cell ) ) | |
71 | { | |
72 | if ( HitCells.Contains( cell ) ) return; | |
73 | if ( cell.IsHit ) return; | |
74 | HitCells.Add( cell ); | |
75 | cell.BroadcastHit(); | |
76 | HitCount--; | |
77 | if ( HitCount == 0 ) Hit(); | |
78 | } | |
79 | } | |
80 | | |
81 | void Hit() | |
82 | { | |
83 | HitCount--; | |
84 | if ( HitCount <= 0 ) | |
85 | { | |
86 | BroadcastDestroyEffect(); | |
87 | GameObject.Destroy(); | |
88 | } | |
89 | } | |
90 | | |
91 | [Rpc.Broadcast] | |
92 | void BroadcastDestroyEffect() | |
93 | { | |
94 | Sound.Play( "break-rocks", WorldPosition ); | |
95 | if ( ParticlePrefab is not null ) | |
96 | { | |
97 | ParticlePrefab.Clone( WorldPosition ); | |
98 | } | |
99 | } | |
100 | } | |
101 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Controls/InputGlyph.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox | |
2 | @using Sandbox.UI | |
3 | @namespace Battlebugs | |
4 | @inherits Sandbox.UI.Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root /> | |
8 | | |
9 | @code { | |
10 | private string _inputAction; | |
11 | private InputGlyphSize _inputGlyphSize; | |
12 | private GlyphStyle _glyphStyle; | |
13 | | |
14 | public void SetButton(string inputAction) | |
15 | { | |
16 | if (_inputAction == inputAction) | |
17 | return; | |
18 | | |
19 | _inputAction = inputAction; | |
20 | Update(); | |
21 | } | |
22 | | |
23 | public override void SetProperty(string name, string value) | |
24 | { | |
25 | switch (name) | |
26 | { | |
27 | case "button": | |
28 | { | |
29 | SetButton(value); | |
30 | Update(); | |
31 | | |
32 | break; | |
33 | } | |
34 | | |
35 | case "size": | |
36 | { | |
37 | InputGlyphSize.TryParse(value, true, out _inputGlyphSize); | |
38 | Update(); | |
39 | | |
40 | break; | |
41 | } | |
42 | | |
43 | case "style": | |
44 | { | |
45 | _glyphStyle = value switch | |
46 | { | |
47 | "knockout" => GlyphStyle.Knockout, | |
48 | "light" => GlyphStyle.Light, | |
49 | "dark" => GlyphStyle.Dark, | |
50 | _ => _glyphStyle | |
51 | }; | |
52 | | |
53 | Update(); | |
54 | break; | |
55 | } | |
56 | } | |
57 | | |
58 | base.SetProperty(name, value); | |
59 | } | |
60 | | |
61 | private void Update() | |
62 | { | |
63 | var texture = Input.GetGlyph(_inputAction, _inputGlyphSize, _glyphStyle); | |
64 | Style.BackgroundImage = texture; | |
65 | Style.Width = texture.Width; | |
66 | Style.Height = texture.Height; | |
67 | } | |
68 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Controls/InputGlyph.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | InputGlyph | |
2 | { | |
3 | background-size: contain; | |
4 | background-repeat: no-repeat; | |
5 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/BugHealthbar.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits PanelComponent | |
5 | | |
6 | <root style="opacity: @(Alpha)"> | |
7 | <div class="inner"> | |
8 | @if (true) | |
9 | { | |
10 | var amount = Segment.Health / Segment.Bug.StartingHealth; | |
11 | <div class="fill" style="background-color: @(Gradient.Evaluate(1f - amount).Hex); width: @(amount * 100f)%;" )" /> | |
12 | } | |
13 | </div> | |
14 | </root> | |
15 | | |
16 | @code | |
17 | { | |
18 | [Property] BugSegment Segment { get; set; } | |
19 | [Property] public float Alpha { get; set; } = 0f; | |
20 | [Property] Gradient Gradient { get; set; } | |
21 | | |
22 | protected override int BuildHash() => System.HashCode.Combine(Alpha, Segment.Health); | |
23 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/BugHealthbar.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | BugHealthbar | |
2 | { | |
3 | position: absolute; | |
4 | top: 0; | |
5 | left: 0; | |
6 | right: 0; | |
7 | bottom: 0; | |
8 | background-color: #444; | |
9 | padding: 16px; | |
10 | border-radius: 32px; | |
11 | overflow: hidden; | |
12 | | |
13 | .inner | |
14 | { | |
15 | width: 100%; | |
16 | height: 100%; | |
17 | | |
18 | .fill | |
19 | { | |
20 | height: 100%; | |
21 | } | |
22 | } | |
23 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/InspectorPanel.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits PanelComponent | |
5 | | |
6 | | |
7 | <root class="@(Segment is null ? "hidden" : "")"> | |
8 | @if (Segment is null) return; | |
9 | <div class="header"> | |
10 | <h1>@Segment.Bug.Name</h1> | |
11 | <div class="segments"> | |
12 | @for (var i = 0; i < Segment.Bug.SegmentCount; i++) | |
13 | { | |
14 | var index = Segment.Bug.SegmentCount - 1 - i; | |
15 | var segment = Scene.GetAllComponents<BugSegment>().FirstOrDefault(x => x.GameObject.Name == Segment.GameObject.Name && x.Index == index); | |
16 | @* <div class="@(segment == Segment ? "this" : "")" style="background-color: @((segment?.Bug?.Color ?? Color.Black).Hex)" /> *@ | |
17 | <img class="@(segment == Segment ? "this" : "")" src="@GetIcon(Segment.Bug, index)" /> | |
18 | } | |
19 | </div> | |
20 | </div> | |
21 | <div class="healthbar"> | |
22 | <div class="inner"> | |
23 | @if (true) | |
24 | { | |
25 | var value = Segment.Health / Segment.Bug.StartingHealth; | |
26 | <div class="fill" style="background-color: @(HealthGradient.Evaluate(1f - value).Hex); width: @(value * 100f)%;" /> | |
27 | } | |
28 | </div> | |
29 | </div> | |
30 | </root> | |
31 | | |
32 | @code | |
33 | { | |
34 | public static InspectorPanel Instance { get; private set; } | |
35 | public BugSegment Segment { get; set; } | |
36 | | |
37 | [Property] Gradient HealthGradient { get; set; } | |
38 | | |
39 | protected override void OnAwake() | |
40 | { | |
41 | Instance = this; | |
42 | } | |
43 | | |
44 | protected override void OnUpdate() | |
45 | { | |
46 | if (!Segment.IsValid()) return; | |
47 | | |
48 | var pos = Scene.Camera.PointToScreenPixels(Segment.WorldPosition); | |
49 | pos.x /= Scene.Camera.ScreenRect.Size.x; | |
50 | pos.y /= Scene.Camera.ScreenRect.Size.y; | |
51 | pos *= 100f; | |
52 | | |
53 | Panel.Style.Left = Length.Percent(pos.x + 2.5f); | |
54 | Panel.Style.Bottom = Length.Percent(100f - pos.y); | |
55 | } | |
56 | | |
57 | string GetIcon(BugResource bug, int index) | |
58 | { | |
59 | if (index == 0) return bug.GetTailIcon(); | |
60 | if (index == bug.SegmentCount - 1) return bug.GetHeadIcon(); | |
61 | return bug.GetBodyIcon(); | |
62 | } | |
63 | | |
64 | protected override int BuildHash() => System.HashCode.Combine(Segment); | |
65 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/InspectorPanel.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | InspectorPanel { | |
2 | position: absolute; | |
3 | width: 300px; | |
4 | height: 100px; | |
5 | background-color: rgba(black, 0.7); | |
6 | opacity: 1; | |
7 | transition: opacity 0.2s ease-out; | |
8 | padding: 8px; | |
9 | color: white; | |
10 | font-family: "paper cuts 2"; | |
11 | font-size: 16px; | |
12 | border-radius: 16px; | |
13 | flex-direction: column; | |
14 | gap: 8px; | |
15 | justify-content: space-evenly; | |
16 | | |
17 | .header { | |
18 | .segments { | |
19 | flex-grow: 1; | |
20 | justify-content: flex-end; | |
21 | gap: 8px; | |
22 | | |
23 | > img { | |
24 | width: 24px; | |
25 | height: 24px; | |
26 | | |
27 | &.this { | |
28 | background-color: rgba(white, 0.1); | |
29 | } | |
30 | } | |
31 | } | |
32 | } | |
33 | | |
34 | .healthbar { | |
35 | width: 100%; | |
36 | height: 32px; | |
37 | padding: 4px; | |
38 | background-color: #333; | |
39 | | |
40 | .inner { | |
41 | width: 100%; | |
42 | height: 100%; | |
43 | background-color: black; | |
44 | | |
45 | .fill { | |
46 | height: 100%; | |
47 | } | |
48 | } | |
49 | } | |
50 | | |
51 | &.hidden { | |
52 | opacity: 0; | |
53 | } | |
54 | } | |
55 | | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/MainHudPanel.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits PanelComponent | |
5 | | |
6 | <root> | |
7 | <div class="enable-mouse" /> | |
8 | | |
9 | @if (!BoardManager.Local.IsValid()) | |
10 | { | |
11 | @* Spectating UI *@ | |
12 | <GameHud /> | |
13 | | |
14 | return; | |
15 | } | |
16 | | |
17 | @if (!(GameManager.Instance.State == GameState.Waiting || GameManager.Instance.State == GameState.Results)) | |
18 | { | |
19 | <GameHud /> | |
20 | <ControlsPanel /> | |
21 | } | |
22 | | |
23 | @if (GameManager.Instance.State == GameState.Waiting) | |
24 | { | |
25 | <WaitingHud /> | |
26 | } | |
27 | else if (GameManager.Instance.State == GameState.Placing) | |
28 | { | |
29 | <PlacingHud /> | |
30 | } | |
31 | else if (GameManager.Instance.State == GameState.Playing) | |
32 | { | |
33 | <SidePanel /> | |
34 | <WeaponHud /> | |
35 | <ShopPanel /> | |
36 | } | |
37 | else if (GameManager.Instance.State == GameState.Results) | |
38 | { | |
39 | <ResultsHud /> | |
40 | } | |
41 | </root> | |
42 | | |
43 | @code | |
44 | { | |
45 | protected override int BuildHash() => System.HashCode.Combine(BoardManager.Local, GameManager.Instance.State); | |
46 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/MainHudPanel.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | MainHudPanel { | |
2 | position: absolute; | |
3 | top: 0; | |
4 | left: 0; | |
5 | right: 0; | |
6 | bottom: 0; | |
7 | font-family: "paper cuts 2"; | |
8 | cursor: "bug-default"; | |
9 | | |
10 | .enable-mouse { | |
11 | position: absolute; | |
12 | top: 0; | |
13 | left: 0; | |
14 | width: 0px; | |
15 | height: 0px; | |
16 | pointer-events: all; | |
17 | } | |
18 | } | |
19 | | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/MainMenu.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits PanelComponent | |
5 | | |
6 | <root> | |
7 | <div class="title">BATTLEBUGS</div> | |
8 | <div class="content"> | |
9 | <div class="button-container"> | |
10 | @if (State == MenuState.Main) | |
11 | { | |
12 | <div class="button" onclick="@(() => ButtonPlay())">Play</div> | |
13 | <div class="button blue" onclick="@(() => ButtonStats())">Stats</div> | |
14 | <div class="button red" onclick="@(() => Quit())">Quit</div> | |
15 | } | |
16 | else if (State == MenuState.Play) | |
17 | { | |
18 | <div class="button" onclick="@(() => StartGame(true))">Bot Match</div> | |
19 | <div class="button" onclick="@(() => StartGame(false))">Host Game</div> | |
20 | <div class="button red" onclick="@(() => ButtonBack())">Back</div> | |
21 | } | |
22 | else if (State == MenuState.Stats) | |
23 | { | |
24 | <div class="button red" onclick="@(() => ButtonBack())">Back</div> | |
25 | } | |
26 | </div> | |
27 | @if (State == MenuState.Play) | |
28 | { | |
29 | <div class="content-panel"> | |
30 | <LobbyList /> | |
31 | </div> | |
32 | } | |
33 | else if (State == MenuState.Stats) | |
34 | { | |
35 | <div class="content-panel"> | |
36 | <StatsPanel /> | |
37 | </div> | |
38 | } | |
39 | </div> | |
40 | </root> | |
41 | | |
42 | @code | |
43 | { | |
44 | public static MainMenu Instance { get; private set; } | |
45 | public static bool IsCpuGame { get; private set; } = true; | |
46 | | |
47 | public MenuState State = MenuState.Main; | |
48 | Angles cameraAngles = Angles.Zero; | |
49 | | |
50 | public enum MenuState | |
51 | { | |
52 | Main, | |
53 | Play, | |
54 | Stats | |
55 | } | |
56 | | |
57 | protected override void OnAwake() | |
58 | { | |
59 | Instance = this; | |
60 | } | |
61 | | |
62 | protected override void OnStart() | |
63 | { | |
64 | cameraAngles = Scene.Camera.WorldRotation; | |
65 | } | |
66 | | |
67 | protected override void OnUpdate() | |
68 | { | |
69 | var camAngles = cameraAngles + new Angles(Mouse.Position.y - (Screen.Height / 2f), -Mouse.Position.x - (-Screen.Width / 2f), 0) * 0.0015f; | |
70 | Scene.Camera.WorldRotation = Angles.Lerp(Scene.Camera.WorldRotation, camAngles, Time.Delta * 5.0f); | |
71 | } | |
72 | | |
73 | void ButtonPlay() | |
74 | { | |
75 | State = MenuState.Play; | |
76 | Sound.Play("ui.button.press"); | |
77 | } | |
78 | | |
79 | void ButtonStats() | |
80 | { | |
81 | State = MenuState.Stats; | |
82 | Sound.Play("ui.button.press"); | |
83 | } | |
84 | | |
85 | void ButtonBack() | |
86 | { | |
87 | State = MenuState.Main; | |
88 | Sound.Play("ui.navigate.back"); | |
89 | } | |
90 | | |
91 | void StartGame(bool isCpuGame = false) | |
92 | { | |
93 | IsCpuGame = isCpuGame; | |
94 | Game.ActiveScene.LoadFromFile("scenes/game.scene"); | |
95 | } | |
96 | | |
97 | void Quit() | |
98 | { | |
99 | if (Game.IsEditor) | |
100 | { | |
101 | Log.Info("Quit"); | |
102 | } | |
103 | else | |
104 | { | |
105 | Game.Close(); | |
106 | } | |
107 | } | |
108 | | |
109 | protected override int BuildHash() => System.HashCode.Combine(State); | |
110 | } | |
111 | | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/MainMenu.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | MainMenu | |
2 | { | |
3 | position: absolute; | |
4 | top: 0; | |
5 | left: 0; | |
6 | right: 0; | |
7 | bottom: 0; | |
8 | justify-content: center; | |
9 | align-items: center; | |
10 | font-weight: bold; | |
11 | font-family: "paper cuts 2"; | |
12 | pointer-events: all; | |
13 | cursor: "bug-default"; | |
14 | background-color: rgba(black, 0.2); | |
15 | backdrop-filter: blur( 1px ); | |
16 | flex-direction: column; | |
17 | padding: 50px; | |
18 | | |
19 | .title | |
20 | { | |
21 | align-self: flex-end; | |
22 | font-size: 128px; | |
23 | color: #fff; | |
24 | text-shadow: 8px 8px 0px black; | |
25 | flex-shrink: 0; | |
26 | } | |
27 | | |
28 | > .content | |
29 | { | |
30 | gap: 32px; | |
31 | width: 100%; | |
32 | height: 100%; | |
33 | | |
34 | .button-container | |
35 | { | |
36 | flex-direction: column; | |
37 | justify-content: flex-end; | |
38 | flex-shrink: 0; | |
39 | gap: 8px; | |
40 | } | |
41 | | |
42 | .content-panel | |
43 | { | |
44 | height: 100%; | |
45 | flex-grow: 1; | |
46 | background-color: rgba(black, 0.8); | |
47 | } | |
48 | } | |
49 | | |
50 | .button | |
51 | { | |
52 | width: 500px; | |
53 | background-color: #4CAF50; | |
54 | border: none; | |
55 | color: white; | |
56 | padding: 15px 32px; | |
57 | text-align: center; | |
58 | font-size: 54px; | |
59 | margin: 4px 2px; | |
60 | cursor: pointer; | |
61 | box-shadow: 6px 6px 0px black; | |
62 | cursor: "bug-hover"; | |
63 | | |
64 | &:hover | |
65 | { | |
66 | background-color: #45a049; | |
67 | sound-in: ui.button.over; | |
68 | } | |
69 | | |
70 | &.red | |
71 | { | |
72 | background-color: #f44336; | |
73 | | |
74 | &:hover | |
75 | { | |
76 | background-color: #d32f2f; | |
77 | } | |
78 | } | |
79 | | |
80 | &.blue | |
81 | { | |
82 | background-color: #2196F3; | |
83 | | |
84 | &:hover | |
85 | { | |
86 | background-color: #1e87f0; | |
87 | } | |
88 | } | |
89 | } | |
90 | } | |
91 | | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/PauseMenu.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @using Sandbox.Network; | |
4 | @namespace Battlebugs | |
5 | @inherits PanelComponent | |
6 | | |
7 | <root class="@(IsOpen ? "show" : "")"> | |
8 | <label class="title">Paused</label> | |
9 | <div class="content"> | |
10 | <button onclick=@(() => IsOpen = false)>Resume</button> | |
11 | <button class="red" onclick=@(() => ReturnToMainMenu())>Quit to Main Menu</button> | |
12 | </div> | |
13 | </root> | |
14 | | |
15 | @code | |
16 | { | |
17 | public static PauseMenu Instance { get; private set; } | |
18 | [Property] SceneFile SceneFile { get; set; } | |
19 | | |
20 | public bool IsOpen { get; private set; } | |
21 | | |
22 | protected override void OnAwake() | |
23 | { | |
24 | Instance = this; | |
25 | } | |
26 | | |
27 | protected override void OnUpdate() | |
28 | { | |
29 | if (Input.EscapePressed) | |
30 | { | |
31 | IsOpen = !IsOpen; | |
32 | Input.EscapePressed = false; | |
33 | } | |
34 | } | |
35 | | |
36 | public void ReturnToMainMenu() | |
37 | { | |
38 | Networking.Disconnect(); | |
39 | Game.ActiveScene.Load(SceneFile); | |
40 | } | |
41 | | |
42 | protected override int BuildHash() => System.HashCode.Combine(IsOpen); | |
43 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/PanelComponents/PauseMenu.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | PauseMenu | |
2 | { | |
3 | position: absolute; | |
4 | top: 0; | |
5 | left: 0; | |
6 | right: 0; | |
7 | bottom: 0; | |
8 | background-color: rgba(black, 0.9); | |
9 | z-index: 1000; | |
10 | cursor: "bug-default"; | |
11 | font-family: "paper cuts 2"; | |
12 | color: white; | |
13 | font-size: 32px; | |
14 | padding: 256px; | |
15 | justify-content: center; | |
16 | align-items: center; | |
17 | flex-direction: column; | |
18 | pointer-events: all; | |
19 | opacity: 0; | |
20 | transition: all 0.1s ease-in-out; | |
21 | | |
22 | .title | |
23 | { | |
24 | font-size: 128px; | |
25 | text-shadow: 8px 8px 0px black; | |
26 | } | |
27 | | |
28 | > .content | |
29 | { | |
30 | flex-grow: 1; | |
31 | flex-direction: column; | |
32 | justify-content: center; | |
33 | align-items: center; | |
34 | gap: 8px; | |
35 | | |
36 | button | |
37 | { | |
38 | width: 500px; | |
39 | background-color: #4CAF50; | |
40 | border: none; | |
41 | color: white; | |
42 | padding: 15px 32px; | |
43 | text-align: center; | |
44 | font-size: 54px; | |
45 | margin: 4px 2px; | |
46 | cursor: pointer; | |
47 | box-shadow: 6px 6px 0px black; | |
48 | cursor: "bug-hover"; | |
49 | | |
50 | &:hover | |
51 | { | |
52 | background-color: #45a049; | |
53 | sound-in: ui.button.over; | |
54 | } | |
55 | | |
56 | &:active | |
57 | { | |
58 | background-color: #3e8e41; | |
59 | sound-in: ui.button.press; | |
60 | } | |
61 | | |
62 | &.red | |
63 | { | |
64 | background-color: #f44336; | |
65 | | |
66 | &:hover | |
67 | { | |
68 | background-color: #d32f2f; | |
69 | } | |
70 | | |
71 | &:active | |
72 | { | |
73 | background-color: #c62828; | |
74 | } | |
75 | } | |
76 | } | |
77 | } | |
78 | | |
79 | &.show | |
80 | { | |
81 | opacity: 1; | |
82 | } | |
83 | | |
84 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/BugList/BugList.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | <div class="section"> | |
9 | <label class="header">Your Bugs</label> | |
10 | <div class="bugs"> | |
11 | @foreach (var reference in BoardManager.Local.BugReferences.OrderBy(x => x.GetBug().SegmentCount)) | |
12 | { | |
13 | <BugListEntry BugReference=@reference /> | |
14 | } | |
15 | </div> | |
16 | </div> | |
17 | <div class="section"> | |
18 | <label class="header">Opponent's Bugs</label> | |
19 | <div class="bugs"> | |
20 | @foreach (var reference in BoardManager.Opponent.BugReferences.OrderBy(x => x.GetBug().SegmentCount)) | |
21 | { | |
22 | <BugListEntry BugReference=@reference /> | |
23 | } | |
24 | </div> | |
25 | </div> | |
26 | </root> | |
27 | | |
28 | @code | |
29 | { | |
30 | protected override int BuildHash() => System.HashCode.Combine(""); | |
31 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/BugList/BugList.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | BugList | |
2 | { | |
3 | padding: 8px; | |
4 | flex-direction: column; | |
5 | align-items: center; | |
6 | transition: left 1s ease-out; | |
7 | | |
8 | .section | |
9 | { | |
10 | flex-direction: column; | |
11 | } | |
12 | | |
13 | .header | |
14 | { | |
15 | font-weight: 700; | |
16 | | |
17 | &:not(:first-child) { | |
18 | margin-top: 16px; | |
19 | } | |
20 | } | |
21 | | |
22 | .bugs | |
23 | { | |
24 | flex-direction: column; | |
25 | width: 100%; | |
26 | gap: 4px; | |
27 | } | |
28 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/BugList/BugListEntry.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using System; | |
2 | @using Sandbox; | |
3 | @using Sandbox.UI; | |
4 | @namespace Battlebugs | |
5 | @inherits Panel | |
6 | @attribute [StyleSheet] | |
7 | | |
8 | <root> | |
9 | @for (int i = 0; i < Bug.SegmentCount; i++) | |
10 | { | |
11 | <BugListSegment GameObject=@GetObject(BugReference.ObjectIds[Bug.SegmentCount - 1 - i]) Bug=@Bug Index=@i /> | |
12 | } | |
13 | </root> | |
14 | | |
15 | @code | |
16 | { | |
17 | public BoardManager.BugReference BugReference { get; set; } | |
18 | | |
19 | BugResource Bug => BugReference.GetBug(); | |
20 | Dictionary<string, GameObject> _gameObjects = new(); | |
21 | | |
22 | GameObject GetObject(string id) | |
23 | { | |
24 | if (!_gameObjects.TryGetValue(id, out var obj)) | |
25 | { | |
26 | obj = Scene.Directory.FindByGuid(Guid.Parse(id)); | |
27 | _gameObjects[id] = obj; | |
28 | } | |
29 | return obj; | |
30 | } | |
31 | | |
32 | protected override int BuildHash() => System.HashCode.Combine(BugReference); | |
33 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/BugList/BugListEntry.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | BugListEntry | |
2 | { | |
3 | flex-direction: row; | |
4 | gap: 2px; | |
5 | pointer-events: all; | |
6 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/BugList/BugListSegment.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root class="@(GameObject.IsValid() ? "" : "dead")"> | |
8 | <img src="@Icon" /> | |
9 | </root> | |
10 | | |
11 | @code | |
12 | { | |
13 | public GameObject GameObject { get; set; } | |
14 | public BugResource Bug { get; set; } | |
15 | public int Index { get; set; } | |
16 | BugSegment segment = null; | |
17 | | |
18 | string Icon | |
19 | { | |
20 | get | |
21 | { | |
22 | if (Bug is not null) | |
23 | { | |
24 | if (Index == 0) return Bug.GetHeadIcon(); | |
25 | else if (Index == Bug.SegmentCount - 1) return Bug.GetTailIcon(); | |
26 | else return Bug.GetBodyIcon(); | |
27 | } | |
28 | return ""; | |
29 | } | |
30 | } | |
31 | | |
32 | protected override void OnMouseMove(MousePanelEvent e) | |
33 | { | |
34 | if (GameObject.IsValid() && GameObject.Components.TryGet<BugSegment>(out segment)) | |
35 | { | |
36 | if (segment.IsVisible) | |
37 | { | |
38 | InspectInput.Instance.Deselect(); | |
39 | InspectInput.Instance.Select(segment, true); | |
40 | InspectorPanel.Instance.Segment = segment; | |
41 | } | |
42 | } | |
43 | } | |
44 | | |
45 | protected override void OnMouseOut(MousePanelEvent e) | |
46 | { | |
47 | if (segment is not null && InspectInput.Instance.HighlightedSegment == segment) | |
48 | { | |
49 | InspectInput.Instance.Deselect(); | |
50 | } | |
51 | } | |
52 | | |
53 | protected override int BuildHash() => System.HashCode.Combine(GameObject.IsValid()); | |
54 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/BugList/BugListSegment.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | BugListSegment | |
2 | { | |
3 | img | |
4 | { | |
5 | width: 32px; | |
6 | height: 32px; | |
7 | flex-shrink: 0; | |
8 | font-size: 12px; | |
9 | justify-content: flex-end; | |
10 | align-items: flex-end; | |
11 | | |
12 | label | |
13 | { | |
14 | position: relative; | |
15 | top: 8px; | |
16 | } | |
17 | } | |
18 | | |
19 | &.dead | |
20 | { | |
21 | img | |
22 | { | |
23 | filter: brightness(-100); | |
24 | } | |
25 | } | |
26 | | |
27 | &:hover | |
28 | { | |
29 | background-color: rgba(white, 0.1); | |
30 | } | |
31 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/ControlsPanel.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root class="@(CanSee() ? "show" : "")"> | |
8 | <div class="container"> | |
9 | <label class="header">Controls</label> | |
10 | <div class="inputs"> | |
11 | @if (GameManager.Instance.State == GameState.Placing) | |
12 | { | |
13 | <div class="input"> | |
14 | <InputGlyph button="Attack1" /> | |
15 | <label>Drag to Create Bug</label> | |
16 | </div> | |
17 | <div class="input"> | |
18 | <InputGlyph button="Attack2" /> | |
19 | <label>Cancel Drag / Pick Up</label> | |
20 | </div> | |
21 | } | |
22 | else | |
23 | { | |
24 | <div class="input"> | |
25 | <InputGlyph button="Attack1" /> | |
26 | <label>@(AttackingInput.Instance.ReticleState == 0 ? "Click to Fire" : "Confirm")</label> | |
27 | </div> | |
28 | @if (AttackingInput.Instance.ReticleState == 1) | |
29 | { | |
30 | <div class="input"> | |
31 | <InputGlyph button="Attack2" /> | |
32 | <label>Cancel</label> | |
33 | </div> | |
34 | } | |
35 | } | |
36 | </div> | |
37 | </div> | |
38 | </root> | |
39 | | |
40 | @code | |
41 | { | |
42 | bool CanSee() | |
43 | { | |
44 | return GameManager.Instance.State == GameState.Placing || (GameManager.Instance.CurrentPlayerId == BoardManager.Local.Network.OwnerId && GameManager.Instance.IsFiring); | |
45 | } | |
46 | | |
47 | protected override int BuildHash() => System.HashCode.Combine(GameManager.Instance.State, AttackingInput.Instance.ReticleState, GameManager.Instance.CurrentPlayerId, GameManager.Instance.IsFiring); | |
48 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/ControlsPanel.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | ControlsPanel { | |
2 | position: absolute; | |
3 | top: 0px; | |
4 | right: 0px; | |
5 | bottom: 0px; | |
6 | flex-direction: column; | |
7 | justify-content: center; | |
8 | color: white; | |
9 | font-family: "paper cuts 2"; | |
10 | font-size: 16px; | |
11 | | |
12 | .container { | |
13 | position: relative; | |
14 | left: 300px; | |
15 | padding: 8px; | |
16 | background-color: rgba(black, 0.5); | |
17 | flex-direction: column; | |
18 | align-items: center; | |
19 | width: 250px; | |
20 | transition: left 1s ease-out; | |
21 | | |
22 | .header { | |
23 | font-weight: 700; | |
24 | } | |
25 | | |
26 | .inputs { | |
27 | flex-direction: column; | |
28 | width: 100%; | |
29 | | |
30 | .input { | |
31 | align-items: center; | |
32 | gap: 4px; | |
33 | } | |
34 | } | |
35 | } | |
36 | | |
37 | &.show { | |
38 | .container { | |
39 | left: 0px; | |
40 | } | |
41 | } | |
42 | } | |
43 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/GameHud.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using System; | |
2 | @using Sandbox; | |
3 | @using Sandbox.UI; | |
4 | @namespace Battlebugs | |
5 | @inherits Panel | |
6 | @attribute [StyleSheet] | |
7 | | |
8 | <root> | |
9 | <div class="header"> | |
10 | @if (GameManager.Instance.Boards.Count > 0) | |
11 | { | |
12 | <PlayerHud [email protected](0) /> | |
13 | } | |
14 | <div class="middle"> | |
15 | <div class="bar"> | |
16 | @if (GameManager.Instance.Boards.Count > 0) | |
17 | { | |
18 | var fill = GameManager.Instance.Boards.ElementAt(0).GetScorePercent() * 100f; | |
19 | <div class="fill" style="width: @(fill)%" /> | |
20 | } | |
21 | </div> | |
22 | <div class="banner"> | |
23 | <label>@GetHeader()</label> | |
24 | <div class="timer-bar @(GameManager.Instance.IsFiring ? "show" : "")"> | |
25 | <div class="fill" style="width: @(100f - GameManager.Instance.TimeSinceTurnStart / 15f * 100f)%" /> | |
26 | </div> | |
27 | </div> | |
28 | </div> | |
29 | @if (GameManager.Instance.Boards.Count > 1) | |
30 | { | |
31 | <PlayerHud [email protected](1) /> | |
32 | } | |
33 | </div> | |
34 | </root> | |
35 | | |
36 | @code | |
37 | { | |
38 | public static GameHud Instance { get; private set; } | |
39 | | |
40 | protected override void OnAfterTreeRender(bool firstTime) | |
41 | { | |
42 | base.OnAfterTreeRender(firstTime); | |
43 | | |
44 | if (firstTime) | |
45 | { | |
46 | Instance = this; | |
47 | } | |
48 | } | |
49 | | |
50 | string GetHeader() | |
51 | { | |
52 | if (!BoardManager.Local.IsValid() && GameManager.Instance.Boards.Count >= 2) | |
53 | { | |
54 | if (GameManager.Instance.State == GameState.Placing) | |
55 | { | |
56 | return "Waiting for players to place bugs..."; | |
57 | } | |
58 | else if (GameManager.Instance.CurrentPlayer == GameManager.Instance.Boards.FirstOrDefault()) | |
59 | { | |
60 | return | |
quot;It's {GameManager.Instance.Boards.FirstOrDefault().GameObject.Name}'s turn!"; | |
61 | } | |
62 | | |
63 | return | |
quot;It's {GameManager.Instance.Boards.LastOrDefault().GameObject.Name}'s turn!"; | |
64 | } | |
65 | | |
66 | if (GameManager.Instance.State == GameState.Placing) | |
67 | { | |
68 | return "Place your bugs!"; | |
69 | } | |
70 | if (GameManager.Instance.CurrentPlayer == BoardManager.Local) | |
71 | { | |
72 | return "It's your turn!"; | |
73 | } | |
74 | return "Your opponent is thinking..."; | |
75 | } | |
76 | | |
77 | protected override int BuildHash() => System.HashCode.Combine(GameManager.Instance.State, GameManager.Instance.IsFiring ? GameManager.Instance.TimeSinceTurnStart.ToString() : ""); | |
78 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/GameHud.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | GameHud { | |
2 | position: absolute; | |
3 | top: 0; | |
4 | left: 0; | |
5 | right: 0; | |
6 | bottom: 0; | |
7 | justify-content: center; | |
8 | align-items: center; | |
9 | font-family: "paper cuts 2"; | |
10 | font-size: 32px; | |
11 | color: white; | |
12 | | |
13 | .header { | |
14 | position: absolute; | |
15 | top: 0; | |
16 | left: 0; | |
17 | right: 0; | |
18 | justify-content: center; | |
19 | font-weight: 700; | |
20 | | |
21 | .middle | |
22 | { | |
23 | flex-direction: column; | |
24 | align-items: center; | |
25 | width: 1200px; | |
26 | | |
27 | .bar | |
28 | { | |
29 | width: 100%; | |
30 | height: 24px; | |
31 | background-color: #FF3600; | |
32 | | |
33 | .fill | |
34 | { | |
35 | position: relative; | |
36 | top: 0; | |
37 | left: 0; | |
38 | height: 100%; | |
39 | background-color: #0094FF; | |
40 | } | |
41 | } | |
42 | | |
43 | .banner | |
44 | { | |
45 | width: 80%; | |
46 | height: 100px; | |
47 | justify-content: center; | |
48 | align-items: center; | |
49 | background-color: rgba(black, 0.7); | |
50 | font-size: 38px; | |
51 | border-radius: 0px 0px 32px 32px; | |
52 | flex-direction: column; | |
53 | gap: 8px; | |
54 | | |
55 | .timer-bar | |
56 | { | |
57 | width: 890; | |
58 | height: 8px; | |
59 | background-color: #ffffff3f; | |
60 | opacity: 0; | |
61 | transition: opacity 1s ease-out; | |
62 | | |
63 | .fill | |
64 | { | |
65 | position: relative; | |
66 | top: 0; | |
67 | left: 0; | |
68 | height: 100%; | |
69 | background-color: #ffffff; | |
70 | } | |
71 | | |
72 | &.show | |
73 | { | |
74 | opacity: 1; | |
75 | } | |
76 | } | |
77 | } | |
78 | } | |
79 | } | |
80 | } | |
81 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/PlayerHud/ChatEntry.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | @Message | |
9 | </root> | |
10 | | |
11 | @code | |
12 | { | |
13 | public string Message { get; set; } | |
14 | | |
15 | TimeSince timeSinceCreated = 0; | |
16 | | |
17 | public override void Tick() | |
18 | { | |
19 | base.Tick(); | |
20 | | |
21 | if (timeSinceCreated > 10 && !HasClass("fade")) | |
22 | { | |
23 | Delete(); | |
24 | } | |
25 | } | |
26 | | |
27 | protected override int BuildHash() => System.HashCode.Combine(Message); | |
28 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/PlayerHud/PlayerHud.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | <div class="player"> | |
9 | <img src="ui/player-banner.png" /> | |
10 | <div class="content"> | |
11 | @if (Board.Network.Owner is null) | |
12 | { | |
13 | <img src="ui/cpu.png" /> | |
14 | } | |
15 | else | |
16 | { | |
17 | <img src=@( | |
quot;avatar:{Board.Network.Owner.SteamId}") /> | |
18 | } | |
19 | <span class="name">@Board.GameObject.Name</span> | |
20 | <span class="score">🪙 @Board.Coins</span> | |
21 | </div> | |
22 | </div> | |
23 | <div class="chat-container"> | |
24 | @if (Board == BoardManager.Local) | |
25 | { | |
26 | <TextEntry @ref="InputBox" onsubmit=@OnMessageSend AllowEmojiReplace=@(true) /> | |
27 | } | |
28 | <div class="messages" @ref="ChatMessages" /> | |
29 | </div> | |
30 | </root> | |
31 | | |
32 | @code | |
33 | { | |
34 | public BoardManager Board { get; set; } | |
35 | TextEntry InputBox { get; set; } | |
36 | Panel ChatMessages { get; set; } | |
37 | | |
38 | public static List<PlayerHud> Instances { get; private set; } = new List<PlayerHud>(); | |
39 | | |
40 | protected override void OnAfterTreeRender(bool firstTime) | |
41 | { | |
42 | base.OnAfterTreeRender(firstTime); | |
43 | | |
44 | if (firstTime) | |
45 | { | |
46 | Instances.Add(this); | |
47 | } | |
48 | } | |
49 | | |
50 | void OnMessageSend() | |
51 | { | |
52 | var message = InputBox.Text; | |
53 | if (string.IsNullOrWhiteSpace(message)) | |
54 | { | |
55 | InputBox.Blur(); | |
56 | return; | |
57 | } | |
58 | | |
59 | GameManager.Instance.SendChatMessage(message); | |
60 | InputBox.Text = ""; | |
61 | } | |
62 | | |
63 | public override void Tick() | |
64 | { | |
65 | base.Tick(); | |
66 | | |
67 | if (Input.Pressed("Chat")) | |
68 | { | |
69 | InputBox?.Focus(); | |
70 | } | |
71 | } | |
72 | | |
73 | public override void OnDeleted() | |
74 | { | |
75 | Instances.Remove(this); | |
76 | } | |
77 | | |
78 | public void AddChatMessage(string message) | |
79 | { | |
80 | var entry = new ChatEntry(); | |
81 | entry.Message = message; | |
82 | ChatMessages.AddChild(entry); | |
83 | StateHasChanged(); | |
84 | } | |
85 | | |
86 | protected override int BuildHash() => System.HashCode.Combine(Board?.Coins, InputBox?.HasFocus); | |
87 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/PlayerHud/PlayerHud.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | PlayerHud { | |
2 | flex-direction: column; | |
3 | align-items: center; | |
4 | width: 160px; | |
5 | gap: 8px; | |
6 | | |
7 | .player { | |
8 | position: relative; | |
9 | flex-direction: column; | |
10 | color: black; | |
11 | font-size: 24px; | |
12 | width: 160px; | |
13 | height: 294px; | |
14 | color: white; | |
15 | text-shadow: 1px 1px 1px black; | |
16 | | |
17 | > img { | |
18 | position: absolute; | |
19 | width: 100%; | |
20 | height: 100%; | |
21 | background-size: 100%; | |
22 | z-index: -5; | |
23 | } | |
24 | | |
25 | .content { | |
26 | flex-direction: column; | |
27 | padding: 16px; | |
28 | height: 260px; | |
29 | gap: 4px; | |
30 | align-items: center; | |
31 | | |
32 | img { | |
33 | aspect-ratio: 1; | |
34 | width: 100%; | |
35 | } | |
36 | } | |
37 | } | |
38 | | |
39 | .chat-container { | |
40 | width: 240px; | |
41 | flex-direction: column; | |
42 | gap: 8px; | |
43 | | |
44 | TextEntry { | |
45 | width: 100%; | |
46 | min-height: 50px; | |
47 | height: auto; | |
48 | background-color: rgb(231, 208, 144); | |
49 | border: 2px solid rgb(155, 134, 77); | |
50 | border-radius: 8px; | |
51 | color: black; | |
52 | pointer-events: all; | |
53 | font-size: 16px; | |
54 | display: none; | |
55 | cursor: "bug-textedit"; | |
56 | | |
57 | > label { | |
58 | max-width: 240px; | |
59 | white-space: normal; | |
60 | } | |
61 | | |
62 | &:focus { | |
63 | display: flex; | |
64 | } | |
65 | } | |
66 | | |
67 | .messages { | |
68 | flex-direction: column-reverse; | |
69 | gap: 8px; | |
70 | color: black; | |
71 | | |
72 | ChatEntry { | |
73 | width: 100%; | |
74 | background-color: rgb(255, 255, 255); | |
75 | border: 2px solid rgb(167, 167, 167); | |
76 | border-radius: 8px; | |
77 | font-size: 18px; | |
78 | padding: 2px 4px; | |
79 | opacity: 1; | |
80 | transition: opacity 0.25s ease-out; | |
81 | text-align: center; | |
82 | justify-content: center; | |
83 | | |
84 | &:intro { | |
85 | opacity: 0; | |
86 | } | |
87 | | |
88 | &:outro { | |
89 | transition: opacity 2s linear; | |
90 | opacity: 0; | |
91 | } | |
92 | } | |
93 | } | |
94 | } | |
95 | | |
96 | &:last-child { | |
97 | .player { | |
98 | > img { | |
99 | filter: hue-rotate(140deg); | |
100 | } | |
101 | } | |
102 | } | |
103 | } | |
104 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/SidePanel.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | <div class="container"> | |
9 | <BugList /> | |
10 | <button onclick=@(() => ShopPanel.Instance.Open())><i>shopping_cart</i>Shop</button> | |
11 | </div> | |
12 | </root> | |
13 | | |
14 | @code | |
15 | { | |
16 | protected override int BuildHash() => System.HashCode.Combine(""); | |
17 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/SidePanel.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | SidePanel { | |
2 | position: absolute; | |
3 | top: 150px; | |
4 | left: 0px; | |
5 | bottom: 0px; | |
6 | flex-direction: column; | |
7 | justify-content: center; | |
8 | color: white; | |
9 | font-family: "paper cuts 2"; | |
10 | font-size: 16px; | |
11 | | |
12 | .container { | |
13 | padding: 8px; | |
14 | background-color: rgba(black, 0.5); | |
15 | flex-direction: column; | |
16 | align-items: center; | |
17 | width: 250px; | |
18 | transition: left 1s ease-out; | |
19 | | |
20 | button { | |
21 | padding: 4px; | |
22 | color: white; | |
23 | font-family: "paper cuts 2"; | |
24 | font-size: 16px; | |
25 | cursor: "bug-hover"; | |
26 | transition: background-color 0.1s ease-out; | |
27 | align-items: center; | |
28 | gap: 8px; | |
29 | width: 100%; | |
30 | justify-content: center; | |
31 | pointer-events: all; | |
32 | background-color: #4CAF50; | |
33 | | |
34 | &:hover { | |
35 | background-color: #45a049; | |
36 | } | |
37 | | |
38 | &:active { | |
39 | background-color: #3e8e41; | |
40 | sound-in: ui.button.press; | |
41 | } | |
42 | } | |
43 | } | |
44 | } | |
45 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/WeaponHud/WeaponButton.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root class=@GetClass()> | |
8 | <div class="icon"> | |
9 | <img [email protected] /> | |
10 | <label>@(Value < 0 ? "∞" : | |
quot;x{Value}")</label> | |
11 | </div> | |
12 | <label>@Weapon.Name</label> | |
13 | <div class="border" /> | |
14 | | |
15 | <div class="info-panel"> | |
16 | <div class="header"> | |
17 | <img [email protected] /> | |
18 | <div class="info"> | |
19 | <label class="name">@Weapon.Name</label> | |
20 | <label class="count">Count: @(Value < 0 ? "∞" : Value.ToString())</label> | |
21 | </div> | |
22 | </div> | |
23 | <label>@Weapon.Description</label> | |
24 | </div> | |
25 | </root> | |
26 | | |
27 | @code | |
28 | { | |
29 | public WeaponResource Weapon { get; set; } | |
30 | | |
31 | int Value => BoardManager.Local.WeaponInventory[Weapon]; | |
32 | | |
33 | string GetClass() | |
34 | { | |
35 | string str = BoardManager.Local.SelectedWeapon == Weapon ? "selected" : ""; | |
36 | if (Value == 0) | |
37 | { | |
38 | str += " disabled"; | |
39 | } | |
40 | return str; | |
41 | } | |
42 | protected override void OnMouseDown(MousePanelEvent e) | |
43 | { | |
44 | if (Value == 0) return; | |
45 | | |
46 | BoardManager.Local.SelectedWeapon = Weapon; | |
47 | } | |
48 | | |
49 | protected override int BuildHash() => System.HashCode.Combine(BoardManager.Local.SelectedWeapon, Value); | |
50 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/WeaponHud/WeaponButton.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | WeaponButton { | |
2 | position: relative; | |
3 | flex-direction: column; | |
4 | align-items: center; | |
5 | color: white; | |
6 | font-family: "paper cuts 2"; | |
7 | font-size: 24px; | |
8 | gap: 8px; | |
9 | padding: 8px 32px; | |
10 | text-shadow: 0 0 4px black; | |
11 | background-color: rgba(black, 0.2); | |
12 | pointer-events: all; | |
13 | cursor: "bug-hover"; | |
14 | width: 160px; | |
15 | justify-content: space-evenly; | |
16 | | |
17 | .icon { | |
18 | position: relative; | |
19 | | |
20 | img { | |
21 | width: 64px; | |
22 | height: 64px; | |
23 | } | |
24 | | |
25 | > label { | |
26 | position: absolute; | |
27 | bottom: -8px; | |
28 | right: -24px; | |
29 | } | |
30 | } | |
31 | | |
32 | > label { | |
33 | text-align: center; | |
34 | } | |
35 | | |
36 | > .info-panel { | |
37 | position: absolute; | |
38 | top: 0px; | |
39 | left: -64px; | |
40 | right: -64px; | |
41 | height: 0px; | |
42 | background-color: rgba(black, 0.8); | |
43 | overflow: hidden; | |
44 | opacity: 0; | |
45 | transition: all 0.3s ease; | |
46 | flex-direction: column; | |
47 | padding: 8px; | |
48 | font-size: 18px; | |
49 | gap: 8px; | |
50 | | |
51 | .header { | |
52 | gap: 8px; | |
53 | font-size: 24px; | |
54 | | |
55 | img { | |
56 | width: 64px; | |
57 | height: 64px; | |
58 | } | |
59 | | |
60 | .info { | |
61 | flex-direction: column; | |
62 | | |
63 | .count { | |
64 | font-size: 18px; | |
65 | } | |
66 | } | |
67 | } | |
68 | } | |
69 | | |
70 | > .border { | |
71 | position: absolute; | |
72 | top: 0; | |
73 | left: 0; | |
74 | right: 0; | |
75 | bottom: 0; | |
76 | } | |
77 | | |
78 | &:hover { | |
79 | background-color: rgba(black, 0.5); | |
80 | sound-in: ui.button.over; | |
81 | | |
82 | > .info-panel { | |
83 | top: -215px; | |
84 | height: 215px; | |
85 | opacity: 1; | |
86 | } | |
87 | } | |
88 | | |
89 | &:active { | |
90 | sound-in: ui.button.press; | |
91 | } | |
92 | | |
93 | &.selected { | |
94 | > .border { | |
95 | border: 4px solid white; | |
96 | } | |
97 | } | |
98 | | |
99 | &.disabled { | |
100 | cursor: "bug-disabled"; | |
101 | opacity: 0.5; | |
102 | } | |
103 | } | |
104 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/WeaponHud/WeaponHud.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | @if (GameManager.Instance.CurrentPlayer != BoardManager.Local || !GameManager.Instance.IsFiring) return; | |
9 | | |
10 | <div class="container"> | |
11 | <div class="weapons"> | |
12 | @foreach (var entry in BoardManager.Local.WeaponInventory) | |
13 | { | |
14 | <WeaponButton [email protected] /> | |
15 | } | |
16 | </div> | |
17 | </div> | |
18 | </root> | |
19 | | |
20 | @code | |
21 | { | |
22 | protected override int BuildHash() => System.HashCode.Combine(GameManager.Instance.IsFiring, GameManager.Instance.CurrentPlayerId); | |
23 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/GameHud/WeaponHud/WeaponHud.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | WeaponHud { | |
2 | position: absolute; | |
3 | left: 0; | |
4 | right: 0; | |
5 | bottom: 0; | |
6 | justify-content: center; | |
7 | align-items: center; | |
8 | font-family: "paper cuts 2"; | |
9 | font-size: 32px; | |
10 | pointer-events: all; | |
11 | | |
12 | .container { | |
13 | background-color: rgba(black, 0.5); | |
14 | padding: 16px; | |
15 | padding-bottom: 0px; | |
16 | | |
17 | .weapons { | |
18 | gap: 16px; | |
19 | } | |
20 | } | |
21 | } | |
22 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/HintPanel/HintPanel.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits PanelComponent | |
5 | | |
6 | <root> | |
7 | </root> | |
8 | | |
9 | @code | |
10 | { | |
11 | public static HintPanel Instance { get; private set; } | |
12 | | |
13 | public record Entry(string Icon, string Text, float Duration); | |
14 | | |
15 | List<Entry> Queue = new(); | |
16 | TimeSince timeSinceLastCheck = 0; | |
17 | | |
18 | protected override void OnAwake() | |
19 | { | |
20 | Instance = this; | |
21 | timeSinceLastCheck = 0; | |
22 | } | |
23 | | |
24 | protected override void OnFixedUpdate() | |
25 | { | |
26 | if (timeSinceLastCheck > 4f) | |
27 | { | |
28 | timeSinceLastCheck = 0; | |
29 | if (Queue.Count > 0) | |
30 | { | |
31 | CreateNotification(Queue[0]); | |
32 | Queue.RemoveAt(0); | |
33 | } | |
34 | } | |
35 | } | |
36 | | |
37 | public void AddEntry(string icon, string text, float duration = 6f) | |
38 | { | |
39 | Queue.Add(new Entry(icon, text, duration)); | |
40 | } | |
41 | | |
42 | void CreateNotification(Entry entry) | |
43 | { | |
44 | var panelEntry = new HintPanelEntry(); | |
45 | panelEntry.Entry = entry; | |
46 | Panel.AddChild(panelEntry); | |
47 | StateHasChanged(); | |
48 | Sound.Play("notify-hint"); | |
49 | } | |
50 | | |
51 | bool hasSeenRedCell = false; | |
52 | public void RedCellNotification() | |
53 | { | |
54 | if (hasSeenRedCell) return; | |
55 | | |
56 | AddEntry("color:#ff0000", "A red cell means there is no bug anywhere on that cell.", 12f); | |
57 | hasSeenRedCell = true; | |
58 | } | |
59 | | |
60 | bool hasSeenYellowCell = false; | |
61 | public void YellowCellNotification() | |
62 | { | |
63 | if (hasSeenYellowCell) return; | |
64 | | |
65 | AddEntry("color:#ffff00", "A yellow cell means there is still a bug on that cell.\nIf the cell is yellow and the bug is not visible, you haven't hit the bug yet.", 15f); | |
66 | hasSeenYellowCell = true; | |
67 | } | |
68 | | |
69 | bool hasSeenGreenCell = false; | |
70 | public void GreenCellNotification() | |
71 | { | |
72 | if (hasSeenGreenCell) return; | |
73 | | |
74 | AddEntry("color:#00ff00", "Once you've destroyed a bug on a given cell, that cell will turn green.\nThe bug panel on the left will also show the destroyed segment.", 16f); | |
75 | hasSeenGreenCell = true; | |
76 | } | |
77 | | |
78 | bool hasSeenCellCoin = false; | |
79 | public void CellCoinNotification() | |
80 | { | |
81 | if (hasSeenCellCoin) return; | |
82 | | |
83 | AddEntry("🪙", "Some cells contain coins. Coins can be spent at the shop to re-stock your weapons.", 12f); | |
84 | hasSeenCellCoin = true; | |
85 | } | |
86 | | |
87 | protected override int BuildHash() => System.HashCode.Combine(""); | |
88 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/HintPanel/HintPanel.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | HintPanel { | |
2 | position: absolute; | |
3 | bottom: 32px; | |
4 | right: 32px; | |
5 | font-family: Tahoma; | |
6 | font-size: 20px; | |
7 | font-weight: bold; | |
8 | color: white; | |
9 | text-shadow: 1px 1px 0 black; | |
10 | align-items: flex-end; | |
11 | gap: 8px; | |
12 | flex-direction: column; | |
13 | font-family: "paper cuts 2"; | |
14 | | |
15 | HintPanelEntry { | |
16 | position: relative; | |
17 | top: 0px; | |
18 | left: 0px; | |
19 | opacity: 1; | |
20 | gap: 12px; | |
21 | background-color: rgba(black, 0.5); | |
22 | // in-out-bounce bezier | |
23 | transition: all 0.4s elastic-in; | |
24 | padding: 10px 20px; | |
25 | border-radius: 4px; | |
26 | align-items: center; | |
27 | white-space: pre; | |
28 | | |
29 | .color-icon | |
30 | { | |
31 | width: 32px; | |
32 | height: 32px; | |
33 | border-radius: 8px; | |
34 | box-shadow: 2px 2px 4px rgba(black, 0.5); | |
35 | } | |
36 | | |
37 | i | |
38 | { | |
39 | font-size: 28px; | |
40 | } | |
41 | | |
42 | &:intro { | |
43 | top: 32px; | |
44 | left: 300px; | |
45 | opacity: 0; | |
46 | } | |
47 | | |
48 | &:outro { | |
49 | opacity: 0; | |
50 | left: 300px; | |
51 | transition: all 0.4s ease-in; | |
52 | } | |
53 | } | |
54 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/HintPanel/HintPanelEntry.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | | |
6 | <root> | |
7 | @if (Entry.Icon.StartsWith("color:")) | |
8 | { | |
9 | var color = Entry.Icon.Substring(6); | |
10 | <div class="color-icon" style="background-color: @color" /> | |
11 | } | |
12 | else | |
13 | { | |
14 | <i>@Entry.Icon</i> | |
15 | } | |
16 | <label class="message">@Entry.Text</label> | |
17 | </root> | |
18 | | |
19 | @code | |
20 | { | |
21 | | |
22 | public HintPanel.Entry Entry { get; set; } | |
23 | TimeSince timeSinceCreated = 0; | |
24 | | |
25 | public override void Tick() | |
26 | { | |
27 | if (timeSinceCreated > Entry.Duration) | |
28 | { | |
29 | Delete(); | |
30 | } | |
31 | } | |
32 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/LobbyList.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using System.Threading.Tasks; | |
2 | @using Sandbox; | |
3 | @using Sandbox.UI; | |
4 | @using Sandbox.Network; | |
5 | @namespace Battlebugs | |
6 | @inherits Panel | |
7 | @attribute [StyleSheet] | |
8 | | |
9 | <root> | |
10 | <label class="header">Lobbies</label> | |
11 | <div class="lobbies"> | |
12 | @if (!refreshing && list.Count > 0) | |
13 | { | |
14 | @foreach (var lobby in list) | |
15 | { | |
16 | <div class="lobby" onclick=@(() => JoinLobby(lobby))> | |
17 | <img src="ui/gamepad.png" /> | |
18 | <div class="info"> | |
19 | <label class="name">@lobby.Name</label> | |
20 | @* <label class="desc">Looking for opponent...</label> *@ | |
21 | </div> | |
22 | <div class="players"> | |
23 | <i>person</i> | |
24 | <label>@lobby.Members/2</label> | |
25 | </div> | |
26 | </div> | |
27 | } | |
28 | } | |
29 | else | |
30 | { | |
31 | <div class="no-lobbies"> | |
32 | No lobbies found... | |
33 | </div> | |
34 | } | |
35 | @* Uncomment the block below to preview a lobby entry *@ | |
36 | @* <div class="lobby"> | |
37 | <img src="ui/gamepad.png" /> | |
38 | <div class="info"> | |
39 | <label class="name">Carson vs Bakscratch</label> | |
40 | <label class="desc">Waiting for players...</label> | |
41 | </div> | |
42 | <div class="players"> | |
43 | <i>person</i> | |
44 | <label>1/2</label> | |
45 | </div> | |
46 | </div> *@ | |
47 | </div> | |
48 | </root> | |
49 | | |
50 | @code | |
51 | { | |
52 | List<LobbyInformation> list = new(); | |
53 | bool refreshing = true; | |
54 | | |
55 | protected override void OnAfterTreeRender(bool firstTime) | |
56 | { | |
57 | base.OnAfterTreeRender(firstTime); | |
58 | | |
59 | if (firstTime) | |
60 | { | |
61 | _ = RefreshLobbyList(); | |
62 | } | |
63 | } | |
64 | | |
65 | async Task RefreshLobbyList() | |
66 | { | |
67 | while (true) | |
68 | { | |
69 | await Refresh(); | |
70 | await Task.DelayRealtimeSeconds(5f); | |
71 | } | |
72 | } | |
73 | | |
74 | async Task Refresh() | |
75 | { | |
76 | refreshing = true; | |
77 | StateHasChanged(); | |
78 | | |
79 | list = await Networking.QueryLobbies(); | |
80 | | |
81 | refreshing = false; | |
82 | StateHasChanged(); | |
83 | } | |
84 | | |
85 | void JoinLobby(LobbyInformation lobby) | |
86 | { | |
87 | Networking.Connect(lobby.LobbyId); | |
88 | } | |
89 | | |
90 | protected override int BuildHash() => System.HashCode.Combine(""); | |
91 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/LobbyList.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | LobbyList | |
2 | { | |
3 | width: 100%; | |
4 | height: 100%; | |
5 | padding: 32px; | |
6 | color: white; | |
7 | font-size: 42px; | |
8 | flex-direction: column; | |
9 | gap: 16px; | |
10 | justify-content: space-evenly; | |
11 | pointer-events: all; | |
12 | | |
13 | .header | |
14 | { | |
15 | font-size: 72px; | |
16 | } | |
17 | | |
18 | .lobbies | |
19 | { | |
20 | flex-grow: 1; | |
21 | background-color: rgba(black, 0.7); | |
22 | overflow-x: hidden; | |
23 | overflow-y: scroll; | |
24 | padding: 16px; | |
25 | flex-direction: column; | |
26 | | |
27 | .lobby | |
28 | { | |
29 | width: 100%; | |
30 | background-color: rgba(black, 0.5); | |
31 | padding: 16px; | |
32 | flex-shrink: 0; | |
33 | cursor: "bug-hover"; | |
34 | align-items: center; | |
35 | gap: 16px; | |
36 | | |
37 | img | |
38 | { | |
39 | width: 72px; | |
40 | height: 72px; | |
41 | } | |
42 | | |
43 | > .info | |
44 | { | |
45 | flex-direction: column; | |
46 | font-size: 32px; | |
47 | | |
48 | .desc | |
49 | { | |
50 | font-size: 24px; | |
51 | } | |
52 | } | |
53 | | |
54 | > .players | |
55 | { | |
56 | flex-grow: 1; | |
57 | justify-content: flex-end; | |
58 | align-items: center; | |
59 | gap: 8px; | |
60 | padding-right: 16px; | |
61 | } | |
62 | | |
63 | &:hover | |
64 | { | |
65 | background-color: rgba(white, 0.01); | |
66 | } | |
67 | | |
68 | &:active | |
69 | { | |
70 | background-color: rgba(white, 0.02); | |
71 | } | |
72 | } | |
73 | } | |
74 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/PlacingHud.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | @if (!BoardManager.Local.IsValid()) return; | |
9 | | |
10 | <div class="bug-inventory"> | |
11 | <div class="container"> | |
12 | <label class="header">Bug Inventory</label> | |
13 | <div class="content"> | |
14 | @if (true) | |
15 | { | |
16 | @foreach (var bugItem in BoardManager.Local.BugInventory.OrderBy(x => x.Key.SegmentCount)) | |
17 | { | |
18 | <div class="bug-inventory-item"> | |
19 | <div class="bug"> | |
20 | @for (int i = 0; i < bugItem.Key.SegmentCount; i++) | |
21 | { | |
22 | <img src=@GetIcon(bugItem.Key, i) /> | |
23 | } | |
24 | </div> | |
25 | <label>x@(bugItem.Value)</label> | |
26 | </div> | |
27 | } | |
28 | } | |
29 | </div> | |
30 | </div> | |
31 | </div> | |
32 | <div class="footer"> | |
33 | @if (GameManager.Instance.Boards.Any(x => x.IsReady)) | |
34 | { | |
35 | <label>@(BoardManager.Local.IsReady ? "Waiting for opponent..." : "Opponent is ready...")</label> | |
36 | } | |
37 | <div class="buttons"> | |
38 | <button class="place-for-me" onclick=@(() => BoardManager.Local.SetupBoardRandomly())>Place Bugs For Me</button> | |
39 | <button class="clear-bugs" onclick=@(() => BoardManager.Local.ClearAllBugs())>Reset Bugs</button> | |
40 | <button class="@ReadyClasses()" Tooltip=@ReadyTooltip() onclick=@ToggleReady>@ReadyText()</button> | |
41 | </div> | |
42 | </div> | |
43 | </root> | |
44 | | |
45 | @code | |
46 | { | |
47 | string ReadyClasses() | |
48 | { | |
49 | string classes = "ready-up"; | |
50 | if (BoardManager.Local?.BugInventory?.Any(x => x.Value > 0) ?? true) | |
51 | { | |
52 | classes += " disabled"; | |
53 | } | |
54 | return classes; | |
55 | } | |
56 | | |
57 | string ReadyTooltip() | |
58 | { | |
59 | if (BoardManager.Local?.BugInventory?.Any(x => x.Value > 0) ?? true) | |
60 | { | |
61 | return "You must place all bugs before you can ready up!"; | |
62 | } | |
63 | return ""; | |
64 | } | |
65 | | |
66 | string ReadyText() | |
67 | { | |
68 | return BoardManager.Local.IsReady ? "Un-Ready" : "Ready"; | |
69 | } | |
70 | | |
71 | void ToggleReady() | |
72 | { | |
73 | if (BoardManager.Local?.BugInventory?.Any(x => x.Value > 0) ?? true) return; | |
74 | | |
75 | BoardManager.Local.ToggleReady(); | |
76 | } | |
77 | | |
78 | string GetIcon(BugResource bug, int index) | |
79 | { | |
80 | if (index == 0) return bug.GetHeadIcon(); | |
81 | if (index == bug.SegmentCount - 1) return bug.GetTailIcon(); | |
82 | return bug.GetBodyIcon(); | |
83 | } | |
84 | | |
85 | protected override int BuildHash() => System.HashCode.Combine(BoardManager.Local, BoardManager.Local?.BugInventory.Sum(x => x.Value), BoardManager.Local?.IsReady, GameManager.Instance.Boards.Any(x => x.IsReady)); | |
86 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/PlacingHud.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | PlacingHud { | |
2 | position: absolute; | |
3 | top: 0; | |
4 | left: 0; | |
5 | right: 0; | |
6 | bottom: 0; | |
7 | font-family: "paper cuts 2"; | |
8 | | |
9 | .bug-inventory { | |
10 | position: absolute; | |
11 | top: 0px; | |
12 | bottom: 0px; | |
13 | left: 0px; | |
14 | flex-direction: column; | |
15 | justify-content: center; | |
16 | gap: 8px; | |
17 | | |
18 | .container { | |
19 | flex-direction: column; | |
20 | justify-content: center; | |
21 | align-items: center; | |
22 | color: white; | |
23 | font-family: "paper cuts 2"; | |
24 | font-size: 16px; | |
25 | padding: 8px; | |
26 | background-color: rgba(black, 0.5); | |
27 | gap: 8px; | |
28 | | |
29 | .header { | |
30 | font-weight: 700; | |
31 | gap: 8px; | |
32 | } | |
33 | | |
34 | .content { | |
35 | flex-direction: column; | |
36 | gap: 4px; | |
37 | } | |
38 | } | |
39 | | |
40 | .bug-inventory-item { | |
41 | gap: 12px; | |
42 | align-items: center; | |
43 | | |
44 | .bug { | |
45 | gap: 2px; | |
46 | width: 180px; | |
47 | justify-content: flex-end; | |
48 | } | |
49 | | |
50 | img { | |
51 | width: 32px; | |
52 | height: 32px; | |
53 | flex-shrink: 0; | |
54 | font-size: 12px; | |
55 | justify-content: flex-end; | |
56 | align-items: flex-end; | |
57 | | |
58 | label { | |
59 | position: relative; | |
60 | top: 8px; | |
61 | } | |
62 | } | |
63 | | |
64 | label { | |
65 | color: white; | |
66 | font-weight: 700; | |
67 | } | |
68 | } | |
69 | } | |
70 | | |
71 | .footer { | |
72 | position: absolute; | |
73 | bottom: 0; | |
74 | left: 0; | |
75 | right: 0; | |
76 | background-color: rgba(0, 0, 0, 0.5); | |
77 | color: white; | |
78 | font-size: 24px; | |
79 | padding: 10px; | |
80 | justify-content: center; | |
81 | pointer-events: all; | |
82 | flex-direction: column; | |
83 | gap: 12px; | |
84 | | |
85 | > * { | |
86 | width: 100%; | |
87 | justify-content: center; | |
88 | text-align: center; | |
89 | } | |
90 | | |
91 | button { | |
92 | background-color: #4CAF50; | |
93 | color: white; | |
94 | padding: 10px 24px; | |
95 | border: none; | |
96 | border-radius: 4px; | |
97 | cursor: "bug-hover"; | |
98 | font-size: 24px; | |
99 | margin: 0 8px; | |
100 | transition: background-color 0.3s; | |
101 | | |
102 | &:hover { | |
103 | background-color: #45a049; | |
104 | } | |
105 | | |
106 | &.clear-bugs { | |
107 | background-color: #f44336; | |
108 | | |
109 | &:hover { | |
110 | background-color: #d32f2f; | |
111 | } | |
112 | } | |
113 | | |
114 | &.place-for-me { | |
115 | background-color: #2196F3; | |
116 | | |
117 | &:hover { | |
118 | background-color: #0d8bf2; | |
119 | } | |
120 | } | |
121 | | |
122 | &.ready-up { | |
123 | | |
124 | &.disabled { | |
125 | background-color: #666; | |
126 | cursor: "bug-disabled"; | |
127 | } | |
128 | } | |
129 | } | |
130 | } | |
131 | } | |
132 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/ResultsHud.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox.UI; | |
2 | @namespace Battlebugs | |
3 | @inherits Panel | |
4 | @attribute [StyleSheet] | |
5 | | |
6 | <root> | |
7 | @if (!BoardManager.Local.IsValid()) return; | |
8 | | |
9 | <div class="container"> | |
10 | <label class="title">Results</label> | |
11 | <div class="jumbotron"> | |
12 | <div class="winner"> | |
13 | <div class="info"> | |
14 | @if (Winner.Network.Owner is null) | |
15 | { | |
16 | <img src="ui/cpu.png" /> | |
17 | } | |
18 | else | |
19 | { | |
20 | <img src=@( | |
quot;avatar:{Winner.Network.Owner.SteamId}") /> | |
21 | } | |
22 | <div class="info"> | |
23 | <label class="name">@(Winner.GameObject.Name)</label> | |
24 | <label class="subtitle">Is the winner!</label> | |
25 | </div> | |
26 | <label class="crown">👑</label> | |
27 | </div> | |
28 | <div class="stats"> | |
29 | <span class="coins">🪙 @Winner.Coins</span> | |
30 | <label>@Winner.CoinsSpent Coins Spent</label> | |
31 | <label>@Winner.BugsKilled Bugs Killed</label> | |
32 | </div> | |
33 | </div> | |
34 | <div class="divider"> | |
35 | <div class="line" /> | |
36 | <label>VS</label> | |
37 | <div class="line" /> | |
38 | </div> | |
39 | <div class="loser"> | |
40 | <div class="stats"> | |
41 | <span class="coins">🪙 @Loser.Coins</span> | |
42 | <label>@Loser.CoinsSpent Coins Spent</label> | |
43 | <label>@Loser.BugsKilled Bugs Killed</label> | |
44 | </div> | |
45 | <div class="info"> | |
46 | <div class="info"> | |
47 | <label class="name">@(Loser.GameObject.Name)</label> | |
48 | <label class="subtitle">Is the loser!</label> | |
49 | </div> | |
50 | @if (Loser.Network.Owner is null) | |
51 | { | |
52 | <img src="ui/cpu.png" /> | |
53 | } | |
54 | else | |
55 | { | |
56 | <img src=@( | |
quot;avatar:{Loser.Network.Owner.SteamId}") /> | |
57 | } | |
58 | <label class="boot">😞</label> | |
59 | </div> | |
60 | </div> | |
61 | </div> | |
62 | <div class="content"> | |
63 | <BugList /> | |
64 | </div> | |
65 | <button onclick=@(() => PauseMenu.Instance?.ReturnToMainMenu())>Return to Main Menu</button> | |
66 | </div> | |
67 | </root> | |
68 | | |
69 | @code | |
70 | { | |
71 | BoardManager Winner => GameManager.Instance.Boards.FirstOrDefault(x => x.GetHealthPercent() != 0); | |
72 | BoardManager Loser => GameManager.Instance.Boards.FirstOrDefault(x => x.GetHealthPercent() <= 0); | |
73 | protected override int BuildHash() => System.HashCode.Combine(""); | |
74 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/ResultsHud.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | ResultsHud | |
2 | { | |
3 | position: absolute; | |
4 | top: 0; | |
5 | left: 0; | |
6 | right: 0; | |
7 | bottom: 0; | |
8 | justify-content: center; | |
9 | align-items: center; | |
10 | font-family: "paper cuts 2"; | |
11 | font-size: 32px; | |
12 | background-color: rgba(black, 0.4); | |
13 | opacity: 1; | |
14 | transform: scale(1); | |
15 | transition: all 0.5s ease-out; | |
16 | pointer-events: all; | |
17 | | |
18 | > .container | |
19 | { | |
20 | width: 40%; | |
21 | height: 80%; | |
22 | background-color: rgba(black, 0.8); | |
23 | flex-direction: column; | |
24 | align-items: center; | |
25 | padding: 32px; | |
26 | color: white; | |
27 | gap: 16px; | |
28 | | |
29 | > .title | |
30 | { | |
31 | font-size: 48px; | |
32 | } | |
33 | | |
34 | > .jumbotron | |
35 | { | |
36 | flex-grow: 1; | |
37 | width: 100%; | |
38 | background-color: rgba(white, 0.05); | |
39 | padding: 24px; | |
40 | flex-direction: column; | |
41 | justify-content: space-between; | |
42 | | |
43 | > div | |
44 | { | |
45 | position: relative; | |
46 | align-items: center; | |
47 | width: 100%; | |
48 | | |
49 | .info | |
50 | { | |
51 | gap: 16px; | |
52 | | |
53 | img | |
54 | { | |
55 | width: 128px; | |
56 | height: 128px; | |
57 | flex-shrink: 0; | |
58 | } | |
59 | | |
60 | .info | |
61 | { | |
62 | flex-direction: column; | |
63 | gap: 0px; | |
64 | justify-content: center; | |
65 | | |
66 | .name | |
67 | { | |
68 | font-size: 48px; | |
69 | } | |
70 | | |
71 | .subtitle | |
72 | { | |
73 | color: grey; | |
74 | } | |
75 | } | |
76 | } | |
77 | | |
78 | .stats | |
79 | { | |
80 | flex-grow: 1; | |
81 | flex-direction: column; | |
82 | align-items: flex-end; | |
83 | font-size: 24px; | |
84 | | |
85 | .coins | |
86 | { | |
87 | font-size: 32px; | |
88 | } | |
89 | } | |
90 | } | |
91 | | |
92 | .divider | |
93 | { | |
94 | width: 100%; | |
95 | gap: 8px; | |
96 | align-items: center; | |
97 | color: rgb(114, 114, 114); | |
98 | | |
99 | .line | |
100 | { | |
101 | width: 100%; | |
102 | height: 2px; | |
103 | background-color: rgb(114, 114, 114); | |
104 | } | |
105 | | |
106 | label | |
107 | { | |
108 | position: relative; | |
109 | flex-shrink: 0; | |
110 | top: 2px; | |
111 | } | |
112 | } | |
113 | | |
114 | .winner | |
115 | { | |
116 | .crown | |
117 | { | |
118 | position: absolute; | |
119 | top: -64px; | |
120 | left: 21px; | |
121 | font-size: 64px; | |
122 | transform-origin: 50% 100%; | |
123 | animation: tilt-left-right 2s infinite ease-in-out alternate; | |
124 | } | |
125 | } | |
126 | | |
127 | .loser | |
128 | { | |
129 | justify-content: flex-end; | |
130 | | |
131 | .info | |
132 | { | |
133 | align-items: flex-end; | |
134 | } | |
135 | | |
136 | .stats | |
137 | { | |
138 | align-items: flex-start; | |
139 | } | |
140 | | |
141 | .boot | |
142 | { | |
143 | position: absolute; | |
144 | top: -42px; | |
145 | right: 72px; | |
146 | font-size: 64px; | |
147 | animation: tilt-left-right 2s infinite ease-in-out alternate; | |
148 | animation-delay: 0.5s; | |
149 | } | |
150 | } | |
151 | } | |
152 | | |
153 | > .content | |
154 | { | |
155 | width: 100%; | |
156 | flex-shrink: 0; | |
157 | padding: 16px; | |
158 | overflow-x: hidden; | |
159 | overflow-y: scroll; | |
160 | flex-direction: column; | |
161 | align-items: center; | |
162 | gap: 16px; | |
163 | | |
164 | | |
165 | BugList | |
166 | { | |
167 | width: 100%; | |
168 | align-items: center; | |
169 | flex-direction: row; | |
170 | justify-content: space-evenly; | |
171 | } | |
172 | } | |
173 | } | |
174 | | |
175 | button | |
176 | { | |
177 | background-color: #4CAF50; | |
178 | border: none; | |
179 | color: white; | |
180 | padding: 8px 32px; | |
181 | text-align: center; | |
182 | font-size: 32px; | |
183 | cursor: pointer; | |
184 | box-shadow: 6px 6px 0px black; | |
185 | cursor: "bug-hover"; | |
186 | | |
187 | &:hover | |
188 | { | |
189 | background-color: #45a049; | |
190 | sound-in: ui.button.over; | |
191 | } | |
192 | | |
193 | &:active | |
194 | { | |
195 | background-color: #3e8e41; | |
196 | sound-in: ui.button.press; | |
197 | } | |
198 | | |
199 | } | |
200 | | |
201 | &:intro | |
202 | { | |
203 | opacity: 0; | |
204 | transform: scale(1.1); | |
205 | } | |
206 | } | |
207 | | |
208 | @keyframes tilt-left-right | |
209 | { | |
210 | 0% | |
211 | { | |
212 | transform: rotate(-5deg); | |
213 | } | |
214 | 100% | |
215 | { | |
216 | transform: rotate(5deg); | |
217 | } | |
218 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/ShopPanel/ShopPanel.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root class="@(IsOpen ? "show" : "")"> | |
8 | <div class="container"> | |
9 | <span class="header"><i>shopping_cart</i>Shop</span> | |
10 | <i class="close" onclick=@(() => IsOpen = false)>close</i> | |
11 | <div class="content"> | |
12 | @foreach (var weapon in BoardManager.Local.WeaponInventory.OrderBy(x => x.Key.Cost).Where(x => x.Key.Cost > 0)) | |
13 | { | |
14 | <ShopPanelEntry [email protected] /> | |
15 | } | |
16 | </div> | |
17 | </div> | |
18 | </root> | |
19 | | |
20 | @code | |
21 | { | |
22 | public static ShopPanel Instance { get; private set; } | |
23 | | |
24 | public bool IsOpen { get; private set; } = false; | |
25 | | |
26 | protected override void OnAfterTreeRender(bool firstTime) | |
27 | { | |
28 | base.OnAfterTreeRender(firstTime); | |
29 | | |
30 | if (firstTime) | |
31 | { | |
32 | Instance = this; | |
33 | } | |
34 | } | |
35 | | |
36 | public void Open() | |
37 | { | |
38 | Sound.Play("ui.navigate.forward"); | |
39 | IsOpen = true; | |
40 | } | |
41 | | |
42 | protected override int BuildHash() => System.HashCode.Combine(IsOpen); | |
43 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/ShopPanel/ShopPanel.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | ShopPanel { | |
2 | position: absolute; | |
3 | top: 0; | |
4 | left: 0; | |
5 | right: 0; | |
6 | bottom: 0; | |
7 | background-color: rgba(black, 0.4); | |
8 | opacity: 0; | |
9 | transform: scale(1.1); | |
10 | transition: all 0.2s ease-out; | |
11 | color: white; | |
12 | font-family: "paper cuts 2"; | |
13 | font-size: 24px; | |
14 | pointer-events: all; | |
15 | | |
16 | > .container { | |
17 | position: absolute; | |
18 | top: 20%; | |
19 | left: 30%; | |
20 | bottom: 20%; | |
21 | right: 30%; | |
22 | background-color: rgba(black, 0.9); | |
23 | padding: 16px; | |
24 | flex-direction: column; | |
25 | align-items: center; | |
26 | | |
27 | > .header { | |
28 | font-size: 42px; | |
29 | gap: 16px; | |
30 | align-items: center; | |
31 | } | |
32 | | |
33 | > .content { | |
34 | width: 100%; | |
35 | flex-grow: 1; | |
36 | padding: 8px; | |
37 | // overflow-x: hidden; | |
38 | // overflow-y: scroll; | |
39 | flex-direction: column; | |
40 | gap: 8px; | |
41 | } | |
42 | | |
43 | > .close { | |
44 | position: absolute; | |
45 | top: 8px; | |
46 | right: 8px; | |
47 | font-size: 32px; | |
48 | cursor: "bug-hover"; | |
49 | | |
50 | &:hover { | |
51 | sound-in: ui.button.over; | |
52 | background-color: rgba(white, 0.05); | |
53 | } | |
54 | | |
55 | &:active { | |
56 | sound-in: ui.navigate.back; | |
57 | } | |
58 | } | |
59 | } | |
60 | | |
61 | &.show { | |
62 | opacity: 1; | |
63 | transform: scale(1); | |
64 | } | |
65 | } | |
66 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/ShopPanel/ShopPanelEntry.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root onclick=@Purchase> | |
8 | <img [email protected] /> | |
9 | <div class="info"> | |
10 | <div class="top"> | |
11 | <label class="name">@Weapon.Name</label> | |
12 | @if (Weapon.Cost > 0) | |
13 | { | |
14 | <span class="cost">🪙 @Weapon.Cost</span> | |
15 | } | |
16 | else | |
17 | { | |
18 | <span class="cost">🚫</span> | |
19 | } | |
20 | </div> | |
21 | <div class="description"> | |
22 | @(Weapon.Cost > 0 ? Weapon.Description : "This weapon cannot be purchased.") | |
23 | </div> | |
24 | </div> | |
25 | </root> | |
26 | | |
27 | @code | |
28 | { | |
29 | public WeaponResource Weapon { get; set; } | |
30 | | |
31 | void Purchase() | |
32 | { | |
33 | if (Weapon.Cost > 0 && BoardManager.Local.Coins >= Weapon.Cost) | |
34 | { | |
35 | Sound.Play("shop-purchase"); | |
36 | BoardManager.Local.PurchaseWeapon(Weapon); | |
37 | } | |
38 | else | |
39 | { | |
40 | Sound.Play("ui.button.deny"); | |
41 | } | |
42 | } | |
43 | | |
44 | protected override int BuildHash() => System.HashCode.Combine(Weapon); | |
45 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/ShopPanel/ShopPanelEntry.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | ShopPanelEntry { | |
2 | flex-grow: 1; | |
3 | background-color: rgba(0, 0, 0, 0.61); | |
4 | padding: 16px; | |
5 | flex-shrink: 0; | |
6 | gap: 16px; | |
7 | align-items: center; | |
8 | cursor: "bug-hover"; | |
9 | | |
10 | > img { | |
11 | width: 92px; | |
12 | height: 92px; | |
13 | flex-shrink: 0; | |
14 | } | |
15 | | |
16 | .info { | |
17 | flex-direction: column; | |
18 | | |
19 | .top { | |
20 | gap: 8px; | |
21 | } | |
22 | | |
23 | .description { | |
24 | font-size: 20px; | |
25 | opacity: 0.5; | |
26 | } | |
27 | } | |
28 | | |
29 | &:hover { | |
30 | sound-in: ui.button.over; | |
31 | background-color: rgba(0, 0, 0, 0.8); | |
32 | } | |
33 | | |
34 | &:active { | |
35 | background-color: rgba(0, 0, 0, 0.5); | |
36 | } | |
37 | } | |
38 | | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/StatsPanel.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | <label class="header">Stats</label> | |
9 | <div class="entry"> | |
10 | <label class="stat">Games Played</label> | |
11 | <label class="value">@Stats.Get("games_played").ValueString</label> | |
12 | </div> | |
13 | <div class="entry"> | |
14 | <label class="stat">Games Won</label> | |
15 | <label class="value">@Stats.Get("games_won").ValueString</label> | |
16 | </div> | |
17 | <div class="entry"> | |
18 | <label class="stat">Games Lost</label> | |
19 | <label class="value">@Stats.Get("games_lost").ValueString</label> | |
20 | </div> | |
21 | <div class="entry"> | |
22 | <label class="stat">Coins Earned</label> | |
23 | <label class="value">@Stats.Get("coins_earned").ValueString</label> | |
24 | </div> | |
25 | <div class="entry"> | |
26 | <label class="stat">Coins Spent</label> | |
27 | <label class="value">@Stats.Get("coins_spent").ValueString</label> | |
28 | </div> | |
29 | <div class="entry"> | |
30 | <label class="stat">Bugs Killed</label> | |
31 | <label class="value">@Stats.Get("bugs_killed").ValueString</label> | |
32 | </div> | |
33 | <div class="entry"> | |
34 | <label class="stat">Damage Dealt</label> | |
35 | <label class="value">@Stats.Get("damage_dealt").ValueString</label> | |
36 | </div> | |
37 | </root> | |
38 | | |
39 | @code | |
40 | { | |
41 | Sandbox.Services.Stats.PlayerStats Stats | |
42 | { | |
43 | get | |
44 | { | |
45 | if (_stats == null) | |
46 | { | |
47 | _stats = Sandbox.Services.Stats.GetLocalPlayerStats(Game.Ident); | |
48 | } | |
49 | return _stats; | |
50 | | |
51 | } | |
52 | } | |
53 | Sandbox.Services.Stats.PlayerStats _stats = null; | |
54 | | |
55 | protected override int BuildHash() => System.HashCode.Combine(Stats); | |
56 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/StatsPanel.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | StatsPanel | |
2 | { | |
3 | width: 100%; | |
4 | height: 100%; | |
5 | padding: 32px; | |
6 | color: white; | |
7 | font-size: 42px; | |
8 | flex-direction: column; | |
9 | gap: 16px; | |
10 | justify-content: space-evenly; | |
11 | | |
12 | .header | |
13 | { | |
14 | font-size: 72px; | |
15 | } | |
16 | | |
17 | .entry | |
18 | { | |
19 | width: 100%; | |
20 | justify-content: space-between; | |
21 | } | |
22 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/WaitingHud.razor: | |
-------------------------------------------------------------------------------- | |
1 | @using Sandbox; | |
2 | @using Sandbox.UI; | |
3 | @namespace Battlebugs | |
4 | @inherits Panel | |
5 | @attribute [StyleSheet] | |
6 | | |
7 | <root> | |
8 | <label>Waiting for an opponent...</label> | |
9 | <i>refresh</i> | |
10 | </root> | |
11 | | |
12 | @code | |
13 | { | |
14 | protected override int BuildHash() => System.HashCode.Combine(""); | |
15 | } | |
-------------------------------------------------------------------------------- | |
/code/UI/Panels/WaitingHud.razor.scss: | |
-------------------------------------------------------------------------------- | |
1 | WaitingHud { | |
2 | position: absolute; | |
3 | top: 0; | |
4 | left: 0; | |
5 | right: 0; | |
6 | bottom: 0; | |
7 | background-color: rgba(black, 0.4); | |
8 | backdrop-filter: blur(1px); | |
9 | justify-content: center; | |
10 | align-items: center; | |
11 | font-family: "paper cuts 2"; | |
12 | font-size: 58px; | |
13 | font-weight: 700; | |
14 | color: white; | |
15 | flex-direction: column; | |
16 | gap: 64px; | |
17 | pointer-events: all; | |
18 | | |
19 | i { | |
20 | font-size: 128px; | |
21 | animation: rotateright 1s linear infinite; | |
22 | } | |
23 | } | |
24 | | |
25 | @keyframes rotateright | |
26 | { | |
27 | from | |
28 | { | |
29 | transform: rotate(0deg); | |
30 | } | |
31 | to | |
32 | { | |
33 | transform: rotate(360deg); | |
34 | } | |
35 | } | |
-------------------------------------------------------------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment