Skip to content

Commit 12ae72d

Browse files
committed
Bunch of improvements and additions to make fast movement work more consistently
1 parent 4668831 commit 12ae72d

File tree

6 files changed

+120
-53
lines changed

6 files changed

+120
-53
lines changed

CHANGELOG.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3131
- New `SoundContainer` features.
3232
Lua property `Paused` (R/W) to pause or unpause all sounds of a SoundContainer. Newly played sounds will not begin playback until unpaused.
3333
Lua function `GetAudibleVolume` to get the real audible volume of a SoundContainer's sounds as a float from 0 to 1. This accounts for literally everything, including game volume.
34-
34+
35+
- New `LimbPath` features.
36+
New `LimbPath` INI property `ScaleMultiplier`, which is a vector. This will scale the X/Y axes of the limbpath accordingly, allowing for easily adjusting limbpaths to differently sized actors.
37+
New `LimbPath` INI property `SegmentEndedThreshold`, which defines the distance the limb must be from the end of the segment before it's considered complete. This defaults to 2.5.
38+
The `TravelSpeedMultiplier` property has been adjusted to work more consistently when interacting with script.
39+
3540
- New `AEmitter` and `PEmitter` INI and Lua (R/W) property `PlayBurstSound` which denotes whether the BurstSound should play when appropriate. This should not be confused for a trigger - it's just a enable/disable toggle to avoid having to remove and add BurstSound altogether.
3641

3742
- Allow lua scripts to use LuaJIT's BitOp module (see https://bitop.luajit.org/api.html)
@@ -40,10 +45,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
4045

4146
<details><summary><b>Changed</b></summary>
4247

43-
- Conquest activities will once again fall-back to using base dropships and rockets if a random selection of the selected tech's craft can't find one capable of carrying passengers and/or cargo.
44-
45-
- All music-related functionality from AudioMan has been removed due to the addition of the MusicMan. Generic DynamicSongs have been put in to use instead.
46-
Mod activities that used to queue up all the vanilla music should now instead call, for example, `MusicMan:PlayDynamicSong("Generic Battle Music")`
48+
- Improved navigation, making running and fast walkpaths much more consistent.
4749

4850
- Increased fog-of-war resolution in all vanilla activities, and conquest, from 20x20 to 4x4.
4951
The Ronin Scrambler, the basic scanner, and `SceneMan:CastUnseenRay` have been changed to accomodate fog-of-war resolutions as fine as 1x1 and as course as 20x20.
@@ -52,7 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5254
- All vanilla scenario activities have had their settings polished, respecting settings which make sense and disabling settings which don't.
5355
You can now have fog of war in the test scene, and can no longer require path to orbit in Zero-G Diggers-Only One Man Army.
5456

55-
- The Signal Hunt activity no longer has a preview image, as it was not formatted correctly and spoiled the interior structure of the cave.
57+
- Conquest activities will once again fall-back to using base dropships and rockets if a random selection of the selected tech's craft can't find one capable of carrying passengers and/or cargo.
5658

5759
- `MovableMan:OpenAllDoors()`, when passed `NOTEAM`, will now open/close doors specifically for `NOTEAM` (instead of all doors).
5860

@@ -74,6 +76,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7476

7577
</details>
7678

79+
<details><summary><b>Removed</b></summary>
80+
81+
- All music-related functionality from AudioMan has been removed due to the addition of the MusicMan. Generic DynamicSongs have been put in to use instead.
82+
Mod activities that used to queue up all the vanilla music should now instead call, for example, `MusicMan:PlayDynamicSong("Generic Battle Music")`
83+
84+
- The Signal Hunt activity no longer has a preview image, as it was not formatted correctly and spoiled the interior structure of the cave.
85+
86+
</details>
87+
7788
## [Release v6.2.2] - 2024/02/24
7889

7990
<details><summary><b>Added</b></summary>

