-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathPlayerShip.cs
316 lines (235 loc) · 9.42 KB
/
PlayerShip.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
using Godot;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using redhatgamedev.srt.v1;
public partial class PlayerShip : CharacterBody2D
{
public Serilog.Core.Logger _serilogger;
public double Thrust = 1f; // effective acceleration
public double MaxSpeed = 5;
public double StopThreshold = 10f;
public double GoThreshold = 90f;
public double CurrentVelocity = 0;
public double RotationThrust = 1.5f;
public double CurrentRotation = 0;
public int HitPoints = 100;
public ConcurrentQueue<Vector2> MovementQueue = new ConcurrentQueue<Vector2>();
public String uuid;
// for the server we're interfaced with
Server MyServer;
// for now only one missile at a time
SpaceMissile MyMissile = null;
public int MissileSpeed = 300;
public float MissileLife = 2;
// the reload time is the minimum time between missile firings
// relevant when two players are very close to one another and
// prevents missile spamming
float MissileReloadTime = 2;
double MissileReloadCountdown;
bool MissileReady = true;
public int MissileDamage = 25;
Node2D shipThing = null;
CollisionShape2D clickBox = null;
Layout theLayout;
PackedScene MissilePackedScene = (PackedScene)ResourceLoader.Load("res://SpaceMissile.tscn");
bool QueuedForRemoval = false; // used when this player is about to be removed from play
bool cameraCurrent = false;
Camera2D myCamera;
Label angularVelocityLabel;
Label linearVelocityLabel;
Label hitPointsLabel;
Label positionLabel;
Label hexLabel;
// used by the debug UI to show which player has the camera focus
public bool isFocused = false;
Sprite2D shipSprite;
public GameEvent.GameObject CreatePlayerGameObjectBuffer()
{
GameEvent.GameObject gameObject = new GameEvent.GameObject();
gameObject.GameObjectType = GameEvent.GameObjectType.GameObjectTypePlayer;
gameObject.Uuid = uuid;
// TODO: only send if changed?
gameObject.PositionX = (int)GlobalPosition.X;
gameObject.PositionY = (int)GlobalPosition.Y;
// TODO: only send if changed?
gameObject.Angle = RotationDegrees;
// need to send the velocity because that's how the client shows the speedometer
gameObject.AbsoluteVelocity = (float)CurrentVelocity;
// TODO: only send this if it's a change from previous?
gameObject.HitPoints = HitPoints;
return gameObject;
}
public void ExpireMissile()
{
_serilogger.Verbose($"PlayerShip.cs: removing missile {MyMissile.uuid} belongs to {MyMissile.MyPlayer.uuid}");
MyServer.RemoveMissile(MyMissile);
MyMissile = null;
}
public void FireMissile(string missileUUID = null)
{
// only one missile allowed for now
if (MyMissile != null)
{
_serilogger.Debug($"PlayerShip.cs: Missile for player {uuid} exists - skipping");
return;
}
// check if reload complete
if (MissileReady == false)
{
_serilogger.Debug($"PlayerShip.cs: player {uuid} not done with reload - skipping");
return;
}
Node missileNode = MissilePackedScene.Instantiate();
MyMissile = (SpaceMissile)missileNode;
// TODO: need to check for UUID collision
_serilogger.Debug($"PlayerShip.cs: Supplied UUID is {missileUUID}");
if (missileUUID != null)
// use the suggested UUID
{ MyMissile.uuid = missileUUID; }
else
{ MyMissile.uuid = Guid.NewGuid().ToString(); }
_serilogger.Debug($"PlayerShip.cs: Missile UUID is {MyMissile.uuid}");
// missile should point in the same direction as the ship
MyMissile.Rotation = Rotation;
// TODO: need to offset this to the front of the ship
// start at our position
MyMissile.Position = GlobalPosition;
// negative direction is "up"
Vector2 offset = new Vector2(0, -60);
// rotate the offset to match the current ship heading
offset = offset.Rotated(Rotation);
MyMissile.Position = MyMissile.Position + offset;
// set missile's parameters based on current modifiers
MyMissile.MissileSpeed = MissileSpeed;
MyMissile.MissileLife = MissileLife;
MyMissile.MissileDamage = MissileDamage;
// this is a poop way to do this
MyMissile.MyPlayer = this;
// send the missile creation message
_serilogger.Debug($"PlayerShip.cs: creating missile {MyMissile.uuid} belongs to {MyMissile.MyPlayer.uuid}");
Node rootNode = GetNode<Node>("/root");
rootNode.AddChild(MyMissile);
MyServer.InstantiateMissile(MyMissile);
// set the reload countdown
MissileReloadCountdown = MissileReloadTime;
MissileReady = false;
}
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
// initialize the logging configuration
MyServer = GetNode<Server>("/root/Server");
_serilogger = MyServer._serilogger;
Node2D shipThing = (Node2D)GetParent();
Label playerIDLabel = (Label)shipThing.GetNode("Stat/IDLabel");
clickBox = shipThing.GetNode<CollisionShape2D>("ClickBox");
// TODO: deal with really long UUIDs
playerIDLabel.Text = uuid;
myCamera = GetNode<Camera2D>("Camera2D");
shipSprite = GetNode<Sprite2D>("Sprite2D");
// TODO: we are doing instant rotation so probably should rename this
angularVelocityLabel = (Label)shipThing.GetNode("Stat/AngularVelocity");
linearVelocityLabel = (Label)shipThing.GetNode("Stat/LinearVelocity");
hitPointsLabel = (Label)shipThing.GetNode("Stat/HitPoints");
positionLabel = (Label)shipThing.GetNode("Stat/Position");
hexLabel = (Label)shipThing.GetNode("Stat/Hex");
theLayout = MyServer.HexLayout;
}
public void TakeDamage(int Damage)
{
_serilogger.Debug($"PlayerShip.cs: {uuid}: Taking damage: {Damage}");
HitPoints -= Damage;
_serilogger.Debug($"PlayerShip.cs: {uuid}: Hitpoints: {HitPoints}");
if (HitPoints <= 0)
{
_serilogger.Debug($"PlayerShip.cs: Hitpoints zeroed for {uuid}! Remove the player!");
QueuedForRemoval = true;
RemovePlayer();
}
}
void RemovePlayer()
{
_serilogger.Debug($"PlayerShip.cs: Enqueuing player removal: {uuid}");
MyServer.PlayerRemoveQueue.Enqueue(uuid);
}
void CheckMissileReload(double delta)
{
// nothing to check if we are already reloaded
if (MissileReady == true) { return; }
MissileReloadCountdown -= delta;
if (MissileReloadCountdown <= 0)
{
_serilogger.Debug($"PlayerShip.cs: player {uuid} missile reload countdown complete");
MissileReady = true;
}
}
public void UpdateFocused()
{
if (isFocused) shipSprite.Modulate = new Color(4,4,4,1);
else shipSprite.Modulate = new Color(1,1,1,1);
}
public override void _Process(double delta)
{
if (QueuedForRemoval) return;
if (shipThing == null) shipThing = (Node2D)GetParent();
// figure out the hex from the pixel position
FractionalHex theHex = theLayout.PixelToHex(new Point(GlobalPosition.X, GlobalPosition.Y));
hexLabel.Text = $"q: {theHex.HexRound().q}, r: {theHex.HexRound().r}, s: {theHex.HexRound().s}";
angularVelocityLabel.Text = $"Rot: {RotationDegrees}";
linearVelocityLabel.Text = $"Vel: {CurrentVelocity}";
hitPointsLabel.Text = $"HP: {HitPoints}";
positionLabel.Text = $"X: {GlobalPosition.X} Y: {GlobalPosition.Y}";
// reposition the click box to be located where the ship thing is
clickBox.Position = GlobalPosition;
CheckMissileReload(delta);
UpdateFocused();
}
public override void _PhysicsProcess(double delta)
{
if (shipThing == null) shipThing = (Node2D)GetParent();
// somewhat based on: https://kidscancode.org/godot_recipes/2d/topdown_movement/
// "rotate and move" / asteroids-style-ish
double rotation_dir = 0; // in case we need it
_serilogger.Verbose($"{uuid}: handling physics");
Vector2 thisMovement = Vector2.Zero;
if (MovementQueue.TryDequeue(out thisMovement))
{
_serilogger.Verbose($"UUID: {uuid} X: {thisMovement.X} Y: {thisMovement.Y}");
if (thisMovement.Y > 0)
{
CurrentVelocity = Mathf.Lerp(CurrentVelocity, MaxSpeed, Thrust * delta);
// max out speed when velocity gets above threshold for same reason
if (CurrentVelocity > MaxSpeed * (GoThreshold/100)) { CurrentVelocity = MaxSpeed; }
}
if (thisMovement.Y < 0)
{
CurrentVelocity = Mathf.Lerp(CurrentVelocity, 0, Thrust * delta);
// cut speed when velocity gets below threshold, otherwise LERPing
// results in never actually stopping.
if (CurrentVelocity < MaxSpeed * (StopThreshold/100)) { CurrentVelocity = 0; }
}
if (thisMovement.X != 0)
{
rotation_dir = thisMovement.X;
}
_serilogger.Verbose($"UUID: {uuid} Velocity: {CurrentVelocity}");
}
Vector2 velocity = -(Transform.Y * (float)CurrentVelocity);
_serilogger.Verbose($"UUID: {uuid} Vector X: {velocity.X} Y: {velocity.Y} ");
Rotation += (float)(rotation_dir * RotationThrust * delta);
// TODO: implement collision mechanics
MoveAndCollide(velocity);
// TODO: need to adust the clamp when the starfield is lopsided in the early
// game
// clamp the player to the starfield radius
Int32 starFieldRadiusPixels = MyServer.StarFieldRadiusPixels;
Vector2 currentGlobalPosition = GlobalPosition;
if (currentGlobalPosition.Length() > starFieldRadiusPixels)
{
Vector2 newPosition = starFieldRadiusPixels * currentGlobalPosition.Normalized();
GlobalPosition = newPosition;
}
}
}