gg.easy.airship 0.1.2163 → 0.1.2164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.editorconfig ADDED
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*.cs]
4
+ csharp_new_line_before_open_brace = none
5
+ csharp_new_line_before_else = false
6
+ csharp_new_line_before_catch = false
7
+ csharp_new_line_before_finally = false
8
+ csharp_new_line_before_members_in_object_initializers = false
9
+ csharp_new_line_before_members_in_anonymous_types = false
@@ -1,11 +1,11 @@
1
1
  // ReSharper disable InconsistentNaming
2
2
  namespace Code {
3
3
  public static class AirshipConst {
4
- public const int playerVersion = 12;
4
+ public const int playerVersion = 13;
5
5
 
6
6
  /// <summary>
7
7
  /// The server will kick clients that have a playerVersion lower than this value.
8
8
  /// </summary>
9
- public const int minAcceptedPlayerVersionOnServer = 12;
9
+ public const int minAcceptedPlayerVersionOnServer = 13;
10
10
  }
11
11
  }
@@ -0,0 +1,145 @@
1
+ using System;
2
+ using System.IO;
3
+ using System.IO.Compression;
4
+ using System.Threading.Tasks;
5
+ using Code.Http.Internal;
6
+ using Code.Http.Public;
7
+ using Code.Platform.Shared;
8
+ using Proyecto26;
9
+ using UnityEngine;
10
+
11
+ namespace Code.Analytics {
12
+ [Serializable]
13
+ public class PostArtifactResponse {
14
+ public string id;
15
+ public string url;
16
+ }
17
+
18
+ [Serializable]
19
+ public class RequestBody {
20
+ public string type;
21
+ public string name;
22
+ public string contentType;
23
+ public long contentLength;
24
+ }
25
+
26
+ public class ClientFolderUploader : MonoBehaviour {
27
+
28
+ // Start in the past otherwise they can't do this for the first 10 seconds since time starts at 0.
29
+ private float timeOfLastUpload = -10;
30
+
31
+ public void ButtonClick() {
32
+ if (Time.unscaledTime - timeOfLastUpload < 10) {
33
+ Debug.LogWarning("[ClientFolderUploader] Upload already in progress or too soon after last upload");
34
+ return;
35
+ }
36
+ timeOfLastUpload = Time.unscaledTime;
37
+
38
+ _ = UploadAsync();
39
+ }
40
+
41
+ private async Task UploadAsync() {
42
+ try {
43
+ await Upload();
44
+ Debug.Log("[ClientFolderUploader] Upload completed successfully");
45
+ } catch (Exception ex) {
46
+ Debug.LogError($"[ClientFolderUploader] Upload failed: {ex.Message}");
47
+ }
48
+ }
49
+
50
+ public async Task Upload() {
51
+ try {
52
+ var path = Path.GetDirectoryName(Application.consoleLogPath);
53
+
54
+ var zipPath = Path.Combine(Application.temporaryCachePath, "logs.zip");
55
+ if (File.Exists(zipPath)) {
56
+ File.Delete(zipPath);
57
+ }
58
+
59
+ var playerLogFile = Path.Combine(path, "Player.log");
60
+ var playerPrevLogFile = Path.Combine(path, "Player-prev.log");
61
+ var editorLogFile = Path.Combine(path, "Editor.log");
62
+ var editorPrevLogFile = Path.Combine(path, "Editor-prev.log");
63
+
64
+ Debug.Log("[ClientFolderUploader] Creating zip archive");
65
+ try {
66
+ using (var archive = ZipFile.Open(zipPath, ZipArchiveMode.Create)) {
67
+ var fileExists = false;
68
+ if (File.Exists(playerLogFile)) {
69
+ fileExists = true;
70
+ Debug.Log("[ClientFolderUploader] Adding Player.log");
71
+ archive.CreateEntryFromFile(playerLogFile, "Player.log");
72
+ }
73
+
74
+ if (File.Exists(playerPrevLogFile)) {
75
+ fileExists = true;
76
+ Debug.Log("[ClientFolderUploader] Adding Player-prev.log");
77
+ archive.CreateEntryFromFile(playerPrevLogFile, "Player-prev.log");
78
+ }
79
+
80
+ if (File.Exists(editorLogFile)) {
81
+ fileExists = true;
82
+ Debug.Log("[ClientFolderUploader] Adding Editor.log");
83
+ archive.CreateEntryFromFile(editorLogFile, "Editor.log");
84
+ }
85
+
86
+ if (File.Exists(editorPrevLogFile)) {
87
+ fileExists = true;
88
+ Debug.Log("[ClientFolderUploader] Adding Editor-prev.log");
89
+ archive.CreateEntryFromFile(editorPrevLogFile, "Editor-prev.log");
90
+ }
91
+
92
+ if (!fileExists) {
93
+ return;
94
+ }
95
+ }
96
+ } catch (Exception ex) {
97
+ Debug.LogError($"[ClientFolderUploader] Zip creation failed: {ex.Message}");
98
+ throw;
99
+ }
100
+
101
+ var contentType = "application/zip";
102
+ var contentLength = new FileInfo(zipPath).Length;
103
+
104
+ var body = new RequestBody {
105
+ type = "CLIENT_DEBUG_ARCHIVE",
106
+ name = "PlayerLogs.zip",
107
+ contentType = contentType,
108
+ contentLength = contentLength,
109
+ };
110
+
111
+ Debug.Log("[ClientFolderUploader] Creating artifact");
112
+ var url = $"{AirshipPlatformUrl.contentService}/artifacts/platform/signed-url";
113
+
114
+ Http.HttpResponse res;
115
+ if (String.IsNullOrEmpty(InternalHttpManager.authToken)) {
116
+ res = await HttpManager.PostAsync(url, JsonUtility.ToJson(body));
117
+ } else {
118
+ res = await InternalHttpManager.PostAsync(url, JsonUtility.ToJson(body));
119
+ }
120
+
121
+ if (res.statusCode < 200 || res.statusCode >= 300) {
122
+ Debug.LogError($"[ClientFolderUploader] Error response from server: {res.statusCode} - {res.error}");
123
+ throw new Exception($"Failed to get signed URL: {res.error}");
124
+ }
125
+
126
+ var response = JsonUtility.FromJson<PostArtifactResponse>(res.data);
127
+
128
+ Debug.Log("[ClientFolderUploader] Uploading file");
129
+ await HttpManager.PutAsync(new RequestHelper() {
130
+ Uri = response.url,
131
+ BodyRaw = File.ReadAllBytes(zipPath),
132
+ Headers = new System.Collections.Generic.Dictionary<string, string>
133
+ {
134
+ { "Content-Type", contentType },
135
+ },
136
+ }, "");
137
+
138
+ File.Delete(zipPath);
139
+ } catch (Exception ex) {
140
+ Debug.LogError($"[ClientFolderUploader] Error during upload process: {ex.Message}");
141
+ throw;
142
+ }
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 474c8118d514c47d2b470bdd6dd04d84
@@ -8,7 +8,11 @@ public class ClientBootstrap : MonoBehaviour
8
8
  private void Start()
9
9
  {
10
10
  if (RunCore.IsClient()) {
11
+ #if UNITY_STANDALONE_OSX
12
+ Application.targetFrameRate = -1;
13
+ #else
11
14
  Application.targetFrameRate = (int)Math.Ceiling(Screen.currentResolution.refreshRateRatio.value);
15
+ #endif
12
16
  Application.logMessageReceived += AnalyticsRecorder.RecordLogMessageToAnalytics;
13
17
  }
14
18
  }
@@ -76,6 +76,8 @@ namespace Code.Network.StateSystem
76
76
  // Non-auth server command tracking
77
77
  // Note: we also re-use some of the above command buffer fields
78
78
  private SortedList<int, State> serverReceivedStateBuffer = new SortedList<int, State>();
79
+ // Last observed state from OnTick when running as a non-auth server.
80
+ private State serverLastObservedState;
79
81
 
80
82
  // Client processing for input prediction
81
83
  private State clientLastConfirmedState;
@@ -224,6 +226,12 @@ namespace Code.Network.StateSystem
224
226
  simManager.OnSetSnapshot -= this.OnSetSnapshot;
225
227
  simManager.OnCaptureSnapshot -= this.OnCaptureSnapshot;
226
228
  simManager.OnLagCompensationCheck -= this.OnLagCompensationCheck;
229
+
230
+ this.OnClientReceiveSnapshot -= ClientReceiveSnapshot;
231
+ this.OnClientReceiveDiff -= ClientReceiveDiff;
232
+ this.OnServerReceiveSnapshot -= ServerReceiveSnapshot;
233
+ this.OnServerReceiveInput -= ServerReceiveInputCommand;
234
+ this.OnServerReceiveFullSnapshotRequest -= ServerReceiveFullSnapshotRequest;
227
235
  }
228
236
 
229
237
  private void SendNetworkMessages() {
@@ -810,20 +818,30 @@ namespace Code.Network.StateSystem
810
818
  // not a shared timeline.
811
819
  latestState.tick = tick;
812
820
  latestState.time = time;
821
+ this.serverLastObservedState = latestState;
813
822
  // Use this as the current state for the server
814
823
  this.stateSystem.SetCurrentState(latestState);
815
824
  // Since it's new, update our server interpolation functions
816
- this.stateSystem.InterpolateReachedState(latestState);
817
- // Add the state to our history as we would in a authoritative setup
818
- this.stateHistory.Set(tick, latestState);
825
+ this.stateSystem.InterpolateReachedState(latestState);
819
826
  }
820
827
  }
821
828
 
822
- public void NonAuthServerCaptureSnapshot(int tick, double time, bool replay)
823
- {
824
- // Non server auth will accept the client auth as the official possition. No need to capture
825
- // the state after the physics tick as the position that was pulled from the buffer in OnTick
826
- // was already added to the state timeline as the official position.
829
+ public void NonAuthServerCaptureSnapshot(int tick, double time, bool replay) {
830
+ if (this.serverLastObservedState != null) {
831
+ // Non server auth will accept the client auth as the official position. No need to get
832
+ // the state after the physics tick as the position that was pulled from the buffer in OnTick
833
+ // is the official position.
834
+ var authState = this.serverLastObservedState.Clone() as State;
835
+ authState.tick = tick;
836
+ authState.time = time;
837
+ this.stateHistory.Set(tick, authState);
838
+ }
839
+ else {
840
+ // If we haven't received any auth state from the client, just use what the server has so we have
841
+ // something to send to observers.
842
+ var currentState = this.stateSystem.GetCurrentState(this.serverLastProcessedCommandNumber, tick, time);
843
+ this.stateHistory.Set(tick, currentState);
844
+ }
827
845
  }
828
846
 
829
847
  public void NonAuthServerSetSnapshot(int tick)
@@ -977,7 +995,7 @@ namespace Code.Network.StateSystem
977
995
  if (!this.observerHistory.GetAround(NetworkTime.time, out State prevState, out State nextState))
978
996
  {
979
997
  // if (clientTime < this.observerHistory.Keys[0]) return; // Our local time hasn't advanced enough to render the positions reported. No need to log debug
980
- // Debug.LogWarning("Frame " + Time.frameCount + " not enough state history for rendering. " + this.observerHistory.Keys.Count +
998
+ // Debug.LogWarning("Frame " + Time.frameCount + " not enough state history for rendering " + this.name + ". " + this.observerHistory.Keys.Count +
981
999
  // " entries. First " + this.observerHistory.Keys[0] + " Last " +
982
1000
  // this.observerHistory.Keys[^1] + " Target " + NetworkTime.time + " Buffer is: " + NetworkClient.bufferTime + " Estimated Latency (1 way): " +
983
1001
  // (NetworkTime.rtt / 2) + " TScale: " + Time.timeScale);
@@ -1177,7 +1195,7 @@ namespace Code.Network.StateSystem
1177
1195
  // Debug.Log("Server received command " + command.commandNumber + " for " + this.gameObject.name);
1178
1196
  // This should only occur if the server is authoritative.
1179
1197
  if (!serverAuth) {
1180
- Debug.LogWarning($"Received input command from {this.name}, but the networking mode is not server authoritative. Command will be ignored.");
1198
+ // Debug.LogWarning($"Received input command from {this.name}, but the networking mode is not server authoritative. Command will be ignored.");
1181
1199
  return;
1182
1200
  }
1183
1201
 
@@ -1198,8 +1216,8 @@ namespace Code.Network.StateSystem
1198
1216
  // Reject commands if our buffer is full.
1199
1217
  if (this.serverCommandBuffer.Count > this.serverCommandBufferMaxSize)
1200
1218
  {
1201
- Debug.LogWarning("Dropping command " + command.commandNumber +
1202
- " for "+ this.name + " due to exceeding command buffer size. First command in buffer is " + this.serverCommandBuffer.Values[0]);
1219
+ // Debug.LogWarning("Dropping command " + command.commandNumber +
1220
+ // " for "+ this.name + " due to exceeding command buffer size. First command in buffer is " + this.serverCommandBuffer.Values[0]);
1203
1221
  return;
1204
1222
  }
1205
1223
 
@@ -28,8 +28,8 @@ namespace Code.Player.Character.MovementSystems.Character {
28
28
  CharacterInputData> {
29
29
  [FormerlySerializedAs("rigidbody")] public Rigidbody rb;
30
30
  public Transform rootTransform;
31
- public Transform airshipTransform; //The visual transform controlled by this script
32
- public Transform graphicTransform; //A transform that games can animate
31
+ public Transform airshipTransform; // The visual transform controlled by this script. This always has the exact rotations used for movement
32
+ public Transform graphicTransform; // A transform that games can animate. This may have slightly altered rotation for visuals
33
33
  public CharacterMovementSettings movementSettings;
34
34
  public BoxCollider mainCollider;
35
35
 
@@ -63,6 +63,19 @@ namespace Code.Player.Character.MovementSystems.Character {
63
63
  "Controls the speed in which characters in orbit camera rotate to face look direction. Degrees per second.")]
64
64
  public float smoothedRotationSpeed = 360f;
65
65
 
66
+ [Tooltip("If true, the character body will automatically rotate in the direction of the look vector.")]
67
+ public bool rotateAutomatically = true;
68
+
69
+ [Tooltip("If enabled, the head will be rotated to look in the same direction as the look vector. The body will rotate only when needed. \"Rotate Automatically\" must also be checked.")]
70
+ public bool rotateHeadToLookVector = true;
71
+
72
+ [Tooltip("How much influence the look vector has on the look rotation.")]
73
+ [Range(0, 1)]
74
+ public float lookVectorInfluence = 0.4f;
75
+
76
+ [Tooltip("How far the head can rotate before the body rotates in degrees.")] [Range(0, 180)]
77
+ public int headRotationThreshold = 60;
78
+
66
79
  [Tooltip(
67
80
  "If true animations will be played on the server. This should be true if you care about character movement animations server-side (like for hit boxes).")]
68
81
  public bool playAnimationOnServer = true;
@@ -71,6 +84,8 @@ namespace Code.Player.Character.MovementSystems.Character {
71
84
 
72
85
  private CharacterPhysics physics;
73
86
  private Transform _cameraTransform;
87
+ private CharacterRig _rig;
88
+ private Quaternion initialHeadRotation;
74
89
  private bool _smoothLookVector = false;
75
90
 
76
91
  /**
@@ -209,6 +224,19 @@ namespace Code.Player.Character.MovementSystems.Character {
209
224
  physics = new CharacterPhysics(this);
210
225
  }
211
226
 
227
+ _rig = GetComponentInChildren<CharacterRig>();
228
+
229
+ // Verify rig is setup correctly for rotateHeadToLookVector
230
+ if (rotateHeadToLookVector) {
231
+ if (_rig == null || _rig.head == null || _rig.spine == null) {
232
+ rotateHeadToLookVector = false;
233
+ Debug.LogWarning(
234
+ "[CharacterMovement] Disabling rotateHeadToLookVector, cannot find rig's head transform.");
235
+ } else {
236
+ initialHeadRotation = _rig.head.rotation;
237
+ }
238
+ }
239
+
212
240
  _cameraTransform = Camera.main.transform;
213
241
  }
214
242
 
@@ -274,14 +302,9 @@ namespace Code.Player.Character.MovementSystems.Character {
274
302
  if (!rb.isKinematic) {
275
303
  rb.linearVelocity = snapshot.velocity;
276
304
  }
277
-
278
- var lookTarget = new Vector3(snapshot.lookVector.x, 0, snapshot.lookVector.z);
279
- if (lookTarget == Vector3.zero) {
280
- lookTarget = new Vector3(0, 0, .01f);
281
- }
282
-
283
- airshipTransform.rotation = Quaternion.LookRotation(lookTarget);
284
-
305
+
306
+ HandleCharacterRotation(snapshot.lookVector);
307
+
285
308
  OnSetSnapshot?.Invoke(snapshot);
286
309
  }
287
310
 
@@ -370,13 +393,9 @@ namespace Code.Player.Character.MovementSystems.Character {
370
393
  var rootPosition = rb.position;
371
394
 
372
395
  // Apply rotation when ticking on the server. This rotation is automatically applied on the owning client in LateUpdate.
396
+ // and for observers in Interpolate()
373
397
  if (isServer && !isClient) {
374
- var lookTarget = new Vector3(command.lookVector.x, 0, command.lookVector.z);
375
- if (lookTarget == Vector3.zero) {
376
- lookTarget = new Vector3(0, 0, .01f);
377
- }
378
-
379
- airshipTransform.rotation = Quaternion.LookRotation(lookTarget);
398
+ HandleCharacterRotation(command.lookVector);
380
399
  }
381
400
 
382
401
  //Ground checks
@@ -554,7 +573,7 @@ namespace Code.Player.Character.MovementSystems.Character {
554
573
  var isMoving = currentVelocity.sqrMagnitude > .1f;
555
574
  var inAir = didJump || (!detectedGround && !currentMoveSnapshot.prevStepUp);
556
575
  var tryingToSprint = movementSettings.onlySprintForward
557
- ? command.sprint && graphicTransform.InverseTransformVector(command.moveDir).z > 0.1f
576
+ ? command.sprint && airshipTransform.InverseTransformVector(command.moveDir).z > 0.1f
558
577
  : //Only sprint if you are moving forward
559
578
  command.sprint && command.moveDir.magnitude > 0.1f; //Only sprint if you are moving
560
579
 
@@ -1422,11 +1441,6 @@ namespace Code.Player.Character.MovementSystems.Character {
1422
1441
  newLook.z = 0.01f;
1423
1442
  }
1424
1443
 
1425
- airshipTransform.rotation = Quaternion.Lerp(
1426
- Quaternion.LookRotation(oldLook),
1427
- Quaternion.LookRotation(newLook),
1428
- (float)delta);
1429
-
1430
1444
  lookVector = Vector3.Lerp(snapshotOld.lookVector, snapshotNew.lookVector, (float)delta);
1431
1445
 
1432
1446
  OnInterpolateState?.Invoke(snapshotOld, snapshotNew, delta);
@@ -1459,22 +1473,104 @@ namespace Code.Player.Character.MovementSystems.Character {
1459
1473
  }
1460
1474
 
1461
1475
  public void LateUpdate() {
1462
- // We only update rotation in late update if we are running on a client that is controlling
1463
- // this system
1476
+ // We only update rotation in late update if we are running on a client
1464
1477
  if (isServer && !isClient) {
1465
1478
  return;
1466
1479
  }
1467
1480
 
1468
- if (mode == NetworkedStateSystemMode.Observer) {
1469
- return;
1470
- }
1481
+ HandleCharacterRotation(this.lookVector);
1482
+ }
1471
1483
 
1472
- var lookTarget = new Vector3(lookVector.x, 0, lookVector.z);
1484
+ /**
1485
+ * Internal character rotation handling. Used in a few places to kick off updating the character's
1486
+ * transform and visual rotation based on the configuration of the character.
1487
+ */
1488
+ private void HandleCharacterRotation(Vector3 lookVector) {
1489
+ if (!rotateAutomatically) return;
1490
+ UpdateCharacterRotation(lookVector);
1491
+ }
1492
+
1493
+ /**
1494
+ * This function allows you to set the rotation of the character body and head when "rotateAutomatically" is turned off.
1495
+ * It will use the specified look direction and respect configured visual settings. This function should be called
1496
+ * in LateUpdate every frame with an updated look direction.
1497
+ *
1498
+ * Calling this only once will update the head and body rotation just enough to look in the specified direction.
1499
+ * It will not align the rotation exactly. If you want to align the rotation exactly. First set the airshipTransform
1500
+ * and graphicsHolder transforms to the desired look direction before calling this function. You can also use
1501
+ * UpdateHeadRotation to set a look direction for the head directly.
1502
+ *
1503
+ * This function is not networked.
1504
+ */
1505
+ public void UpdateCharacterRotation(Vector3 lookDirection) {
1506
+ var lookTarget = new Vector3(lookDirection.x, 0, lookDirection.z);
1473
1507
  if (lookTarget == Vector3.zero) {
1474
1508
  lookTarget = new Vector3(0, 0, .01f);
1475
1509
  }
1476
1510
 
1477
- airshipTransform.rotation = Quaternion.LookRotation(lookTarget).normalized;
1511
+ // Debug.Log("Handle rotation: " + rotateHeadToLookVector);
1512
+ if (!rotateHeadToLookVector) {
1513
+ airshipTransform.rotation = Quaternion.LookRotation(lookTarget).normalized;
1514
+ return;
1515
+ }
1516
+
1517
+ UpdateBodyRotation(lookTarget);
1518
+ UpdateHeadRotation(lookDirection);
1519
+ }
1520
+
1521
+ private void UpdateBodyRotation(Vector3 direction) {
1522
+ direction.y = 0; // Don't rotate the character off the ground
1523
+
1524
+ // If we are moving, start rotating towards the correct direction immediately. Don't negate any additional rotation
1525
+ if (this.currentMoveSnapshot.velocity.magnitude > 0) {
1526
+ airshipTransform.rotation = Quaternion.LookRotation(direction).normalized;
1527
+ graphicTransform.rotation = Quaternion.Slerp(graphicTransform.rotation, airshipTransform.rotation, smoothedRotationSpeed * Mathf.Deg2Rad * Time.deltaTime);
1528
+ return;
1529
+ }
1530
+
1531
+ // Since graphicTransform is a child of the airship transform, we "undo" the
1532
+ // change we are going to apply so that we can rotate the graphicTransform independently
1533
+ Quaternion previousParentRotation = airshipTransform.rotation;
1534
+ airshipTransform.rotation = Quaternion.LookRotation(direction).normalized;
1535
+ Quaternion deltaRotation = airshipTransform.rotation * Quaternion.Inverse(previousParentRotation);
1536
+ graphicTransform.rotation = Quaternion.Inverse(deltaRotation) * graphicTransform.rotation;
1537
+
1538
+ // Now calculate if we need to rotate the graphicTransform (body) or if the head
1539
+ // rotation will be enough.
1540
+ Vector3 currentForward = graphicTransform.rotation * Vector3.forward;
1541
+ currentForward.y = 0;
1542
+
1543
+ currentForward.Normalize();
1544
+ direction.Normalize();
1545
+
1546
+ float angle = Vector3.SignedAngle(currentForward, direction, Vector3.up);
1547
+ if (Mathf.Abs(angle) > headRotationThreshold)
1548
+ {
1549
+ float rotateAmount = Mathf.Abs(angle) - headRotationThreshold;
1550
+ float sign = Mathf.Sign(angle);
1551
+
1552
+ // We only rotate just enough to allow us to not snap our neck, but don't rotate the body
1553
+ // any more than that.
1554
+ Quaternion partialRotation = Quaternion.AngleAxis(rotateAmount * sign, Vector3.up);
1555
+ graphicTransform.rotation = partialRotation * graphicTransform.rotation;
1556
+ }
1557
+ }
1558
+
1559
+ /**
1560
+ * Sets the head look direction independently of the body using "Look Vector Influence". Does _NOT_ limit the
1561
+ * amount of rotation from the body forward direction. Use UpdateCharacterRotation for rotation that take the configured
1562
+ * limits into account.
1563
+ *
1564
+ * This function is not networked.
1565
+ */
1566
+ public void UpdateHeadRotation(Vector3 direction) {
1567
+ if (direction.magnitude == 0) {
1568
+ direction = new Vector3(0, 0, 0.01f);
1569
+ }
1570
+
1571
+ var characterUp = _rig.spine ? _rig.spine.up : Vector3.up;
1572
+ var lerpedLookRotation = Vector3.Lerp(Quaternion.Inverse(initialHeadRotation) * _rig.head.rotation * Vector3.forward, direction, lookVectorInfluence);
1573
+ _rig.head.rotation = initialHeadRotation * Quaternion.LookRotation(lerpedLookRotation, characterUp);
1478
1574
  }
1479
1575
 
1480
1576
  public void Update() {
@@ -1548,7 +1644,7 @@ namespace Code.Player.Character.MovementSystems.Character {
1548
1644
  moveDirInput = moveDir;
1549
1645
  break;
1550
1646
  case MoveDirectionMode.Character:
1551
- moveDirInput = graphicTransform.TransformDirection(moveDir);
1647
+ moveDirInput = airshipTransform.TransformDirection(moveDir);
1552
1648
  break;
1553
1649
  case MoveDirectionMode.Camera:
1554
1650
  var forwardZeroY = _cameraTransform.forward;
@@ -1625,7 +1721,7 @@ namespace Code.Player.Character.MovementSystems.Character {
1625
1721
  if (mode == NetworkedStateSystemMode.Input || (mode == NetworkedStateSystemMode.Authority && isClient)) {
1626
1722
  if (_smoothLookVector && moveDirInput != Vector3.zero) {
1627
1723
  lookVector = Vector3.RotateTowards(
1628
- graphicTransform.forward,
1724
+ airshipTransform.forward,
1629
1725
  moveDirInput.normalized,
1630
1726
  smoothedRotationSpeed * Mathf.Deg2Rad * Time.deltaTime,
1631
1727
  0f