Source/Entities/AHuman.cpp

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,8 +1372,13 @@ void AHuman::UpdateWalkAngle(AHuman::Layer whichLayer) {
13721372
const float maxAngleDegrees = 40.0F;
13731373
float terrainRotationDegs = std::clamp((hitPosRight - hitPosLeft).GetAbsDegAngle(), -maxAngleDegrees, maxAngleDegrees);
13741374

1375+
//const float fastMovementAngleReduction = 10.0F;
1376+
//const float ourMaxMovementSpeed = std::max(m_Paths[FGROUND][WALK].GetSpeed(), m_Paths[BGROUND][WALK].GetSpeed()) * 0.5F;
1377+
//float speedRotationDegs = Lerp(0.0F, ourMaxMovementSpeed, fastMovementAngleReduction, -fastMovementAngleReduction, m_Vel.m_X);
1378+
float speedRotationDegs = 0.0F; // TODO, this makes things stumble more although looks a bit better
1379+
13751380
Matrix walkAngle;
1376-
walkAngle.SetDegAngle(terrainRotationDegs);
1381+
walkAngle.SetDegAngle(terrainRotationDegs + speedRotationDegs);
13771382
m_WalkAngle[whichLayer] = walkAngle;
13781383
}
13791384
}
@@ -1409,12 +1414,6 @@ void AHuman::UpdateCrouching() {
14091414
float finalWalkPathYOffset = std::clamp(Lerp(0.0F, 1.0F, -m_WalkPathOffset.m_Y, desiredWalkPathYOffset, 0.3F), 0.0F, m_MaxWalkPathCrouchShift);
14101415
m_WalkPathOffset.m_Y = -finalWalkPathYOffset;
14111416

1412-
// If crouching, move at reduced speed
1413-
const float crouchSpeedMultiplier = 0.5F;
1414-
float travelSpeedMultiplier = Lerp(0.0F, m_MaxWalkPathCrouchShift, 1.0F, crouchSpeedMultiplier, -m_WalkPathOffset.m_Y);
1415-
m_Paths[FGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier);
1416-
m_Paths[BGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier);
1417-
14181417
// Adjust our X offset to try to keep our legs under our centre-of-mass
14191418
const float ratioBetweenBodyAndHeadToAimFor = 0.15F;
14201419
float predictedPosition = ((m_pHead->GetPos().m_X - m_Pos.m_X) * ratioBetweenBodyAndHeadToAimFor) + m_Vel.m_X;
@@ -1424,6 +1423,35 @@ void AHuman::UpdateCrouching() {
14241423
}
14251424
}
14261425

1426+
void AHuman::UpdateLimbPathSpeed() {
1427+
// Reset travel speed so limbpath GetSpeed() gives us sensible values
1428+
m_Paths[FGROUND][m_MoveState].SetTravelSpeedMultiplier(1.0F);
1429+
m_Paths[BGROUND][m_MoveState].SetTravelSpeedMultiplier(1.0F);
1430+
1431+
if (m_MoveState == WALK || m_MoveState == CRAWL) {
1432+
// If crouching, move at reduced speed
1433+
const float crouchSpeedMultiplier = 0.5F;
1434+
float travelSpeedMultiplier = Lerp(0.0F, m_MaxWalkPathCrouchShift, 1.0F, crouchSpeedMultiplier, -m_WalkPathOffset.m_Y);
1435+
1436+
// If we're moving slowly horizontally, move at reduced speed (otherwise our legs kick about wildly as we're not yet up to speed)
1437+
// Calculate a min multiplier that is based on the total walkpath speed (so a fast walkpath has a smaller multipler). This is so a slow walkpath gets up to speed faster
1438+
const float ourMaxMovementSpeed = std::max(m_Paths[FGROUND][m_MoveState].GetSpeed(), m_Paths[BGROUND][m_MoveState].GetSpeed()) * 0.5F;
1439+
const float minSpeed = 2.0F;
1440+
const float minMultiplier = minSpeed / ourMaxMovementSpeed;
1441+
travelSpeedMultiplier *= Lerp(0.0F, ourMaxMovementSpeed, minMultiplier, 1.0F, std::abs(m_Vel.m_X));
1442+
1443+
m_Paths[FGROUND][m_MoveState].SetTravelSpeedMultiplier(travelSpeedMultiplier);
1444+
m_Paths[BGROUND][m_MoveState].SetTravelSpeedMultiplier(travelSpeedMultiplier);
1445+
1446+
// Also extend our stride depending on speed
1447+
const float strideXMultiplier = Lerp(0.0F, ourMaxMovementSpeed, 0.8F, 1.2F, std::abs(m_Vel.m_X));
1448+
const float strideYMultiplier = Lerp(0.0F, ourMaxMovementSpeed, 0.9F, 1.1F, std::abs(m_Vel.m_X));
1449+
Vector scale = Vector(strideXMultiplier, strideYMultiplier);
1450+
m_Paths[FGROUND][m_MoveState].SetScaleMultiplier(scale);
1451+
m_Paths[BGROUND][m_MoveState].SetScaleMultiplier(scale);
1452+
}
1453+
}
1454+
14271455
void AHuman::PreControllerUpdate() {
14281456
ZoneScoped;
14291457

@@ -1947,6 +1975,8 @@ void AHuman::PreControllerUpdate() {
19471975
UpdateWalkAngle(BGROUND);
19481976
}
19491977

1978+
UpdateLimbPathSpeed();
1979+
19501980
// WALKING, OR WE ARE JETPACKING AND STUCK
19511981
if (m_MoveState == WALK || (m_MoveState == JUMP && isStill)) {
19521982
m_Paths[FGROUND][STAND].Terminate();

Source/Entities/AHuman.h

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -415,13 +415,6 @@ namespace RTE {
415415
/// held dormant in an inventory.
416416
void ResetAllTimers() override;
417417

418-
/// Detects slopes in terrain and updates the walk path rotation for the corresponding Layer accordingly.
419-
/// @param whichLayer The Layer in question.
420-
void UpdateWalkAngle(AHuman::Layer whichLayer);
421-
422-
/// Detects overhead ceilings and crouches for them.
423-
void UpdateCrouching();
424-
425418
/// Gets the walk path rotation for the specified Layer.
426419
/// @param whichLayer The Layer in question.
427420
/// @return The walk angle in radians.
@@ -570,6 +563,16 @@ namespace RTE {
570563
/// @param progressScalar A normalized scalar that determines the magnitude of the reticle, to indicate force in the throw.
571564
void DrawThrowingReticle(BITMAP* targetBitmap, const Vector& targetPos = Vector(), float progressScalar = 1.0F) const;
572565

566+
/// Detects slopes in terrain and updates the walk path rotation for the corresponding Layer accordingly.
567+
/// @param whichLayer The Layer in question.
568+
void UpdateWalkAngle(AHuman::Layer whichLayer);
569+
570+
/// Detects overhead ceilings and crouches for them.
571+
void UpdateCrouching();
572+
573+
/// Updates our limbpath speed based on our current movement speed and style (crouching, walking etc).
574+
void UpdateLimbPathSpeed();
575+
573576
// Member variables
574577
static Entity::ClassInfo m_sClass;
575578
// Articulated head.

Source/Entities/AtomGroup.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,11 +1268,12 @@ bool AtomGroup::PushAsLimb(const Vector& jointPos, const Vector& velocity, const
12681268
owner->GetController()->IsState(MOVE_RIGHT) && pushImpulse.m_X < 0.0F;
12691269
if (againstTravelDirection) {
12701270
// Filter some of our impulse out. We're pushing against an obstacle, but we don't want to kick backwards!
1271-
const float againstIntendedDirectionMultiplier = 0.5F;
1271+
const float againstIntendedDirectionMultiplier = 0.3F;
12721272

12731273
if (!owner->GetController()->IsState(BODY_CROUCH) && !owner->GetController()->IsState(MOVE_DOWN)) {
12741274
// Translate it into to upwards motion to step over what we're walking into instead ;)
1275-
pushImpulse.m_Y -= std::abs(pushImpulse.m_X * (1.0F - againstIntendedDirectionMultiplier));
1275+
// Disabled just now.. causes us to jump up at high speeds
1276+
//pushImpulse.m_Y -= std::abs(pushImpulse.m_X * (1.0F - againstIntendedDirectionMultiplier));
12761277
}
12771278

12781279
pushImpulse.m_X *= againstIntendedDirectionMultiplier;

Source/Entities/LimbPath.cpp

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ void LimbPath::Clear() {
2727
for (int i = 0; i < SPEEDCOUNT; ++i) {
2828
m_TravelSpeed[i] = 0.0;
2929
}
30-
m_TravelSpeedMultiplier = 1.0F;
30+
m_SegmentEndedThreshold = 2.5F;
31+
m_BaseTravelSpeedMultiplier = 1.0F;
32+
m_CurrentTravelSpeedMultiplier = 1.0F;
33+
m_BaseScaleMultiplier = Vector(1.0F, 1.0F);
34+
m_CurrentScaleMultiplier = Vector(1.0F, 1.0F);
3135
m_WhichSpeed = NORMAL;
3236
m_PushForce = 0.0;
3337
m_JointPos.Reset();
@@ -103,7 +107,11 @@ int LimbPath::Create(const LimbPath& reference) {
103107
for (int i = 0; i < SPEEDCOUNT; ++i) {
104108
m_TravelSpeed[i] = reference.m_TravelSpeed[i];
105109
}
106-
m_TravelSpeedMultiplier = reference.m_TravelSpeedMultiplier;
110+
m_SegmentEndedThreshold = reference.m_SegmentEndedThreshold;
111+
m_BaseTravelSpeedMultiplier = reference.m_BaseTravelSpeedMultiplier;
112+
m_CurrentTravelSpeedMultiplier = reference.m_CurrentTravelSpeedMultiplier;
113+
m_BaseScaleMultiplier = reference.m_BaseScaleMultiplier;
114+
m_CurrentScaleMultiplier = reference.m_CurrentScaleMultiplier;
107115
m_PushForce = reference.m_PushForce;
108116
m_TimeLeft = reference.m_TimeLeft;
109117
m_TotalLength = reference.m_TotalLength;
@@ -132,6 +140,7 @@ int LimbPath::ReadProperty(const std::string_view& propName, Reader& reader) {
132140
}
133141
});
134142
MatchProperty("EndSegCount", { reader >> m_FootCollisionsDisabledSegment; });
143+
MatchProperty("SegmentEndedThreshold", { reader >> m_SegmentEndedThreshold; });
135144

136145
MatchProperty("SlowTravelSpeed", {
137146
reader >> m_TravelSpeed[SLOW];
@@ -145,7 +154,8 @@ int LimbPath::ReadProperty(const std::string_view& propName, Reader& reader) {
145154
reader >> m_TravelSpeed[FAST];
146155
// m_TravelSpeed[FAST] = m_TravelSpeed[FAST] * 2;
147156
});
148-
MatchProperty("TravelSpeedMultiplier", { reader >> m_TravelSpeedMultiplier; });
157+
MatchProperty("TravelSpeedMultiplier", { reader >> m_BaseTravelSpeedMultiplier; });
158+
MatchProperty("ScaleMultiplier", { reader >> m_BaseScaleMultiplier; });
149159
MatchProperty("PushForce", {
150160
reader >> m_PushForce;
151161
// m_PushForce = m_PushForce / 1.5;
@@ -162,24 +172,21 @@ Vector LimbPath::RotatePoint(const Vector& point) const {
162172
int LimbPath::Save(Writer& writer) const {
163173
Entity::Save(writer);
164174

165-
writer.NewProperty("StartOffset");
166-
writer << m_Start;
167-
writer.NewProperty("StartSegCount");
168-
writer << m_StartSegCount;
175+
writer.NewPropertyWithValue("StartOffset", m_Start);
176+
writer.NewPropertyWithValue("StartSegCount", m_StartSegCount);
169177
for (std::deque<Vector>::const_iterator itr = m_Segments.begin(); itr != m_Segments.end(); ++itr) {
170178
writer.NewProperty("AddSegment");
171179
writer << *itr;
172180
}
173-
writer.NewProperty("SlowTravelSpeed");
174-
writer << m_TravelSpeed[SLOW];
175-
writer.NewProperty("NormalTravelSpeed");
176-
writer << m_TravelSpeed[NORMAL];
177-
writer.NewProperty("FastTravelSpeed");
178-
writer << m_TravelSpeed[FAST];
179-
writer.NewProperty("TravelSpeedMultiplier");
180-
writer << m_TravelSpeedMultiplier;
181-
writer.NewProperty("PushForce");
182-
writer << m_PushForce;
181+
writer.NewPropertyWithValue("EndSegCount", m_FootCollisionsDisabledSegment);
182+
writer.NewPropertyWithValue("SegmentEndedThreshold", m_SegmentEndedThreshold);
183+
184+
writer.NewPropertyWithValue("SlowTravelSpeed", m_TravelSpeed[SLOW]);
185+
writer.NewPropertyWithValue("NormalTravelSpeed", m_TravelSpeed[NORMAL]);
186+
writer.NewPropertyWithValue("FastTravelSpeed", m_TravelSpeed[FAST]);
187+
writer.NewPropertyWithValue("TravelSpeedMultiplier", m_BaseTravelSpeedMultiplier);
188+
writer.NewPropertyWithValue("ScaleMultiplier", m_BaseScaleMultiplier);
189+
writer.NewPropertyWithValue("PushForce", m_PushForce);
183190

184191
return 0;
185192
}
@@ -214,7 +221,7 @@ Vector LimbPath::GetProgressPos() {
214221
Vector LimbPath::GetCurrentSegTarget() {
215222
Vector returnVec(m_Start);
216223
if (IsStaticPoint()) {
217-
return m_JointPos + RotatePoint(returnVec);
224+
return m_JointPos + (RotatePoint(returnVec) * GetScaleMultiplier());
218225
}
219226

220227
std::deque<Vector>::const_iterator itr;
@@ -227,13 +234,13 @@ Vector LimbPath::GetCurrentSegTarget() {
227234
returnVec += *m_CurrentSegment;
228235
}
229236

230-
return m_JointPos + RotatePoint(returnVec);
237+
return m_JointPos + (RotatePoint(returnVec) * GetScaleMultiplier());
231238
}
232239

233240
Vector LimbPath::GetCurrentVel(const Vector& limbPos) {
234241
Vector returnVel;
235242
Vector distVect = g_SceneMan.ShortestDistance(limbPos, GetCurrentSegTarget());
236-
float adjustedTravelSpeed = (m_TravelSpeed[m_WhichSpeed] / (1.0F + std::abs(m_JointVel.GetY()) * 0.1F)) * m_TravelSpeedMultiplier;
243+
float adjustedTravelSpeed = (m_TravelSpeed[m_WhichSpeed] / (1.0F + std::abs(m_JointVel.GetY()) * 0.1F)) * GetTravelSpeedMultiplier();
237244

238245
if (IsStaticPoint()) {
239246
returnVel = distVect * c_MPP / 0.020 /* + m_JointVel*/;
@@ -290,9 +297,7 @@ void LimbPath::ReportProgress(const Vector& limbPos) {
290297
float distance = distVec.GetMagnitude();
291298
float segMag = (*(m_CurrentSegment)).GetMagnitude();
292299

293-
// TODO: Don't hardcode this!")
294-
const float segmentEndedThreshold = 1.5F;
295-
if (distance < segmentEndedThreshold) {
300+
if (distance < m_SegmentEndedThreshold) {
296301
if (++(m_CurrentSegment) == m_Segments.end()) {
297302
--(m_CurrentSegment);
298303
// Get normalized progress measure toward the target.
@@ -301,7 +306,7 @@ void LimbPath::ReportProgress(const Vector& limbPos) {
301306
}
302307
// Next segment!
303308
else {
304-
m_SegProgress = 0.0;
309+
m_SegProgress = 0.0F;
305310
m_SegTimer.Reset();
306311
m_Ended = false;
307312
}

Source/Entities/LimbPath.h

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ namespace RTE {
130130

131131
/// Gets the speed that a limb traveling this LimbPath should have.
132132
/// @return A float describing the speed in m/s.
133-
float GetSpeed() const { return m_TravelSpeed[m_WhichSpeed] * m_TravelSpeedMultiplier; }
133+
float GetSpeed() const { return m_TravelSpeed[m_WhichSpeed] * GetTravelSpeedMultiplier(); }
134134

135135
/// Gets the speed that a limb traveling this LimbPath should have for the specified preset.
136136
/// @param speedPreset Predefined speed preset to set the value for.
@@ -144,11 +144,19 @@ namespace RTE {
144144

145145
/// Sets the current travel speed multiplier.
146146
/// @param newValue The new travel speed multiplier.
147-
void SetTravelSpeedMultiplier(float newValue) { m_TravelSpeedMultiplier = newValue; }
147+
void SetTravelSpeedMultiplier(float newValue) { m_CurrentTravelSpeedMultiplier = newValue; }
148148

149149
/// Gets the current travel speed multiplier.
150150
/// @return The current travel speed multiplier.
151-
float GetTravelSpeedMultiplier() const { return m_TravelSpeedMultiplier; }
151+
float GetTravelSpeedMultiplier() const { return m_BaseTravelSpeedMultiplier * m_CurrentTravelSpeedMultiplier; }
152+
153+
/// Sets the current scale multiplier.
154+
/// @param newValue The new scale multiplier.
155+
void SetScaleMultiplier(Vector newValue) { m_CurrentScaleMultiplier = newValue; }
156+
157+
/// Gets the current scale multiplier.
158+
/// @return The current scale multiplier.
159+
Vector GetScaleMultiplier() const { return m_BaseScaleMultiplier * m_CurrentScaleMultiplier; }
152160

153161
/// Gets the force that a limb traveling this LimbPath can push against
154162
/// stuff in the scene with. It will increase to the double if progress
@@ -173,12 +181,12 @@ namespace RTE {
173181
/// Gets the total time that this entire path should take to travel along
174182
/// with the current speed setting, including the start segments.
175183
/// @return The total time (ms) this should take to travel along, if unobstructed.
176-
float GetTotalPathTime() const { return ((m_TotalLength * c_MPP) / (m_TravelSpeed[m_WhichSpeed] * m_TravelSpeedMultiplier)) * 1000; }
184+
float GetTotalPathTime() const { return ((m_TotalLength * c_MPP * GetScaleMultiplier().GetMagnitude()) / (m_TravelSpeed[m_WhichSpeed] * GetTravelSpeedMultiplier())) * 1000; }
177185

178186
/// Gets the total time that this path should take to travel along
179187
/// with the current speed setting, NOT including the start segments.
180188
/// @return The total time (ms) this should take to travel along, if unobstructed.
181-
float GetRegularPathTime() const { return ((m_RegularLength * c_MPP) / (m_TravelSpeed[m_WhichSpeed] * m_TravelSpeedMultiplier)) * 1000; }
189+
float GetRegularPathTime() const { return ((m_RegularLength * c_MPP * GetScaleMultiplier().GetMagnitude()) / (m_TravelSpeed[m_WhichSpeed] * GetTravelSpeedMultiplier())) * 1000; }
182190

183191
/// Gets the ratio of time since the path was restarted and the total time
184192
/// it should take to travel along the path with the current speed setting,
@@ -381,8 +389,17 @@ namespace RTE {
381389
// The constant speed that the limb traveling this path has in m/s.
382390
float m_TravelSpeed[SPEEDCOUNT];
383391

384-
// The current travel speed multiplier
385-
float m_TravelSpeedMultiplier;
392+
// How close we must get to the end of each segment to consider it finished
393+
float m_SegmentEndedThreshold;
394+
395+
// The base/current travel speed multiplier
396+
float m_BaseTravelSpeedMultiplier;
397+
float m_CurrentTravelSpeedMultiplier;
398+
399+
// The base/current scale multiplier (we extend the walkpath when running fast to take longer strides)
400+
// This is a vector to allow scaling on seperate axis
401+
Vector m_BaseScaleMultiplier;
402+
Vector m_CurrentScaleMultiplier;
386403

387404
// The current speed setting.
388405
int m_WhichSpeed;

0 commit comments

Comments
 (0)