diff --git a/Activities/GameActivity.cpp b/Activities/GameActivity.cpp index 9e13b8d37..a8e401356 100644 --- a/Activities/GameActivity.cpp +++ b/Activities/GameActivity.cpp @@ -1562,8 +1562,7 @@ void GameActivity::Update() m_ControlledActor[player]->GetController()->SetDisabled(true); // Player is done setting waypoints - if (m_PlayerController[player].IsState(PRESS_SECONDARY)) - { + if (m_PlayerController[player].IsState(PRESS_SECONDARY) || m_PlayerController[player].IsState(ACTOR_NEXT_PREP) || m_PlayerController[player].IsState(ACTOR_PREV_PREP)) { // Stop drawing the waypoints // m_ControlledActor[player]->DrawWaypoints(false); // Update the player's move path now to the first waypoint set @@ -1629,8 +1628,7 @@ void GameActivity::Update() m_ControlledActor[player]->GetController()->SetDisabled(true); // Player is done setting waypoints - if (m_PlayerController[player].IsState(PRESS_SECONDARY)) - { + if (m_PlayerController[player].IsState(PRESS_SECONDARY) || m_PlayerController[player].IsState(ACTOR_NEXT_PREP) || m_PlayerController[player].IsState(ACTOR_PREV_PREP)) { // Give player control back to actor m_ControlledActor[player]->GetController()->SetDisabled(false); // Switch back to normal view @@ -2112,7 +2110,7 @@ void GameActivity::Update() g_FrameMan.SetScreenText("Your order has arrived!", ScreenOfPlayer(player), 333); m_MessageTimer[player].Reset(); - pDeliveryCraft->ResetEmissionTimers(); // Reset the engine timers so they don't emit a massive burst after being added to the world + pDeliveryCraft->ResetAllTimers(); pDeliveryCraft->Update(); // Add the delivery craft to the world, TRANSFERRING OWNERSHIP diff --git a/CHANGELOG.md b/CHANGELOG.md index d76819aab..4a146433c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] -
Added +
**Added** - Executable can be compiled as 64bit. @@ -149,6 +149,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added `MovableObject` Lua function `EnableOrDisableAllScripts` that allows you to enable or disable all scripts on a `MovableObject` based on the passed in value. +- Added `AEmitter` and `PEmitter` Lua (R/W) properties `NegativeThrottleMultiplier` and `PositiveThrottleMultiplier` that affect the emission rate relative to throttle. + - Added `Attachable` Lua function and INI property `InheritsFrame` which lets `Attachables` inherit their parent's frame. It is set to false by default. - Added `MovableObject` Lua (R/W) and INI properties `ApplyWoundDamageOnCollision` and `ApplyWoundBurstDamageOnCollision` which allow `MovableObject`s to apply the `EntryWound` damage/burst damage that would occur when they penetrate another object, without actually creating a wound. @@ -179,15 +181,73 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `Settings.ini` property `ShowEnemyHUD` which allows disabling of enemy actor HUD in its entirety. -- New `DataModule` property `IsFaction = 0/1` which determines if a module is a playable faction (in MetaGame, etc.). This replaces the need to put "Tech" in the module name. Defaults to false (0). +- New `DataModule` INI and Lua (R) property `IsFaction` which determines whether a module is a playable faction (in MetaGame, etc.). This replaces the need to put "Tech" in the module name. Defaults to false (0). + +- New `MOSRotating` INI and Lua (R) property `WoundCountAffectsImpulseLimitRatio` which can be used to make objects more prone to gibbing from impulse when they have also received wounds. + +- New `Gib` INI property `SpreadMode` which sports two new spread logic variants which alter the way velocity is applied to the `GibParticle`s when they spawn. This can be used to create richer explosion effects. + `SpreadMode = 0` is the default, fully randomized spread according to `MinVelocity`, `MaxVelocity` and `Spread` values. Think: a piece of grenade fragment, launching out in an arbitrary direction. + `SpreadMode = 1` is the same as the default, but with evenly spaced out angles. Think: an air blast shockwave, dispersing evenly outward from the explosion. + `SpreadMode = 2` has an entirely different behavior of its own, which utilizes the fermat spiral as means to evenly disperse the particles in a circular area, according to `MaxVelocity` and `MinVelocity`. Since this mode will always result in a full, 360-degree spread, the `Spread` property can be used to add randomization to the gib particles. Think: a cloud of smoke. + +- New `Actor` INI and Lua (R/W) property `StableRecoverDelay` which determines how long it takes for an actor to regain `STABLE` status after being rendered `UNSTABLE`. + +- New `AHuman` Lua (R) property `ThrowProgress` which returns the current throw chargeup progress as a scalar from 0 to 1. + +- New `HDFirearm` INI and Lua (R/W) property `ShellVelVariation` which can be used to randomize the magnitude at which shells are ejected. + +- New `HDFirearm` Lua (R) property `ReloadProgress` which returns the current reload progress as a scalar from 0 to 1. + +- New `HDFirearm` INI and Lua (R/W) property `Reloadable` which can be used to disable the ability to reload said device. + +- New `HDFirearm` Lua (R) property `RoundInMagCapacity` which returns the maximum capacity of the `Magazine` or, if there's not currently a `Magazine`, the maximum capacity of the next `Magazine`. + This means that the property will always return the maximum ammo capacity of the device, even when reloading. + +- New `Entity` Lua (R) property `ModuleName` which returns the filename of the data module from which the entity originates from. + +- `Arm`s will now react to the recoil of `HeldDevice`s. This is affected by the `Arm`'s `GripStrength` and the `HeldDevice`'s `RecoilTransmission`, in the same way as recoil itself. + +- `HDFirearm` reload progress now shows up as a HUD element. + +- New `Round` INI property `LifeVariation` which can be used to randomize the `Lifetime` of shot particles. + +- Exposed `MOSprite` property `PrevRotAngle` to Lua (R). + +- New `ACraft` INI and Lua (R/W) property `ScuttleOnDeath` which can be used to disable the automatic self-destruct sequence when the craft's health drops down to zero. + +- New `Settings.ini` property `UnheldItemsHUDDisplayRange = numPixels` that hides the HUD of stranded items at a set distance. Default is 500 (25 meters). + Value of -1 or anything below means all HUDs will be hidden and the only indication an item can be picked up will be on the Actor's HUD when standing on top of it. + Value of 0 means there is no range limit and all items on Scene will display the pick-up HUD. + Valid range values are 1-1000, anything above will be considered as no range limit. - Various improvements to the Buy Menu. You can now navigate tabs with the actor swap buttons, and the menu will smartly navigate when you add an `Actor` to your shop list, so you can quickly select weapons, etc.. There is also a new `Settings.ini` property, `SmartBuyMenuNavigation = 0/1`, which allows you to turn off this smart buy menu navigation, in case you prefer not to have it. +- Exposed `ACraft` property `HatchDelay` to Lua (R/W). +
+
**Changed** + +- `ACRocket`s can now function without a full set of thrusters. This also means that "Null Emitter" thrusters are no longer required for rockets. + +- Changed `MOSprite` property `SpriteAnimMode` `Enum` `LOOPWHENMOVING` to `LOOPWHENACTIVE` as it also describes active devices. + +- Changed `Activity` Lua (R) properties `Running`, `Paused` and `ActivityOver` to `IsRunning`, `IsPaused` and `IsOver` respectively. (NOTE: corresponding `ActivityMan` functions remain unchanged) + +- Exposed `ThrownDevice` properties `StartThrowOffset` and `EndThrowOffset` to Lua (R/W). -
Changed +- `HeldDevice`s can now show up as "Tools" in the buy menu, rather than just as "Shields". + +- Keyboard-only controlled `AHuman`s and `ACrab`s can now strafe while sharp-aiming. + +- Lowered the default `AHuman` Head damage multiplier from 5 to 4. + +- "Fixed" grenades and other fast-moving objects bouncing violently off of doors and other stationary objects. + +- `AEmitter` and `PEmitter` throttle logic has changed: + The properties `MinThrottleRange` and `MaxThrottleRange` have been changed to `NegativeThrottleMultiplier` and `PositiveThrottleMultiplier` respectively. + The new logic uses the multipliers to multiply the emission rate relative to the absolute throttle value. `NegativeThrottleMultiplier` is used when throttle is negative, and vice versa. - Doors in `Team = -1` will now open up for all actors. @@ -205,7 +265,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Craft will now automatically scuttle when opening doors at a 90° angle rather than 45°. -- `AHuman` can now aim slightly while walking, however not while reloading. +- `AHuman`s can now sharp-aim slightly while walking, however not while reloading. - Recoil when firing weapons now affects sharp aim. @@ -313,11 +373,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Placing "Tech" in a `DataModule`'s `ModuleName` no longer makes the module a playable faction (in MetaGame, etc.). The `IsFaction` property should be used instead. The word "Tech" will also not be omitted from the module name when displayed in any faction selection dropdown list. + +- Renamed Lua methods `GetRadRotated` and `GetDegRotated` to `GetRadRotatedCopy` and `GetDegRotatedCopy` for clarity.
-
Fixed +
**Fixed** + +- Fixed the logic for `Gib` and `Emission` property `LifeVariation` where it would round down to zero, giving particles infinite lifetime. - Fixed legs going bonkers for one frame when turning around. @@ -370,7 +434,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Removed +
**Removed** - Removed obsolete graphics drivers and their `Settings.ini` properties `ForceOverlayedWindowGfxDriver` and `ForceNonOverlayedWindowGfxDriver`. @@ -404,7 +468,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [0.1.0 pre-release 3.0][0.1.0-pre3.0] - 2020/12/25 -
Added +
**Added** - Implemented Lua Just-In-Time compilation (MoonJIT 2.2.0). @@ -570,7 +634,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Changed +
**Changed** - Codebase now uses the C++17 standard. @@ -679,7 +743,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Fixed +
**Fixed** - Fix crash when returning to `MetaGame` scenario screen after activity end. @@ -710,7 +774,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Removed +
**Removed** - Removed the ability to remove scripts from objects with Lua. This is no longer needed cause of code efficiency increases. @@ -738,7 +802,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [0.1.0 pre-release 2][0.1.0-pre2] - 2020/05/08 -
Added +
**Added** - Lua binding for `Box::IntersectsBox(otherBox)`, that returns true if 2 boxes intersect. @@ -859,7 +923,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Changed +
**Changed** - Codebase now uses the C++14 standard. @@ -916,7 +980,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Fixed +
**Fixed** - Fixed LuaBind being all sorts of messed up. All lua bindings now work properly like they were before updating to the v141 toolset. @@ -939,7 +1003,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Removed +
**Removed** - Removed all Gorilla Audio and SDL Mixer related code and files. @@ -966,7 +1030,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [0.1.0 pre-release 1][0.1.0-pre1] - 2020/01/27 -
Added +
**Added** - You can now run the game with command line parameters, including `-h` to see help and `-c` to send ingame console input to cout. @@ -995,7 +1059,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Changed +
**Changed** - `ACrab` aim limits now adjust to crab body rotation. @@ -1020,7 +1084,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Fixed +
**Fixed** - SFX slider now works properly. @@ -1039,7 +1103,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-
Removed +
**Removed** - All licensing-related code has been removed since it's no longer needed. diff --git a/Entities/ACDropShip.cpp b/Entities/ACDropShip.cpp index 99ac79036..3b2cf9430 100644 --- a/Entities/ACDropShip.cpp +++ b/Entities/ACDropShip.cpp @@ -43,7 +43,6 @@ void ACDropShip::Clear() m_LateralControl = 0; m_LateralControlSpeed = 6.0f; m_AutoStabilize = 1; - m_ScuttleIfFlippedTime = 4000; m_MaxEngineAngle = 20.0f; } @@ -106,7 +105,6 @@ int ACDropShip::Create(const ACDropShip &reference) { m_LateralControl = reference.m_LateralControl; m_LateralControlSpeed = reference.m_LateralControlSpeed; m_AutoStabilize = reference.m_AutoStabilize; - m_ScuttleIfFlippedTime = reference.m_ScuttleIfFlippedTime; m_MaxEngineAngle = reference.m_MaxEngineAngle; @@ -139,8 +137,6 @@ int ACDropShip::ReadProperty(const std::string_view &propName, Reader &reader) { reader >> m_HatchSwingRange; } else if (propName == "AutoStabilize") { reader >> m_AutoStabilize; - } else if (propName == "ScuttleIfFlippedTime") { - reader >> m_ScuttleIfFlippedTime; } else if (propName == "MaxEngineAngle") { reader >> m_MaxEngineAngle; } else if (propName == "LateralControlSpeed") { @@ -179,8 +175,6 @@ int ACDropShip::Save(Writer &writer) const writer << m_HatchSwingRange; writer.NewProperty("AutoStabilize"); writer << m_AutoStabilize; - writer.NewProperty("ScuttleIfFlippedTime"); - writer << m_ScuttleIfFlippedTime; writer.NewProperty("MaxEngineAngle"); writer << m_MaxEngineAngle; writer.NewProperty("LateralControlSpeed"); @@ -683,69 +677,6 @@ void ACDropShip::Update() ///////////////////////////////////////////////// // Update MovableObject, adds on the forces etc, updated viewpoint ACraft::Update(); - - /////////////////////////////////// - // Explosion logic - - if (m_Status == DEAD) - GibThis(); - - //////////////////////////////////////// - // Balance stuff - - // Get the rotation in radians. - float rot = m_Rotation.GetRadAngle(); - - // Eliminate rotations over half a turn - if (std::fabs(rot) > c_PI) { - rot += (rot > 0) ? -c_TwoPI : c_TwoPI; - m_Rotation.SetRadAngle(rot); - } - // If tipped too far for too long, die - if (rot < c_HalfPI && rot > -c_HalfPI) - { - m_FlippedTimer.Reset(); - } - // Start death process if tipped over for too long - else if (m_ScuttleIfFlippedTime >= 0 && m_FlippedTimer.IsPastSimMS(m_ScuttleIfFlippedTime) && m_Status != DYING) // defult is 4s - { - m_Status = DYING; - m_DeathTmr.Reset(); - } - - // Flash if dying, warning of impending explosion - if (m_Status == DYING) - { - if (m_DeathTmr.IsPastSimMS(500) && m_DeathTmr.AlternateSim(100)) - FlashWhite(10); - } - -/* -// rot = fabs(rot) < c_QuarterPI ? rot : (rot > 0 ? c_QuarterPI : -c_QuarterPI); - - // Rotational balancing spring calc - if (m_Status == STABLE) { - // Break the spring if close to target angle. - if (fabs(rot) > 0.1) - m_AngularVel -= rot * fabs(rot); - else if (fabs(m_AngularVel) > 0.1) - m_AngularVel *= 0.5; - } - // Unstable, or without balance - else if (m_Status == DYING) { -// float rotTarget = rot > 0 ? c_HalfPI : -c_HalfPI; - float rotTarget = c_HalfPI; - float rotDiff = rotTarget - rot; - if (fabs(rotDiff) > 0.1) - m_AngularVel += rotDiff * rotDiff; - else - m_Status = DEAD; - -// else if (fabs(m_AngularVel) > 0.1) -// m_AngularVel *= 0.5; - } - m_Rotation.SetRadAngle(rot); -*/ } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -868,27 +799,4 @@ void ACDropShip::SetLeftHatch(Attachable *newHatch) { } } -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - -////////////////////////////////////////////////////////////////////////////////////////// -// Method: ResetEmissionTimers -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Reset the timers of all emissions so they will start/stop at the -// correct relative offsets from now. - -void ACDropShip::ResetEmissionTimers() -{ - if (m_pRThruster && m_pRThruster->IsAttached()) - m_pRThruster->ResetEmissionTimers(); - - if (m_pLThruster && m_pLThruster->IsAttached()) - m_pLThruster->ResetEmissionTimers(); - - if (m_pURThruster && m_pURThruster->IsAttached()) - m_pURThruster->ResetEmissionTimers(); - - if (m_pULThruster && m_pULThruster->IsAttached()) - m_pULThruster->ResetEmissionTimers(); -} } // namespace RTE diff --git a/Entities/ACDropShip.h b/Entities/ACDropShip.h index 8b2ee19f0..0c37b4598 100644 --- a/Entities/ACDropShip.h +++ b/Entities/ACDropShip.h @@ -249,17 +249,6 @@ ClassInfoGetters; void SetRightHatch(Attachable *newHatch); -////////////////////////////////////////////////////////////////////////////////////////// -// Virtual method: ResetEmissionTimers -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Reset the timers of all emissions so they will start/stop at the -// correct relative offsets from now. -// Arguments: None. -// Return value: None. - - void ResetEmissionTimers() override; - - ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: GetMaxEngineAngle ////////////////////////////////////////////////////////////////////////////////////////// @@ -342,8 +331,6 @@ ClassInfoGetters; // Automatically stabilize the craft with the upper thrusters? Defaults to yes. int m_AutoStabilize; - // The craft explodes if it has been on its side for more than this many MS (default 4000). Disable by setting to -1. - float m_ScuttleIfFlippedTime; // Maximum engine rotation in degrees float m_MaxEngineAngle; diff --git a/Entities/ACRocket.cpp b/Entities/ACRocket.cpp index b4499231e..76f0bc3c1 100644 --- a/Entities/ACRocket.cpp +++ b/Entities/ACRocket.cpp @@ -50,7 +50,6 @@ void ACRocket::Clear() m_pURThruster = 0; m_pULThruster = 0; m_GearState = RAISED; - m_ScuttleIfFlippedTime = 4000; for (int i = 0; i < GearStateCount; ++i) { m_Paths[RIGHT][i].Reset(); m_Paths[LEFT][i].Reset(); @@ -146,8 +145,6 @@ int ACRocket::Create(const ACRocket &reference) { m_Paths[LEFT][i].Create(reference.m_Paths[LEFT][i]); } - m_ScuttleIfFlippedTime = reference.m_ScuttleIfFlippedTime; - return 0; } @@ -193,8 +190,6 @@ int ACRocket::ReadProperty(const std::string_view &propName, Reader &reader) { reader >> m_Paths[RIGHT][LOWERING]; } else if (propName == "RaisingGearLimbPath") { reader >> m_Paths[RIGHT][RAISING]; - } else if (propName == "ScuttleIfFlippedTime") { - reader >> m_ScuttleIfFlippedTime; } else { return ACraft::ReadProperty(propName, reader); } @@ -239,8 +234,6 @@ int ACRocket::Save(Writer &writer) const writer << m_Paths[RIGHT][LOWERING]; writer.NewProperty("RaisingGearLimbPath"); writer << m_Paths[RIGHT][RAISING]; - writer.NewProperty("ScuttleIfFlippedTime"); - writer << m_ScuttleIfFlippedTime; return 0; } @@ -499,210 +492,82 @@ void ACRocket::Update() ///////////////////////////////// // Controller update and handling -// TODO: Improve and make optional thrusters more robust! - // Make sure we have all thrusters - if (m_pMThruster && m_pRThruster && m_pLThruster && m_pURThruster && m_pULThruster) - { - if (m_Status != DEAD && m_Status != DYING) - { - // Fire main thrusters - if (m_Controller.IsState(MOVE_UP) || m_Controller.IsState(AIM_UP)) - { - if (!m_pMThruster->IsEmitting()) - { - m_pMThruster->TriggerBurst(); - // This is to make sure se get loose from being sideways stuck - m_ForceDeepCheck = true; - } - m_pMThruster->EnableEmission(true); - // Engines are noisy! - m_pMThruster->AlarmOnEmit(m_Team); - - if (m_HatchState == OPEN) { - CloseHatch(); - m_HatchTimer.Reset(); - } - } - else - { - m_pMThruster->EnableEmission(false); - - // Fire reverse thrusters - if (m_Controller.IsState(MOVE_DOWN) || m_Controller.IsState(AIM_DOWN)) - { - if (!m_pURThruster->IsEmitting()) - m_pURThruster->TriggerBurst(); - if (!m_pULThruster->IsEmitting()) - m_pULThruster->TriggerBurst(); - m_pURThruster->EnableEmission(true); - m_pULThruster->EnableEmission(true); - } - else - { - m_pURThruster->EnableEmission(false); - m_pULThruster->EnableEmission(false); - } - } - - // Fire left thrusters - if (m_Controller.IsState(MOVE_RIGHT)/* || m_Rotation > 0.1*/) - { - if (!m_pLThruster->IsEmitting()) - m_pLThruster->TriggerBurst(); - m_pLThruster->EnableEmission(true); - } - else - m_pLThruster->EnableEmission(false); - - // Fire right thrusters - if (m_Controller.IsState(MOVE_LEFT)/* || m_Rotation < 0.1*/) - { - if (!m_pRThruster->IsEmitting()) - m_pRThruster->TriggerBurst(); - m_pRThruster->EnableEmission(true); - } - else - m_pRThruster->EnableEmission(false); - /* - if (m_Controller.IsState(PRESS_FACEBUTTON)) - { - if (m_GearState == RAISED) - m_GearState = LOWERING; - else if (m_GearState == LOWERED) - m_GearState = RAISING; - } - */ - if (m_Controller.IsState(PRESS_FACEBUTTON)) - { - if (m_HatchState == CLOSED) - DropAllInventory(); - else if (m_HatchState == OPEN) - CloseHatch(); - } - } - else - { - m_pMThruster->EnableEmission(false); - m_pRThruster->EnableEmission(false); - m_pLThruster->EnableEmission(false); - m_pURThruster->EnableEmission(false); - m_pULThruster->EnableEmission(false); - } - -/* - else if (m_Controller && m_Controller.IsState(MOVE_RIGHT) || m_Controller.IsState(MOVE_LEFT)) - { - if (m_MoveState != WALK && m_MoveState != CROUCH) - m_StrideStart = true; - - if (m_MoveState == BODY_JUMPSTART || m_MoveState == BODY_JUMPSTART) - m_MoveState = CROUCH; - else - m_MoveState = WALK; - - if (m_Controller.IsState(MOVE_FAST)) - { - m_Paths[FGROUND][WALK].SetSpeed(FAST); - m_Paths[BGROUND][WALK].SetSpeed(FAST); - } - else - { - m_Paths[FGROUND][WALK].SetSpeed(NORMAL); - m_Paths[BGROUND][WALK].SetSpeed(NORMAL); - } - - if ((m_Controller.IsState(MOVE_RIGHT) && m_HFlipped) || (m_Controller.IsState(MOVE_LEFT) && !m_HFlipped)) - { - m_HFlipped = !m_HFlipped; - m_Paths[FGROUND][WALK].Terminate(); - m_Paths[BGROUND][WALK].Terminate(); - m_Paths[FGROUND][CLIMB].Terminate(); - m_Paths[BGROUND][CLIMB].Terminate(); - m_Paths[FGROUND][STAND].Terminate(); - m_Paths[BGROUND][STAND].Terminate(); - m_StrideStart = true; - } - } - else - m_MoveState = STAND; - - if (m_Controller.IsState(WEAPON_CHANGE_NEXT)) - { - if (m_pFGArm && m_pFGArm->IsAttached()) - { - m_pFGArm->SetDevice(SwapNextDevice(m_pFGArm->ReleaseDevice())); - m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); - } - } -*/ - // No Controller present - if (m_Controller.IsDisabled()) - { - m_pMThruster->EnableEmission(false); - m_pRThruster->EnableEmission(false); - m_pLThruster->EnableEmission(false); - m_pURThruster->EnableEmission(false); - m_pULThruster->EnableEmission(false); + if ((m_Status == STABLE || m_Status == UNSTABLE) && !m_Controller.IsDisabled()) { + if (m_pMThruster) { + if (m_Controller.IsState(MOVE_UP) || m_Controller.IsState(AIM_UP)) { + if (!m_pMThruster->IsEmitting()) { + m_pMThruster->TriggerBurst(); + m_ForceDeepCheck = true; + } + m_pMThruster->EnableEmission(true); + m_pMThruster->AlarmOnEmit(m_Team); + + if (m_HatchState == OPEN) { + CloseHatch(); + m_HatchTimer.Reset(); + } + } else { + m_pMThruster->EnableEmission(false); + } } + if (m_pULThruster || m_pURThruster) { + if (m_Controller.IsState(MOVE_DOWN) || m_Controller.IsState(AIM_DOWN)) { + if (m_pURThruster) { + if (!m_pURThruster->IsEmitting()) { m_pURThruster->TriggerBurst(); } + m_pURThruster->EnableEmission(true); + } + if (m_pULThruster) { + if (!m_pULThruster->IsEmitting()) { m_pULThruster->TriggerBurst(); } + m_pULThruster->EnableEmission(true); + } + } else { + if (m_pURThruster) { m_pURThruster->EnableEmission(false); } + if (m_pULThruster) { m_pULThruster->EnableEmission(false); } + } + } + if (m_pLThruster) { + if (m_Controller.IsState(MOVE_RIGHT)) { + if (!m_pLThruster->IsEmitting()) { m_pLThruster->TriggerBurst(); } + m_pLThruster->EnableEmission(true); + } else { + m_pLThruster->EnableEmission(false); + } + } + if (m_pRThruster) { + if (m_Controller.IsState(MOVE_LEFT)) { + if (!m_pRThruster->IsEmitting()) { m_pRThruster->TriggerBurst(); } + m_pRThruster->EnableEmission(true); + } else { + m_pRThruster->EnableEmission(false); + } + } + if (m_Controller.IsState(PRESS_FACEBUTTON)) { + if (m_HatchState == CLOSED) { + DropAllInventory(); + } else if (m_HatchState == OPEN) { + CloseHatch(); + } + } + } else { + if (m_pMThruster) { m_pMThruster->EnableEmission(false); } + if (m_pRThruster) { m_pRThruster->EnableEmission(false); } + if (m_pLThruster) { m_pLThruster->EnableEmission(false); } + if (m_pURThruster) { m_pURThruster->EnableEmission(false); } + if (m_pULThruster) { m_pULThruster->EnableEmission(false); } } -// m_aSprite->SetAngle((m_AimAngle / 180) * 3.141592654); -// m_aSprite->SetScale(2.0); - /////////////////////////////////////////////////// // Travel the landing gear AtomGroup:s + if (m_pMThruster) { + m_GearState = m_pMThruster->IsEmitting() ? LandingGearState::RAISED : LandingGearState::LOWERED; - // RAISE the gears - if (m_pMThruster && m_pMThruster->IsEmitting())// && m_pMThruster->IsSetToBurst()) - { - m_Paths[RIGHT][RAISED].SetHFlip(m_HFlipped); - m_Paths[LEFT][RAISED].SetHFlip(!m_HFlipped); - - if (m_pRLeg) - m_pRFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pRLeg->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_Vel, - m_Rotation, - m_Paths[RIGHT][RAISED], - deltaTime, - 0, - true); - if (m_pLLeg) - m_pLFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pLLeg->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_Vel, - m_Rotation, - m_Paths[LEFT][RAISED], - deltaTime, - 0, - true); - - m_GearState = RAISED; - } - // LOWER the gears - else if (m_pMThruster && !m_pMThruster->IsEmitting())// && m_GearState != LOWERED) - { - m_Paths[RIGHT][LOWERED].SetHFlip(m_HFlipped); - m_Paths[LEFT][LOWERED].SetHFlip(!m_HFlipped); - - if (m_pRLeg) - m_pRFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pRLeg->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_Vel, - m_Rotation, - m_Paths[RIGHT][LOWERED], - deltaTime, - 0, - true); - if (m_pLLeg) - m_pLFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pLLeg->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_Vel, - m_Rotation, - m_Paths[LEFT][LOWERED], - deltaTime, - 0, - true); - m_GearState = LOWERED; - } - + m_Paths[RIGHT][m_GearState].SetHFlip(m_HFlipped); + m_Paths[LEFT][m_GearState].SetHFlip(!m_HFlipped); + + if (m_pRLeg) { m_pRFootGroup->PushAsLimb(m_Pos.GetFloored() + RotateOffset(m_pRLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[RIGHT][m_GearState], deltaTime, nullptr, true); } + if (m_pLLeg) { m_pLFootGroup->PushAsLimb(m_Pos.GetFloored() + RotateOffset(m_pLLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[LEFT][m_GearState], deltaTime, nullptr, true); } + } ///////////////////////////////// // Manage Attachable:s @@ -718,68 +583,6 @@ void ACRocket::Update() // Update MovableObject, adds on the forces etc, updated viewpoint ACraft::Update(); - /////////////////////////////////// - // Explosion logic - - if (m_Status == DEAD) - GibThis(); - - //////////////////////////////////////// - // Balance stuff - - // Get the rotation in radians. - float rot = m_Rotation.GetRadAngle(); - - // Eliminate rotations over half a turn - if (std::fabs(rot) > c_PI) { - rot += (rot > 0) ? -c_TwoPI : c_TwoPI; - m_Rotation.SetRadAngle(rot); - } - // If tipped too far for too long, die - if (rot < c_HalfPI && rot > -c_HalfPI) - { - m_FlippedTimer.Reset(); - } - // Start death process if tipped over for too long - else if (m_ScuttleIfFlippedTime >= 0 && m_FlippedTimer.IsPastSimMS(m_ScuttleIfFlippedTime) && m_Status != DYING) - { - m_Status = DYING; - m_DeathTmr.Reset(); - } - - // Flash if dying, warning of impending explosion - if (m_Status == DYING) - { - if (m_DeathTmr.IsPastSimMS(500) && m_DeathTmr.AlternateSim(100)) - FlashWhite(10); - } -/* -// rot = fabs(rot) < c_QuarterPI ? rot : (rot > 0 ? c_QuarterPI : -c_QuarterPI); - - // Rotational balancing spring calc - if (m_Status == STABLE) { - // Break the spring if close to target angle. - if (fabs(rot) > 0.1) - m_AngularVel -= rot * fabs(rot); - else if (fabs(m_AngularVel) > 0.1) - m_AngularVel *= 0.5; - } - // Unstable, or without balance - else if (m_Status == DYING) { -// float rotTarget = rot > 0 ? c_HalfPI : -c_HalfPI; - float rotTarget = c_HalfPI; - float rotDiff = rotTarget - rot; - if (fabs(rotDiff) > 0.1) - m_AngularVel += rotDiff * rotDiff; - else - m_Status = DEAD; - -// else if (fabs(m_AngularVel) > 0.1) -// m_AngularVel *= 0.5; - } - m_Rotation.SetRadAngle(rot); -*/ - //////////////////////////////////////// // Hatch Operation @@ -954,33 +757,6 @@ void ACRocket::SetULeftThruster(AEmitter *newThruster) { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////////////////////////// -// Method: ResetEmissionTimers -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Reset the timers of all emissions so they will start/stop at the -// correct relative offsets from now. - -void ACRocket::ResetEmissionTimers() -{ - if (m_pMThruster && m_pMThruster->IsAttached()) - m_pMThruster->ResetEmissionTimers(); - - if (m_pRThruster && m_pRThruster->IsAttached()) - m_pRThruster->ResetEmissionTimers(); - - if (m_pLThruster && m_pLThruster->IsAttached()) - m_pLThruster->ResetEmissionTimers(); - - if (m_pURThruster && m_pURThruster->IsAttached()) - m_pURThruster->ResetEmissionTimers(); - - if (m_pULThruster && m_pULThruster->IsAttached()) - m_pULThruster->ResetEmissionTimers(); -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void ACRocket::Draw(BITMAP *pTargetBitmap, const Vector &targetPos, DrawMode mode, bool onlyPhysical) const { ACraft::Draw(pTargetBitmap, targetPos, mode, onlyPhysical); diff --git a/Entities/ACRocket.h b/Entities/ACRocket.h index 3d2e2e178..abc2d08bd 100644 --- a/Entities/ACRocket.h +++ b/Entities/ACRocket.h @@ -159,17 +159,6 @@ ClassInfoGetters; void Update() override; -////////////////////////////////////////////////////////////////////////////////////////// -// Virtual method: ResetEmissionTimers -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Reset the timers of all emissions so they will start/stop at the -// correct relative offsets from now. -// Arguments: None. -// Return value: None. - - void ResetEmissionTimers() override; - - ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: Draw ////////////////////////////////////////////////////////////////////////////////////////// @@ -321,8 +310,6 @@ ClassInfoGetters; // Limb paths for different movement states. // [0] is for the right limbs, and [1] is for left. LimbPath m_Paths[2][GearStateCount]; - // The craft explodes if it has been on its side for more than this many MS (default 4000). Disable by setting to -1. - float m_ScuttleIfFlippedTime; ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp index ad5170111..2a25242db 100644 --- a/Entities/ACrab.cpp +++ b/Entities/ACrab.cpp @@ -59,7 +59,7 @@ void ACrab::Clear() m_pJetpack = 0; m_JetTimeTotal = 0.0; m_JetTimeLeft = 0.0; - m_JetAngleRange = 0.25; + m_JetAngleRange = 0.25F; m_MoveState = STAND; for (int side = 0; side < SIDECOUNT; ++side) { @@ -659,7 +659,7 @@ bool ACrab::OnSink(const Vector &pos) bool ACrab::AddPieMenuSlices(PieMenuGUI *pPieMenu) { - PieSlice reloadSlice("Reload", PieSlice::PieSliceIndex::PSI_RELOAD, PieSlice::SliceDirection::UP); + PieSlice reloadSlice("Reload", PieSlice::PieSliceIndex::PSI_RELOAD, PieSlice::SliceDirection::UP, !FirearmsAreFull() && m_Status != INACTIVE); pPieMenu->AddSlice(reloadSlice); PieSlice sentryAISlice("Sentry AI Mode", PieSlice::PieSliceIndex::PSI_SENTRY, PieSlice::SliceDirection::DOWN); @@ -781,6 +781,19 @@ bool ACrab::FirearmIsEmpty() const { ////////////////////////////////////////////////////////////////////////////////////////// // Description: Indicates whether the currently held HDFirearm's is almost out of ammo. +bool ACrab::FirearmsAreFull() const { + if (m_pTurret && m_pTurret->IsAttached() && m_pTurret->HasMountedDevice()) { + for (const HeldDevice *mountedDevice : m_pTurret->GetMountedDevices()) { + if (const HDFirearm *mountedFirearm = dynamic_cast(mountedDevice); mountedFirearm && !mountedFirearm->IsFull()) { + return false; + } + } + } + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// + bool ACrab::FirearmNeedsReload() const { if (m_pTurret && m_pTurret->IsAttached() && m_pTurret->HasMountedDevice()) { for (const HeldDevice *mountedDevice : m_pTurret->GetMountedDevices()) { @@ -1112,14 +1125,13 @@ void ACrab::UpdateAI() } else { - // Stop and then turn around after a period of time, or if bumped into another actor (like a rocket) - if (m_PatrolTimer.IsPastSimMS(8000) || - /*g_SceneMan.CastNotMaterialRay(m_Pos, Vector(m_CharHeight / 4, 0), g_MaterialAir, Vector(), 4, false)*/ - g_SceneMan.CastMORay(m_Pos, Vector((m_LateralMoveState == LAT_RIGHT ? m_CharHeight : -m_CharHeight) / 3, 0), m_MOID, IgnoresWhichTeam(), g_MaterialGrass, false, 4) != g_NoMOID) - { - m_PatrolTimer.Reset(); - m_LateralMoveState = LAT_STILL; - } + Vector hitPos; + Vector trace((m_LateralMoveState == LAT_RIGHT ? GetRadius() : -GetRadius()) * 0.5F, 0); + // Stop and turn around after a period of time, or if bumped into another actor (like a rocket), or if walking off a ledge. + if (m_PatrolTimer.IsPastSimMS(8000) || g_SceneMan.CastMORay(m_Pos, trace, m_MOID, IgnoresWhichTeam(), g_MaterialGrass, false, 5) != g_NoMOID || !g_SceneMan.CastStrengthRay(m_Pos + trace, Vector(0, GetRadius()), 5.0F, hitPos, 5, g_MaterialGrass)) { + m_PatrolTimer.Reset(); + m_LateralMoveState = LAT_STILL; + } } } // Going to a goal, potentially through a set of waypoints @@ -2110,6 +2122,7 @@ void ACrab::Update() { float deltaTime = g_TimerMan.GetDeltaTimeSecs(); float mass = GetMass(); + Vector analogAim = m_Controller.GetAnalogAim(); // Set Default direction of all the paths! for (int side = 0; side < SIDECOUNT; ++side) @@ -2130,8 +2143,6 @@ void ACrab::Update() // Jetpack throttle depletes relative to jet time, but only if throttle range values have been defined float jetTimeRatio = std::max(m_JetTimeLeft / m_JetTimeTotal, 0.0F); m_pJetpack->SetThrottle(jetTimeRatio * 2.0F - 1.0F); - float minScale = 1.0F - m_pJetpack->GetMinThrottle(); - m_pJetpack->SetFlashScale(minScale + (1.0F + m_pJetpack->GetMaxThrottle() - minScale) * jetTimeRatio); } // Start Jetpack burn if (m_Controller.IsState(BODY_JUMPSTART) && m_JetTimeLeft > 0 && m_Status != INACTIVE) @@ -2142,10 +2153,7 @@ void ACrab::Update() m_pJetpack->EnableEmission(true); // Quadruple this for the burst m_JetTimeLeft = std::max(m_JetTimeLeft - g_TimerMan.GetDeltaTimeMS() * 10.0F, 0.0F); - } - // Jetpack is burning - else if (m_Controller.IsState(BODY_JUMP) && m_JetTimeLeft > 0) - { + } else if (m_Controller.IsState(BODY_JUMP) && m_JetTimeLeft > 0 && m_Status != INACTIVE) { m_pJetpack->EnableEmission(true); // Jetpacks are noisy! m_pJetpack->AlarmOnEmit(m_Team); @@ -2184,8 +2192,7 @@ void ACrab::Update() //////////////////////////////////// // Movement direction - if (m_Controller.IsState(MOVE_RIGHT) || m_Controller.IsState(MOVE_LEFT) || m_MoveState == JUMP) - { + if (m_Controller.IsState(MOVE_RIGHT) || m_Controller.IsState(MOVE_LEFT) || m_MoveState == JUMP && m_Status != INACTIVE) { if (m_MoveState != JUMP) { // Restart the stride if we're just starting to walk or crawl @@ -2205,22 +2212,14 @@ void ACrab::Update() } } - // Walk backwards if the aiming is done in the opposite direction of travel - if (fabs(m_Controller.GetAnalogAim().m_X) > 0.1) - { - // Walk backwards if necessary - for (int side = 0; side < SIDECOUNT; ++side) - { + // Walk backwards if the aiming is already focused in the opposite direction of travel. + if (std::abs(analogAim.m_X) > 0 || m_Controller.IsState(AIM_SHARP)) { + for (int side = 0; side < SIDECOUNT; ++side) { m_Paths[side][FGROUND][m_MoveState].SetHFlip(m_Controller.IsState(MOVE_LEFT)); m_Paths[side][BGROUND][m_MoveState].SetHFlip(m_Controller.IsState(MOVE_LEFT)); } - } - // Flip if we're moving in the opposite direction - else if ((m_Controller.IsState(MOVE_RIGHT) && m_HFlipped) || (m_Controller.IsState(MOVE_LEFT) && !m_HFlipped)) - { + } else if ((m_Controller.IsState(MOVE_RIGHT) && m_HFlipped) || (m_Controller.IsState(MOVE_LEFT) && !m_HFlipped)) { m_HFlipped = !m_HFlipped; -// // Instead of simply carving out a silhouette of the now flipped actor, isntead disable any atoms which are embedded int eh terrain until they emerge again -// m_ForceDeepCheck = true; m_CheckTerrIntersection = true; MoveOutOfTerrain(g_MaterialGrass); for (int side = 0; side < SIDECOUNT; ++side) @@ -2241,7 +2240,7 @@ void ACrab::Update() //////////////////////////////////// // Reload held MO, if applicable - if (m_Controller.IsState(WEAPON_RELOAD) && FirearmNeedsReload()) { + if (m_Controller.IsState(WEAPON_RELOAD) && !FirearmsAreFull() && m_Status != INACTIVE) { ReloadFirearms(); if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } @@ -2261,34 +2260,25 @@ void ACrab::Update() float adjustedAimRangeUpperLimit = (m_HFlipped) ? m_AimRangeUpperLimit - rotAngle : m_AimRangeUpperLimit + rotAngle; float adjustedAimRangeLowerLimit = (m_HFlipped) ? -m_AimRangeLowerLimit - rotAngle : -m_AimRangeLowerLimit + rotAngle; - if (m_Controller.IsState(AIM_UP)) - { + if (m_Controller.IsState(AIM_UP) && m_Status != INACTIVE) { // Set the timer to some base number so we don't get a sluggish feeling at start of aim if (m_AimState != AIMUP) m_AimTmr.SetElapsedSimTimeMS(150); m_AimState = AIMUP; m_AimAngle += m_Controller.IsState(AIM_SHARP) ? MIN(m_AimTmr.GetElapsedSimTimeMS() * 0.00005, 0.05) : MIN(m_AimTmr.GetElapsedSimTimeMS() * 0.00015, 0.1); - } - else if (m_Controller.IsState(AIM_DOWN)) - { + } else if (m_Controller.IsState(AIM_DOWN) && m_Status != INACTIVE) { // Set the timer to some base number so we don't get a sluggish feeling at start of aim if (m_AimState != AIMDOWN) m_AimTmr.SetElapsedSimTimeMS(150); m_AimState = AIMDOWN; m_AimAngle -= m_Controller.IsState(AIM_SHARP) ? MIN(m_AimTmr.GetElapsedSimTimeMS() * 0.00005, 0.05) : MIN(m_AimTmr.GetElapsedSimTimeMS() * 0.00015, 0.1); - } - // Analog aim - else if (m_Controller.GetAnalogAim().GetMagnitude() > 0.1) - { - Vector aim = m_Controller.GetAnalogAim(); + } else if (analogAim.GetMagnitude() > 0.1F && m_Status != INACTIVE) { // Hack to avoid the GetAbsRadAngle to mangle an aim angle straight down - if (aim.m_X == 0) - aim.m_X += m_HFlipped ? -0.01 : 0.01; - m_AimAngle = aim.GetAbsRadAngle(); + if (analogAim.m_X == 0) { analogAim.m_X += 0.01F * GetFlipFactor(); } + m_AimAngle = analogAim.GetAbsRadAngle(); // Check for flip change - if ((aim.m_X > 0 && m_HFlipped) || (aim.m_X < 0 && !m_HFlipped)) - { + if ((analogAim.m_X > 0 && m_HFlipped) || (analogAim.m_X < 0 && !m_HFlipped)) { m_HFlipped = !m_HFlipped; // Instead of simply carving out a silhouette of the now flipped actor, isntead disable any atoms which are embedded int eh terrain until they emerge again //m_ForceDeepCheck = true; @@ -2317,49 +2307,31 @@ void ACrab::Update() ////////////////////////////// // Sharp aim calculation -// TODO: make the delay data driven by both the actor and the device! - // - if (m_Controller.IsState(AIM_SHARP) && m_MoveState == STAND && m_Vel.GetMagnitude() < 5.0) - { -/* - float halfDelay = m_SharpAimDelay / 2; - // Accelerate for first half - if (!m_SharpAimTimer.IsPastSimMS(halfDelay)) - m_SharpAimProgress = (float)m_SharpAimTimer.GetElapsedSimTimeMS() / (float)m_SharpAimDelay; - // Decelerate for second half - else if (!m_SharpAimTimer.IsPastSimMS(m_SharpAimDelay) - m_SharpAimProgress - // At max - else - m_SharpAimProgress = 1.0; -*/ - float aimMag = m_Controller.GetAnalogAim().GetMagnitude(); + if (m_Controller.IsState(AIM_SHARP) && m_Status == STABLE && m_Vel.GetMagnitude() < 5.0F) { + float aimMag = analogAim.GetMagnitude(); - // If aim sharp is being done digitally, then translate to full analog aim mag - if (aimMag < 0.1) - aimMag = 1.0; + // If aim sharp is being done digitally, then translate to full magnitude. + if (aimMag < 0.1F) { aimMag = 1.0F; } + if (m_MoveState == WALK) { aimMag *= 0.3F; } - if (m_SharpAimTimer.IsPastSimMS(m_SharpAimDelay)) - { - // Only go slower outward - if (m_SharpAimProgress < aimMag) - m_SharpAimProgress += (aimMag - m_SharpAimProgress) * 0.035; - else - m_SharpAimProgress = aimMag; - } - else - m_SharpAimProgress = 0; - } - else - { - m_SharpAimProgress = 0; - m_SharpAimTimer.Reset(); - } + if (m_SharpAimTimer.IsPastSimMS(m_SharpAimDelay)) { + // Only go slower outward. + if (m_SharpAimProgress < aimMag) { + m_SharpAimProgress += (aimMag - m_SharpAimProgress) * 0.035F; + } else { + m_SharpAimProgress = aimMag; + } + } else { + m_SharpAimProgress *= 0.95F; + } + } else { + m_SharpAimProgress = std::max(m_SharpAimProgress * 0.95F - 0.1F, 0.0F); + } //////////////////////////////////// // Fire/Activate held devices - if (m_pTurret && m_pTurret->IsAttached()) { + if (m_pTurret && m_pTurret->IsAttached() && m_Status != INACTIVE) { for (HeldDevice *mountedDevice : m_pTurret->GetMountedDevices()) { mountedDevice->SetSharpAim(m_SharpAimProgress); if (m_Controller.IsState(WEAPON_FIRE)) { @@ -2858,10 +2830,9 @@ void ACrab::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr // Player AI drawing - // Device aiming reticule - if (m_Controller.IsState(AIM_SHARP) && m_pTurret && m_pTurret->IsAttached() && m_pTurret->HasMountedDevice()) - m_pTurret->GetFirstMountedDevice()->DrawHUD(pTargetBitmap, targetPos, whichScreen, m_Controller.IsPlayerControlled()); - + if ((m_Controller.IsState(AIM_SHARP) || (m_Controller.IsPlayerControlled() && !m_Controller.IsState(PIE_MENU_ACTIVE))) && m_pTurret && m_pTurret->IsAttached() && m_pTurret->HasMountedDevice()) { + m_pTurret->GetFirstMountedDevice()->DrawHUD(pTargetBitmap, targetPos, whichScreen, m_Controller.IsState(AIM_SHARP) && m_Controller.IsPlayerControlled()); + } ////////////////////////////////////// // Draw stat info HUD char str[64]; @@ -2915,13 +2886,10 @@ void ACrab::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr str[1] = 0; pSymbolFont->DrawAligned(&allegroBitmap, drawPos.m_X - 11, drawPos.m_Y + m_HUDStack, str, GUIFont::Centre); - float jetTimeRatio = m_JetTimeLeft / m_JetTimeTotal; -// TODO: Don't hardcode this shit - char gaugeColor = jetTimeRatio > 0.6 ? 149 : (jetTimeRatio > 0.3 ? 77 : 13); - rectfill(pTargetBitmap, drawPos.m_X, drawPos.m_Y + m_HUDStack + 6, drawPos.m_X + (16 * jetTimeRatio), drawPos.m_Y + m_HUDStack + 7, gaugeColor); -// rect(pTargetBitmap, drawPos.m_X, drawPos.m_Y + m_HUDStack - 2, drawPos.m_X + 24, drawPos.m_Y + m_HUDStack - 4, 238); -// std::snprintf(str, sizeof(str), "%.0f Kg", mass); -// pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Left); + float jetTimeRatio = m_JetTimeLeft / m_JetTimeTotal; + int gaugeColor = jetTimeRatio > 0.6F ? 149 : (jetTimeRatio > 0.3F ? 77 : 13); + rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 7, drawPos.GetFloorIntX() + 16, drawPos.GetFloorIntY() + m_HUDStack + 8, 245); + rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 6, drawPos.GetFloorIntX() + static_cast(15.0F * jetTimeRatio), drawPos.GetFloorIntY() + m_HUDStack + 7, gaugeColor); m_HUDStack += -10; } @@ -2933,8 +2901,10 @@ void ACrab::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr if (!textString.empty()) { textString += " | "; } if (mountedFirearm->IsReloading()) { textString += "Reloading"; + rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 13, drawPos.GetFloorIntX() + 29, drawPos.GetFloorIntY() + m_HUDStack + 14, 245); + rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 12, drawPos.GetFloorIntX() + static_cast(28.0F * mountedFirearm->GetReloadProgress() + 0.5F), drawPos.GetFloorIntY() + m_HUDStack + 13, 77); } else { - textString += mountedFirearm->GetRoundInMagCount() > 0 ? std::to_string(mountedFirearm->GetRoundInMagCount()) : "Infinite"; + textString += mountedFirearm->GetRoundInMagCount() < 0 ? "Infinite" : std::to_string(mountedFirearm->GetRoundInMagCount()); } } } diff --git a/Entities/ACrab.h b/Entities/ACrab.h index 85d60ddf6..e1eb52b61 100644 --- a/Entities/ACrab.h +++ b/Entities/ACrab.h @@ -348,6 +348,12 @@ class ACrab : bool FirearmNeedsReload() const; + /// + /// Indicates whether all of the MountedDevices are at full capacity. + /// + /// Whether all of the MountedDevices are at full capacity. + bool FirearmsAreFull() const; + ////////////////////////////////////////////////////////////////////////////////////////// // Method: FirearmIsSemiAuto diff --git a/Entities/ACraft.cpp b/Entities/ACraft.cpp index 2a4567757..6cacf8c2f 100644 --- a/Entities/ACraft.cpp +++ b/Entities/ACraft.cpp @@ -283,6 +283,8 @@ void ACraft::Clear() m_MaxPassengers = -1; m_DeliveryDelayMultiplier = 1.0; + m_ScuttleIfFlippedTime = 4000; + m_ScuttleOnDeath = true; } @@ -337,6 +339,8 @@ int ACraft::Create(const ACraft &reference) m_MaxPassengers = reference.m_MaxPassengers; m_DeliveryDelayMultiplier = reference.m_DeliveryDelayMultiplier; + m_ScuttleIfFlippedTime = reference.m_ScuttleIfFlippedTime; + m_ScuttleOnDeath = reference.m_ScuttleOnDeath; return 0; } @@ -377,6 +381,10 @@ int ACraft::ReadProperty(const std::string_view &propName, Reader &reader) reader >> m_LandingCraft; else if (propName == "MaxPassengers") reader >> m_MaxPassengers; + else if (propName == "ScuttleIfFlippedTime") + reader >> m_ScuttleIfFlippedTime; + else if (propName == "ScuttleOnDeath") + reader >> m_ScuttleOnDeath; else return Actor::ReadProperty(propName, reader); @@ -417,6 +425,10 @@ int ACraft::Save(Writer &writer) const writer.NewProperty("MaxPassengers"); writer << m_MaxPassengers; + writer.NewProperty("ScuttleIfFlippedTime"); + writer << m_ScuttleIfFlippedTime; + writer.NewProperty("ScuttleOnDeath"); + writer << m_ScuttleOnDeath; return 0; } @@ -853,6 +865,13 @@ void ACraft::GibThis(const Vector &impactImpulse, MovableObject *movableObjectTo Actor::GibThis(impactImpulse, movableObjectToIgnore); } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void ACraft::ResetAllTimers() { + MOSRotating::ResetAllTimers(); + + m_FlippedTimer.Reset(); +} ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: Update @@ -889,18 +908,6 @@ void ACraft::Update() } } - // Check for death - if (m_Health <= 0 && m_Status != DYING && m_Status != DEAD) - { - m_Status = DYING; - m_DeathTmr.Reset(); - DropAllInventory(); - } -/* - if (m_Status == DYING && m_DeathTmr.GetElapsedSimTimeMS() > m_HatchDelay * 2) - m_Status = DEAD; -*/ - /////////////////////////////////////////////////// // Doors open logic @@ -973,13 +980,27 @@ void ACraft::Update() } } + if (m_Status == DEAD) { + if (m_ScuttleOnDeath || m_AIMode == AIMODE_SCUTTLE) { GibThis(); } + } else if (m_Status == DYING) { + if ((m_ScuttleOnDeath || m_AIMode == AIMODE_SCUTTLE) && m_DeathTmr.IsPastSimMS(500) && m_DeathTmr.AlternateSim(100)) { FlashWhite(10); } + } else if (m_Health <= 0 || m_AIMode == AIMODE_SCUTTLE || (m_ScuttleIfFlippedTime >= 0 && m_FlippedTimer.IsPastSimMS(m_ScuttleIfFlippedTime))) { + m_Status = DYING; + m_DeathTmr.Reset(); + DropAllInventory(); + } - // Only make the death happen after the user lets go tf the AI menu - if (m_Status != DEAD && m_AIMode == AIMODE_SCUTTLE) - { - DropAllInventory(); - m_Status = DYING; - } + // Get the rotation in radians. + float rot = m_Rotation.GetRadAngle(); + + // Eliminate rotations over half a turn. + if (std::abs(rot) > c_PI) { + rot += (rot > 0) ? -c_TwoPI : c_TwoPI; + m_Rotation.SetRadAngle(rot); + } + if (rot < c_HalfPI && rot > -c_HalfPI) { + m_FlippedTimer.Reset(); + } ///////////////////////////////////////// // Misc. diff --git a/Entities/ACraft.h b/Entities/ACraft.h index b3ac12319..c9a7ba9f1 100644 --- a/Entities/ACraft.h +++ b/Entities/ACraft.h @@ -478,17 +478,10 @@ enum bool HasDelivered() { return m_HasDelivered; } - -////////////////////////////////////////////////////////////////////////////////////////// -// Virtual method: ResetEmissionTimers -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Reset the timers of all emissions so they will start/stop at the -// correct relative offsets from now. -// Arguments: None. -// Return value: None. - - virtual void ResetEmissionTimers() {} - + /// + /// Resets all the timers related to this, including the scuttle timer. + /// + void ResetAllTimers() override; ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: OnMOHit @@ -572,6 +565,30 @@ enum void SetDeliveryDelayMultiplier(float newValue) { m_DeliveryDelayMultiplier = newValue; } + /// + /// Gets whether this ACraft will scuttle automatically on death. + /// + /// Whether this ACraft will scuttle automatically on death. + bool GetScuttleOnDeath() const { return m_ScuttleOnDeath; } + + /// + /// Sets whether this ACraft will scuttle automatically on death. + /// + /// Whether this ACraft will scuttle automatically on death. + void SetScuttleOnDeath(bool scuttleOnDeath) { m_ScuttleOnDeath = scuttleOnDeath; } + + /// + /// Gets the hatch opening/closing delay of this ACraft. + /// + /// The hatch delay of this ACraft. + int GetHatchDelay() const { return m_HatchDelay; } + + /// + /// Sets the hatch opening/closing delay of this ACraft. + /// + /// The new hatch delay of this ACraft. + void SetHatchDelay(int newDelay) { m_HatchDelay = newDelay; } + /// /// Destroys this ACraft and creates its specified Gibs in its place with appropriate velocities. Any Attachables are removed and also given appropriate velocities. /// @@ -657,6 +674,8 @@ enum SoundContainer *m_CrashSound; // The maximum number of actors that fit in the inventory int m_MaxPassengers; + int m_ScuttleIfFlippedTime; //!< The time after which the craft will scuttle automatically, if tipped over. + bool m_ScuttleOnDeath; //!< Whether the craft will self-destruct at zero health. static bool s_CrabBombInEffect; //!< Flag to determine if a craft is triggering the Crab Bomb effect. diff --git a/Entities/AEmitter.cpp b/Entities/AEmitter.cpp index 493c32664..99158fae8 100644 --- a/Entities/AEmitter.cpp +++ b/Entities/AEmitter.cpp @@ -38,8 +38,8 @@ void AEmitter::Clear() m_WasEmitting = false; m_EmitCount = 0; m_EmitCountLimit = 0; - m_MinThrottleRange = 1.0F; - m_MaxThrottleRange = 1.0F; + m_NegativeThrottleMultiplier = 1.0F; + m_PositiveThrottleMultiplier = 1.0F; m_Throttle = 0; m_EmissionsIgnoreThis = false; m_BurstScale = 1.0F; @@ -85,8 +85,8 @@ int AEmitter::Create(const AEmitter &reference) { m_EmitEnabled = reference.m_EmitEnabled; m_EmitCount = reference.m_EmitCount; m_EmitCountLimit = reference.m_EmitCountLimit; - m_MinThrottleRange = reference.m_MinThrottleRange; - m_MaxThrottleRange = reference.m_MaxThrottleRange; + m_NegativeThrottleMultiplier = reference.m_NegativeThrottleMultiplier; + m_PositiveThrottleMultiplier = reference.m_PositiveThrottleMultiplier; m_Throttle = reference.m_Throttle; m_EmissionsIgnoreThis = reference.m_EmissionsIgnoreThis; m_BurstScale = reference.m_BurstScale; @@ -138,10 +138,10 @@ int AEmitter::ReadProperty(const std::string_view &propName, Reader &reader) { reader >> ppm; // Go through all emissions and set the rate so that it emulates the way it used to work, for mod backwards compatibility. for (Emission *emission : m_EmissionList) { emission->m_PPM = ppm / static_cast(m_EmissionList.size()); } - } else if (propName == "MinThrottleRange") { - reader >> m_MinThrottleRange; - } else if (propName == "MaxThrottleRange") { - reader >> m_MaxThrottleRange; + } else if (propName == "NegativeThrottleMultiplier") { + reader >> m_NegativeThrottleMultiplier; + } else if (propName == "PositiveThrottleMultiplier") { + reader >> m_PositiveThrottleMultiplier; } else if (propName == "Throttle") { reader >> m_Throttle; } else if (propName == "EmissionsIgnoreThis") { @@ -212,10 +212,10 @@ int AEmitter::Save(Writer &writer) const writer << m_EmitCountLimit; writer.NewProperty("EmissionsIgnoreThis"); writer << m_EmissionsIgnoreThis; - writer.NewProperty("MinThrottleRange"); - writer << m_MinThrottleRange; - writer.NewProperty("MaxThrottleRange"); - writer << m_MaxThrottleRange; + writer.NewProperty("NegativeThrottleMultiplier"); + writer << m_NegativeThrottleMultiplier; + writer.NewProperty("PositiveThrottleMultiplier"); + writer << m_PositiveThrottleMultiplier; writer.NewProperty("Throttle"); writer << m_Throttle; writer.NewProperty("BurstScale"); @@ -344,12 +344,13 @@ float AEmitter::EstimateImpulse(bool burst) } - // Figure out the throttle factor + // Scale the emission rate up or down according to the appropriate throttle multiplier. float throttleFactor = 1.0F; - if (m_Throttle < 0) { // Negative throttle, scale down according to the min throttle range - throttleFactor += std::abs(m_MinThrottleRange) * m_Throttle; - } else if (m_Throttle > 0) { // Positive throttle, scale up - throttleFactor += std::abs(m_MaxThrottleRange) * m_Throttle; + float absThrottle = std::abs(m_Throttle); + if (m_Throttle < 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_NegativeThrottleMultiplier * absThrottle); + } else if (m_Throttle > 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_PositiveThrottleMultiplier * absThrottle); } // Apply the throttle factor to the emission rate per update if (burst) { return m_AvgBurstImpulse * throttleFactor; } @@ -432,12 +433,15 @@ void AEmitter::Update() // TODO: Potentially get this once outside instead, like in attach/detach") MovableObject *pRootParent = GetRootParent(); + // Scale the emission rate up or down according to the appropriate throttle multiplier. float throttleFactor = 1.0F; - if (m_Throttle < 0) { // Negative throttle, scale down according to the min throttle range - throttleFactor += std::abs(m_MinThrottleRange) * m_Throttle; - } else if (m_Throttle > 0) { // Positive throttle, scale up - throttleFactor += std::abs(m_MaxThrottleRange) * m_Throttle; + float absThrottle = std::abs(m_Throttle); + if (m_Throttle < 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_NegativeThrottleMultiplier * absThrottle); + } else if (m_Throttle > 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_PositiveThrottleMultiplier * absThrottle); } + m_FlashScale = throttleFactor; // Check burst triggering against whether the spacing is fulfilled if (m_BurstTriggered && (m_BurstSpacing <= 0 || m_BurstTimer.IsPastSimMS(m_BurstSpacing))) { @@ -522,8 +526,7 @@ void AEmitter::Update() pParticle->SetRotAngle(emitVel.GetAbsRadAngle() + (m_HFlipped ? -c_PI : 0)); pParticle->SetHFlipped(m_HFlipped); - if (pParticle->GetLifetime() != 0) - pParticle->SetLifetime(pParticle->GetLifetime() * (1.0F + ((*eItr)->GetLifeVariation() * RandomNormalNum()))); + if (pParticle->GetLifetime() != 0) { pParticle->SetLifetime(std::max(static_cast(pParticle->GetLifetime() * (1.0F + ((*eItr)->GetLifeVariation() * RandomNormalNum()))), 1)); } pParticle->SetTeam(m_Team); pParticle->SetIgnoresTeamHits(true); diff --git a/Entities/AEmitter.h b/Entities/AEmitter.h index be4d8d25f..ab316d88d 100644 --- a/Entities/AEmitter.h +++ b/Entities/AEmitter.h @@ -263,16 +263,29 @@ ClassInfoGetters; float GetThrottle() const { return m_Throttle; } /// - /// Gets the minimum throttle range of this AEmitter. + /// Gets the negative throttle multiplier of this AEmitter. /// - /// The minimum throttle range of this AEmitter. - float GetMinThrottle() const { return m_MinThrottleRange; } + /// The negative throttle multiplier of this AEmitter. + float GetNegativeThrottleMultiplier() const { return m_NegativeThrottleMultiplier; } /// - /// Gets the maximum throttle range of this AEmitter. + /// Gets the positive throttle multiplier of this AEmitter. /// - /// The maximum throttle range of this AEmitter. - float GetMaxThrottle() const { return m_MaxThrottleRange; } + /// The positive throttle multiplier of this AEmitter. + float GetPositiveThrottleMultiplier() const { return m_PositiveThrottleMultiplier; } + + /// + /// Sets the negative throttle multiplier of this AEmitter. + /// + /// The new throttle multiplier of this AEmitter. + void SetNegativeThrottleMultiplier(float newValue) { m_NegativeThrottleMultiplier = newValue; } + + /// + /// Sets the positive throttle multiplier of this AEmitter. + /// + /// The new throttle multiplier of this AEmitter. + void SetPositiveThrottleMultiplier(float newValue) { m_PositiveThrottleMultiplier = newValue; } + /* ////////////////////////////////////////////////////////////////////////////////////////// // Method: SetEmitRate @@ -632,8 +645,8 @@ ClassInfoGetters; long m_EmitCount; // The max number of emissions to emit per emit being enabled long m_EmitCountLimit; - float m_MinThrottleRange; //!< The range negative throttle has on emission rate. 1.0 means the rate can be throttled down to 0%, 0 means negative throttle has no effect - float m_MaxThrottleRange; //!< The range positive throttle has on emission rate. 1.0 means the rate can be throttled up to 200%, 0 means positive throttle has no effect + float m_NegativeThrottleMultiplier; //!< The multiplier applied to the emission rate when throttle is negative. Relative to the absolute throttle value. + float m_PositiveThrottleMultiplier; //!< The multiplier applied to the emission rate when throttle is positive. Relative to the absolute throttle value. float m_Throttle; //!< The normalized throttle which controls the MSPE between 1.0 * m_MSPERange and -1.0 * m_MSPERange. 0 means emit the regular m_PPM amount. // Whether or not this' emissions ignore hits with itself, even if they are set to hit other MOs. bool m_EmissionsIgnoreThis; diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index ff470ddb7..ca0a0f515 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -80,6 +80,7 @@ void AHuman::Clear() m_SharpAimRevertTimer.Reset(); m_FGArmFlailScalar = 0.0F; m_BGArmFlailScalar = 0.7F; + m_EquipHUDTimer.Reset(); m_DeviceState = SCANNING; m_SweepState = NOSWEEP; @@ -513,7 +514,7 @@ void AHuman::SetHead(Attachable *newHead) { dynamic_cast(parent)->SetHead(attachable); }}); - if (m_pHead->HasNoSetDamageMultiplier()) { m_pHead->SetDamageMultiplier(5.0F); } + if (m_pHead->HasNoSetDamageMultiplier()) { m_pHead->SetDamageMultiplier(4.0F); } if (m_pHead->IsDrawnAfterParent()) { m_pHead->SetDrawnNormallyByParent(false); } m_pHead->SetInheritsRotAngle(false); } @@ -755,7 +756,6 @@ bool AHuman::AddPieMenuSlices(PieMenuGUI *pPieMenu) PieSlice reloadSlice(GetEquippedItem() || GetEquippedBGItem() ? "Reload" : (m_pFGArm ? "Not holding anything!" : "NO ARM!"), PieSlice::PieSliceIndex::PSI_RELOAD, PieSlice::SliceDirection::UP, ((m_pFGArm && m_pFGArm->GetHeldDevice() && !m_pFGArm->GetHeldDevice()->IsFull()) || (m_pBGArm && m_pBGArm->GetHeldDevice() && !m_pBGArm->GetHeldDevice()->IsFull())) && m_Status != INACTIVE); pPieMenu->AddSlice(reloadSlice); } - //To-do: don't oneline this? PieSlice dropSlice(m_pFGArm && m_pFGArm->GetHeldMO() ? "Drop " + m_pFGArm->GetHeldMO()->GetPresetName() : m_pBGArm ? (m_pBGArm->GetHeldMO() ? "Drop " + m_pBGArm->GetHeldMO()->GetPresetName() : (!m_Inventory.empty() ? "Drop Inventory" : "Not holding anything!")) : (m_pFGArm ? "Not holding anything!" : "NO ARM!"), PieSlice::PieSliceIndex::PSI_DROP, PieSlice::SliceDirection::DOWN, ((m_pFGArm && m_pFGArm->GetHeldMO()) || (m_pBGArm && (m_pBGArm->GetHeldMO() || !m_Inventory.empty()))) && m_Status != INACTIVE); pPieMenu->AddSlice(dropSlice); @@ -1398,11 +1398,7 @@ bool AHuman::EquipShieldInBGArm() // Move the hand to a poisition so it looks like the new device was drawn from inventory m_pBGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); - // Play the device switching sound only if activity is running - if (g_ActivityMan.ActivityRunning()) - { - if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } - } + if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } return true; } @@ -1411,31 +1407,28 @@ bool AHuman::EquipShieldInBGArm() return false; } - ////////////////////////////////////////////////////////////////////////////////////////// -// Virtual Method: UnequipBGArm -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Unequips whatever is in the BG arm and puts it into the inventory. -// Arguments: None. -// Return value: Whether there was anything to unequip. -bool AHuman::UnequipBGArm() -{ - if (!(m_pBGArm && m_pBGArm->IsAttached())) - return false; +bool AHuman::UnequipFGArm() { + if (m_pFGArm && m_pFGArm->HoldsSomething()) { + m_pFGArm->GetHeldDevice()->Deactivate(); + m_Inventory.push_back(m_pFGArm->ReleaseHeldMO()); + return true; + } + return false; +} - // Put back into the inventory what we had in our hand, if anything - if (m_pBGArm->HoldsSomething()) - { - m_pBGArm->GetHeldDevice()->Deactivate(); - m_Inventory.push_back(m_pBGArm->ReleaseHeldMO()); - return true; - } +////////////////////////////////////////////////////////////////////////////////////////// - return false; +bool AHuman::UnequipBGArm() { + if (m_pBGArm && m_pBGArm->HoldsSomething()) { + m_pBGArm->GetHeldDevice()->Deactivate(); + m_Inventory.push_back(m_pBGArm->ReleaseHeldMO()); + return true; + } + return false; } - ////////////////////////////////////////////////////////////////////////////////////////// // Virtual Method: GetEquippedItem ////////////////////////////////////////////////////////////////////////////////////////// @@ -1901,14 +1894,13 @@ void AHuman::UpdateAI() } else { - // Stop and then turn around after a period of time, or if bumped into another actor (like a rocket) - if (m_PatrolTimer.IsPastSimMS(8000) || - /*g_SceneMan.CastNotMaterialRay(m_Pos, Vector(m_CharHeight / 4, 0), g_MaterialAir, Vector(), 4, false)*/ - g_SceneMan.CastMORay(m_Pos, Vector((m_LateralMoveState == LAT_RIGHT ? m_CharHeight : -m_CharHeight) / 3, 0), m_MOID, IgnoresWhichTeam(), g_MaterialGrass, false, 4) != g_NoMOID) - { - m_PatrolTimer.Reset(); - m_LateralMoveState = LAT_STILL; - } + Vector hitPos; + Vector trace((m_LateralMoveState == LAT_RIGHT ? GetRadius() : -GetRadius()) * 0.5F, 0); + // Stop and turn around after a period of time, or if bumped into another actor (like a rocket), or if walking off a ledge. + if (m_PatrolTimer.IsPastSimMS(8000) || g_SceneMan.CastMORay(m_Pos, trace, m_MOID, IgnoresWhichTeam(), g_MaterialGrass, false, 5) != g_NoMOID || !g_SceneMan.CastStrengthRay(m_Pos + trace, Vector(0, GetRadius()), 5.0F, hitPos, 5, g_MaterialGrass)) { + m_PatrolTimer.Reset(); + m_LateralMoveState = LAT_STILL; + } } } // Going to a goal, potentially through a set of waypoints @@ -3104,73 +3096,46 @@ int AHuman::OnPieMenu(Actor *pieMenuActor) { void AHuman::Update() { float deltaTime = g_TimerMan.GetDeltaTimeSecs(); - // Get the rotation in radians. float rot = m_Rotation.GetRadAngle(); + Vector analogAim = m_Controller.GetAnalogAim(); - // Set Default direction of all the paths! - m_Paths[FGROUND][WALK].SetHFlip(m_HFlipped); - m_Paths[BGROUND][WALK].SetHFlip(m_HFlipped); - m_Paths[FGROUND][CROUCH].SetHFlip(m_HFlipped); - m_Paths[BGROUND][CROUCH].SetHFlip(m_HFlipped); - m_Paths[FGROUND][CRAWL].SetHFlip(m_HFlipped); - m_Paths[BGROUND][CRAWL].SetHFlip(m_HFlipped); - m_Paths[FGROUND][ARMCRAWL].SetHFlip(m_HFlipped); - m_Paths[BGROUND][ARMCRAWL].SetHFlip(m_HFlipped); - m_Paths[FGROUND][CLIMB].SetHFlip(m_HFlipped); - m_Paths[BGROUND][CLIMB].SetHFlip(m_HFlipped); - m_Paths[FGROUND][STAND].SetHFlip(m_HFlipped); - m_Paths[BGROUND][STAND].SetHFlip(m_HFlipped); + m_Paths[FGROUND][m_MoveState].SetHFlip(m_HFlipped); + m_Paths[BGROUND][m_MoveState].SetHFlip(m_HFlipped); //////////////////////////////////// // Jetpack activation and blast direction - if (m_pJetpack) - { + if (m_pJetpack) { if (m_JetTimeTotal > 0) { // Jetpack throttle depletes relative to jet time, but only if throttle range values have been defined float jetTimeRatio = std::max(m_JetTimeLeft / m_JetTimeTotal, 0.0F); m_pJetpack->SetThrottle(jetTimeRatio * 2.0F - 1.0F); - float minScale = 1.0F - m_pJetpack->GetMinThrottle(); - m_pJetpack->SetFlashScale(minScale + (1.0F + m_pJetpack->GetMaxThrottle() - minScale) * jetTimeRatio); } - // Start Jetpack burn - if (m_Controller.IsState(BODY_JUMPSTART) && m_JetTimeLeft > 0 && m_Status != INACTIVE) - { + if (m_Controller.IsState(BODY_JUMPSTART) && m_JetTimeLeft > 0 && m_Status != INACTIVE) { m_pJetpack->TriggerBurst(); - // This is to make sure se get loose from being stuck m_ForceDeepCheck = true; m_pJetpack->EnableEmission(true); - // Quadruple this for the burst m_JetTimeLeft = std::max(m_JetTimeLeft - g_TimerMan.GetDeltaTimeMS() * 10.0F, 0.0F); - } - // Jetpack is ordered to be burning, or the pie menu is on and was burning before it went on - else if ((m_Controller.IsState(BODY_JUMP) || (m_MoveState == JUMP && m_Controller.IsState(PIE_MENU_ACTIVE))) && m_JetTimeLeft > 0) - { + } else if ((m_Controller.IsState(BODY_JUMP) || (m_MoveState == JUMP && m_Controller.IsState(PIE_MENU_ACTIVE))) && m_JetTimeLeft > 0 && m_Status != INACTIVE) { m_pJetpack->EnableEmission(true); - // Jetpacks are noisy! m_pJetpack->AlarmOnEmit(m_Team); - // Deduct from the jetpack time m_JetTimeLeft = std::max(m_JetTimeLeft - g_TimerMan.GetDeltaTimeMS(), 0.0F); m_MoveState = JUMP; m_Paths[FGROUND][JUMP].Restart(); m_Paths[BGROUND][JUMP].Restart(); - } - // Jetpack is off/turning off - else { + } else { m_pJetpack->EnableEmission(false); if (m_MoveState == JUMP) { m_MoveState = STAND; } - // Replenish the jetpack time, twice as fast m_JetTimeLeft = std::min(m_JetTimeLeft + g_TimerMan.GetDeltaTimeMS() * 2.0F, m_JetTimeTotal); } float maxAngle = c_HalfPI * m_JetAngleRange; // If pie menu is on, keep the angle to what it was before. if (!m_Controller.IsState(PIE_MENU_ACTIVE)) { - // Direct the jetpack nozzle according to movement stick if analog input is present. + // Direct the jetpack nozzle according to either analog stick input or aim angle. if (m_Controller.GetAnalogMove().GetMagnitude() > 0.1F) { float jetAngle = std::clamp(m_Controller.GetAnalogMove().GetAbsRadAngle() - c_HalfPI, -maxAngle, maxAngle); m_pJetpack->SetEmitAngle(FacingAngle(jetAngle - c_HalfPI)); - // Use the aim angle if we're getting digital input. } else { // Halve the jet angle when looking downwards so the actor isn't forced to go sideways // TODO: don't hardcode this value? @@ -3185,114 +3150,93 @@ void AHuman::Update() //////////////////////////////////// // Movement direction - // If the pie menu is on, try to preserve whatever move state we had before it going into effect - if (m_Controller.IsState(PIE_MENU_ACTIVE)) - { - // Just keep the previous movestate, don't stand up or stop walking or stop jumping - } - else if (m_Controller.IsState(MOVE_RIGHT) || m_Controller.IsState(MOVE_LEFT) || m_MoveState == JUMP && m_Status != INACTIVE) - { - // Only if not jumping, OR if jumping, and apparently stuck on something - then help out with the limbs - if (m_MoveState != JUMP || (m_MoveState == JUMP && m_Vel.GetLargest() < 0.1)) - { - // Restart the stride if we're just starting to walk or crawl - if ((m_MoveState != WALK && !m_Controller.IsState(BODY_CROUCH)) || - (m_MoveState != CRAWL && m_Controller.IsState(BODY_CROUCH))) - { - m_StrideStart = true; - MoveOutOfTerrain(g_MaterialGrass); + bool isStill = m_Vel.GetMagnitude() + m_PrevVel.GetMagnitude() < 1.0F; + + // If the pie menu is on, try to preserve whatever move state we had before it going into effect. + if (!m_Controller.IsState(PIE_MENU_ACTIVE)) { + bool crouching = m_Controller.IsState(BODY_CROUCH); + if ((m_Controller.IsState(MOVE_RIGHT) || m_Controller.IsState(MOVE_LEFT) || m_MoveState == JUMP) && m_Status != INACTIVE) { + for (int i = WALK; i < MOVEMENTSTATECOUNT; ++i) { + m_Paths[FGROUND][i].SetHFlip(m_HFlipped); + m_Paths[BGROUND][i].SetHFlip(m_HFlipped); } + // Only if not jumping, OR if jumping, and apparently stuck on something - then help out with the limbs. + if (m_MoveState != JUMP || isStill) { + // Restart the stride if we're just starting to walk or crawl. + if ((m_MoveState != WALK && !crouching) || (m_MoveState != CRAWL && crouching)) { + m_StrideStart = true; + MoveOutOfTerrain(g_MaterialGrass); + } - // Crawling or walking? - m_MoveState = m_Controller.IsState(BODY_CROUCH) ? CRAWL : WALK; + m_MoveState = crouching ? CRAWL : WALK; - // Engage prone state, this makes the body's rotational spring pull it horizontal instead of upright - if (m_MoveState == CRAWL && m_ProneState == NOTPRONE) - { - m_ProneState = GOPRONE; - m_ProneTimer.Reset(); - } + // Engage prone state, this makes the body's rotational spring pull it horizontal instead of upright. + if (m_MoveState == CRAWL && m_ProneState == NOTPRONE) { + m_ProneState = GOPRONE; + m_ProneTimer.Reset(); + } - m_Paths[FGROUND][m_MoveState].SetSpeed(m_Controller.IsState(MOVE_FAST) ? FAST : NORMAL); - m_Paths[BGROUND][m_MoveState].SetSpeed(m_Controller.IsState(MOVE_FAST) ? FAST : NORMAL); - } + m_Paths[FGROUND][m_MoveState].SetSpeed(m_Controller.IsState(MOVE_FAST) ? FAST : NORMAL); + m_Paths[BGROUND][m_MoveState].SetSpeed(m_Controller.IsState(MOVE_FAST) ? FAST : NORMAL); + } - // Walk backwards if the aiming is done in the opposite direction of travel - if (std::abs(m_Controller.GetAnalogAim().m_X) > 0.1F) - { - // Walk backwards if necessary - m_Paths[FGROUND][m_MoveState].SetHFlip(m_Controller.IsState(MOVE_LEFT)); - m_Paths[BGROUND][m_MoveState].SetHFlip(m_Controller.IsState(MOVE_LEFT)); - } - // Flip if we're moving in the opposite direction - else if ((m_Controller.IsState(MOVE_RIGHT) && m_HFlipped) || (m_Controller.IsState(MOVE_LEFT) && !m_HFlipped)) - { - m_HFlipped = !m_HFlipped; - // // Instead of simply carving out a silhouette of the now flipped actor, isntead disable any atoms which are embedded int eh terrain until they emerge again - // m_ForceDeepCheck = true; - m_CheckTerrIntersection = true; - if (m_ProneState == NOTPRONE) { MoveOutOfTerrain(g_MaterialGrass); } - m_Paths[FGROUND][m_MoveState].SetHFlip(m_HFlipped); - m_Paths[BGROUND][m_MoveState].SetHFlip(m_HFlipped); - - m_Paths[FGROUND][WALK].Terminate(); - m_Paths[BGROUND][WALK].Terminate(); - m_Paths[FGROUND][CROUCH].Terminate(); - m_Paths[BGROUND][CROUCH].Terminate(); - m_Paths[FGROUND][CLIMB].Terminate(); - m_Paths[BGROUND][CLIMB].Terminate(); - m_Paths[FGROUND][CRAWL].Terminate(); - m_Paths[BGROUND][CRAWL].Terminate(); - m_Paths[FGROUND][ARMCRAWL].Terminate(); - m_Paths[BGROUND][ARMCRAWL].Terminate(); - m_Paths[FGROUND][STAND].Terminate(); - m_Paths[BGROUND][STAND].Terminate(); - m_StrideStart = true; - // Stop the going prone spring - if (m_ProneState == GOPRONE) { m_ProneState = PRONE; } + // Walk backwards if the aiming is already focused in the opposite direction of travel. + if (std::abs(analogAim.m_X) > 0 || m_Controller.IsState(AIM_SHARP)) { + m_Paths[FGROUND][m_MoveState].SetHFlip(m_Controller.IsState(MOVE_LEFT)); + m_Paths[BGROUND][m_MoveState].SetHFlip(m_Controller.IsState(MOVE_LEFT)); + } else if ((m_Controller.IsState(MOVE_RIGHT) && m_HFlipped) || (m_Controller.IsState(MOVE_LEFT) && !m_HFlipped)) { + m_HFlipped = !m_HFlipped; + m_CheckTerrIntersection = true; + if (m_ProneState == NOTPRONE) { MoveOutOfTerrain(g_MaterialGrass); } + + for (int i = WALK; i < MOVEMENTSTATECOUNT; ++i) { + m_Paths[FGROUND][i].SetHFlip(m_HFlipped); + m_Paths[BGROUND][i].SetHFlip(m_HFlipped); + m_Paths[FGROUND][i].Terminate(); + m_Paths[BGROUND][i].Terminate(); + } + m_StrideStart = true; + // Stop the going prone spring. + if (m_ProneState == GOPRONE) { m_ProneState = PRONE; } + } + } else { + m_ArmClimbing[FGROUND] = false; + m_ArmClimbing[BGROUND] = false; + if (crouching) { + // Don't go back to crouching if we're already prone, the player has to let go of the crouch button first. If already laying down, just stay put. + m_MoveState = m_ProneState == NOTPRONE ? CROUCH : NOMOVE; + } else { + m_MoveState = STAND; + } } - // Not moving, so check if we need to be crouched or not - } else if (m_Controller.IsState(BODY_CROUCH)) { - // Don't go back to crouching if we're already prone, player has to let go of the crouch button first - // If already laying down, just don't do anything and keep laying there - m_MoveState = m_ProneState == NOTPRONE ? CROUCH : NOMOVE; - } else { - m_MoveState = STAND; + // Disengage the prone state as soon as crouch is released. + if (!crouching) { m_ProneState = NOTPRONE; } } - // Disengage the prone state as soon as the crouch is released when the pie menu isn't active - if (!m_Controller.IsState(BODY_CROUCH) && !m_Controller.IsState(PIE_MENU_ACTIVE)) - m_ProneState = NOTPRONE; - //////////////////////////////////// // Change and reload held MovableObjects bool reloadFG = false; if (m_pFGArm && m_Status != INACTIVE) { + bool changeNext = m_Controller.IsState(WEAPON_CHANGE_NEXT); + bool changePrev = m_Controller.IsState(WEAPON_CHANGE_PREV); HDFirearm * pFireArm = dynamic_cast(m_pFGArm->GetHeldMO()); - if (m_Controller.IsState(WEAPON_CHANGE_NEXT)) { - if (!m_Inventory.empty() || GetEquippedBGItem()) { - if (pFireArm) { pFireArm->StopActivationSound(); } - if (m_Inventory.empty()) { UnequipBGArm(); } - - m_pFGArm->SetHeldMO(SwapNextInventory(m_pFGArm->ReleaseHeldMO())); - m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); - m_PieNeedsUpdate = true; - - EquipShieldInBGArm(); - } - } - if (m_Controller.IsState(WEAPON_CHANGE_PREV)) { - if (!m_Inventory.empty() || GetEquippedBGItem()) { + if ((changeNext || changePrev) && (!m_Inventory.empty() || UnequipBGArm())) { + if (changeNext && changePrev) { + UnequipFGArm(); + } else { if (pFireArm) { pFireArm->StopActivationSound(); } - if (m_Inventory.empty()) { UnequipBGArm(); } - - m_pFGArm->SetHeldMO(SwapPrevInventory(m_pFGArm->ReleaseHeldMO())); - m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); - m_PieNeedsUpdate = true; - + if (changeNext) { + m_pFGArm->SetHeldMO(SwapNextInventory(m_pFGArm->ReleaseHeldMO())); + } else { + m_pFGArm->SetHeldMO(SwapPrevInventory(m_pFGArm->ReleaseHeldMO())); + } EquipShieldInBGArm(); } + m_EquipHUDTimer.Reset(); + m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); + m_PieNeedsUpdate = true; + m_SharpAimProgress = 0; } // Reload held MO, if applicable @@ -3346,69 +3290,43 @@ void AHuman::Update() m_AimAngle -= m_Controller.IsState(AIM_SHARP) ? std::min(m_AimTmr.GetElapsedSimTimeMS() * 0.00005, 0.05) : std::min(m_AimTmr.GetElapsedSimTimeMS() * 0.00015, 0.1); - if (m_AimAngle < -m_AimRange) - m_AimAngle = -m_AimRange; - } - // Analog aim - else if (m_Controller.GetAnalogAim().GetMagnitude() > 0.1 && m_Status != INACTIVE) - { - Vector aim = m_Controller.GetAnalogAim(); - // Hack to avoid the GetAbsRadAngle to mangle an aim angle straight down - if (aim.m_X == 0) - aim.m_X += m_HFlipped ? -0.01 : 0.01; - m_AimAngle = aim.GetAbsRadAngle(); - - // Check for flip change - if ((aim.m_X > 0 && m_HFlipped) || (aim.m_X < 0 && !m_HFlipped)) - { - m_HFlipped = !m_HFlipped; -// // Instead of simply carving out a silhouette of the now flipped actor, isntead disable any atoms which are embedded int eh terrain until they emerge again -// m_ForceDeepCheck = true; - m_CheckTerrIntersection = true; - if (m_ProneState == NOTPRONE) - MoveOutOfTerrain(g_MaterialGrass); - m_Paths[FGROUND][WALK].Terminate(); - m_Paths[BGROUND][WALK].Terminate(); - m_Paths[FGROUND][CLIMB].Terminate(); - m_Paths[BGROUND][CLIMB].Terminate(); - m_Paths[FGROUND][CRAWL].Terminate(); - m_Paths[BGROUND][CRAWL].Terminate(); - m_Paths[FGROUND][ARMCRAWL].Terminate(); - m_Paths[BGROUND][ARMCRAWL].Terminate(); - m_Paths[FGROUND][STAND].Terminate(); - m_Paths[BGROUND][STAND].Terminate(); - m_StrideStart = true; - // Stop the going prone spring - if (m_ProneState == GOPRONE) - m_ProneState = PRONE; - } - // Correct angle based on flip - m_AimAngle = FacingAngle(m_AimAngle); - // Clamp so it's within the range - Clamp(m_AimAngle, m_AimRange, -m_AimRange); - } - else - m_AimState = AIMSTILL; + if (m_AimAngle < -m_AimRange) { m_AimAngle = -m_AimRange; } + + } else if (analogAim.GetMagnitude() > 0 && m_Status != INACTIVE) { + // Hack to avoid the GetAbsRadAngle from mangling an aim angle straight down. + if (analogAim.m_X == 0) { analogAim.m_X += 0.01F * GetFlipFactor(); } + m_AimAngle = analogAim.GetAbsRadAngle(); + + if ((analogAim.m_X > 0 && m_HFlipped) || (analogAim.m_X < 0 && !m_HFlipped)) { + m_HFlipped = !m_HFlipped; + m_CheckTerrIntersection = true; + if (m_ProneState == NOTPRONE) { MoveOutOfTerrain(g_MaterialGrass); } + for (int i = STAND; i < CLIMB; ++i) { + m_Paths[FGROUND][i].SetHFlip(m_HFlipped); + m_Paths[BGROUND][i].SetHFlip(m_HFlipped); + m_Paths[FGROUND][i].Terminate(); + m_Paths[BGROUND][i].Terminate(); + } + m_StrideStart = true; + // Stop the going prone spring. + if (m_ProneState == GOPRONE) { m_ProneState = PRONE; } + } + // Correct angle based on flip. + m_AimAngle = FacingAngle(m_AimAngle); + // Clamp so it's within the range. + Clamp(m_AimAngle, m_AimRange, -m_AimRange); + } else { + m_AimState = AIMSTILL; + } + float adjustedAimAngle = m_AimAngle * GetFlipFactor(); ////////////////////////////// // Sharp aim calculation // TODO: make the delay data driven by both the actor and the device! // - if (m_Controller.IsState(AIM_SHARP) && (m_MoveState == STAND || m_MoveState == CROUCH || m_MoveState == NOMOVE || m_MoveState == WALK) && m_Vel.GetMagnitude() < 5.0F) { -/* - float halfDelay = m_SharpAimDelay / 2; - // Accelerate for first half - if (!m_SharpAimTimer.IsPastSimMS(halfDelay)) - m_SharpAimProgress = (float)m_SharpAimTimer.GetElapsedSimTimeMS() / (float)m_SharpAimDelay; - // Decelerate for second half - else if (!m_SharpAimTimer.IsPastSimMS(m_SharpAimDelay) - m_SharpAimProgress - // At max - else - m_SharpAimProgress = 1.0; -*/ - float aimMag = m_Controller.GetAnalogAim().GetMagnitude(); + if (m_Controller.IsState(AIM_SHARP) && m_Status == STABLE && (m_MoveState == STAND || m_MoveState == CROUCH || m_MoveState == NOMOVE || m_MoveState == WALK) && m_Vel.GetMagnitude() < 5.0F && GetEquippedItem()) { + float aimMag = analogAim.GetMagnitude(); // If aim sharp is being done digitally, then translate to full analog aim mag if (aimMag < 0.1F) { aimMag = 1.0F; } @@ -3439,8 +3357,8 @@ void AHuman::Update() // Fire/Activate held devices ThrownDevice *pThrown = nullptr; - if (m_pFGArm && m_pFGArm->IsAttached()) { - // DOn't reach toward anything + if (m_pFGArm && m_Status != INACTIVE) { + // Force arm to idle by reaching toward a virtually inaccessible point. m_pFGArm->ReachToward(Vector()); // Activate held device, if it's not a thrown device. @@ -3462,17 +3380,16 @@ void AHuman::Update() if (!pThrown->ActivatesWhenReleased()) { pThrown->Activate(); } } m_ArmsState = THROWING_PREP; - m_pFGArm->ReachToward(m_Pos + (m_pFGArm->GetParentOffset() + pThrown->GetStartThrowOffset().RadRotate(m_AimAngle + m_AngularVel * g_TimerMan.GetDeltaTimeSecs())).GetXFlipped(m_HFlipped) * m_Rotation); + m_pFGArm->ReachToward(m_Pos + (m_pFGArm->GetParentOffset() + pThrown->GetStartThrowOffset().RadRotate(m_AimAngle + m_AngularVel * deltaTime)).GetXFlipped(m_HFlipped) * m_Rotation); } else if (m_ArmsState == THROWING_PREP) { m_ArmsState = THROWING_RELEASE; - //TODO: figure out how to properly use EndThrowOffset, since it doesn't play much a role for just one frame - m_pFGArm->SetHandPos(m_Pos + (m_pFGArm->GetParentOffset() + pThrown->GetEndThrowOffset().RadRotate(m_AimAngle * GetFlipFactor())).GetXFlipped(m_HFlipped) * m_Rotation); + // TODO: figure out how to properly use EndThrowOffset, since it doesn't play much a role for just one frame! + m_pFGArm->SetHandPos(m_Pos + (m_pFGArm->GetParentOffset() + pThrown->GetEndThrowOffset().RadRotate(adjustedAimAngle)).GetXFlipped(m_HFlipped) * m_Rotation); MovableObject *pMO = m_pFGArm->ReleaseHeldMO(); if (pMO) { - pMO->SetPos(m_Pos + (m_pFGArm->GetParentOffset() + Vector(m_pFGArm->GetMaxLength(), -m_pFGArm->GetMaxLength() * 0.5F)).GetXFlipped(m_HFlipped).RadRotate(m_AimAngle * GetFlipFactor()) * m_Rotation); - float throwScalar = static_cast(std::min(m_ThrowTmr.GetElapsedSimTimeMS(), static_cast(m_ThrowPrepTime)) / m_ThrowPrepTime); + pMO->SetPos(m_Pos + (m_pFGArm->GetParentOffset() + Vector(m_pFGArm->GetMaxLength(), -m_pFGArm->GetMaxLength() * 0.5F)).GetXFlipped(m_HFlipped).RadRotate(adjustedAimAngle) * m_Rotation); float maxThrowVel = pThrown->GetMaxThrowVel(); float minThrowVel = pThrown->GetMinThrowVel(); if (maxThrowVel == 0) { @@ -3480,11 +3397,11 @@ void AHuman::Update() maxThrowVel = (m_pFGArm->GetThrowStrength() + std::abs(m_AngularVel * 0.5F)) / std::sqrt(std::abs(pMO->GetMass()) + 1.0F); minThrowVel = maxThrowVel * 0.2F; } - Vector tossVec(minThrowVel + (maxThrowVel - minThrowVel) * throwScalar, 0.5F * RandomNormalNum()); + Vector tossVec(minThrowVel + (maxThrowVel - minThrowVel) * GetThrowProgress(), 0.5F * RandomNormalNum()); tossVec.RadRotate(m_AimAngle); pMO->SetVel(tossVec.GetXFlipped(m_HFlipped) * m_Rotation); pMO->SetAngularVel(m_AngularVel + RandomNum(-5.0F, 2.5F) * GetFlipFactor()); - pMO->SetRotAngle(m_AimAngle * GetFlipFactor()); + pMO->SetRotAngle(adjustedAimAngle); if (pMO->IsHeldDevice()) { pMO->SetTeam(m_Team); @@ -3516,20 +3433,20 @@ void AHuman::Update() m_ArmsState = WEAPON_READY; } - if (m_pBGArm && m_pBGArm->IsAttached() && m_pBGArm->HoldsHeldDevice()) { + if (m_pBGArm && m_pBGArm->HoldsHeldDevice() && m_Status != INACTIVE) { HeldDevice *pDevice = m_pBGArm->GetHeldDevice(); - if (pDevice->IsReloading()) { m_SharpAimTimer.Reset(); m_SharpAimProgress = 0; - if (m_pFGArm && m_pFGArm->IsAttached() && !GetEquippedItem()) { m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); } + if (m_pFGArm && !m_pFGArm->HoldsSomething()) { m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); } } - if (reloadFG == false && !pDevice->IsFull() && m_Controller.IsState(WEAPON_RELOAD) && !m_pItemInReach) { - if (m_pFGArm && m_pFGArm->IsAttached() && !GetEquippedItem()) { m_pFGArm->SetHandPos(pDevice->GetMagazinePos()); } + if (reloadFG == false && !pDevice->IsFull() && m_Controller.IsState(WEAPON_RELOAD)) { + if (m_pFGArm && !m_pFGArm->HoldsSomething()) { m_pFGArm->SetHandPos(pDevice->GetMagazinePos()); } pDevice->Reload(); if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } } - if (pDevice->DoneReloading() && m_pFGArm && m_pFGArm->IsAttached() && !GetEquippedItem()) { m_pFGArm->SetHandPos(pDevice->GetMagazinePos()); } + if (pDevice->DoneReloading() && m_pFGArm && !m_pFGArm->HoldsSomething()) { m_pFGArm->SetHandPos(pDevice->GetMagazinePos()); } + pDevice->SetSharpAim(m_SharpAimProgress); if (m_Controller.IsState(WEAPON_FIRE)) { pDevice->Activate(); @@ -3591,12 +3508,15 @@ void AHuman::Update() } g_MovableMan.AddParticle(pMO); } - } else { + } else if (!m_Inventory.empty()) { DropAllInventory(); m_pBGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); } } + EquipShieldInBGArm(); + m_SharpAimProgress = 0; m_PieNeedsUpdate = true; + m_EquipHUDTimer.Reset(); } //////////////////////////////////////// @@ -3608,39 +3528,37 @@ void AHuman::Update() // Try to detect a new item if (m_pFGArm && m_Status == STABLE) { reach += m_pFGArm->GetMaxLength(); - reachPoint = m_pFGArm->GetPos(); + reachPoint = m_pFGArm->GetPos() + m_pFGArm->GetJointOffset().GetXFlipped(m_HFlipped).RadRotate(m_pFGArm->GetRotAngle()); if (!m_pItemInReach) { - MOID itemMOID = g_SceneMan.CastMORay(reachPoint, Vector((m_HFlipped ? -reach : reach) * RandomNum(), RandomNum(0.0F, reach)), m_MOID, Activity::NoTeam, g_MaterialGrass, true, 2); + MOID itemMOID = g_SceneMan.CastMORay(reachPoint, Vector(reach * RandomNum(), 0).RadRotate(GetAimAngle(true) + RandomNum(-c_HalfPI, 0.0F) * GetFlipFactor()), m_MOID, Activity::NoTeam, g_MaterialGrass, true, 2); MovableObject *pItem = g_MovableMan.GetMOFromID(itemMOID); if (pItem) { - m_pItemInReach = pItem ? dynamic_cast(pItem->GetRootParent()) : 0; + m_pItemInReach = pItem ? dynamic_cast(pItem->GetRootParent()) : nullptr; if (m_pItemInReach) { m_PieNeedsUpdate = true; } } } } // Item currently set to be within reach has expired or is now out of range - if (m_pItemInReach && (m_pItemInReach->IsUnPickupable() || (m_pItemInReach->HasPickupLimitations() && !m_pItemInReach->IsPickupableBy(this)) || !g_MovableMan.IsDevice(m_pItemInReach) || g_SceneMan.ShortestDistance(reachPoint, m_pItemInReach->GetPos(), g_SceneMan.SceneWrapsX()).GetMagnitude() > reach + m_pItemInReach->GetRadius())) { - m_pItemInReach = 0; + if (m_pItemInReach && (!m_pFGArm || m_pItemInReach->IsUnPickupable() || (m_pItemInReach->HasPickupLimitations() && !m_pItemInReach->IsPickupableBy(this)) || !g_MovableMan.IsDevice(m_pItemInReach) || g_SceneMan.ShortestDistance(reachPoint, m_pItemInReach->GetPos(), g_SceneMan.SceneWrapsX()).GetMagnitude() > reach + m_pItemInReach->GetRadius())) { + m_pItemInReach = nullptr; m_PieNeedsUpdate = true; } - // Pick up the designated item - if (m_pItemInReach && m_pFGArm && m_Controller.IsState(WEAPON_PICKUP) && m_Status != INACTIVE) - { - // Remove the item from the scene, it's gong into the hands of this - if (g_MovableMan.RemoveMO(m_pItemInReach)) - { - // Replace whatever's in the FG arm (if anything) with what we are picking up - MovableObject *pMO = m_pFGArm->ReleaseHeldMO(); - if (pMO) { m_Inventory.push_back(pMO); } - m_pFGArm->SetHeldMO(m_pItemInReach); - m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); + if (m_pItemInReach && m_pFGArm && m_Controller.IsState(WEAPON_PICKUP) && m_Status != INACTIVE && g_MovableMan.RemoveMO(m_pItemInReach)) { + MovableObject *pMO = m_pFGArm->ReleaseHeldMO(); + if (pMO) { m_Inventory.push_back(pMO); } + m_pFGArm->SetHeldMO(m_pItemInReach); + m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped)); + m_pItemInReach = nullptr; - m_PieNeedsUpdate = true; - if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } - } + EquipShieldInBGArm(); + m_SharpAimProgress = 0; + m_PieNeedsUpdate = true; + if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } + + m_EquipHUDTimer.Reset(); } /////////////////////////////////////////////////// @@ -3659,8 +3577,7 @@ void AHuman::Update() } // WALKING, OR WE ARE JETPACKING AND STUCK - if (m_MoveState == WALK || (m_MoveState == JUMP && m_Vel.GetLargest() < 1.0)) - { + if (m_MoveState == WALK || (m_MoveState == JUMP && isStill)) { m_Paths[FGROUND][STAND].Terminate(); m_Paths[BGROUND][STAND].Terminate(); @@ -3671,9 +3588,8 @@ void AHuman::Update() bool playStride = false; - // Make sure we are starting a stride if we're basically stopped - if (fabs(m_Vel.GetLargest()) < 0.5) - m_StrideStart = true; + // Make sure we are starting a stride if we're basically stopped. + if (isStill) { m_StrideStart = true; } if (m_pFGLeg && (!m_pBGLeg || (!(m_Paths[FGROUND][WALK].PathEnded() && BGLegProg < 0.5) || m_StrideStart))) { @@ -3771,9 +3687,8 @@ void AHuman::Update() m_Paths[BGROUND][CLIMB].Terminate(); } - // Restart the climbing stroke if the current one seems to be taking too long without movement - if ((m_ArmClimbing[FGROUND] || m_ArmClimbing[BGROUND]) && fabs(m_Vel.GetLargest()) < 0.5 && m_StrideTimer.IsPastSimMS(m_Paths[BGROUND][CLIMB].GetTotalPathTime() / 4)) - { + // Restart the climbing stroke if the current one seems to be taking too long with no movement. + if ((m_ArmClimbing[FGROUND] || m_ArmClimbing[BGROUND]) && isStill && m_StrideTimer.IsPastSimMS(static_cast(m_Paths[BGROUND][CLIMB].GetTotalPathTime() * 0.5F))) { m_StrideStart = true; m_Paths[FGROUND][CLIMB].Terminate(); m_Paths[BGROUND][CLIMB].Terminate(); @@ -3868,203 +3783,82 @@ void AHuman::Update() m_Paths[FGROUND][CRAWL].Terminate(); m_Paths[BGROUND][CRAWL].Terminate(); } - } - // JUMPING - else if ((m_pFGLeg || m_pBGLeg) && m_MoveState == JUMP) { - //TODO 4zK Uncomment this section to keep the limb held static - /* - if (m_pFGLeg) { - m_pFGFootGroup->SetLimbPos(m_Pos + RotateOffset(m_Paths[FGROUND][STAND].GetStartOffset())); - } - if (m_pBGLeg) { - m_pBGFootGroup->SetLimbPos(m_Pos + RotateOffset(m_Paths[BGROUND][STAND].GetStartOffset())); - } - */ - - //TODO 4zK Uncomment this section to make the limb follow its jump path. I believe this was data's original intention but - // 1. The existing standard jump limbpath is awful, the actor spends all its time squatting - // 2. I'm not sure of the details, but this push as limb doesn't seem to be advancing the jump limbpath, so it doesn't work very well, even with my efforts to properly reset it - /* - if (m_pFGLeg && (!m_Paths[FGROUND][m_MoveState].PathEnded() || m_JetTimeLeft == m_JetTimeTotal)) { - m_pFGFootGroup->PushAsLimb( - m_Pos + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped), - m_Vel, - Matrix(), - m_Paths[FGROUND][m_MoveState], - deltaTime); - } - if (m_pBGLeg && (!m_Paths[BGROUND][m_MoveState].PathEnded() || m_JetTimeLeft == m_JetTimeTotal)) { - m_pBGFootGroup->PushAsLimb( - m_Pos + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped), - m_Vel, - Matrix(), - m_Paths[BGROUND][m_MoveState], - deltaTime); - } - */ - if (m_pFGLeg && (!m_Paths[FGROUND][m_MoveState].PathEnded() || m_JetTimeLeft == m_JetTimeTotal)) { - m_pFGFootGroup->FlailAsLimb( - m_Pos, - m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_pFGLeg->GetMaxLength(), - g_SceneMan.GetGlobalAcc() * g_TimerMan.GetDeltaTimeSecs(), - m_AngularVel, - m_pFGLeg->GetMass(), - g_TimerMan.GetDeltaTimeSecs()); - } - if (m_pBGLeg && (!m_Paths[BGROUND][m_MoveState].PathEnded() || m_JetTimeLeft == m_JetTimeTotal)) { - m_pBGFootGroup->FlailAsLimb( - m_Pos, - m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped) *m_Rotation, - m_pBGLeg->GetMaxLength(), - g_SceneMan.GetGlobalAcc() *g_TimerMan.GetDeltaTimeSecs(), - m_AngularVel, - m_pBGLeg->GetMass(), - g_TimerMan.GetDeltaTimeSecs()); - } + } else if (m_pFGLeg || m_pBGLeg) { + if (m_MoveState == JUMP) { + // TODO: Utilize jump paths in an intuitive way! + if (m_pFGLeg) { m_pFGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pFGLeg->GetParentOffset()), m_pFGLeg->GetMaxLength(), m_PrevVel, m_AngularVel, m_pFGLeg->GetMass(), deltaTime); } + + if (m_pBGLeg) { m_pBGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pBGLeg->GetParentOffset()), m_pBGLeg->GetMaxLength(), m_PrevVel, m_AngularVel, m_pBGLeg->GetMass(), deltaTime); } + + if (m_JetTimeLeft <= 0) { + m_MoveState = STAND; + m_Paths[FGROUND][JUMP].Terminate(); + m_Paths[BGROUND][JUMP].Terminate(); + m_Paths[FGROUND][STAND].Terminate(); + m_Paths[BGROUND][STAND].Terminate(); + m_Paths[FGROUND][WALK].Terminate(); + m_Paths[BGROUND][WALK].Terminate(); + } + } else { + m_Paths[FGROUND][JUMP].Terminate(); + m_Paths[BGROUND][JUMP].Terminate(); + if (m_MoveState == CROUCH) { + m_Paths[FGROUND][WALK].Terminate(); + m_Paths[BGROUND][WALK].Terminate(); + m_Paths[FGROUND][CRAWL].Terminate(); + m_Paths[BGROUND][CRAWL].Terminate(); - if (m_JetTimeLeft <= 0) { - m_MoveState = STAND; - m_Paths[FGROUND][JUMP].Terminate(); - m_Paths[BGROUND][JUMP].Terminate(); - m_Paths[FGROUND][STAND].Terminate(); - m_Paths[BGROUND][STAND].Terminate(); - m_Paths[FGROUND][WALK].Terminate(); - m_Paths[BGROUND][WALK].Terminate(); - } - } - // CROUCHING - else if ((m_pFGLeg || m_pBGLeg) && m_MoveState == CROUCH) - { - m_Paths[FGROUND][WALK].Terminate(); - m_Paths[BGROUND][WALK].Terminate(); - m_Paths[FGROUND][CRAWL].Terminate(); - m_Paths[BGROUND][CRAWL].Terminate(); + if (m_pFGLeg) { m_pFGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped), m_Vel, limbPathRotation, m_Paths[FGROUND][CROUCH], deltaTime); } - if (m_pFGLeg) - m_pFGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped), - m_Vel, - Matrix(), - m_Paths[FGROUND][CROUCH], - deltaTime); + if (m_pBGLeg) { m_pBGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped), m_Vel, limbPathRotation, m_Paths[BGROUND][CROUCH], deltaTime); } - if (m_pBGLeg) - m_pBGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped), - m_Vel, - Matrix(), - m_Paths[BGROUND][CROUCH], - deltaTime); - } - // STANDING - else if (m_pFGLeg || m_pBGLeg) - { - m_Paths[FGROUND][WALK].Terminate(); - m_Paths[BGROUND][WALK].Terminate(); - m_Paths[FGROUND][CRAWL].Terminate(); - m_Paths[BGROUND][CRAWL].Terminate(); - - if (m_pFGLeg) - m_pFGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped), - m_Vel, - Matrix(), - m_Paths[FGROUND][STAND], - deltaTime, - 0, - false); - - if (m_pBGLeg) - m_pBGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped), - m_Vel, - Matrix(), - m_Paths[BGROUND][STAND], - deltaTime, - 0, - false); - } - } - // Not stable/standing, so make sure the end of limbs are moving around limply in a ragdoll fashion - else - { + } else { + m_Paths[FGROUND][WALK].Terminate(); + m_Paths[BGROUND][WALK].Terminate(); + m_Paths[FGROUND][CRAWL].Terminate(); + m_Paths[BGROUND][CRAWL].Terminate(); + m_Paths[FGROUND][ARMCRAWL].Terminate(); + m_Paths[BGROUND][ARMCRAWL].Terminate(); -// TODO: Make the limb atom groups fly around and react to terrain, without getting stuck etc - bool wrapped = false; - Vector limbPos; - if (m_pFGArm) - { -// m_pFGHandGroup->SetLimbPos(m_pFGArm->GetHandPos(), m_HFlipped); - m_pFGHandGroup->FlailAsLimb(m_Pos, - m_pFGArm->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_pFGArm->GetMaxLength(), - g_SceneMan.GetGlobalAcc() * g_TimerMan.GetDeltaTimeSecs(), - m_AngularVel, - m_pFGArm->GetMass(), - g_TimerMan.GetDeltaTimeSecs()); - } - if (m_pBGArm) - { -// m_pBGHandGroup->SetLimbPos(m_pBGArm->GetHandPos(), m_HFlipped); - m_pBGHandGroup->FlailAsLimb(m_Pos, - m_pBGArm->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_pBGArm->GetMaxLength(), - g_SceneMan.GetGlobalAcc() * g_TimerMan.GetDeltaTimeSecs(), - m_AngularVel, - m_pBGArm->GetMass(), - g_TimerMan.GetDeltaTimeSecs()); - } - if (m_pFGLeg) - { -// m_pFGFootGroup->SetLimbPos(m_pFGLeg->GetAnklePos(), m_HFlipped); - m_pFGFootGroup->FlailAsLimb(m_Pos, - m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_pFGLeg->GetMaxLength(), - g_SceneMan.GetGlobalAcc() * g_TimerMan.GetDeltaTimeSecs(), - m_AngularVel, - m_pFGLeg->GetMass(), - g_TimerMan.GetDeltaTimeSecs()); - } - if (m_pBGLeg) - { -// m_pBGFootGroup->SetLimbPos(m_pBGLeg->GetAnklePos(), m_HFlipped); - m_pBGFootGroup->FlailAsLimb(m_Pos, - m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped) * m_Rotation, - m_pBGLeg->GetMaxLength(), - g_SceneMan.GetGlobalAcc() * g_TimerMan.GetDeltaTimeSecs(), - m_AngularVel, - m_pBGLeg->GetMass(), - g_TimerMan.GetDeltaTimeSecs()); - } - } + if (m_pFGLeg) { m_pFGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pFGLeg->GetParentOffset().GetXFlipped(m_HFlipped), m_Vel, limbPathRotation, m_Paths[FGROUND][STAND], deltaTime, 0, !m_pBGLeg); } + + if (m_pBGLeg) { m_pBGFootGroup->PushAsLimb(m_Pos.GetFloored() + m_pBGLeg->GetParentOffset().GetXFlipped(m_HFlipped), m_Vel, limbPathRotation, m_Paths[BGROUND][STAND], deltaTime, 0, !m_pFGLeg); } + } + } + } + } else { + // Not stable/standing, so make sure the end of limbs are moving around limply in a ragdoll fashion. + // TODO: Make the limb atom groups fly around and react to terrain, without getting stuck etc. + if (m_pFGArm) { m_pFGHandGroup->FlailAsLimb(m_Pos, RotateOffset(m_pFGArm->GetParentOffset()), m_pFGArm->GetMaxLength(), m_PrevVel * m_pFGArm->GetJointStiffness(), m_AngularVel, m_pFGArm->GetMass(), deltaTime); } + + if (m_pBGArm) { m_pBGHandGroup->FlailAsLimb(m_Pos, RotateOffset(m_pBGArm->GetParentOffset()), m_pBGArm->GetMaxLength(), m_PrevVel * m_pBGArm->GetJointStiffness(), m_AngularVel, m_pBGArm->GetMass(), deltaTime); } + + if (m_pFGLeg) { m_pFGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pFGLeg->GetParentOffset()), m_pFGLeg->GetMaxLength(), m_PrevVel * m_pFGLeg->GetJointStiffness(), m_AngularVel, m_pFGLeg->GetMass(), deltaTime); } + + if (m_pBGLeg) { m_pBGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pBGLeg->GetParentOffset()), m_pBGLeg->GetMaxLength(), m_PrevVel * m_pBGLeg->GetJointStiffness(), m_AngularVel, m_pBGLeg->GetMass(), deltaTime); } + } ///////////////////////////////// // Manage Attachable:s - if (m_pHead && m_pHead->IsAttached()) { + if (m_pHead) { float toRotate = 0; // Only rotate the head to match the aim angle if body is stable and upright if (m_Status == STABLE && std::abs(rot) < (c_HalfPI + c_QuarterPI)) { - toRotate = m_pHead->GetRotMatrix().GetRadAngleTo((m_AimAngle * GetFlipFactor()) * m_LookToAimRatio + rot * (0.9F - m_LookToAimRatio)) * 0.15F; - } - // If dying upright, make head slump forward or back depending on body lean -// TODO: Doesn't work too well, but probably could -// else if ((m_Status == DEAD || m_Status == DYING) && fabs(m_Rotation.GetRadAngle()) < c_QuarterPI) -// { -// toRotate = m_pHead->GetRotMatrix().GetRadAngleTo(m_Rotation.GetRadAngle() + ((m_HFlipped && m_Rotation.GetRadAngle() > 0) || (!m_HFlipped && m_Rotation.GetRadAngle() > 0) ? c_PI : -c_PI) * 0.6); -// toRotate *= 0.10; -// } - // Make head just keep rotating loosely with the body if unstable or upside down - else { - toRotate = m_pHead->GetRotMatrix().GetRadAngleTo(m_Rotation.GetRadAngle()); - toRotate *= 0.10F; + toRotate = m_pHead->GetRotMatrix().GetRadAngleTo((adjustedAimAngle) * m_LookToAimRatio + rot * (0.9F - m_LookToAimRatio)) * 0.15F; + } else { + // Rotate the head loosely along with the body if upside down, unstable or dying. + toRotate = m_pHead->GetRotMatrix().GetRadAngleTo(rot) * m_pHead->GetJointStiffness() * (std::abs(toRotate) + c_QuarterPI); } // Now actually rotate by the amount calculated above m_pHead->SetRotAngle(m_pHead->GetRotAngle() + toRotate); } - if (m_pFGLeg && m_pFGLeg->IsAttached()) { + if (m_pFGLeg) { m_pFGLeg->EnableIdle(m_ProneState == NOTPRONE && m_Status != UNSTABLE); m_pFGLeg->SetTargetPosition(m_pFGFootGroup->GetLimbPos(m_HFlipped)); } - if (m_pBGLeg && m_pBGLeg->IsAttached()) { + if (m_pBGLeg) { m_pBGLeg->EnableIdle(m_ProneState == NOTPRONE && m_Status != UNSTABLE); m_pBGLeg->SetTargetPosition(m_pBGFootGroup->GetLimbPos(m_HFlipped)); } @@ -4078,7 +3872,7 @@ void AHuman::Update() affectingBodyAngle = std::abs(std::sin(rot)) * rot * m_FGArmFlailScalar * (1.0F - aimScalar); } - m_pFGArm->SetRotAngle(affectingBodyAngle + m_AimAngle * GetFlipFactor()); + m_pFGArm->SetRotAngle(affectingBodyAngle + adjustedAimAngle); if (m_Status == STABLE) { if (m_ArmClimbing[FGROUND]) { @@ -4094,7 +3888,7 @@ void AHuman::Update() } if (m_pBGArm) { - m_pBGArm->SetRotAngle(std::abs(std::sin(rot)) * rot * m_BGArmFlailScalar + (m_AimAngle * GetFlipFactor())); + m_pBGArm->SetRotAngle(std::abs(std::sin(rot)) * rot * m_BGArmFlailScalar + (adjustedAimAngle)); if (m_Status == STABLE) { if (m_ArmClimbing[BGROUND]) { // Can't climb or crawl with the shield @@ -4102,19 +3896,17 @@ void AHuman::Update() m_pBGArm->ReachToward(m_pBGHandGroup->GetLimbPos(m_HFlipped)); } else { - // Re-equip shield in BG arm after climbing - EquipShieldInBGArm(); if (m_pFGArm && m_pFGArm->HoldsHeldDevice() && !m_pBGArm->HoldsHeldDevice()) { - m_pBGArm->Reach(m_pFGArm->GetHeldDevice()->GetSupportPos()); - - // BGArm does reach to support the device held by FGArm. - if (m_pBGArm->DidReach()) { - m_pFGArm->GetHeldDevice()->SetSupported(true); - m_pBGArm->SetRecoil(m_pFGArm->GetHeldDevice()->GetRecoilForce(), m_pFGArm->GetHeldDevice()->GetRecoilOffset(), m_pFGArm->GetHeldDevice()->IsRecoiled()); - } else { - // BGArm did not reach to support the device. Count device as supported anyway, if crouching - m_pFGArm->GetHeldDevice()->SetSupported(m_MoveState == CROUCH || m_ProneState == PRONE); - m_pBGArm->SetRecoil(Vector(), Vector(), false); + if (!EquipShieldInBGArm()) { + m_pBGArm->Reach(m_pFGArm->GetHeldDevice()->GetSupportPos()); + if (m_pBGArm->DidReach()) { + m_pFGArm->GetHeldDevice()->SetSupported(true); + m_pBGArm->SetRecoil(m_pFGArm->GetHeldDevice()->GetRecoilForce(), m_pFGArm->GetHeldDevice()->GetRecoilOffset(), m_pFGArm->GetHeldDevice()->IsRecoiled()); + } else { + // BGArm did not reach to support the device. Count device as supported anyway, if crouching. + m_pFGArm->GetHeldDevice()->SetSupported(m_MoveState == CROUCH || m_ProneState == PRONE); + m_pBGArm->SetRecoil(Vector(), Vector(), false); + } } } else { // Use an unreachable position to force this arm to idle, so it wont bug out where the AtomGroup was left off @@ -4144,9 +3936,9 @@ void AHuman::Update() // Reset this each frame m_SharpAimMaxedOut = false; - if (m_pFGArm && m_pFGArm->IsAttached() && m_pFGArm->HoldsHeldDevice()) - { - float maxLength = m_pFGArm->GetHeldDevice()->GetSharpLength(); + if (m_pFGArm && m_pFGArm->HoldsHeldDevice()) { + HeldDevice *heldDevice = m_pFGArm->GetHeldDevice(); + float maxLength = heldDevice->GetSharpLength(); if (maxLength == 0) { m_SharpAimProgress = 0; m_SharpAimMaxedOut = true; @@ -4155,17 +3947,27 @@ void AHuman::Update() } // Use a non-terrain check ray to cap the magnitude, so we can't see into objects etc if (m_SharpAimProgress > 0) { - if (m_pFGArm->GetHeldDevice()->IsRecoiled()) { - float totalGripStrength = m_pFGArm->GetGripStrength() + (m_pBGArm && m_pBGArm->IsAttached() && m_pBGArm->HoldsHeldDevice() ? m_pBGArm->GetGripStrength() : 0); - m_SharpAimProgress *= 1 - std::min(m_pFGArm->GetHeldDevice()->GetRecoilForce().GetMagnitude() / std::max(totalGripStrength * m_pFGArm->GetHeldDevice()->GetGripStrengthMultiplier(), 1.0F), 1.0F); + // TODO: make an uniform function to get the total GripStrength of an AHuman? + float totalGripStrength = m_pFGArm->GetGripStrength(); + if (m_pBGArm) { + if (m_pBGArm->HoldsHeldDevice()) { + HeldDevice *heldBGDevice = m_pBGArm->GetHeldDevice(); + if (heldBGDevice->IsRecoiled()) { + m_SharpAimProgress *= 1.0F - std::min(heldBGDevice->GetRecoilForce().GetMagnitude() / std::max(m_pBGArm->GetGripStrength() * heldBGDevice->GetGripStrengthMultiplier(), 1.0F), 1.0F); + } + } else if (heldDevice->GetSupported()) { + totalGripStrength += m_pBGArm->GetGripStrength(); + } + } + if (heldDevice->IsRecoiled()) { + m_SharpAimProgress *= 1.0F - std::min(heldDevice->GetRecoilForce().GetMagnitude() / std::max(totalGripStrength * heldDevice->GetGripStrengthMultiplier(), 1.0F), 1.0F); } Vector notUsed; Vector sharpAimVector(maxLength, 0); sharpAimVector *= aimMatrix; // See how far along the sharp aim vector there is opaque air -// float result = g_SceneMan.CastNotMaterialRay(m_pFGArm->GetHeldDevice()->GetMuzzlePos(), sharpAimVector, g_MaterialAir, 5); - float result = g_SceneMan.CastObstacleRay(m_pFGArm->GetHeldDevice()->GetMuzzlePos(), sharpAimVector, notUsed, notUsed, GetRootID(), IgnoresWhichTeam(), g_MaterialAir, 5); + float result = g_SceneMan.CastObstacleRay(heldDevice->GetMuzzlePos(), sharpAimVector, notUsed, notUsed, GetRootID(), IgnoresWhichTeam(), g_MaterialAir, 5); // If we didn't find anything but air before the sharpdistance, then don't alter the sharp distance if (result >= 0 && result < (maxLength * m_SharpAimProgress)) { @@ -4173,11 +3975,7 @@ void AHuman::Update() m_SharpAimMaxedOut = true; } } - // Indicate maxed outedness if we really are, too - if (m_SharpAimProgress > 0.9) - m_SharpAimMaxedOut = true; - -// sharpDistance *= m_Controller.GetAnalogAim().GetMagnitude(); + if (m_SharpAimProgress > 0.9F) { m_SharpAimMaxedOut = true; } aimSight.m_X += maxLength * m_SharpAimProgress; } @@ -4228,16 +4026,14 @@ void AHuman::Update() // Balance stuff // Eliminate full rotations - while (fabs(rot) > c_TwoPI) { + while (std::abs(rot) > c_TwoPI) { rot -= rot > 0 ? c_TwoPI : -c_TwoPI; } // Eliminate rotations over half a turn - if (fabs(rot) > c_PI) - { + if (std::abs(rot) > c_PI) { rot = (rot > 0 ? -c_PI : c_PI) + (rot - (rot > 0 ? c_PI : -c_PI)); // If we're upside down, we're unstable damnit - if (m_Status != DYING && m_Status != DEAD) - m_Status = UNSTABLE; + if (m_Status != DYING && m_Status != DEAD) { m_Status = UNSTABLE; } m_StableRecoverTimer.Reset(); } @@ -4250,80 +4046,57 @@ void AHuman::Update() float rotTarget = m_HFlipped ? c_HalfPI : -c_HalfPI; float rotDiff = rotTarget - rot; - if (m_ProneState == GOPRONE) - { - if (!m_ProneTimer.IsPastSimMS(333)) - { - if (fabs(rotDiff) > 0.1 && fabs(rotDiff) < c_PI) - { - m_AngularVel += rotDiff * 0.45;// * fabs(rotDiff); - m_Vel.m_X += (m_HFlipped ? -fabs(rotDiff) : fabs(rotDiff)) * 0.25; - } - } - // Done going down, now stay down without spring - else - { - m_AngularVel *= 0.5; - m_ProneState = PRONE; + if (m_ProneState == GOPRONE) { + if (!m_ProneTimer.IsPastSimMS(333)) { + if (std::abs(rotDiff) > 0.1F && std::abs(rotDiff) < c_PI) { + m_AngularVel += rotDiff * 0.4F; + m_Vel.m_X += (m_HFlipped ? -std::abs(rotDiff) : std::abs(rotDiff)) / std::max(m_Vel.GetMagnitude(), 4.0F); + } + } else { + // Done going down, now stay down without spring. + m_AngularVel *= 0.5F; + m_ProneState = PRONE; } -/* - // Break the spring if close to target angle. - if (-c_HalfPI + fabs(rot) > 0.1) - m_AngularVel -= rot * fabs(rot); - else if (fabs(m_AngularVel) > 0.1) - m_AngularVel *= 0.5; -*/ - } - // If down, try to keep flat against the ground - else if (m_ProneState == PRONE) - { - if (fabs(rotDiff) > c_SixteenthPI && fabs(rotDiff) < c_HalfPI) - m_AngularVel += rotDiff * 0.65;// * fabs(rotDiff); - else if (fabs(m_AngularVel) > 0.3) - m_AngularVel *= 0.85; - } + } else if (m_ProneState == PRONE) { + // If down, try to keep flat against the ground. + if (std::abs(rotDiff) > c_SixteenthPI && std::abs(rotDiff) < c_HalfPI) { + m_AngularVel += rotDiff * 0.65F; + } else if (std::abs(m_AngularVel) > 0.3F) { + m_AngularVel *= 0.85F; + } + } } else { // Upright body posture float rotDiff = rot - (GetRotAngleTarget(m_MoveState) * (m_AimAngle > 0 ? 1.0F - (m_AimAngle / c_HalfPI) : 1.0F) * GetFlipFactor()); m_AngularVel = m_AngularVel * (0.98F - 0.06F * (m_Health / m_MaxHealth)) - (rotDiff * 0.5F); } } - // Keel over - else if (m_Status == UNSTABLE && fabs(m_AngularVel) < 5) - { - float rotTarget = 0; - // If traveling at speed, then always start falling forward - if (fabs(m_Vel.m_X) > 1.0) - rotTarget = m_HFlipped ? c_HalfPI : -c_HalfPI; - // Go whichever way we're already rotated - else - rotTarget = rot > 0 ? c_HalfPI : -c_HalfPI; - - float rotDiff = rotTarget - rot; - if (fabs(rotDiff) > 0.1 && fabs(rotDiff) < c_PI) - { - m_AngularVel += rotDiff * 0.05; -// m_Vel.m_X += (rotTarget > 0 ? -fabs(rotDiff) : fabs(rotDiff)) * 0.35; - } - } - // While dying, pull body quickly toward down toward horizontal - else if (m_Status == DYING) - { - float rotTarget = rot > 0 ? c_HalfPI : -c_HalfPI; -// float rotTarget = m_HFlipped ? c_HalfPI : -c_HalfPI; - float rotDiff = rotTarget - rot; - if (!m_DeathTmr.IsPastSimMS(125) && fabs(rotDiff) > 0.1 && fabs(rotDiff) < c_PI) - { - m_AngularVel += rotDiff * 0.5;//fabs(rotDiff); -// m_Vel.m_X += (m_HFlipped ? -fabs(rotDiff) : fabs(rotDiff)) * 0.35; - m_Vel.m_X += (rotTarget > 0 ? -fabs(rotDiff) : fabs(rotDiff)) * 0.35; - } - else - m_Status = DEAD; + else if (m_Status == UNSTABLE) { + float rotTarget = 0; + // If traveling at speed, always start falling forward. + if (std::abs(m_Vel.m_X) > 1.0F) { + rotTarget = m_HFlipped ? c_HalfPI : -c_HalfPI; + } else { + // Otherwise, go whichever way we're already rotated. + rotTarget = rot > 0 ? c_HalfPI : -c_HalfPI; + } -// else if (fabs(m_AngularVel) > 0.1) -// m_AngularVel *= 0.5; - } + float rotDiff = rotTarget - rot; + if (std::abs(rotDiff) > 0.1F && std::abs(rotDiff) < c_PI) { + m_AngularVel += rotDiff * 0.05F; + } + } else if (m_Status == DYING) { + float rotTarget = m_Vel.m_X - (rot + m_AngularVel) > 0 ? -c_HalfPI : c_HalfPI; + float rotDiff = rotTarget - rot; + if (!m_DeathTmr.IsPastSimMS(125) && std::abs(rotDiff) > 0.1F && std::abs(rotDiff) < c_PI) { + // TODO: finetune this for situations like low gravity! + float velScalar = 0.5F; //* (g_SceneMan.GetGlobalAcc().GetY * m_GlobalAccScalar) / GetPPM(); + m_AngularVel += rotDiff * velScalar; + m_Vel.m_X += (rotTarget > 0 ? -std::abs(rotDiff) : std::abs(rotDiff)) * velScalar * 0.5F; + } else { + m_Status = DEAD; + } + } m_Rotation.SetRadAngle(rot); /////////////////////////////////////////////////// @@ -4335,12 +4108,9 @@ void AHuman::Update() m_Health -= 0.1F; } - if (m_Status == DYING) - { - if (m_pFGArm && m_pFGArm->IsAttached()) - m_pFGArm->DropEverything(); - if (m_pBGArm && m_pBGArm->IsAttached()) - m_pBGArm->DropEverything(); + if (m_Status == DYING) { + if (m_pFGArm) { m_pFGArm->DropEverything(); } + if (m_pBGArm) { m_pBGArm->DropEverything(); } } ///////////////////////////////////////// @@ -4351,12 +4121,8 @@ void AHuman::Update() ////////////////////////////////////////////////////////////////////////////////////////// -// Virtual method: DrawThrowingReticule -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Draws an aiming aid in front of this HeldDevice for throwing. -void AHuman::DrawThrowingReticule(BITMAP *pTargetBitmap, const Vector &targetPos, double amount) const -{ +void AHuman::DrawThrowingReticle(BITMAP *targetBitmap, const Vector &targetPos, float progressScalar) const { const int pointCount = 9; Vector points[pointCount]; //Color colors[pointCount]; @@ -4368,23 +4134,23 @@ void AHuman::DrawThrowingReticule(BITMAP *pTargetBitmap, const Vector &targetPos Vector outOffset(15.0F * GetFlipFactor(), -5.0F); - acquire_bitmap(pTargetBitmap); + acquire_bitmap(targetBitmap); - for (int i = 0; i < pointCount * amount; ++i) { + for (int i = 0; i < pointCount * progressScalar; ++i) { points[i].FlipX(m_HFlipped); points[i] += outOffset; points[i].RadRotate((m_AimAngle * GetFlipFactor()) + m_Rotation.GetRadAngle()); points[i] += m_Pos; - if (m_pFGArm && m_pFGArm->IsAttached()) + if (m_pFGArm) points[i] += m_pFGArm->GetParentOffset(); - // Put the flickering glows on the reticule dots, in absolute scene coordinates + // Put the flickering glows on the reticle dots, in absolute scene coordinates g_PostProcessMan.RegisterGlowDotEffect(points[i], YellowDot, 55 + RandomNum(0, 100)); - putpixel(pTargetBitmap, points[i].GetFloorIntX() - targetPos.GetFloorIntX(), points[i].GetFloorIntY() - targetPos.GetFloorIntY(), g_YellowGlowColor); + putpixel(targetBitmap, points[i].GetFloorIntX() - targetPos.GetFloorIntX(), points[i].GetFloorIntY() - targetPos.GetFloorIntY(), g_YellowGlowColor); } - release_bitmap(pTargetBitmap); + release_bitmap(targetBitmap); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -4482,15 +4248,13 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc // Player AI drawing - // Device aiming reticule - if (m_Controller.IsState(AIM_SHARP) && m_pFGArm && m_pFGArm->IsAttached() && m_pFGArm->HoldsHeldDevice()) { - m_pFGArm->GetHeldDevice()->DrawHUD(pTargetBitmap, targetPos, whichScreen, m_Controller.IsPlayerControlled()); - } - - - // Throwing reticule - if (m_ArmsState == THROWING_PREP) { - DrawThrowingReticule(pTargetBitmap, targetPos, std::min(m_ThrowTmr.GetElapsedSimTimeMS() / m_ThrowPrepTime, 1.0)); + if (m_pFGArm && m_pFGArm->HoldsHeldDevice()) { + // Draw the aiming dots for the currently held device. + if (m_ArmsState == THROWING_PREP) { + DrawThrowingReticle(pTargetBitmap, targetPos, GetThrowProgress()); + } else if (m_Controller.IsState(AIM_SHARP) || (m_Controller.IsPlayerControlled() && !m_Controller.IsState(PIE_MENU_ACTIVE))) { + m_pFGArm->GetHeldDevice()->DrawHUD(pTargetBitmap, targetPos, whichScreen, m_Controller.IsState(AIM_SHARP) && m_Controller.IsPlayerControlled()); + } } ////////////////////////////////////// @@ -4505,7 +4269,7 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc { AllegroBitmap allegroBitmap(pTargetBitmap); /* - // Device aiming reticule + // Device aiming reticle if (m_Controller.IsState(AIM_SHARP) && m_pFGArm && m_pFGArm->IsAttached() && m_pFGArm->HoldsHeldDevice()) m_pFGArm->GetHeldDevice()->DrawHUD(pTargetBitmap, targetPos, whichScreen);*/ @@ -4536,7 +4300,7 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc } // Weight and jetpack energy - if (m_pJetpack && m_pJetpack->IsAttached() && m_Controller.IsState(BODY_JUMP)) + if (m_pJetpack && m_Controller.IsState(BODY_JUMP) && m_Status != INACTIVE) { // Draw empty fuel indicator if (m_JetTimeLeft < 100) @@ -4555,21 +4319,25 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc } // null-terminate str[1] = 0; - pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 11, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre); + pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 9, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre); float jetTimeRatio = m_JetTimeLeft / m_JetTimeTotal; -// TODO: Don't hardcode this shit - int gaugeColor = jetTimeRatio > 0.6F ? 149 : (jetTimeRatio > 0.3F ? 77 : 13); - rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 6, drawPos.GetFloorIntX() + (16 * jetTimeRatio), drawPos.GetFloorIntY() + m_HUDStack + 7, gaugeColor); -// rect(pTargetBitmap, drawPos.m_X, drawPos.m_Y + m_HUDStack - 2, drawPos.m_X + 24, drawPos.m_Y + m_HUDStack - 4, 238); -// std::snprintf(str, sizeof(str), "%.0f Kg", mass); -// pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Left); - - m_HUDStack += -10; - } + int gaugeColor = jetTimeRatio > 0.6F ? 149 : (jetTimeRatio > 0.3F ? 77 : 13); + rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 7, drawPos.GetFloorIntX() + 16, drawPos.GetFloorIntY() + m_HUDStack + 8, 245); + rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 6, drawPos.GetFloorIntX() + static_cast(15.0F * jetTimeRatio), drawPos.GetFloorIntY() + m_HUDStack + 7, gaugeColor); + + m_HUDStack -= 10; + if (m_pFGArm && !m_EquipHUDTimer.IsPastRealMS(500)) { + if (m_pFGArm->HoldsSomething()) { + pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 3, m_pFGArm->GetHeldMO()->GetPresetName().c_str(), GUIFont::Centre); + } else { + pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 3, "EMPTY", GUIFont::Centre); + } + m_HUDStack -= 9; + } + } // Held-related GUI stuff - else if (m_pFGArm && m_pFGArm->IsAttached()) - { + else if (m_pFGArm) { HDFirearm *pHeldFirearm = dynamic_cast(m_pFGArm->GetHeldDevice()); // Ammo @@ -4580,24 +4348,37 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc str[0] = -56; str[1] = 0; pSymbolFont->DrawAligned(&allegroBitmap, drawPos.m_X - 10, drawPos.m_Y + m_HUDStack, str, GUIFont::Left); - std::string fgWeaponString = pHeldFirearm->GetRoundInMagCount() < 0 ? "Infinite" : std::to_string(pHeldFirearm->GetRoundInMagCount()); - fgWeaponString = pHeldFirearm->IsReloading() ? "Reloading" : fgWeaponString; + std::string fgWeaponString; + + if (pHeldFirearm->IsReloading()) { + fgWeaponString = "Reloading"; + rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 13, drawPos.GetFloorIntX() + 29, drawPos.GetFloorIntY() + m_HUDStack + 14, 245); + rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 12, drawPos.GetFloorIntX() + static_cast(28.0F * pHeldFirearm->GetReloadProgress() + 0.5F), drawPos.GetFloorIntY() + m_HUDStack + 13, 77); + } else { + fgWeaponString = pHeldFirearm->GetRoundInMagCount() < 0 ? "Infinite" : std::to_string(pHeldFirearm->GetRoundInMagCount()); + } if (bgHeldItem && bgHeldFirearm) { - std::string bgWeaponString = bgHeldFirearm->GetRoundInMagCount() < 0 ? "Infinite" : std::to_string(bgHeldFirearm->GetRoundInMagCount()); - bgWeaponString = bgHeldFirearm->IsReloading() ? "Reloading" : bgWeaponString; - std::snprintf(str, sizeof(str), "%s | %s", fgWeaponString.c_str(), bgWeaponString.c_str()); + std::string bgWeaponString; + + if (bgHeldFirearm->IsReloading()) { + bgWeaponString = "Reloading"; + int textWidth = pSmallFont->CalculateWidth(fgWeaponString) + 6; + rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1 + textWidth, drawPos.GetFloorIntY() + m_HUDStack + 13, drawPos.GetFloorIntX() + 29 + textWidth, drawPos.GetFloorIntY() + m_HUDStack + 14, 245); + rectfill(pTargetBitmap, drawPos.GetFloorIntX() + textWidth, drawPos.GetFloorIntY() + m_HUDStack + 12, drawPos.GetFloorIntX() + static_cast(28.0F * bgHeldFirearm->GetReloadProgress() + 0.5F) + textWidth, drawPos.GetFloorIntY() + m_HUDStack + 13, 77); + } else { + bgWeaponString = bgHeldFirearm->GetRoundInMagCount() < 0 ? "Infinite" : std::to_string(bgHeldFirearm->GetRoundInMagCount()); + } + std::snprintf(str, sizeof(str), "%s | %s", fgWeaponString.c_str(), bgWeaponString.c_str()); } else { std::snprintf(str, sizeof(str), "%s", fgWeaponString.c_str()); } - pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Left); + pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 3, str, GUIFont::Left); - m_HUDStack += -10; + m_HUDStack -= 10; } - // Device changing GUI - if (m_Controller.IsState(PIE_MENU_ACTIVE)) - { + if (m_Controller.IsState(PIE_MENU_ACTIVE) || !m_EquipHUDTimer.IsPastRealMS(700)) { /* // Display Gold tally if gold chunk is in hand if (m_pFGArm->HoldsSomething() && m_pFGArm->GetHeldMO()->IsGold() && GetGoldCarried() > 0) @@ -4607,26 +4388,15 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc std::snprintf(str, sizeof(str), "%.0f oz", GetGoldCarried()); pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack + 2, str, GUIFont::Left); - m_HUDStack += -11; + m_HUDStack -= 11; } */ - if (m_pFGArm->HoldsSomething()) - { -/* - std::snprintf(str, sizeof(str), " Œ Drop"); - pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 12, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Left); - m_HUDStack += -9; -*/ -// std::snprintf(str, sizeof(str), " %s", m_pFGArm->GetHeldMO()->GetPresetName().c_str()); - pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X, drawPos.m_Y + m_HUDStack + 3, m_pFGArm->GetHeldMO()->GetPresetName().c_str(), GUIFont::Centre); - m_HUDStack += -9; - } - else - { -// std::snprintf(str, sizeof(str), "æ EMPTY ø"); - pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X, drawPos.m_Y + m_HUDStack + 3, "EMPTY", GUIFont::Centre); - m_HUDStack += -9; - } + if (m_pFGArm->HoldsSomething()) { + pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 3, m_pFGArm->GetHeldMO()->GetPresetName().c_str(), GUIFont::Centre); + } else { + pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 3, "EMPTY", GUIFont::Centre); + } + m_HUDStack -= 9; /* // Reload GUI, only show when there's nothing to pick up if (!m_pItemInReach && m_pFGArm->HoldsSomething() && pHeldFirearm && !pHeldFirearm->IsFull()) @@ -4641,19 +4411,13 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc { std::snprintf(str, sizeof(str), "NO ARM!"); pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X + 2, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Centre); - m_HUDStack += -9; + m_HUDStack -= 9; } // Pickup GUI - if (!m_Controller.IsState(PIE_MENU_ACTIVE)) - { - if (m_pItemInReach && g_MovableMan.IsDevice(m_pItemInReach) && m_pFGArm && m_pFGArm->IsAttached()) - { - std::snprintf(str, sizeof(str), " %c %s", -49, m_pItemInReach->GetPresetName().c_str()); - pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 12, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Left); - } - else - m_pItemInReach = 0; + if (!m_Controller.IsState(PIE_MENU_ACTIVE) && m_pItemInReach) { + std::snprintf(str, sizeof(str), " %c %s", -49, m_pItemInReach->GetPresetName().c_str()); + pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 3, str, GUIFont::Centre); } /* // AI Mode select GUI HUD diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 18cba29c6..1e46bfee5 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -563,14 +563,22 @@ ClassInfoGetters; // bool EquipDualWieldableInBGArm(); + /// + /// Gets the throw chargeup progress of this AHuman. + /// + /// The throw chargeup progress, as a scalar from 0 to 1. + float GetThrowProgress() const { return m_ThrowPrepTime > 0 ? static_cast(std::min(m_ThrowTmr.GetElapsedSimTimeMS() / static_cast(m_ThrowPrepTime), 1.0)) : 1.0F; } -////////////////////////////////////////////////////////////////////////////////////////// -// Virtual Method: UnequipBGArm -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Unequips whatever is in the BG arm and puts it into the inventory. -// Arguments: None. -// Return value: Whether there was anything to unequip. + /// + /// Unequips whatever is in the FG arm and puts it into the inventory. + /// + /// Whether there was anything to unequip. + bool UnequipFGArm(); + /// + /// Unequips whatever is in the BG arm and puts it into the inventory. + /// + /// Whether there was anything to unequip. bool UnequipBGArm(); @@ -915,17 +923,13 @@ ClassInfoGetters; void ChunkGold(); -////////////////////////////////////////////////////////////////////////////////////////// -// Method: DrawThrowingReticule -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Draws an aiming aid in front of this HeldDevice for throwing. -// Arguments: A pointer to a BITMAP to draw on. -// The absolute position of the target bitmap's upper left corner in the Scene. -// A normalized scalar that determines how much of the magnitude of the -// reticule should be drawn, to indicate force in the throw. -// Return value: None. - - void DrawThrowingReticule(BITMAP *pTargetBitmap, const Vector &targetPos = Vector(), double amount = 1.0) const; + /// + /// Draws an aiming aid in front of this AHuman for throwing. + /// + /// A pointer to a BITMAP to draw on. + /// The absolute position of the target bitmap's upper left corner in the Scene. + /// A normalized scalar that determines the magnitude of the reticle, to indicate force in the throw. + void DrawThrowingReticle(BITMAP *targetBitmap, const Vector &targetPos = Vector(), float progressScalar = 1.0F) const; // Member variables @@ -989,12 +993,10 @@ ClassInfoGetters; Timer m_ThrowTmr; // The duration it takes this AHuman to fully charge a throw. long m_ThrowPrepTime; - // For timing the transition from sharp aim back to regular aim - Timer m_SharpAimRevertTimer; - // The rate at which this AHuman's FG Arm follows the the bodily rotation. Best to keep this at 0 so it doesn't complicate aiming. - float m_FGArmFlailScalar; - // The rate at which this AHuman's BG Arm follows the the bodily rotation. Set to a negative value for a "counterweight" effect. - float m_BGArmFlailScalar; + Timer m_SharpAimRevertTimer; //!< For timing the transition from sharp aim back to regular aim. + float m_FGArmFlailScalar; //!< The rate at which this AHuman's FG Arm follows the the bodily rotation. Best to keep this at 0 so it doesn't complicate aiming. + float m_BGArmFlailScalar; //!< The rate at which this AHuman's BG Arm follows the the bodily rotation. Set to a negative value for a "counterweight" effect. + Timer m_EquipHUDTimer; //!< Timer for showing the name of any newly equipped Device. //////////////// // AI States diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp index 0d3231ef2..0862919cf 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -73,22 +73,23 @@ void Actor::Clear() { m_LastSecondTimer.Reset(); m_LastSecondPos.Reset(); m_RecentMovement.Reset(); - m_RecentMovementMag = 0.0F; - m_TravelImpulseDamage = 750; - m_StableVel.SetXY(15, 25); + m_RecentMovementMag = 0; + m_TravelImpulseDamage = 750.0F; + m_StableVel.SetXY(15.0F, 25.0F); + m_StableRecoverDelay = 1000; m_HeartBeat.Reset(); m_NewControlTmr.Reset(); m_DeathTmr.Reset(); - m_GoldCarried = 0.0F; + m_GoldCarried = 0; m_GoldPicked = false; m_AimState = AIMSTILL; - m_AimAngle = 0.0F; + m_AimAngle = 0; m_AimRange = c_HalfPI; - m_AimDistance = 0.0F; + m_AimDistance = 0; m_AimTmr.Reset(); m_SharpAimTimer.Reset(); m_SharpAimDelay = 250; - m_SharpAimProgress = 0.0F; + m_SharpAimProgress = 0; m_SharpAimMaxedOut = false; m_PointingTarget.Reset(); m_SeenTargetPos.Reset(); @@ -96,8 +97,8 @@ void Actor::Clear() { m_AlarmTimer.SetSimTimeLimitMS(3000); m_AlarmTimer.SetElapsedSimTimeMS(4000); m_LastAlarmPos.Reset(); - m_SightDistance = 450; - m_Perceptiveness = 0.5; + m_SightDistance = 450.0F; + m_Perceptiveness = 0.5F; m_CanRevealUnseen = true; m_CharHeight = 0; m_HolsterOffset.Reset(); @@ -123,17 +124,17 @@ void Actor::Clear() { m_MoveVector.Reset(); m_MovePath.clear(); m_UpdateMovePath = true; - m_MoveProximityLimit = 100; + m_MoveProximityLimit = 100.0F; m_LateralMoveState = LAT_STILL; m_MoveOvershootTimer.Reset(); m_ObstacleState = PROCEEDING; m_TeamBlockState = NOTBLOCKED; m_BlockTimer.Reset(); - m_BestTargetProximity = 10000.0f; + m_BestTargetProximity = 10000.0F; m_ProgressTimer.Reset(); m_StuckTimer.Reset(); m_FallTimer.Reset(); - m_DigStrength = 1; + m_DigStrength = 1.0F; m_DamageMultiplier = 1.0F; } @@ -206,6 +207,7 @@ int Actor::Create(const Actor &reference) m_LastSecondPos = reference.m_LastSecondPos; m_TravelImpulseDamage = reference.m_TravelImpulseDamage; m_StableVel = reference.m_StableVel; + m_StableRecoverDelay = reference.m_StableRecoverDelay; m_GoldCarried = reference.m_GoldCarried; m_AimState = reference.m_AimState; m_AimRange = reference.m_AimRange; @@ -339,6 +341,8 @@ int Actor::ReadProperty(const std::string_view &propName, Reader &reader) reader >> m_TravelImpulseDamage; else if (propName == "StableVelocityThreshold") reader >> m_StableVel; + else if (propName == "StableRecoveryDelay") + reader >> m_StableRecoverDelay; else if (propName == "AimAngle") reader >> m_AimAngle; else if (propName == "AimRange") @@ -420,6 +424,8 @@ int Actor::Save(Writer &writer) const writer << m_TravelImpulseDamage; writer.NewProperty("StableVelocityThreshold"); writer << m_StableVel; + writer.NewProperty("StableRecoveryDelay"); + writer << m_StableRecoverDelay; writer.NewProperty("AimAngle"); writer << m_AimAngle; writer.NewProperty("AimRange"); @@ -860,16 +866,17 @@ bool Actor::SwapInventoryItemsByIndex(int inventoryIndex1, int inventoryIndex2) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// MovableObject * Actor::SetInventoryItemAtIndex(MovableObject *newInventoryItem, int inventoryIndex) { - if (newInventoryItem) { - if (inventoryIndex < 0 || inventoryIndex >= m_Inventory.size()) { - m_Inventory.emplace_back(newInventoryItem); - return nullptr; - } - MovableObject *currentInventoryItemAtIndex = m_Inventory.at(inventoryIndex); - m_Inventory.at(inventoryIndex) = newInventoryItem; - return currentInventoryItemAtIndex; + if (!newInventoryItem) { + return RemoveInventoryItemAtIndex(inventoryIndex); } - return nullptr; + + if (inventoryIndex < 0 || inventoryIndex >= m_Inventory.size()) { + m_Inventory.emplace_back(newInventoryItem); + return nullptr; + } + MovableObject *currentInventoryItemAtIndex = m_Inventory.at(inventoryIndex); + m_Inventory.at(inventoryIndex) = newInventoryItem; + return currentInventoryItemAtIndex; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -885,7 +892,7 @@ void Actor::DropAllInventory() { MovableObject *pObject = 0; Actor *pPassenger = 0; - float velMin, velRange, angularVel; + float velMin, velMax, angularVel; Vector gibROffset, gibVel; for (deque::iterator gItr = m_Inventory.begin(); gItr != m_Inventory.end(); ++gItr) { @@ -895,12 +902,12 @@ void Actor::DropAllInventory() { // Generate the velocities procedurally velMin = 3.0F; - velRange = 10.0F; + velMax = velMin + std::sqrt(m_SpriteRadius); // Randomize the offset from center to be within the original object gibROffset.SetXY(m_SpriteRadius * 0.35F * RandomNormalNum(), m_SpriteRadius * 0.35F * RandomNormalNum()); // Set up its position and velocity according to the parameters of this AEmitter. - pObject->SetPos(m_Pos + gibROffset/*Vector(m_Pos.m_X + 5 * NormalRand(), m_Pos.m_Y + 5 * NormalRand())*/); + pObject->SetPos(m_Pos + gibROffset); pObject->SetRotAngle(m_Rotation.GetRadAngle() + pObject->GetRotMatrix().GetRadAngle()); // Rotational angle pObject->SetAngularVel((pObject->GetAngularVel() * 0.35F) + (pObject->GetAngularVel() * 0.65F / (pObject->GetMass() != 0 ? pObject->GetMass() : 0.0001F)) * RandomNum()); @@ -909,8 +916,8 @@ void Actor::DropAllInventory() if (gibROffset.m_X > m_aSprite[0]->w / 3) { float offCenterRatio = gibROffset.m_X / (m_aSprite[0]->w / 2); - angularVel = fabs(pObject->GetAngularVel() * 0.5F); - angularVel += fabs(pObject->GetAngularVel() * 0.5F * offCenterRatio); + angularVel = std::abs(pObject->GetAngularVel() * 0.5F); + angularVel += std::abs(pObject->GetAngularVel() * 0.5F * offCenterRatio); pObject->SetAngularVel(angularVel * (gibROffset.m_X > 0.0F ? -1 : 1)); } // Gib is too close to center to always make it rotate in one direction, so give it a baseline rotation and then randomize @@ -921,10 +928,11 @@ void Actor::DropAllInventory() // TODO: Optimize making the random angles!") gibVel = gibROffset; - if (gibVel.IsZero()) - gibVel.SetXY(velMin + RandomNum(0.0F, velRange), 0.0F); - else - gibVel.SetMagnitude(velMin + RandomNum(0.0F, velRange)); + if (gibVel.IsZero()) { + gibVel.SetXY(RandomNum(velMin, velMax), 0.0F); + } else { + gibVel.SetMagnitude(RandomNum(velMin, velMax)); + } // Don't! the offset was already rotated! // gibVel = RotateOffset(gibVel); // Distribute any impact implse out over all the gibs @@ -1456,47 +1464,37 @@ void Actor::Update() ///////////////////////////////////////////// // Take damage from large hits during travel - if (m_BodyHitSound && m_TravelImpulse.GetMagnitude() > m_TravelImpulseDamage / 2) { - m_BodyHitSound->Play(m_Pos); - } + float travelImpulseMagnitude = m_TravelImpulse.GetMagnitude(); - if (m_TravelImpulse.GetMagnitude() > m_TravelImpulseDamage) - { - if (m_PainSound) { m_PainSound->Play(m_Pos); } - const float impulse = m_TravelImpulse.GetMagnitude() - m_TravelImpulseDamage; - const float damage = impulse / (m_GibImpulseLimit - m_TravelImpulseDamage) * m_MaxHealth; - if (damage > 0) - m_Health -= damage; - if (m_Status != DYING && m_Status != DEAD) - m_Status = UNSTABLE; - m_ForceDeepCheck = true; - } + if (m_BodyHitSound && travelImpulseMagnitude > m_TravelImpulseDamage * 0.5F) { m_BodyHitSound->Play(m_Pos); } + + if (travelImpulseMagnitude > m_TravelImpulseDamage) { + const float impulse = travelImpulseMagnitude - m_TravelImpulseDamage; + const float damage = std::max(impulse / (m_GibImpulseLimit - m_TravelImpulseDamage) * m_MaxHealth, 0.0F); + m_Health -= damage; + if (damage > 0 && m_Health > 0 && m_PainSound) { m_PainSound->Play(m_Pos); } + if (m_Status != DYING && m_Status != DEAD) { m_Status = UNSTABLE; } + m_ForceDeepCheck = true; + } ///////////////////////////// // Stability logic - if (m_Status == STABLE) - { + if (m_Status == STABLE) { // If moving really fast, we're not able to be stable -// TODO don't hardcode this threshold! - if (fabs(m_Vel.m_X) > fabs(m_StableVel.m_X) || fabs(m_Vel.m_Y) > fabs(m_StableVel.m_Y)) - m_Status = UNSTABLE; + if (std::abs(m_Vel.m_X) > std::abs(m_StableVel.m_X) || std::abs(m_Vel.m_Y) > std::abs(m_StableVel.m_Y)) { m_Status = UNSTABLE; } m_StableRecoverTimer.Reset(); } - else if (m_Status == UNSTABLE) - { + else if (m_Status == UNSTABLE) { // Only regain stability if we're not moving too fast and it's been a while since we lost it - if (m_StableRecoverTimer.IsPastSimMS(1000) && !(fabs(m_Vel.m_X) > fabs(m_StableVel.m_X) || fabs(m_Vel.m_Y) > fabs(m_StableVel.m_Y))) - m_Status = STABLE; + if (m_StableRecoverTimer.IsPastSimMS(m_StableRecoverDelay) && !(std::abs(m_Vel.m_X) > std::abs(m_StableVel.m_X) || std::abs(m_Vel.m_Y) > std::abs(m_StableVel.m_Y))) { m_Status = STABLE; } } // Spread the carried items and gold around before death. - if (m_Status == DYING || m_Status == DEAD) - { + if (m_Status == DYING || m_Status == DEAD) { // Actor may die for a long time, no need to call this more than once - if (m_Inventory.size() > 0) - DropAllInventory(); + if (m_Inventory.size() > 0) { DropAllInventory(); } Material const * AuMat = g_SceneMan.GetMaterial(std::string("Gold")); int goldCount = m_GoldCarried/*std::floor(GetGoldCarried())*/; @@ -1538,13 +1536,9 @@ void Actor::Update() } // Prevent dead actors from rotating like mad - if (m_Status == DYING || m_Status == DEAD) - { - m_AngularVel = m_AngularVel * 0.98; - } + if (m_Status == DYING || m_Status == DEAD) { m_AngularVel = m_AngularVel * 0.98F; } - if (m_Status == DYING && m_DeathTmr.GetElapsedSimTimeMS() > 1000) - m_Status = DEAD; + if (m_Status == DYING && m_DeathTmr.GetElapsedSimTimeMS() > 1000) { m_Status = DEAD; } ////////////////////////////////////////////////////// // Save previous second's position so we can detect larger movement @@ -1562,7 +1556,7 @@ void Actor::Update() if (m_FrameCount > 1) { - if (m_SpriteAnimMode == LOOPWHENMOVING) + if (m_SpriteAnimMode == LOOPWHENACTIVE) { if (m_Controller.IsState(MOVE_LEFT) || m_Controller.IsState(MOVE_RIGHT) || m_Controller.GetAnalogMove().GetLargest() > 0.1) { @@ -1796,7 +1790,6 @@ void Actor::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr } */ std::snprintf(str, sizeof(str), "%.0f", m_Health); -// pSmallFont->DrawAligned(&bitmapInt, drawPos.m_X - 0, drawPos.m_Y - 35, str, GUIFont::Left); pSymbolFont->DrawAligned(&bitmapInt, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack, str, GUIFont::Left); m_HUDStack += -12; diff --git a/Entities/Actor.h b/Entities/Actor.h index 1960950d3..20fb8a6fe 100644 --- a/Entities/Actor.h +++ b/Entities/Actor.h @@ -1313,6 +1313,18 @@ ClassInfoGetters; /// Vector with new values for how fast the actor can travel before losing stability on both axis. void SetStableVel(Vector newVelVector) { m_StableVel = newVelVector; } + /// + /// Gets the recovery delay from UNSTABLE to STABLE, in MS. + /// + /// The recovery delay, in MS. + int GetStableRecoverDelay() const { return m_StableRecoverDelay; } + + /// + /// Sets the recovery delay from UNSTABLE to STABLE, in MS. + /// + /// The recovery delay, in MS. + void SetStableRecoverDelay(int newRecoverDelay) { m_StableRecoverDelay = newRecoverDelay; } + ////////////////////////////////////////////////////////////////////////////////////////// // Protected member variable and method declarations @@ -1347,7 +1359,6 @@ ClassInfoGetters; SoundContainer *m_DeathSound; SoundContainer *m_DeviceSwitchSound; -// bool m_FacingRight; int m_Status; float m_Health; // Maximum health @@ -1371,6 +1382,7 @@ ClassInfoGetters; Timer m_StableRecoverTimer; // Thresholds in both x and y for how fast the actor can travel before losing stability. Meters per second (m/s). Vector m_StableVel; + int m_StableRecoverDelay; //!< The delay before regaining stability after losing it, in MS // Timer for the heartbeat of this Actor Timer m_HeartBeat; // Timer for timing how long this has been under Control diff --git a/Entities/Arm.cpp b/Entities/Arm.cpp index 16b7e8918..9ee9fc621 100644 --- a/Entities/Arm.cpp +++ b/Entities/Arm.cpp @@ -389,9 +389,17 @@ void Arm::UpdateCurrentHandOffset() { Vector targetOffset; if (m_pHeldMO && !dynamic_cast(m_pHeldMO)) { m_DidReach = false; - const HeldDevice *heldDevice = dynamic_cast(m_pHeldMO); - targetOffset = heldDevice->GetStanceOffset() * m_Rotation; - + HeldDevice *heldDevice = dynamic_cast(m_pHeldMO); + // TODO: calculate total grip strength from both arms? (also: fine-tune this shit, and move it elsewhere) + float totalGripStrength = (m_GripStrength || heldDevice->GetJointStrength()) * (heldDevice->GetSupported() ? 2.0F : 1.0F); + targetOffset = heldDevice->GetStanceOffset(); + // Diminish recoil effect when body is horizontal so that the device doesn't get pushed into terrain when prone. + float rotAngleScalar = std::abs(std::cos(m_Parent->GetRotAngle())); + float recoilScalar = std::min((heldDevice->GetRecoilForce() / totalGripStrength).GetMagnitude() * 0.4F, 0.8F) * rotAngleScalar; + targetOffset.SetX(targetOffset.GetX() * (1.0F - recoilScalar)); + // Shift Y offset slightly so the device is more likely to go under the shoulder rather than over it. (otherwise it looks goofy) + if (targetOffset.GetY() <= 0) { targetOffset.SetY(targetOffset.GetY() * (1.0F - recoilScalar) + recoilScalar); } + targetOffset *= m_Rotation; // In order to keep the held device from clipping through terrain, we need to determine where its muzzle position will be, and use that to figure out where its midpoint will be, as well as the distance between the two. Vector newMuzzlePos = (m_JointPos + targetOffset) - RotateOffset(heldDevice->GetJointOffset()) + RotateOffset(heldDevice->GetMuzzleOffset()); Vector midToMuzzle = RotateOffset({heldDevice->GetRadius(), 0}); diff --git a/Entities/AtomGroup.cpp b/Entities/AtomGroup.cpp index 2b7bfd610..a87de9d98 100644 --- a/Entities/AtomGroup.cpp +++ b/Entities/AtomGroup.cpp @@ -1195,10 +1195,10 @@ namespace RTE { limbPath.SetRotation(rotation); limbPath.SetFrameTime(travelTime); - const Vector limbDist = g_SceneMan.ShortestDistance(jointPos, m_LimbPos); + Vector limbDist = g_SceneMan.ShortestDistance(jointPos, m_LimbPos, g_SceneMan.SceneWrapsX()); - // Restart the path if the limb strayed off the path. - if (limbDist.GetMagnitude() > m_OwnerMOSR->GetDiameter()) { limbPath.Terminate(); } + // Pull back the limb if it strayed off the path. + if (limbDist.GetMagnitude() > m_OwnerMOSR->GetRadius()) { m_LimbPos = jointPos + limbDist.SetMagnitude(m_OwnerMOSR->GetRadius()); } // TODO: Change this to a regular while loop if possible. do { @@ -1226,9 +1226,11 @@ namespace RTE { bool didWrap = false; Vector jointPos = ownerPos + jointOffset; - Vector centrifugalVel = jointOffset * std::fabs(angularVel); + Vector totalVel = velocity; + totalVel.RadRotate(angularVel * travelTime); + totalVel += jointOffset * std::abs(angularVel); - Vector pushImpulse = PushTravel(m_LimbPos, velocity + centrifugalVel, 100, didWrap, travelTime, false, false, false); + Vector pushImpulse = PushTravel(m_LimbPos, totalVel, 100, didWrap, travelTime, false, false, false); Vector limbRange = m_LimbPos - jointPos; diff --git a/Entities/Attachable.cpp b/Entities/Attachable.cpp index a5763a13f..17ab8ed67 100644 --- a/Entities/Attachable.cpp +++ b/Entities/Attachable.cpp @@ -347,7 +347,7 @@ namespace RTE { if (std::abs(currentRotAngleOffset - m_PrevRotAngleOffset) > 0.01745F) { // Update for 1 degree differences Matrix atomRotationForSubgroup(rootParentAsMOSR->FacingAngle(GetRotAngle()) - rootParentAsMOSR->FacingAngle(rootParentAsMOSR->GetRotAngle())); Vector atomOffsetForSubgroup(g_SceneMan.ShortestDistance(rootParentAsMOSR->GetPos(), m_Pos, g_SceneMan.SceneWrapsX()).FlipX(rootParentAsMOSR->IsHFlipped())); - Matrix rootParentAngleToUse(rootParentAsMOSR->GetRotAngle() * static_cast(rootParentAsMOSR->GetFlipFactor())); + Matrix rootParentAngleToUse(rootParentAsMOSR->GetRotAngle() * rootParentAsMOSR->GetFlipFactor()); atomOffsetForSubgroup /= rootParentAngleToUse; rootParentAsMOSR->GetAtomGroup()->UpdateSubAtoms(GetAtomSubgroupID(), atomOffsetForSubgroup, atomRotationForSubgroup); } @@ -458,7 +458,7 @@ namespace RTE { m_Team = newParent->GetTeam(); if (InheritsHFlipped() != 0) { m_HFlipped = m_InheritsHFlipped == 1 ? m_Parent->IsHFlipped() : !m_Parent->IsHFlipped(); } if (InheritsRotAngle()) { - SetRotAngle(m_Parent->GetRotAngle() + m_InheritedRotAngleOffset * static_cast(m_Parent->GetFlipFactor())); + SetRotAngle(m_Parent->GetRotAngle() + m_InheritedRotAngleOffset * m_Parent->GetFlipFactor()); m_AngularVel = 0.0F; } UpdatePositionAndJointPositionBasedOnOffsets(); diff --git a/Entities/Gib.cpp b/Entities/Gib.cpp index f3cc735e5..2900e57d7 100644 --- a/Entities/Gib.cpp +++ b/Entities/Gib.cpp @@ -18,6 +18,7 @@ namespace RTE { m_LifeVariation = 0.1F; m_InheritsVel = true; m_IgnoresTeamHits = false; + m_SpreadMode = SpreadMode::SpreadRandom; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -32,6 +33,7 @@ namespace RTE { m_LifeVariation = reference.m_LifeVariation; m_InheritsVel = reference.m_InheritsVel; m_IgnoresTeamHits = reference.m_IgnoresTeamHits; + m_SpreadMode = reference.m_SpreadMode; return 0; } @@ -58,6 +60,8 @@ namespace RTE { reader >> m_InheritsVel; } else if (propName == "IgnoresTeamHits") { reader >> m_IgnoresTeamHits; + } else if (propName == "SpreadMode") { + m_SpreadMode = static_cast(std::stoi(reader.ReadPropValue())); } else { return Serializable::ReadProperty(propName, reader); } diff --git a/Entities/Gib.h b/Entities/Gib.h index 992a16e14..d15cd798c 100644 --- a/Entities/Gib.h +++ b/Entities/Gib.h @@ -18,6 +18,11 @@ namespace RTE { SerializableClassNameGetter; SerializableOverrideMethods; + /// + /// Different types of logic for the Gib to use when applying velocity to its GibParticles. + /// + enum class SpreadMode { SpreadRandom, SpreadEven, SpreadSpiral }; + #pragma region Creation /// /// Constructor method used to instantiate a Gib object in system memory. Create() should be called before using the object. @@ -100,6 +105,12 @@ namespace RTE { /// /// Whether this Gib's GibParticles should ignore hits with the team of the gibbing parent. bool IgnoresTeamHits() const { return m_IgnoresTeamHits; } + + /// + /// Gets this Gib's spread mode, which determines how velocity angles are applied to the GibParticles. + /// + /// The spread mode of this Gib. + SpreadMode GetSpreadMode() const { return m_SpreadMode; } #pragma endregion protected: @@ -113,6 +124,7 @@ namespace RTE { float m_LifeVariation; //!< The per-Gib variation in Lifetime, in percentage of the existing Lifetime of the gib. bool m_InheritsVel; //!< Whether this Gib should inherit the velocity of the exploding parent or not. bool m_IgnoresTeamHits; //!< Whether this Gib should ignore hits with the team of the exploding parent or not. + SpreadMode m_SpreadMode; //!< Determines what kind of logic is used when applying velocity to the GibParticle objects. private: diff --git a/Entities/HDFirearm.cpp b/Entities/HDFirearm.cpp index 886a72c2a..e43e071ca 100644 --- a/Entities/HDFirearm.cpp +++ b/Entities/HDFirearm.cpp @@ -51,6 +51,7 @@ void HDFirearm::Clear() m_ReloadTime = 0; m_FullAuto = false; m_FireIgnoresThis = true; + m_Reloadable = true; m_ShakeRange = 0; m_SharpShakeRange = 0; m_NoSupportFactor = 0; @@ -58,6 +59,7 @@ void HDFirearm::Clear() m_ShellEjectAngle = 150; m_ShellSpreadRange = 0; m_ShellAngVelRange = 0; + m_ShellVelVariation = 0.1F; m_AIFireVel = -1; m_AIBulletLifeTime = 0; m_AIBulletAccScalar = -1; @@ -125,6 +127,7 @@ int HDFirearm::Create(const HDFirearm &reference) { m_ReloadTime = reference.m_ReloadTime; m_FullAuto = reference.m_FullAuto; m_FireIgnoresThis = reference.m_FireIgnoresThis; + m_Reloadable = reference.m_Reloadable; m_ShakeRange = reference.m_ShakeRange; m_SharpShakeRange = reference.m_SharpShakeRange; m_NoSupportFactor = reference.m_NoSupportFactor; @@ -132,6 +135,7 @@ int HDFirearm::Create(const HDFirearm &reference) { m_ShellEjectAngle = reference.m_ShellEjectAngle; m_ShellSpreadRange = reference.m_ShellSpreadRange; m_ShellAngVelRange = reference.m_ShellAngVelRange; + m_ShellVelVariation = reference.m_ShellVelVariation; m_MuzzleOff = reference.m_MuzzleOff; m_EjectOff = reference.m_EjectOff; m_MagOff = reference.m_MagOff; @@ -171,7 +175,7 @@ int HDFirearm::ReadProperty(const std::string_view &propName, Reader &reader) { } else if (propName == "DeactivationSound") { m_DeactivationSound = new SoundContainer; reader >> m_DeactivationSound; - m_DeactivationSound->SetSoundOverlapMode(SoundContainer::SoundOverlapMode::IGNORE_PLAY); + m_DeactivationSound->SetSoundOverlapMode(SoundContainer::SoundOverlapMode::RESTART); } else if (propName == "EmptySound") { m_EmptySound = new SoundContainer; reader >> m_EmptySound; @@ -193,6 +197,8 @@ int HDFirearm::ReadProperty(const std::string_view &propName, Reader &reader) { reader >> m_FullAuto; } else if (propName == "FireIgnoresThis") { reader >> m_FireIgnoresThis; + } else if (propName == "Reloadable") { + reader >> m_Reloadable; } else if (propName == "RecoilTransmission") { reader >> m_JointStiffness; } else if (propName == "IsAnimatedManually") { @@ -216,6 +222,8 @@ int HDFirearm::ReadProperty(const std::string_view &propName, Reader &reader) { } else if (propName == "ShellAngVelRange") { reader >> m_ShellAngVelRange; m_ShellAngVelRange /= 2; + } else if (propName == "ShellVelVariation") { + reader >> m_ShellVelVariation; } else if (propName == "MuzzleOffset") { reader >> m_MuzzleOff; } else if (propName == "EjectionOffset") { @@ -270,6 +278,8 @@ int HDFirearm::Save(Writer &writer) const writer << m_FullAuto; writer.NewProperty("FireIgnoresThis"); writer << m_FireIgnoresThis; + writer.NewProperty("Reloadable"); + writer << m_Reloadable; writer.NewProperty("RecoilTransmission"); writer << m_JointStiffness; writer.NewProperty("IsAnimatedManually"); @@ -288,6 +298,8 @@ int HDFirearm::Save(Writer &writer) const writer << m_ShellSpreadRange * 2; writer.NewProperty("ShellAngVelRange"); writer << m_ShellAngVelRange * 2; + writer.NewProperty("ShellVelocityVariation"); + writer << m_ShellVelVariation; writer.NewProperty("MuzzleOffset"); writer << m_MuzzleOff; writer.NewProperty("EjectionOffset"); @@ -415,6 +427,17 @@ int HDFirearm::GetRoundInMagCount() const return m_pMagazine ? m_pMagazine->GetRoundCount() : 0; } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int HDFirearm::GetRoundInMagCapacity() const { + if (m_pMagazine) { + return m_pMagazine->GetCapacity(); + } else if (m_pMagazineReference) { + return m_pMagazineReference->GetCapacity(); + } + return 0; +} + ////////////////////////////////////////////////////////////////////////////////////////// // Method: GetAIFireVel @@ -645,8 +668,7 @@ void HDFirearm::StopActivationSound() void HDFirearm::Reload() { - if (!m_Reloading) - { + if (!m_Reloading && m_Reloadable) { if (m_pMagazine) { Vector constrainedMagazineOffset = g_SceneMan.ShortestDistance(m_Pos, m_pMagazine->GetPos(), g_SceneMan.SceneWrapsX()).SetMagnitude(2.0F); @@ -661,12 +683,11 @@ void HDFirearm::Reload() // Stop any activation m_Activated = false; - if (m_FireSound && m_FireSound->GetLoopSetting() == -1 && m_FireSound->IsBeingPlayed()) - m_FireSound->Stop(); - + if (m_FireSound && m_FireSound->GetLoopSetting() == -1 && m_FireSound->IsBeingPlayed()) { m_FireSound->Stop(); } if (m_ReloadStartSound) { m_ReloadStartSound->Play(m_Pos); } - m_ReloadTmr.Reset(); - m_Reloading = true; + + m_ReloadTmr.Reset(); + m_Reloading = true; } } @@ -678,8 +699,7 @@ void HDFirearm::Reload() bool HDFirearm::NeedsReloading() const { - if (!m_Reloading) - { + if (!m_Reloading && m_Reloadable) { if (m_pMagazine) { // If we've used over half the rounds, we can profitably go ahead and reload @@ -700,8 +720,7 @@ bool HDFirearm::NeedsReloading() const bool HDFirearm::IsFull() const { - if (!m_Reloading) - { + if (!m_Reloading && m_Reloadable) { if (m_pMagazine) { // If we've used over half the rounds, we can profitably go ahead and reload @@ -813,6 +832,8 @@ void HDFirearm::Update() Vector particlePos; Vector particleVel; + int particleCountMax = pRound->ParticleCount(); + float lifeVariation = pRound->GetLifeVariation(); // Launch all particles in round MovableObject *pParticle = 0; @@ -830,6 +851,9 @@ void HDFirearm::Update() pParticle->SetVel(m_Vel + particleVel); pParticle->SetRotAngle(particleVel.GetAbsRadAngle() + (m_HFlipped ? -c_PI : 0)); pParticle->SetHFlipped(m_HFlipped); + if (lifeVariation != 0 && pParticle->GetLifetime() != 0) { + pParticle->SetLifetime(std::max(static_cast(pParticle->GetLifetime() * (1.0F + (particleCountMax > 1 ? lifeVariation - (lifeVariation * 2.0F * (static_cast(pRound->ParticleCount()) / static_cast(particleCountMax - 1))) : lifeVariation * RandomNormalNum()))), 1)); + } // F = m * a totalFireForce += pParticle->GetMass() * pParticle->GetVel().GetMagnitude(); @@ -876,7 +900,7 @@ void HDFirearm::Update() pShell->SetPos(m_Pos + tempEject); // ##@#@@$ TEMP - shellVel.SetXY(pRound->GetShellVel(), 0); + shellVel.SetXY(pRound->GetShellVel() * (1.0F - RandomNum(0.0F, m_ShellVelVariation)), 0); shellVel.DegRotate(degAimAngle + m_ShellEjectAngle * (m_HFlipped ? -1 : 1) + shellSpread); pShell->SetVel(m_Vel + shellVel); pShell->SetRotAngle(m_Rotation.GetRadAngle()); @@ -905,33 +929,28 @@ void HDFirearm::Update() } pRound = 0; } - } - - // No or empty magazine, so just click. - else if (((m_pMagazine && m_pMagazine->IsEmpty()) || !m_pMagazine) && m_Activated && !m_AlreadyClicked ) - { + } else if (m_Activated && !m_AlreadyClicked) { // Play empty pin click sound. if (m_EmptySound) { m_EmptySound->Play(m_Pos); } // Indicate that we have clicked once during the current activation. m_AlreadyClicked = true; // Auto-reload - Reload(); + if (m_Parent) { Reload(); } } - // No magazine, have started to reload, so put new mag in when done - if (m_Reloading && !m_pMagazine && m_pMagazineReference && m_ReloadTmr.IsPastSimMS(m_ReloadTime)) { - SetMagazine(dynamic_cast(m_pMagazineReference->Clone())); + if (m_Reloading && !m_pMagazine && m_pMagazineReference && m_ReloadTmr.IsPastSimMS(m_ReloadTime)) { + SetMagazine(dynamic_cast(m_pMagazineReference->Clone())); if (m_ReloadEndSound) { m_ReloadEndSound->Play(m_Pos); } - m_ActivationTimer.Reset(); - m_LastFireTmr.Reset(); + m_ActivationTimer.Reset(); + m_LastFireTmr.Reset(); - if (m_PreFireSound && m_Activated) { m_PreFireSound->Play(); } + if (m_PreFireSound && m_Activated) { m_PreFireSound->Play(); } - m_Reloading = false; - m_DoneReloading = true; - } + m_Reloading = false; + m_DoneReloading = true; + } // Do stuff to deactivate after being activated if (!m_Activated) @@ -962,17 +981,13 @@ void HDFirearm::Update() // Set up the recoil shake offset m_RecoilOffset = m_RecoilForce; - m_RecoilOffset.SetMagnitude(1.25); + m_RecoilOffset.SetMagnitude(std::min(m_RecoilOffset.GetMagnitude(), 1.2F)); } AddImpulseForce(m_RecoilForce, m_RecoilOffset); // Display gun animation - if (!m_IsAnimatedManually) - { - if (m_FrameCount > 1) - m_Frame = 1; - } + if (!m_IsAnimatedManually && m_FrameCount > 1) { m_Frame = 1; } // Display gun flame frame. if (m_pFlash) { @@ -988,17 +1003,17 @@ void HDFirearm::Update() } if (m_FireEchoSound) { m_FireEchoSound->Play(m_Pos); } } - } - else { + } else { m_Recoiled = false; - if (!m_IsAnimatedManually) - m_Frame = 0; + // TODO: don't use arbitrary numbers? (see Arm.cpp) + m_RecoilForce *= 0.6F; + if (!m_IsAnimatedManually) { m_Frame = 0; } } // Display and override gun animation if there's a special one if (m_FrameCount > 1) { - if (m_SpriteAnimMode == LOOPWHENMOVING) + if (m_SpriteAnimMode == LOOPWHENACTIVE) { if (m_Activated || m_LastFireTmr.GetElapsedSimTimeMS() < m_DeactivationDelay) { // Max rate of the animation when fully activated and firing @@ -1024,8 +1039,7 @@ void HDFirearm::Update() StopActivationSound(); } } else { - if (!m_IsAnimatedManually) - m_Frame = 0; + if (!m_IsAnimatedManually) { m_Frame = 0; } StopActivationSound(); } } @@ -1074,13 +1088,12 @@ void HDFirearm::Draw(BITMAP *pTargetBitmap, const Vector &targetPos, DrawMode mo m_pFlash->Draw(pTargetBitmap, targetPos, mode, onlyPhysical); } - // Fudge the muzzle pos forward a little bit so the glow aligns nicely - Vector muzzlePos = m_MuzzleOff; - muzzlePos.m_X += 4; - muzzlePos = m_Pos + RotateOffset(muzzlePos); // Set the screen flash effect to draw at the final post processing stage - if (m_FireFrame && m_pFlash && m_pFlash->GetScreenEffect() && mode == g_DrawColor && !onlyPhysical && !g_SceneMan.ObscuredPoint(muzzlePos)) { - g_PostProcessMan.RegisterPostEffect(muzzlePos, m_pFlash->GetScreenEffect(), m_pFlash->GetScreenEffectHash(), 55.0F + RandomNum(0.0F, 200.0F), m_pFlash->GetEffectRotAngle()); + if (m_FireFrame && m_pFlash && m_pFlash->GetScreenEffect() && mode == g_DrawColor && !onlyPhysical) { + Vector muzzlePos = m_Pos + RotateOffset(m_MuzzleOff + Vector(m_pFlash->GetSpriteWidth() * 0.3F, 0)); + if (!g_SceneMan.ObscuredPoint(muzzlePos)) { + g_PostProcessMan.RegisterPostEffect(muzzlePos, m_pFlash->GetScreenEffect(), m_pFlash->GetScreenEffectHash(), RandomNum(m_pFlash->GetEffectStopStrength(), m_pFlash->GetEffectStartStrength()), m_pFlash->GetEffectRotAngle()); + } } } @@ -1105,90 +1118,35 @@ void HDFirearm::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whic HeldDevice::DrawHUD(pTargetBitmap, targetPos, whichScreen); - // Don't bother if the aim distance is really short, or not held - if (!m_Parent || m_SharpAim < 0.15) - return; - - float sharpLength = m_MaxSharpLength * m_SharpAim; + if (!m_Parent || IsReloading() || m_MaxSharpLength == 0) { + return; + } - if (playerControlled) - { - Vector aimPoint1(sharpLength - 9, 0); - Vector aimPoint2(sharpLength - 3, 0); - Vector aimPoint3(sharpLength + 3, 0); - Vector aimPoint4(sharpLength + 9, 0); - aimPoint1 += m_MuzzleOff; - aimPoint2 += m_MuzzleOff; - aimPoint3 += m_MuzzleOff; - aimPoint4 += m_MuzzleOff; - Matrix aimMatrix(m_Rotation); - aimMatrix.SetXFlipped(m_HFlipped); - aimPoint1 *= aimMatrix; - aimPoint2 *= aimMatrix; - aimPoint3 *= aimMatrix; - aimPoint4 *= aimMatrix; - aimPoint1 += m_Pos; - aimPoint2 += m_Pos; - aimPoint3 += m_Pos; - aimPoint4 += m_Pos; - - // Put the flickering glows on the reticule dots, in absolute scene coordinates - int glow = (155 + RandomNum(0, 100)); - g_PostProcessMan.RegisterGlowDotEffect(aimPoint1, YellowDot, glow); - g_PostProcessMan.RegisterGlowDotEffect(aimPoint2, YellowDot, glow); - g_PostProcessMan.RegisterGlowDotEffect(aimPoint3, YellowDot, glow); - g_PostProcessMan.RegisterGlowDotEffect(aimPoint4, YellowDot, glow); - - // Make into target frame coordinates - aimPoint1 -= targetPos; - aimPoint2 -= targetPos; - aimPoint3 -= targetPos; - aimPoint4 -= targetPos; - - // Wrap the points - g_SceneMan.WrapPosition(aimPoint1); - g_SceneMan.WrapPosition(aimPoint2); - g_SceneMan.WrapPosition(aimPoint3); - g_SceneMan.WrapPosition(aimPoint4); - - acquire_bitmap(pTargetBitmap); - putpixel(pTargetBitmap, aimPoint1.m_X, aimPoint1.m_Y, g_YellowGlowColor); - putpixel(pTargetBitmap, aimPoint2.m_X, aimPoint2.m_Y, g_YellowGlowColor); - putpixel(pTargetBitmap, aimPoint3.m_X, aimPoint3.m_Y, g_YellowGlowColor); - putpixel(pTargetBitmap, aimPoint4.m_X, aimPoint4.m_Y, g_YellowGlowColor); - release_bitmap(pTargetBitmap); - } - else - { - Vector aimPoint2(sharpLength - 3, 0); - Vector aimPoint3(sharpLength + 3, 0); - aimPoint2 += m_MuzzleOff; - aimPoint3 += m_MuzzleOff; - Matrix aimMatrix(m_Rotation); - aimMatrix.SetXFlipped(m_HFlipped); - aimPoint2 *= aimMatrix; - aimPoint3 *= aimMatrix; - aimPoint2 += m_Pos; - aimPoint3 += m_Pos; - - // Put the flickering glows on the reticule dots, in absolute scene coordinates - int glow = (55 + RandomNum(0, 100)); - g_PostProcessMan.RegisterGlowDotEffect(aimPoint2, YellowDot, glow); - g_PostProcessMan.RegisterGlowDotEffect(aimPoint3, YellowDot, glow); - - // Make into target frame coordinates - aimPoint2 -= targetPos; - aimPoint3 -= targetPos; - - // Wrap the points - g_SceneMan.WrapPosition(aimPoint2); - g_SceneMan.WrapPosition(aimPoint3); - - acquire_bitmap(pTargetBitmap); - putpixel(pTargetBitmap, aimPoint2.m_X, aimPoint2.m_Y, g_YellowGlowColor); - putpixel(pTargetBitmap, aimPoint3.m_X, aimPoint3.m_Y, g_YellowGlowColor); - release_bitmap(pTargetBitmap); - } + float sharpLength = std::max(m_MaxSharpLength * m_SharpAim, 20.0F); + int glowStrength; + int pointCount; + if (playerControlled && sharpLength > 20.0F) { + pointCount = m_SharpAim > 0.5F ? 4 : 3; + glowStrength = RandomNum(127, 255); + } else { + pointCount = 2; + glowStrength = RandomNum(63, 127); + } + int pointSpacing = 10 - pointCount; + sharpLength -= static_cast(pointSpacing * pointCount) * 0.5F; + Vector muzzleOffset(std::max(m_MuzzleOff.m_X, m_SpriteRadius), m_MuzzleOff.m_Y); + + //acquire_bitmap(pTargetBitmap); + for (int i = 0; i < pointCount; ++i) { + Vector aimPoint(sharpLength + static_cast(pointSpacing * i), 0); + aimPoint = RotateOffset(aimPoint + muzzleOffset) + m_Pos; + + g_PostProcessMan.RegisterGlowDotEffect(aimPoint, YellowDot, glowStrength); + aimPoint -= targetPos; + g_SceneMan.WrapPosition(aimPoint); + putpixel(pTargetBitmap, aimPoint.GetFloorIntX(), aimPoint.GetFloorIntY(), g_YellowGlowColor); + } + //release_bitmap(pTargetBitmap); } } // namespace RTE diff --git a/Entities/HDFirearm.h b/Entities/HDFirearm.h index 01a9f7658..2c7e707ab 100644 --- a/Entities/HDFirearm.h +++ b/Entities/HDFirearm.h @@ -178,6 +178,13 @@ ClassInfoGetters; int GetRoundInMagCount() const; + /// + /// Gets the maximum RoundCount a Magazine of this HDFirearm can hold. + /// If there is no Magazine, it gets the RoundCount of the reference Magazine. + /// + /// An int with the maximum RoundCount the magazine or magazine reference of this HDFirearm can hold. + int GetRoundInMagCapacity() const; + ////////////////////////////////////////////////////////////////////////////////////////// // Method: GetActivationDelay @@ -320,6 +327,17 @@ ClassInfoGetters; void SetParticleSpreadRange(float range) { m_ParticleSpreadRange = range; }; + /// + /// Gets the random velocity variation scalar at which this HDFirearm's shell is to be ejected. + /// + /// A float with the scalar value. + float GetShellVelVariation() const { return m_ShellVelVariation; } + + /// + /// Sets the random velocity variation scalar at which this HDFirearm's shell is to be ejected. + /// + /// The new velocity variation scalar. + void SetShellVelVariation(float newVariation) { m_ShellVelVariation = newVariation; } ////////////////////////////////////////////////////////////////////////////////////////// // Method: GetAIFireVel @@ -534,6 +552,11 @@ ClassInfoGetters; void ResetAllTimers() override { HeldDevice::ResetAllTimers(); m_LastFireTmr.Reset(); m_ReloadTmr.Reset(); } + /// + /// Gets this HDFirearm's reload progress as a scalar from 0 to 1. + /// + /// The reload progress as a scalar from 0 to 1. + float GetReloadProgress() const { return IsReloading() && m_ReloadTime > 0 ? std::min(static_cast(m_ReloadTmr.GetElapsedSimTimeMS() / m_ReloadTime), 1.0F) : 1.0F; } ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: RestDetection @@ -640,6 +663,18 @@ ClassInfoGetters; bool IsFullAuto() const { return m_FullAuto; } + /// + /// Gets whether this HDFirearm is set to be reloadable or not. + /// + /// Whether this HDFirearm is reloadable. + bool IsReloadable() const { return m_Reloadable; } + + /// + /// Sets whether this HDFirearm is reloadable or not and halts the reloading process. + /// + /// Whether this HDFirearm is reloadable. + void SetReloadable(bool isReloadable) { m_Reloadable = isReloadable; m_Reloading = m_Reloading && m_Reloadable; } + ////////////////////////////////////////////////////////////////////////////////////////// // Method: SetFullAuto @@ -803,6 +838,7 @@ ClassInfoGetters; // Whether particles fired from this HDFirearm will ignore hits with itself, // and the root parent of this HDFirearm, regardless if they are set to hit MOs. bool m_FireIgnoresThis; + bool m_Reloadable; //!< Whether this HDFirearm is reloadable by normal means. // Timer for timing how long ago the last round was fired. Timer m_LastFireTmr; @@ -829,6 +865,7 @@ ClassInfoGetters; float m_ShellSpreadRange; // Range of spread in ang vel of ejected shells, in one direction float m_ShellAngVelRange; + float m_ShellVelVariation; //!< The velocity variation scalar of ejected shells. // The muzzle velocity the AI use when aiming this weapon float m_AIFireVel; // The bullet life time the AI use when aiming this weapon diff --git a/Entities/HeldDevice.cpp b/Entities/HeldDevice.cpp index 37d6d7475..70713f85a 100644 --- a/Entities/HeldDevice.cpp +++ b/Entities/HeldDevice.cpp @@ -20,6 +20,7 @@ #include "GUI.h" #include "AllegroBitmap.h" +#include "SettingsMan.h" namespace RTE { @@ -45,6 +46,7 @@ void HeldDevice::Clear() m_MaxSharpLength = 0; m_Supported = false; m_SupportOffset.Reset(); + m_SeenByPlayer.fill(false); m_IsUnPickupable = false; m_PickupableByPresetNames.clear(); m_GripStrengthMultiplier = 1.0F; @@ -464,7 +466,7 @@ void HeldDevice::Update() if (m_FrameCount > 1) { - if (m_SpriteAnimMode == LOOPWHENMOVING && m_Activated) + if (m_SpriteAnimMode == LOOPWHENACTIVE && m_Activated) { float cycleTime = ((long)m_SpriteAnimTimer.GetElapsedSimTimeMS()) % m_SpriteAnimDuration; m_Frame = std::floor((cycleTime / (float)m_SpriteAnimDuration) * (float)m_FrameCount); @@ -542,7 +544,8 @@ void HeldDevice::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whi if (!m_Parent && !IsUnPickupable()) { // Only draw if the team viewing this has seen the space where this is located - int viewingTeam = g_ActivityMan.GetActivity()->GetTeamOfPlayer(g_ActivityMan.GetActivity()->PlayerOfScreen(whichScreen)); + int viewingPlayer = g_ActivityMan.GetActivity()->PlayerOfScreen(whichScreen); + int viewingTeam = g_ActivityMan.GetActivity()->GetTeamOfPlayer(viewingPlayer); if (viewingTeam != Activity::NoTeam) { if (g_SceneMan.IsUnseen(m_Pos.m_X, m_Pos.m_Y, viewingTeam)) @@ -578,28 +581,29 @@ void HeldDevice::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whi GUIFont *pSymbolFont = g_FrameMan.GetLargeFont(); GUIFont *pTextFont = g_FrameMan.GetSmallFont(); if (pSymbolFont && pTextFont) { - AllegroBitmap pBitmapInt(pTargetBitmap); - if (m_BlinkTimer.GetElapsedSimTimeMS() < 300) { - str[0] = -42; + if (m_BlinkTimer.GetElapsedSimTimeMS() < 250) { + str[0] = 0; + } else if (m_BlinkTimer.GetElapsedSimTimeMS() < 500) { + str[0] = -42; str[1] = 0; - } - else if (m_BlinkTimer.GetElapsedSimTimeMS() < 600) { - str[0] = -41; - str[1] = 0; - } - else if (m_BlinkTimer.GetElapsedSimTimeMS() < 900) { - str[0] = -40; + } else if (m_BlinkTimer.GetElapsedSimTimeMS() < 750) { + str[0] = -41; str[1] = 0; - } - else if (m_BlinkTimer.GetElapsedSimTimeMS() < 1200) { - str[0] = 0; - } - else - m_BlinkTimer.Reset(); - - pSymbolFont->DrawAligned(&pBitmapInt, drawPos.m_X - 1, drawPos.m_Y - 20, str, GUIFont::Centre); - std::snprintf(str, sizeof(str), "%s", m_PresetName.c_str()); - pTextFont->DrawAligned(&pBitmapInt, drawPos.m_X + 0, drawPos.m_Y - 29, str, GUIFont::Centre); + } else if (m_BlinkTimer.GetElapsedSimTimeMS() < 1000) { + str[0] = -40; + str[1] = 0; + } else { + m_BlinkTimer.Reset(); + // Check for nearby actors that will toggle this pickup HUD + float range = g_SettingsMan.GetUnheldItemsHUDDisplayRange(); + m_SeenByPlayer.at(viewingPlayer) = range > -1 && (range == 0 || g_SceneMan.ShortestDistance(m_Pos, g_SceneMan.GetScrollTarget(whichScreen), g_SceneMan.SceneWrapsX()).GetMagnitude() < range); + } + if (m_SeenByPlayer.at(viewingPlayer)) { + AllegroBitmap pBitmapInt(pTargetBitmap); + pSymbolFont->DrawAligned(&pBitmapInt, drawPos.GetFloorIntX() - 1, drawPos.GetFloorIntY() - 20, str, GUIFont::Centre); + std::snprintf(str, sizeof(str), "%s", m_PresetName.c_str()); + pTextFont->DrawAligned(&pBitmapInt, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() - 29, str, GUIFont::Centre); + } } } } diff --git a/Entities/HeldDevice.h b/Entities/HeldDevice.h index 70d00da06..d0bfaa2c3 100644 --- a/Entities/HeldDevice.h +++ b/Entities/HeldDevice.h @@ -529,7 +529,7 @@ ClassInfoGetters; // Arguments: None. // Return value: Whetehr magazine is full or not. - virtual bool IsFull() const { return false; } + virtual bool IsFull() const { return true; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -635,6 +635,8 @@ ClassInfoGetters; // If this HeldDevice is currently being supported by a second hand. bool m_Supported; bool m_IsUnPickupable; //!< Whether or not this HeldDevice should be able to be picked up at all. + //TODO: move this smelly thing elsewhere + std::array m_SeenByPlayer; //!< An array of players that can currently see the pickup HUD of this HeldDevice. std::unordered_set m_PickupableByPresetNames; //!< The unordered set of PresetNames that can pick up this HeldDevice if it's dropped. An empty set means there are no PresetName limitations. float m_GripStrengthMultiplier; //!< The multiplier for how well this HeldDevice can be gripped by Arms. // Blink timer for the icon diff --git a/Entities/Leg.cpp b/Entities/Leg.cpp index b9af3ccd9..7bf178817 100644 --- a/Entities/Leg.cpp +++ b/Entities/Leg.cpp @@ -158,9 +158,7 @@ namespace RTE { Attachable::Update(); - if (m_FrameCount == 1) { - m_Frame = 0; - } else { + if (m_FrameCount != 1) { m_NormalizedExtension = std::clamp((m_AnkleOffset.GetMagnitude() - m_MinExtension) / (m_MaxExtension - m_MinExtension), 0.0F, 1.0F); m_Frame = std::min(m_FrameCount - 1, static_cast(std::floor(m_NormalizedExtension * static_cast(m_FrameCount)))); } @@ -173,7 +171,8 @@ namespace RTE { void Leg::UpdateCurrentAnkleOffset() { if (IsAttached()) { Vector targetOffset = g_SceneMan.ShortestDistance(m_JointPos, m_TargetPosition, g_SceneMan.SceneWrapsX()); - if (m_WillIdle && targetOffset.m_Y < -3) { targetOffset = m_IdleOffset.GetXFlipped(m_HFlipped); } + Vector rotatedTargetOffset = targetOffset.GetRadRotatedCopy(m_Parent->GetRotAngle()); + if (m_WillIdle && rotatedTargetOffset.m_Y < -std::abs(rotatedTargetOffset.m_X)) { targetOffset = m_Parent->RotateOffset(m_IdleOffset); } Vector distanceFromTargetOffsetToAnkleOffset(targetOffset - m_AnkleOffset); m_AnkleOffset += distanceFromTargetOffsetToAnkleOffset * m_MoveSpeed; diff --git a/Entities/LimbPath.cpp b/Entities/LimbPath.cpp index 8edc82f85..1d726b17d 100644 --- a/Entities/LimbPath.cpp +++ b/Entities/LimbPath.cpp @@ -300,11 +300,12 @@ Vector LimbPath::GetCurrentVel(const Vector &limbPos) { Vector returnVel; Vector distVect = g_SceneMan.ShortestDistance(limbPos, GetCurrentSegTarget()); + float adjustedTravelSpeed = m_TravelSpeed[m_WhichSpeed] / (1.0F + m_JointVel.GetMagnitude() * 0.1F); if (IsStaticPoint()) { returnVel = distVect * c_MPP / 0.020/* + m_JointVel*/; - returnVel.CapMagnitude(m_TravelSpeed[m_WhichSpeed]); + returnVel.CapMagnitude(adjustedTravelSpeed); returnVel += m_JointVel; // if (distVect.GetMagnitude() < 0.5) @@ -312,7 +313,7 @@ Vector LimbPath::GetCurrentVel(const Vector &limbPos) } else { - returnVel.SetXY(m_TravelSpeed[m_WhichSpeed], 0); + returnVel.SetXY(adjustedTravelSpeed, 0); if (!distVect.IsZero()) returnVel.AbsRotateTo(distVect); diff --git a/Entities/LimbPath.h b/Entities/LimbPath.h index d1dff7855..9bb8ae0b2 100644 --- a/Entities/LimbPath.h +++ b/Entities/LimbPath.h @@ -449,15 +449,6 @@ ClassInfoGetters; void SetHFlip(bool hflipped) { m_HFlipped = hflipped; -/* - if (m_HFlipped != hflipped) { - // Gotta flip before restart. - m_HFlipped = hflipped; - Restart(); - } - else - m_HFlipped = hflipped; -*/ m_Rotation.SetXFlipped(m_HFlipped); } diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index 4f5db3f4b..439db64f5 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -73,6 +73,7 @@ void MOSRotating::Clear() m_GibImpulseLimit = 0; m_GibWoundLimit = 0; m_GibBlastStrength = 10.0F; + m_WoundCountAffectsImpulseLimitRatio = 0.25F; m_GibSound = nullptr; m_EffectOnGib = true; m_pFlipBitmap = 0; @@ -254,6 +255,7 @@ int MOSRotating::Create(const MOSRotating &reference) { m_GibImpulseLimit = reference.m_GibImpulseLimit; m_GibWoundLimit = reference.m_GibWoundLimit; m_GibBlastStrength = reference.m_GibBlastStrength; + m_WoundCountAffectsImpulseLimitRatio = reference.m_WoundCountAffectsImpulseLimitRatio; if (reference.m_GibSound) { m_GibSound = dynamic_cast(reference.m_GibSound->Clone()); } m_EffectOnGib = reference.m_EffectOnGib; m_LoudnessOnGib = reference.m_LoudnessOnGib; @@ -324,8 +326,10 @@ int MOSRotating::ReadProperty(const std::string_view &propName, Reader &reader) reader >> m_GibImpulseLimit; else if (propName == "GibWoundLimit" || propName == "WoundLimit") reader >> m_GibWoundLimit; - else if (propName == "GibBlastStrength") { - reader >> m_GibBlastStrength; + else if (propName == "GibBlastStrength") { + reader >> m_GibBlastStrength; + } else if (propName == "WoundCountAffectsImpulseLimitRatio") { + reader >> m_WoundCountAffectsImpulseLimitRatio; } else if (propName == "GibSound") { if (!m_GibSound) { m_GibSound = new SoundContainer; } reader >> m_GibSound; @@ -989,44 +993,88 @@ void MOSRotating::CreateGibsWhenGibbing(const Vector &impactImpulse, MovableObje } MovableObject *gibParticleClone = dynamic_cast(gibSettingsObject.GetParticlePreset()->Clone()); + int count = gibSettingsObject.GetCount(); + float lifeVariation = gibSettingsObject.GetLifeVariation(); + float spread = gibSettingsObject.GetSpread(); + float minVelocity = gibSettingsObject.GetMinVelocity(); + float maxVelocity = gibSettingsObject.GetMaxVelocity(); + float mass = (gibParticleClone->GetMass() != 0 ? gibParticleClone->GetMass() : 0.0001F); - float minVelocity = gibSettingsObject.GetMinVelocity(); - float velocityRange = gibSettingsObject.GetMaxVelocity() - gibSettingsObject.GetMinVelocity(); - if (gibSettingsObject.GetMinVelocity() == 0 && gibSettingsObject.GetMaxVelocity() == 0) { + int lifetime = gibParticleClone->GetLifetime(); + + if (minVelocity == 0 && maxVelocity == 0) { minVelocity = m_GibBlastStrength / mass; - velocityRange = 10.0F; + maxVelocity = minVelocity + 10.0F; } + float velocityRange = maxVelocity - minVelocity; Vector rotatedGibOffset = RotateOffset(gibSettingsObject.GetOffset()); - for (int i = 0; i < gibSettingsObject.GetCount(); i++) { - if (i > 0) { gibParticleClone = dynamic_cast(gibSettingsObject.GetParticlePreset()->Clone()); } - - if (gibParticleClone->GetLifetime() != 0) { - gibParticleClone->SetLifetime(static_cast(static_cast(gibParticleClone->GetLifetime()) * (1.0F + (gibSettingsObject.GetLifeVariation() * RandomNormalNum())))); - } - - gibParticleClone->SetRotAngle(GetRotAngle() + gibParticleClone->GetRotAngle()); - gibParticleClone->SetAngularVel((gibParticleClone->GetAngularVel() * 0.35F) + (gibParticleClone->GetAngularVel() * 0.65F / mass) * RandomNum()); - if (rotatedGibOffset.GetRoundIntX() > m_aSprite[0]->w / 3) { - float offCenterRatio = rotatedGibOffset.m_X / (static_cast(m_aSprite[0]->w) / 2.0F); - float angularVel = fabs(gibParticleClone->GetAngularVel() * 0.5F) + std::fabs(gibParticleClone->GetAngularVel() * 0.5F * offCenterRatio); - gibParticleClone->SetAngularVel(angularVel * (rotatedGibOffset.m_X > 0 ? -1 : 1)); - } else { - gibParticleClone->SetAngularVel((gibParticleClone->GetAngularVel() * 0.5F + (gibParticleClone->GetAngularVel() * RandomNum())) * (RandomNormalNum() > 0.0F ? 1.0F : -1.0F)); - } - gibParticleClone->SetPos(m_Pos + rotatedGibOffset); - gibParticleClone->SetHFlipped(m_HFlipped); - Vector gibVelocity = rotatedGibOffset.IsZero() ? Vector(minVelocity + RandomNum(0.0F, velocityRange), 0.0F) : rotatedGibOffset.SetMagnitude(minVelocity + RandomNum(0.0F, velocityRange)); - // TODO: Figure out how much the magnitude of an offset should affect spread - float gibSpread = (rotatedGibOffset.IsZero() && gibSettingsObject.GetSpread() == 0.1F) ? c_PI : gibSettingsObject.GetSpread(); - gibVelocity.RadRotate(impactImpulse.GetAbsRadAngle() + (gibSpread * RandomNormalNum())); - gibParticleClone->SetVel(gibVelocity + (gibSettingsObject.InheritsVelocity() ? m_Vel : Vector())); - if (movableObjectToIgnore) { gibParticleClone->SetWhichMOToNotHit(movableObjectToIgnore); } - gibParticleClone->SetTeam(m_Team); - gibParticleClone->SetIgnoresTeamHits(gibSettingsObject.IgnoresTeamHits()); - - g_MovableMan.AddParticle(gibParticleClone); - } + // The "Spiral" spread mode uses the fermat spiral as means to determine the velocity of the gib particles, resulting in a evenly spaced out circle (or ring) of particles. + if (gibSettingsObject.GetSpreadMode() == Gib::SpreadMode::SpreadSpiral) { + float maxRadius = std::sqrt(static_cast(count)); + float scale = velocityRange / maxRadius; + float randAngle = c_PI * RandomNormalNum(); + float goldenAngle = 2.39996F; + + for (int i = 0; i < count; i++) { + if (i > 0) { gibParticleClone = dynamic_cast(gibSettingsObject.GetParticlePreset()->Clone()); } + + float radius = std::sqrt(static_cast(count - i)); + gibParticleClone->SetPos(m_Pos + rotatedGibOffset); + gibParticleClone->SetHFlipped(m_HFlipped); + Vector gibVelocity(radius * scale + minVelocity, 0); + gibVelocity.RadRotate(randAngle + RandomNum(0.0F, spread) + static_cast(i) * goldenAngle); + if (lifetime != 0) { + gibParticleClone->SetLifetime(std::max(static_cast(static_cast(lifetime) * (1.0F - lifeVariation * ((radius / maxRadius) * 0.75F + RandomNormalNum() * 0.25F))), 1)); + } + gibParticleClone->SetRotAngle(gibVelocity.GetAbsRadAngle() + (m_HFlipped ? c_PI : 0)); + gibParticleClone->SetAngularVel((gibParticleClone->GetAngularVel() * 0.35F) + (gibParticleClone->GetAngularVel() * 0.65F / mass) * RandomNum()); + gibParticleClone->SetVel(gibVelocity + (gibSettingsObject.InheritsVelocity() ? (m_PrevVel + m_Vel) / 2 : Vector())); + if (movableObjectToIgnore) { gibParticleClone->SetWhichMOToNotHit(movableObjectToIgnore); } + gibParticleClone->SetTeam(m_Team); + gibParticleClone->SetIgnoresTeamHits(gibSettingsObject.IgnoresTeamHits()); + + g_MovableMan.AddParticle(gibParticleClone); + } + } else { + for (int i = 0; i < count; i++) { + if (i > 0) { gibParticleClone = dynamic_cast(gibSettingsObject.GetParticlePreset()->Clone()); } + + if (gibParticleClone->GetLifetime() != 0) { + gibParticleClone->SetLifetime(std::max(static_cast(static_cast(gibParticleClone->GetLifetime()) * (1.0F + (lifeVariation * RandomNormalNum()))), 1)); + } + + gibParticleClone->SetRotAngle(GetRotAngle() + gibParticleClone->GetRotAngle()); + gibParticleClone->SetAngularVel((gibParticleClone->GetAngularVel() * 0.35F) + (gibParticleClone->GetAngularVel() * 0.65F / mass) * RandomNum()); + if (rotatedGibOffset.GetRoundIntX() > m_aSprite[0]->w / 3) { + float offCenterRatio = rotatedGibOffset.m_X / (static_cast(m_aSprite[0]->w) / 2.0F); + float angularVel = std::abs(gibParticleClone->GetAngularVel() * 0.5F) + std::abs(gibParticleClone->GetAngularVel() * 0.5F * offCenterRatio); + gibParticleClone->SetAngularVel(angularVel * (rotatedGibOffset.m_X > 0 ? -1 : 1)); + } else { + gibParticleClone->SetAngularVel((gibParticleClone->GetAngularVel() * 0.5F + (gibParticleClone->GetAngularVel() * RandomNum())) * (RandomNormalNum() > 0.0F ? 1.0F : -1.0F)); + } + + gibParticleClone->SetPos(m_Pos + rotatedGibOffset); + gibParticleClone->SetHFlipped(m_HFlipped); + Vector gibVelocity = rotatedGibOffset.IsZero() ? Vector(minVelocity + RandomNum(0.0F, velocityRange), 0.0F) : rotatedGibOffset.SetMagnitude(minVelocity + RandomNum(0.0F, velocityRange)); + // TODO: Figure out how much the magnitude of an offset should affect spread + float gibSpread = (rotatedGibOffset.IsZero() && spread == 0.1F) ? c_PI : spread; + + gibVelocity.RadRotate(gibSettingsObject.InheritsVelocity() ? impactImpulse.GetAbsRadAngle() : m_Rotation.GetRadAngle() + (m_HFlipped ? c_PI : 0)); + // The "Even" spread will spread all gib particles evenly in an arc, while maintaining a randomized velocity magnitude. + if (gibSettingsObject.GetSpreadMode() == Gib::SpreadMode::SpreadEven) { + gibVelocity.RadRotate(gibSpread - (gibSpread * 2.0F * static_cast(i) / static_cast(count))); + } else { + gibVelocity.RadRotate(gibSpread * RandomNormalNum()); + } + gibParticleClone->SetVel(gibVelocity + (gibSettingsObject.InheritsVelocity() ? (m_PrevVel + m_Vel) / 2 : Vector())); + if (movableObjectToIgnore) { gibParticleClone->SetWhichMOToNotHit(movableObjectToIgnore); } + gibParticleClone->SetTeam(m_Team); + gibParticleClone->SetIgnoresTeamHits(gibSettingsObject.IgnoresTeamHits()); + + g_MovableMan.AddParticle(gibParticleClone); + } + } } } @@ -1123,9 +1171,13 @@ void MOSRotating::ApplyImpulses() totalImpulse += (*iItr).first; } // If impulse gibbing threshold is enabled for this, see if it's below the total impulse force - if (m_GibImpulseLimit > 0 && totalImpulse.GetMagnitude() > m_GibImpulseLimit) - GibThis(totalImpulse); - + if (m_GibImpulseLimit > 0) { + float impulseLimit = m_GibImpulseLimit; + if (m_WoundCountAffectsImpulseLimitRatio != 0 && m_GibWoundLimit > 0) { + impulseLimit *= 1.0F - (static_cast(m_Wounds.size()) / static_cast(m_GibWoundLimit)) * m_WoundCountAffectsImpulseLimitRatio; + } + if (totalImpulse.GetMagnitude() > impulseLimit) { GibThis(totalImpulse); } + } MOSprite::ApplyImpulses(); } @@ -1395,9 +1447,13 @@ void MOSRotating::PostTravel() MOSprite::PostTravel(); // Check if travel hits created enough impulse forces to gib this - if (m_GibImpulseLimit > 0 && m_TravelImpulse.GetMagnitude() > m_GibImpulseLimit) - GibThis(); - + if (m_GibImpulseLimit > 0) { + float impulseLimit = m_GibImpulseLimit; + if (m_WoundCountAffectsImpulseLimitRatio != 0 && m_GibWoundLimit > 0) { + impulseLimit *= 1.0F - (static_cast(m_Wounds.size()) / static_cast(m_GibWoundLimit)) * m_WoundCountAffectsImpulseLimitRatio; + } + if (m_TravelImpulse.GetMagnitude() > impulseLimit) { GibThis(); } + } // Reset m_DeepHardness = 0; @@ -1587,6 +1643,7 @@ Attachable * MOSRotating::RemoveAttachable(Attachable *attachable, bool addToMov if (!m_ToDelete && attachable->GetParentBreakWound()) { AEmitter *parentBreakWound = dynamic_cast(attachable->GetParentBreakWound()->Clone()); if (parentBreakWound) { + parentBreakWound->SetDrawnAfterParent(attachable->IsDrawnAfterParent()); parentBreakWound->SetEmitAngle((attachable->GetParentOffset() * m_Rotation).GetAbsRadAngle()); AddWound(parentBreakWound, attachable->GetParentOffset(), false); parentBreakWound = nullptr; diff --git a/Entities/MOSRotating.h b/Entities/MOSRotating.h index 96601f1af..8b7a568b1 100644 --- a/Entities/MOSRotating.h +++ b/Entities/MOSRotating.h @@ -645,6 +645,12 @@ ClassInfoGetters; /// The new gib wound limit to use. void SetGibWoundLimit(int newGibWoundLimit) { m_GibWoundLimit = newGibWoundLimit; } + /// + /// Gets the rate at which wound count of this MOSRotating will diminish the impulse limit. + /// + /// The rate at which wound count affects the impulse limit. + float GetWoundCountAffectsImpulseLimitRatio() const { return m_WoundCountAffectsImpulseLimitRatio; } + /// /// Gets the gib blast strength this MOSRotating, i.e. the strength with which Gibs and Attachables will be launched when this MOSRotating is gibbed. /// @@ -962,6 +968,7 @@ ClassInfoGetters; // The number of wound emitters allowed before this gets gibbed. 0 means this can't get gibbed int m_GibWoundLimit; float m_GibBlastStrength; //!< The strength with which Gibs and Attachables will get launched when this MOSRotating is gibbed. + float m_WoundCountAffectsImpulseLimitRatio; //!< The rate at which this MOSRotating's wound count will diminish the impulse limit. // Gib sound effect SoundContainer *m_GibSound; // Whether to flash effect on gib diff --git a/Entities/MOSprite.cpp b/Entities/MOSprite.cpp index cf7f3dcd0..a3a33ac89 100644 --- a/Entities/MOSprite.cpp +++ b/Entities/MOSprite.cpp @@ -182,8 +182,8 @@ int MOSprite::ReadProperty(const std::string_view &propName, Reader &reader) m_SpriteAnimMode = ALWAYSLOOP; else if (mode == "ALWAYSPINGPONG") m_SpriteAnimMode = ALWAYSPINGPONG; - else if (mode == "LOOPWHENMOVING") - m_SpriteAnimMode = LOOPWHENMOVING; + else if (mode == "LOOPWHENACTIVE") + m_SpriteAnimMode = LOOPWHENACTIVE; else Abort */ diff --git a/Entities/MOSprite.h b/Entities/MOSprite.h index caac03acf..1a7c53c59 100644 --- a/Entities/MOSprite.h +++ b/Entities/MOSprite.h @@ -50,7 +50,7 @@ class MOSprite: ALWAYSLOOP, ALWAYSRANDOM, ALWAYSPINGPONG, - LOOPWHENMOVING, + LOOPWHENACTIVE, LOOPWHENOPENCLOSE, PINGPONGOPENCLOSE, OVERLIFETIME, @@ -266,6 +266,11 @@ class MOSprite: float GetRotAngle() const override { return m_Rotation.GetRadAngle(); } + /// + /// Gets the previous rotational angle of this MOSprite, prior to this frame. + /// + /// The previous rotational angle in radians. + float GetPrevRotAngle() const { return m_PrevRotation.GetRadAngle(); } ////////////////////////////////////////////////////////////////////////////////////////// // Method: GetAngularVel diff --git a/Entities/Magazine.h b/Entities/Magazine.h index 2d4fb0d91..db295cb08 100644 --- a/Entities/Magazine.h +++ b/Entities/Magazine.h @@ -157,7 +157,7 @@ ClassInfoGetters; // Arguments: None. // Return value: Whether this Magazine is out of rounds or not. - bool IsEmpty() const { return m_FullCapacity > 0 ? m_RoundCount == 0 : !(m_FullCapacity < 0); } + bool IsEmpty() const { return m_FullCapacity == 0 || m_RoundCount == 0; } ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Entities/PEmitter.cpp b/Entities/PEmitter.cpp index 385e9c283..405658163 100644 --- a/Entities/PEmitter.cpp +++ b/Entities/PEmitter.cpp @@ -37,8 +37,8 @@ namespace RTE { m_WasEmitting = false; m_EmitCount = 0; m_EmitCountLimit = 0; - m_MinThrottleRange = -1; - m_MaxThrottleRange = 1; + m_NegativeThrottleMultiplier = 1.0F; + m_PositiveThrottleMultiplier = 1.0F; m_Throttle = 0; m_EmissionsIgnoreThis = false; m_BurstScale = 1.0; @@ -90,8 +90,8 @@ namespace RTE { m_EmitEnabled = reference.m_EmitEnabled; m_EmitCount = reference.m_EmitCount; m_EmitCountLimit = reference.m_EmitCountLimit; - m_MinThrottleRange = reference.m_MinThrottleRange; - m_MaxThrottleRange = reference.m_MaxThrottleRange; + m_NegativeThrottleMultiplier = reference.m_NegativeThrottleMultiplier; + m_PositiveThrottleMultiplier = reference.m_PositiveThrottleMultiplier; m_Throttle = reference.m_Throttle; m_EmissionsIgnoreThis = reference.m_EmissionsIgnoreThis; m_BurstScale = reference.m_BurstScale; @@ -143,10 +143,10 @@ namespace RTE { for (list::iterator eItr = m_EmissionList.begin(); eItr != m_EmissionList.end(); ++eItr) (*eItr).m_PPM = ppm / m_EmissionList.size(); } - else if (propName == "MinThrottleRange") - reader >> m_MinThrottleRange; - else if (propName == "MaxThrottleRange") - reader >> m_MaxThrottleRange; + else if (propName == "NegativeThrottleMultiplier") + reader >> m_NegativeThrottleMultiplier; + else if (propName == "PositiveThrottleMultiplier") + reader >> m_PositiveThrottleMultiplier; else if (propName == "Throttle") reader >> m_Throttle; else if (propName == "EmissionsIgnoreThis") @@ -213,10 +213,10 @@ namespace RTE { writer << m_EmitCountLimit; writer.NewProperty("EmissionsIgnoreThis"); writer << m_EmissionsIgnoreThis; - writer.NewProperty("MinThrottleRange"); - writer << m_MinThrottleRange; - writer.NewProperty("MaxThrottleRange"); - writer << m_MaxThrottleRange; + writer.NewProperty("NegativeThrottleMultiplier"); + writer << m_NegativeThrottleMultiplier; + writer.NewProperty("PositiveThrottleMultiplier"); + writer << m_PositiveThrottleMultiplier; writer.NewProperty("Throttle"); writer << m_Throttle; writer.NewProperty("BurstScale"); @@ -339,13 +339,14 @@ namespace RTE { } - // Figure out the throttle factor - float throttleFactor = 1.0f; - if (m_Throttle < 0) // Negative throttle, scale down according to the min throttle range - throttleFactor += fabs(m_MinThrottleRange) * m_Throttle; - else if (m_Throttle > 0) // Positive throttle, scale up - throttleFactor += fabs(m_MaxThrottleRange) * m_Throttle; - + // Scale the emission rate up or down according to the appropriate throttle multiplier. + float throttleFactor = 1.0F; + float absThrottle = std::abs(m_Throttle); + if (m_Throttle < 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_NegativeThrottleMultiplier * absThrottle); + } else if (m_Throttle > 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_PositiveThrottleMultiplier * absThrottle); + } // Apply the throttle factor to the emission rate per update if (burst) return m_AvgBurstImpulse * throttleFactor; @@ -397,15 +398,15 @@ namespace RTE { // TODO: Potentially get this once outside instead, like in attach/detach") MovableObject *pRootParent = GetRootParent(); - // Figure out the throttle factor - // Negative throttle, scale down according to the min throttle range - float throttleFactor = 1.0f; - if (m_Throttle < 0) - throttleFactor += fabs(m_MinThrottleRange) * m_Throttle; - // Positive throttle, scale up - else if (m_Throttle > 0) - throttleFactor += fabs(m_MaxThrottleRange) * m_Throttle; - + // Scale the emission rate up or down according to the appropriate throttle multiplier. + float throttleFactor = 1.0F; + float absThrottle = std::abs(m_Throttle); + if (m_Throttle < 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_NegativeThrottleMultiplier * absThrottle); + } else if (m_Throttle > 0) { + throttleFactor = throttleFactor * (1 - absThrottle) + (m_PositiveThrottleMultiplier * absThrottle); + } + m_FlashScale = throttleFactor; // Check burst triggering against whether the spacing is fulfilled if (m_BurstTriggered && (m_BurstSpacing <= 0 || m_BurstTimer.IsPastSimMS(m_BurstSpacing))) { @@ -477,8 +478,7 @@ namespace RTE { emitVel = RotateOffset(emitVel); pParticle->SetVel(parentVel + emitVel); - if (pParticle->GetLifetime() != 0) - pParticle->SetLifetime(pParticle->GetLifetime() * (1.0F + ((*eItr).GetLifeVariation() * RandomNormalNum()))); + if (pParticle->GetLifetime() != 0) { pParticle->SetLifetime(std::max(static_cast(pParticle->GetLifetime() * (1.0F + ((*eItr).GetLifeVariation() * RandomNormalNum()))), 1)); } pParticle->SetTeam(m_Team); pParticle->SetIgnoresTeamHits(true); diff --git a/Entities/PEmitter.h b/Entities/PEmitter.h index 3c9c8d07a..8bc0acb79 100644 --- a/Entities/PEmitter.h +++ b/Entities/PEmitter.h @@ -506,10 +506,8 @@ class PEmitter : long m_EmitCount; // The max number of emissions to emit per emit being enabled long m_EmitCountLimit; - // The range negative throttle has on emission rate. 1.0 means the rate can be throttled down to 0%, 0 means negative throttle has no effect - double m_MinThrottleRange; - // The range positive throttle has on emission rate. 1.0 means the rate can be throttled up to 200%, 0 means negative throttle has no effect - double m_MaxThrottleRange; + float m_NegativeThrottleMultiplier; //!< The multiplier applied to the emission rate when throttle is negative. Relative to the absolute throttle value. + float m_PositiveThrottleMultiplier; //!< The multiplier applied to the emission rate when throttle is positive. Relative to the absolute throttle value. // The normalized throttle which controls the MSPE between 1.0 * m_MSPERange and -1.0 * m_MSPERange. 0 means emit the regular m_PPM amount. float m_Throttle; // Whether or not this' emissions ignore hits with itself, even if they are set to hit other MOs. diff --git a/Entities/Round.cpp b/Entities/Round.cpp index be264cd01..4bec5b44c 100644 --- a/Entities/Round.cpp +++ b/Entities/Round.cpp @@ -13,6 +13,7 @@ namespace RTE { m_ParticleCount = 0; m_FireVel = 0; m_Separation = 0; + m_LifeVariation = 0; m_Shell = 0; m_ShellVel = 0; m_FireSound.Reset(); @@ -51,6 +52,7 @@ namespace RTE { m_ParticleCount = reference.m_ParticleCount; m_FireVel = reference.m_FireVel; m_Separation = reference.m_Separation; + m_LifeVariation = reference.m_LifeVariation; m_Shell = reference.m_Shell; m_ShellVel = reference.m_ShellVel; m_FireSound = reference.m_FireSound; @@ -73,6 +75,8 @@ namespace RTE { reader >> m_FireVel; } else if (propName == "Separation") { reader >> m_Separation; + } else if (propName == "LifeVariation") { + reader >> m_LifeVariation; } else if (propName == "Shell") { m_Shell = dynamic_cast(g_PresetMan.GetEntityPreset(reader)); } else if (propName == "ShellVelocity") { @@ -104,6 +108,8 @@ namespace RTE { writer << m_FireVel; writer.NewProperty("Separation"); writer << m_Separation; + writer.NewProperty("LifeVariation"); + writer << m_LifeVariation; writer.NewProperty("Shell"); writer << m_Shell; writer.NewProperty("ShellVelocity"); diff --git a/Entities/Round.h b/Entities/Round.h index f7ed25e55..ad9687bd8 100644 --- a/Entities/Round.h +++ b/Entities/Round.h @@ -93,6 +93,12 @@ namespace RTE { /// A float with the separation range in pixels. float GetSeparation() const { return m_Separation; } + /// + /// Gets the variation in lifetime of the fired particles in this Round. + /// + /// A float with the life variation scalar. + float GetLifeVariation() const { return m_LifeVariation; } + /// /// Gets the shell casing preset of this Round. Ownership IS NOT transferred! /// @@ -100,9 +106,9 @@ namespace RTE { const MovableObject * GetShell() const { return m_Shell; } /// - /// Gets the velocity at which this round's shell is to be ejected. + /// Gets the maximum velocity at which this round's shell is to be ejected. /// - /// A float with the shell velocity in m/s. + /// A float with the maximum shell velocity in m/s. float GetShellVel() const { return m_ShellVel; } /// @@ -146,9 +152,10 @@ namespace RTE { int m_ParticleCount; //!< How many particle copies there are in this Round. float m_FireVel; //!< The velocity with which this Round is fired. float m_Separation; //!< The range of separation between particles in this Round, in pixels. + float m_LifeVariation; //!< The random variation in life time of each fired particle, in percentage of their life time. const MovableObject *m_Shell; //!< Shell particle MovableObject preset instance. - float m_ShellVel; //!< The velocity with which this Round's shell/casing is launched. + float m_ShellVel; //!< The maximum velocity with which this Round's shell/casing is launched. SoundContainer m_FireSound; //!< The extra firing audio of this Round being fired. diff --git a/Entities/SoundContainer.cpp b/Entities/SoundContainer.cpp index f235ad5c1..82adb84a9 100644 --- a/Entities/SoundContainer.cpp +++ b/Entities/SoundContainer.cpp @@ -173,7 +173,7 @@ namespace RTE { if (HasAnySounds()) { if (IsBeingPlayed()) { if (m_SoundOverlapMode == SoundOverlapMode::RESTART) { - Restart(player); + return Restart(player); } else if (m_SoundOverlapMode == SoundOverlapMode::IGNORE_PLAY) { return false; } diff --git a/Entities/ThrownDevice.h b/Entities/ThrownDevice.h index 513672ed6..2d33059ec 100644 --- a/Entities/ThrownDevice.h +++ b/Entities/ThrownDevice.h @@ -56,17 +56,29 @@ namespace RTE { #pragma region Getters and Setters /// - /// Gets the Start throw offset of this ThrownDevice's joint relative from the parent Actor's position, if attached. + /// Gets the start throw offset of this ThrownDevice's joint relative from the parent Actor's position, if attached. /// /// A const reference to the current start throw parent offset. Vector GetStartThrowOffset() const { return m_StartThrowOffset; } /// - /// Gets the End throw offset of this ThrownDevice's joint relative from the parent Actor's position, if attached. + /// Sets the start throw offset for this ThrownDevice. + /// + /// The new start throw offset. + void SetStartThrowOffset(Vector startOffset) { m_StartThrowOffset = startOffset; } + + /// + /// Gets the end throw offset of this ThrownDevice's joint relative from the parent Actor's position, if attached. /// /// A const reference to the current end throw parent offset. Vector GetEndThrowOffset() const { return m_EndThrowOffset; } + /// + /// Sets the end throw offset for this ThrownDevice. + /// + /// The new end throw offset. + void SetEndThrowOffset(Vector endOffset) { m_EndThrowOffset = endOffset; } + /// /// Gets the minimum throw velocity of this when thrown. /// diff --git a/Lua/LuaBindingsActivities.cpp b/Lua/LuaBindingsActivities.cpp index 59e11691a..0fe302e18 100644 --- a/Lua/LuaBindingsActivities.cpp +++ b/Lua/LuaBindingsActivities.cpp @@ -47,9 +47,9 @@ namespace RTE { .def("TeamFundsChanged", &Activity::TeamFundsChanged) .def("ReportDeath", &Activity::ReportDeath) .def("GetTeamDeathCount", &Activity::GetTeamDeathCount) - .def("Running", &Activity::IsRunning) - .def("Paused", &Activity::IsPaused) - .def("ActivityOver", &Activity::IsOver) + .def("IsRunning", &Activity::IsRunning) + .def("IsPaused", &Activity::IsPaused) + .def("IsOver", &Activity::IsOver) .def("EnteredOrbit", &Activity::EnteredOrbit) .def("SwitchToActor", &Activity::SwitchToActor) .def("SwitchToNextActor", &Activity::SwitchToNextActor) diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 9cb6c0d84..baf309560 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -15,6 +15,7 @@ namespace RTE { .property("Description", &Entity::GetDescription, &Entity::SetDescription) .property("IsOriginalPreset", &Entity::IsOriginalPreset) .property("ModuleID", &Entity::GetModuleID) + .property("ModuleName", &Entity::GetModuleName) .property("RandomWeight", &Entity::GetRandomWeight) .def("Clone", &CloneEntity) @@ -137,6 +138,8 @@ namespace RTE { .property("CrashSound", &ACraft::GetCrashSound, &ACraftSetCrashSound) .property("MaxPassengers", &ACraft::GetMaxPassengers) .property("DeliveryDelayMultiplier", &ACraft::GetDeliveryDelayMultiplier) + .property("ScuttleOnDeath", &ACraft::GetScuttleOnDeath, &ACraft::SetScuttleOnDeath) + .property("HatchDelay", &ACraft::GetHatchDelay, &ACraft::SetHatchDelay) .def("OpenHatch", &ACraft::OpenHatch) .def("CloseHatch", &ACraft::CloseHatch) @@ -204,6 +207,7 @@ namespace RTE { .property("DeathSound", &Actor::GetDeathSound, &ActorSetDeathSound) .property("DeviceSwitchSound", &Actor::GetDeviceSwitchSound, &ActorSetDeviceSwitchSound) .property("ImpulseDamageThreshold", &Actor::GetTravelImpulseDamage, &Actor::SetTravelImpulseDamage) + .property("StableRecoveryDelay", &Actor::GetStableRecoverDelay, &Actor::SetStableRecoverDelay) .property("Status", &Actor::GetStatus, &Actor::SetStatus) .property("Health", &Actor::GetHealth, &Actor::SetHealth) .property("PrevHealth", &Actor::GetPrevHealth) @@ -364,6 +368,8 @@ namespace RTE { .property("EmitAngle", &AEmitter::GetEmitAngle, &AEmitter::SetEmitAngle) .property("GetThrottle", &AEmitter::GetThrottle, &AEmitter::SetThrottle) .property("Throttle", &AEmitter::GetThrottle, &AEmitter::SetThrottle) + .property("NegativeThrottleMultiplier", &AEmitter::GetNegativeThrottleMultiplier, &AEmitter::SetNegativeThrottleMultiplier) + .property("PositiveThrottleMultiplier", &AEmitter::GetPositiveThrottleMultiplier, &AEmitter::SetPositiveThrottleMultiplier) .property("BurstSpacing", &AEmitter::GetBurstSpacing, &AEmitter::SetBurstSpacing) .property("BurstDamage", &AEmitter::GetBurstDamage, &AEmitter::SetBurstDamage) .property("EmitterDamageMultiplier", &AEmitter::GetEmitterDamageMultiplier, &AEmitter::SetEmitterDamageMultiplier) @@ -404,6 +410,7 @@ namespace RTE { .property("JetTimeLeft", &AHuman::GetJetTimeLeft, &AHuman::SetJetTimeLeft) .property("JetAngleRange", &AHuman::GetJetAngleRange, &AHuman::SetJetAngleRange) .property("ThrowPrepTime", &AHuman::GetThrowPrepTime, &AHuman::SetThrowPrepTime) + .property("ThrowProgress", &AHuman::GetThrowProgress) .property("EquippedItem", &AHuman::GetEquippedItem) .property("EquippedBGItem", &AHuman::GetEquippedBGItem) .property("FirearmIsReady", &AHuman::FirearmIsReady) @@ -588,7 +595,9 @@ namespace RTE { .property("RateOfFire", &HDFirearm::GetRateOfFire, &HDFirearm::SetRateOfFire) .property("FullAuto", &HDFirearm::IsFullAuto, &HDFirearm::SetFullAuto) + .property("Reloadable", &HDFirearm::IsReloadable, &HDFirearm::SetReloadable) .property("RoundInMagCount", &HDFirearm::GetRoundInMagCount) + .property("RoundInMagCapacity", &HDFirearm::GetRoundInMagCapacity) .property("Magazine", &HDFirearm::GetMagazine, &HDFirearmSetMagazine) .property("Flash", &HDFirearm::GetFlash, &HDFirearmSetFlash) .property("PreFireSound", &HDFirearm::GetPreFireSound, &HDFirearmSetPreFireSound) @@ -602,10 +611,12 @@ namespace RTE { .property("ActivationDelay", &HDFirearm::GetActivationDelay, &HDFirearm::SetActivationDelay) .property("DeactivationDelay", &HDFirearm::GetDeactivationDelay, &HDFirearm::SetDeactivationDelay) .property("ReloadTime", &HDFirearm::GetReloadTime, &HDFirearm::SetReloadTime) + .property("ReloadProgress", &HDFirearm::GetReloadProgress) .property("ShakeRange", &HDFirearm::GetShakeRange, &HDFirearm::SetShakeRange) .property("SharpShakeRange", &HDFirearm::GetSharpShakeRange, &HDFirearm::SetSharpShakeRange) .property("NoSupportFactor", &HDFirearm::GetNoSupportFactor, &HDFirearm::SetNoSupportFactor) .property("ParticleSpreadRange", &HDFirearm::GetParticleSpreadRange, &HDFirearm::SetParticleSpreadRange) + .property("ShellVelVariation", &HDFirearm::GetShellVelVariation, &HDFirearm::SetShellVelVariation) .property("FiredOnce", &HDFirearm::FiredOnce) .property("FiredFrame", &HDFirearm::FiredFrame) .property("RoundsFired", &HDFirearm::RoundsFired) @@ -755,6 +766,7 @@ namespace RTE { .property("HFlipped", &MOSprite::IsHFlipped, &MOSprite::SetHFlipped) .property("FlipFactor", &MOSprite::GetFlipFactor) .property("RotAngle", &MOSprite::GetRotAngle, &MOSprite::SetRotAngle) + .property("PrevRotAngle", &MOSprite::GetPrevRotAngle) .property("AngularVel", &MOSprite::GetAngularVel, &MOSprite::SetAngularVel) .property("Frame", &MOSprite::GetFrame, &MOSprite::SetFrame) .property("SpriteAnimMode", &MOSprite::GetSpriteAnimMode, &MOSprite::SetSpriteAnimMode) @@ -778,7 +790,7 @@ namespace RTE { luabind::value("ALWAYSLOOP", MOSprite::SpriteAnimMode::ALWAYSLOOP), luabind::value("ALWAYSRANDOM", MOSprite::SpriteAnimMode::ALWAYSRANDOM), luabind::value("ALWAYSPINGPONG", MOSprite::SpriteAnimMode::ALWAYSPINGPONG), - luabind::value("LOOPWHENMOVING", MOSprite::SpriteAnimMode::LOOPWHENMOVING), + luabind::value("LOOPWHENACTIVE", MOSprite::SpriteAnimMode::LOOPWHENACTIVE), luabind::value("LOOPWHENOPENCLOSE", MOSprite::SpriteAnimMode::LOOPWHENOPENCLOSE), luabind::value("PINGPONGOPENCLOSE", MOSprite::SpriteAnimMode::PINGPONGOPENCLOSE), luabind::value("OVERLIFETIME", MOSprite::SpriteAnimMode::OVERLIFETIME), @@ -801,6 +813,7 @@ namespace RTE { .property("GibWoundLimit", (int (MOSRotating:: *)() const) &MOSRotating::GetGibWoundLimit, &MOSRotating::SetGibWoundLimit) .property("GibSound", &MOSRotating::GetGibSound, &MOSRotatingSetGibSound) .property("GibImpulseLimit", &MOSRotating::GetGibImpulseLimit, &MOSRotating::SetGibImpulseLimit) + .property("WoundCountAffectsImpulseLimitRatio", &MOSRotating::GetWoundCountAffectsImpulseLimitRatio) .property("DamageMultiplier", &MOSRotating::GetDamageMultiplier, &MOSRotating::SetDamageMultiplier) .property("WoundCount", (int (MOSRotating:: *)() const) &MOSRotating::GetWoundCount) .property("OrientToVel", &MOSRotating::GetOrientToVel, &MOSRotating::SetOrientToVel) @@ -1166,7 +1179,9 @@ namespace RTE { return ConcreteTypeLuaClassDefinition(ThrownDevice, HeldDevice) .property("MinThrowVel", &ThrownDevice::GetMinThrowVel, &ThrownDevice::SetMinThrowVel) - .property("MaxThrowVel", &ThrownDevice::GetMaxThrowVel, &ThrownDevice::SetMaxThrowVel); + .property("MaxThrowVel", &ThrownDevice::GetMaxThrowVel, &ThrownDevice::SetMaxThrowVel) + .property("StartThrowOffset", &ThrownDevice::GetStartThrowOffset, &ThrownDevice::SetStartThrowOffset) + .property("EndThrowOffset", &ThrownDevice::GetEndThrowOffset, &ThrownDevice::SetEndThrowOffset); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Lua/LuaBindingsSystem.cpp b/Lua/LuaBindingsSystem.cpp index c28c5c253..5f4128ddc 100644 --- a/Lua/LuaBindingsSystem.cpp +++ b/Lua/LuaBindingsSystem.cpp @@ -119,6 +119,7 @@ namespace RTE { .property("Author", &DataModule::GetAuthor) .property("Description", &DataModule::GetDescription) .property("Version", &DataModule::GetVersionNumber) + .property("IsFaction", &DataModule::IsFaction) .def_readwrite("Presets", &DataModule::m_EntityList, luabind::return_stl_iterator); } @@ -271,8 +272,8 @@ namespace RTE { .def("Reset", &Vector::Reset) .def("RadRotate", &Vector::RadRotate) .def("DegRotate", &Vector::DegRotate) - .def("GetRadRotated", &Vector::GetRadRotated) - .def("GetDegRotated", &Vector::GetDegRotated) + .def("GetRadRotatedCopy", &Vector::GetRadRotatedCopy) + .def("GetDegRotatedCopy", &Vector::GetDegRotatedCopy) .def("AbsRotateTo", &Vector::AbsRotateTo) .def("SetXY", &Vector::SetXY); } diff --git a/Managers/SettingsMan.cpp b/Managers/SettingsMan.cpp index 03d7da9f6..36d336286 100644 --- a/Managers/SettingsMan.cpp +++ b/Managers/SettingsMan.cpp @@ -21,6 +21,7 @@ namespace RTE { m_FlashOnBrainDamage = true; m_BlipOnRevealUnseen = true; + m_UnheldItemsHUDDisplayRange = 25; m_EndlessMetaGameMode = false; m_EnableCrabBombs = false; m_CrabBombThreshold = 42; @@ -135,6 +136,8 @@ namespace RTE { reader >> m_BlipOnRevealUnseen; } else if (propName == "MaxUnheldItems") { reader >> g_MovableMan.m_MaxDroppedItems; + } else if (propName == "UnheldItemsHUDDisplayRange") { + SetUnheldItemsHUDDisplayRange(std::stof(reader.ReadPropValue())); } else if (propName == "SloMoThreshold") { reader >> g_MovableMan.m_SloMoThreshold; } else if (propName == "SloMoDurationMS") { @@ -307,6 +310,7 @@ namespace RTE { writer.NewPropertyWithValue("FlashOnBrainDamage", m_FlashOnBrainDamage); writer.NewPropertyWithValue("BlipOnRevealUnseen", m_BlipOnRevealUnseen); writer.NewPropertyWithValue("MaxUnheldItems", g_MovableMan.m_MaxDroppedItems); + writer.NewPropertyWithValue("UnheldItemsHUDDisplayRange", m_UnheldItemsHUDDisplayRange); writer.NewPropertyWithValue("SloMoThreshold", g_MovableMan.m_SloMoThreshold); writer.NewPropertyWithValue("SloMoDurationMS", g_MovableMan.m_SloMoDuration); writer.NewPropertyWithValue("EndlessMetaGameMode", m_EndlessMetaGameMode); diff --git a/Managers/SettingsMan.h b/Managers/SettingsMan.h index 4dc03a931..ba406750d 100644 --- a/Managers/SettingsMan.h +++ b/Managers/SettingsMan.h @@ -89,6 +89,18 @@ namespace RTE { /// New value for Blip on reveal unseen option. void SetBlipOnRevealUnseen(bool newValue) { m_BlipOnRevealUnseen = newValue; } + /// + /// Gets the range in which devices on Scene will show the pick-up HUD. + /// + /// The range in which devices on Scene will show the pick-up HUD, in pixels. -1 means HUDs are hidden, 0 means unlimited range. + float GetUnheldItemsHUDDisplayRange() const { return m_UnheldItemsHUDDisplayRange; } + + /// + /// Sets the range in which devices on Scene will show the pick-up HUD. + /// + /// The new range in which devices on Scene will show the pick-up HUD, in pixels. -1 means HUDs are hidden, 0 means unlimited range. + void SetUnheldItemsHUDDisplayRange(float newRadius) { m_UnheldItemsHUDDisplayRange = std::floor(newRadius); } + /// /// Whether red and white flashes appear when brain is damaged. /// @@ -395,6 +407,7 @@ namespace RTE { bool m_ShowForeignItems; //!< Do not show foreign items in buy menu. bool m_FlashOnBrainDamage; //!< Whether red flashes on brain damage are on or off. bool m_BlipOnRevealUnseen; //!< Blip if unseen is revealed. + float m_UnheldItemsHUDDisplayRange; //!< Range in which devices on Scene will show the pick-up HUD, in pixels. -1 means HUDs are hidden, 0 means unlimited range. bool m_EndlessMetaGameMode; //!< Endless MetaGame mode. bool m_EnableCrabBombs; //!< Whether all actors (except Brains and Doors) should be annihilated if a number exceeding the crab bomb threshold is released at once. int m_CrabBombThreshold; //!< The number of crabs needed to be released at once to trigger the crab bomb effect. diff --git a/Menus/BuyMenuGUI.cpp b/Menus/BuyMenuGUI.cpp index f4b25c5a2..7fff83241 100644 --- a/Menus/BuyMenuGUI.cpp +++ b/Menus/BuyMenuGUI.cpp @@ -2003,7 +2003,7 @@ void BuyMenuGUI::CategoryChange(bool focusOnCategoryTabs) } else if (m_MenuCategory == TOOLS) { - AddObjectsToItemList(catalogList, "HDFirearm", "Tools"); + AddObjectsToItemList(catalogList, "HeldDevice", "Tools"); } else if (m_MenuCategory == GUNS) { @@ -2033,9 +2033,10 @@ void BuyMenuGUI::CategoryChange(bool focusOnCategoryTabs) for (list::iterator oItr = catalogList[moduleID].begin(); oItr != catalogList[moduleID].end(); ++oItr) { pSObject = dynamic_cast(*oItr); - // Buyable and not brain? - if (pSObject && pSObject->IsBuyable() && !pSObject->IsInGroup("Brains")) - tempList.push_back(pSObject); + // Only add buyable and non-brain items, unless they are explicitly set to be available. + if ((pSObject && pSObject->IsBuyable() && !pSObject->IsInGroup("Brains")) || GetOwnedItemsAmount((pSObject)->GetModuleAndPresetName()) > 0 || m_AlwaysAllowedItems.find((pSObject)->GetModuleAndPresetName()) != m_AlwaysAllowedItems.end()) { + tempList.push_back(pSObject); + } } // Don't add anyhting to the real buy item list if the current module didn't yield any valid items diff --git a/Menus/InventoryMenuGUI.cpp b/Menus/InventoryMenuGUI.cpp index 3a23ebd89..3e6fde630 100644 --- a/Menus/InventoryMenuGUI.cpp +++ b/Menus/InventoryMenuGUI.cpp @@ -781,18 +781,10 @@ namespace RTE { g_GUISound.ItemChangeSound()->Play(m_MenuController->GetPlayer()); m_GUIInformationToggleButton->OnLoseFocus(); } else if (guiControl == m_GUIEquippedItemButton) { - if (m_InventoryActorEquippedItems.empty()) { - if (!buttonHeld) { g_GUISound.UserErrorSound()->Play(m_MenuController->GetPlayer()); } - } else { - HandleItemButtonPressOrHold(m_GUIEquippedItemButton, m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).first, 0, buttonHeld); - } + HandleItemButtonPressOrHold(m_GUIEquippedItemButton, m_InventoryActorEquippedItems.empty() ? nullptr : m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).first, 0, buttonHeld); } else if (guiControl == m_GUIOffhandEquippedItemButton) { - if (m_InventoryActorEquippedItems.empty()) { - if (!buttonHeld) { g_GUISound.UserErrorSound()->Play(m_MenuController->GetPlayer()); } - } else { - HandleItemButtonPressOrHold(m_GUIOffhandEquippedItemButton, m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).second, 1, buttonHeld); - m_GUIOffhandEquippedItemButton->OnMouseLeave(0, 0, 0, 0); - } + HandleItemButtonPressOrHold(m_GUIOffhandEquippedItemButton, m_InventoryActorEquippedItems.empty() ? nullptr : m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).second, 1, buttonHeld); + m_GUIOffhandEquippedItemButton->OnMouseLeave(0, 0, 0, 0); } else if (!buttonHeld && guiControl == m_GUIReloadButton) { ReloadSelectedItem(); } else if (!buttonHeld && guiControl == m_GUIDropButton) { @@ -1185,11 +1177,11 @@ namespace RTE { g_GUISound.UserErrorSound()->Play(m_MenuController->GetPlayer()); } } else { - SwapEquippedItemAndInventoryItem(m_GUISelectedItem->Object, pressedButtonItemIndex); + SwapEquippedItemAndInventoryItem(m_GUISelectedItem->EquippedItemIndex, pressedButtonItemIndex); } } else { if (buttonEquippedItemIndex > -1) { - SwapEquippedItemAndInventoryItem(buttonObject, m_GUISelectedItem->InventoryIndex); + SwapEquippedItemAndInventoryItem(pressedButtonItemIndex, m_GUISelectedItem->InventoryIndex); } else { if (pressedButtonItemIndex >= m_InventoryActor->GetInventorySize()) { m_InventoryActor->AddInventoryItem(m_InventoryActor->RemoveInventoryItemAtIndex(m_GUISelectedItem->InventoryIndex)); @@ -1206,32 +1198,35 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void InventoryMenuGUI::SwapEquippedItemAndInventoryItem(MovableObject *equippedItemToSwapOut, int inventoryItemIndexToSwapIn) { - MovableObject *equippedItem = m_GUIInventoryActorCurrentEquipmentSetIndex < m_InventoryActorEquippedItems.size() ? m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).first : nullptr; - MovableObject *offhandEquippedItem = m_GUIInventoryActorCurrentEquipmentSetIndex < m_InventoryActorEquippedItems.size() ? m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).second : nullptr; - bool addOffhandItemToInventory = false; - - if (offhandEquippedItem && inventoryItemIndexToSwapIn >= 0 && inventoryItemIndexToSwapIn < m_InventoryActor->GetInventorySize()) { - const HeldDevice *inventoryItemToSwapIn = dynamic_cast(m_InventoryActor->GetInventory()->at(inventoryItemIndexToSwapIn)); - if (!inventoryItemToSwapIn->IsOneHanded() && !inventoryItemToSwapIn->HasObjectInGroup("Shields")) { - if (equippedItem) { - equippedItemToSwapOut = equippedItem; - addOffhandItemToInventory = true; - } else if (m_InventoryActorIsHuman && !dynamic_cast(m_InventoryActor)->GetFGArm()) { - equippedItemToSwapOut = nullptr; - } - } + void InventoryMenuGUI::SwapEquippedItemAndInventoryItem(int equippedItemIndex, int inventoryItemIndex) { + if (!m_InventoryActorIsHuman) { + g_GUISound.UserErrorSound()->Play(m_MenuController->GetPlayer()); + return; } - if (equippedItemToSwapOut) { - Arm *equippedItemArm = dynamic_cast(equippedItemToSwapOut->GetParent()); - equippedItemArm->SetHeldMO(m_InventoryActor->SetInventoryItemAtIndex(equippedItemArm->ReleaseHeldMO(), inventoryItemIndexToSwapIn)); - equippedItemArm->SetHandPos(m_InventoryActor->GetPos() + m_InventoryActor->GetHolsterOffset().GetXFlipped(m_InventoryActor->IsHFlipped())); - if (addOffhandItemToInventory) { m_InventoryActor->AddInventoryItem(dynamic_cast(offhandEquippedItem->GetParent())->ReleaseHeldMO()); } - m_InventoryActor->GetDeviceSwitchSound()->Play(m_MenuController->GetPlayer()); - } else { + AHuman *inventoryActorAsAHuman = dynamic_cast(m_InventoryActor); + MovableObject *equippedItem = m_GUIInventoryActorCurrentEquipmentSetIndex < m_InventoryActorEquippedItems.size() && !m_InventoryActorEquippedItems.empty() ? m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).first : nullptr; + MovableObject *offhandEquippedItem = m_GUIInventoryActorCurrentEquipmentSetIndex < m_InventoryActorEquippedItems.size() && !m_InventoryActorEquippedItems.empty() ? m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).second : nullptr; + + const HeldDevice *inventoryItemToSwapIn = inventoryItemIndex < m_InventoryActor->GetInventorySize() ? dynamic_cast(m_InventoryActor->GetInventory()->at(inventoryItemIndex)) : nullptr; + bool inventoryItemCanGoInOffhand = !inventoryItemToSwapIn || inventoryItemToSwapIn->IsOneHanded() || inventoryItemToSwapIn->HasObjectInGroup("Shields"); + + equippedItemIndex = !inventoryItemCanGoInOffhand || !inventoryActorAsAHuman->GetBGArm() ? 0 : equippedItemIndex; + MovableObject *equippedItemToSwapOut = equippedItemIndex == 0 ? equippedItem : offhandEquippedItem; + + if (equippedItemIndex == 0 && !inventoryActorAsAHuman->GetFGArm()) { g_GUISound.UserErrorSound()->Play(m_MenuController->GetPlayer()); + return; } + + Arm *equippedItemArm = equippedItemIndex == 0 ? inventoryActorAsAHuman->GetFGArm() : inventoryActorAsAHuman->GetBGArm(); + equippedItemArm->SetHeldMO(m_InventoryActor->SetInventoryItemAtIndex(equippedItemArm->ReleaseHeldMO(), inventoryItemIndex)); + equippedItemArm->SetHandPos(m_InventoryActor->GetPos() + m_InventoryActor->GetHolsterOffset().GetXFlipped(m_InventoryActor->IsHFlipped())); + if (!inventoryItemCanGoInOffhand && offhandEquippedItem) { + m_InventoryActor->AddInventoryItem(inventoryActorAsAHuman->GetBGArm()->ReleaseHeldMO()); + inventoryActorAsAHuman->GetBGArm()->SetHandPos(m_InventoryActor->GetPos() + m_InventoryActor->GetHolsterOffset().GetXFlipped(m_InventoryActor->IsHFlipped())); + } + m_InventoryActor->GetDeviceSwitchSound()->Play(m_MenuController->GetPlayer()); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1246,7 +1241,7 @@ namespace RTE { } else if (HDFirearm *selectedItemObjectAsFirearm = dynamic_cast(m_GUISelectedItem->Object)) { if (m_GUISelectedItem->InventoryIndex > -1) { if (!m_InventoryActorEquippedItems.empty() && m_GUIInventoryActorCurrentEquipmentSetIndex < m_InventoryActorEquippedItems.size()) { - SwapEquippedItemAndInventoryItem(m_InventoryActorEquippedItems.at(m_GUIInventoryActorCurrentEquipmentSetIndex).first, m_GUISelectedItem->InventoryIndex); + SwapEquippedItemAndInventoryItem(0, m_GUISelectedItem->InventoryIndex); } else if (inventoryActorAsAHuman->GetFGArm() || inventoryActorAsAHuman->GetBGArm()) { Arm *armToUse = inventoryActorAsAHuman->GetFGArm() ? inventoryActorAsAHuman->GetFGArm() : inventoryActorAsAHuman->GetBGArm(); armToUse->SetHeldMO(m_InventoryActor->RemoveInventoryItemAtIndex(m_GUISelectedItem->InventoryIndex)); diff --git a/Menus/InventoryMenuGUI.h b/Menus/InventoryMenuGUI.h index 03e533a4b..cc2e3d20c 100644 --- a/Menus/InventoryMenuGUI.h +++ b/Menus/InventoryMenuGUI.h @@ -437,11 +437,12 @@ namespace RTE { //void SwapEquippedItemSet() {} /// - /// Swaps an equipped item with one in the inventory Actor's inventory. + /// Swaps the equipped item at the given equipped item index with one in the inventory Actor's inventory at the given inventory item index. + /// Accounts for either index pointing to empty buttons and any other potential complications. /// - /// A pointer to the equipped item being swapped out. + /// The index of the equipped item being swapped out. /// The index in the inventory of the item being swapped in. - void SwapEquippedItemAndInventoryItem(MovableObject *equippedItemToSwapOut, int inventoryItemIndexToSwapIn); + void SwapEquippedItemAndInventoryItem(int equippedItemIndex, int inventoryItemIndex); /// /// Reloads the selected item if it is equipped, or swaps to it and then reloads it if it isn't. diff --git a/Menus/SettingsGameplayGUI.cpp b/Menus/SettingsGameplayGUI.cpp index 8be7900d1..72162085f 100644 --- a/Menus/SettingsGameplayGUI.cpp +++ b/Menus/SettingsGameplayGUI.cpp @@ -6,6 +6,8 @@ #include "GUICollectionBox.h" #include "GUICheckbox.h" #include "GUITextBox.h" +#include "GUISlider.h" +#include "GUILabel.h" namespace RTE { @@ -44,6 +46,18 @@ namespace RTE { m_CrabBombThresholdTextbox->SetText(std::to_string(g_SettingsMan.GetCrabBombThreshold())); m_CrabBombThresholdTextbox->SetNumericOnly(true); m_CrabBombThresholdTextbox->SetMaxTextLength(3); + + m_UnheldItemsHUDDisplayRangeSlider = dynamic_cast(m_GUIControlManager->GetControl("SliderUnheldItemsHUDRange")); + int unheldItemsHUDDisplayRangeValue = static_cast(g_SettingsMan.GetUnheldItemsHUDDisplayRange()); + if (unheldItemsHUDDisplayRangeValue <= -1) { + m_UnheldItemsHUDDisplayRangeSlider->SetValue(m_UnheldItemsHUDDisplayRangeSlider->GetMinimum()); + } else if (unheldItemsHUDDisplayRangeValue == 0) { + m_UnheldItemsHUDDisplayRangeSlider->SetValue(m_UnheldItemsHUDDisplayRangeSlider->GetMaximum()); + } else { + m_UnheldItemsHUDDisplayRangeSlider->SetValue(static_cast(g_SettingsMan.GetUnheldItemsHUDDisplayRange() / c_PPM)); + } + m_UnheldItemsHUDDisplayRangeLabel = dynamic_cast(m_GUIControlManager->GetControl("LabelUnheldItemsHUDRangeValue")); + UpdateUnheldItemsHUDDisplayRange(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -75,6 +89,22 @@ namespace RTE { m_GameplaySettingsBox->SetFocus(); } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void SettingsGameplayGUI::UpdateUnheldItemsHUDDisplayRange() { + int newValue = m_UnheldItemsHUDDisplayRangeSlider->GetValue(); + if (newValue < 3) { + m_UnheldItemsHUDDisplayRangeLabel->SetText("Hidden"); + g_SettingsMan.SetUnheldItemsHUDDisplayRange(-1.0F); + } else if (newValue > 50) { + m_UnheldItemsHUDDisplayRangeLabel->SetText("Unlimited"); + g_SettingsMan.SetUnheldItemsHUDDisplayRange(0); + } else { + m_UnheldItemsHUDDisplayRangeLabel->SetText("Up to " + std::to_string(newValue) + " meters"); + g_SettingsMan.SetUnheldItemsHUDDisplayRange(static_cast(newValue) * c_PPM); + } + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SettingsGameplayGUI::HandleInputEvents(GUIEvent &guiEvent) { @@ -97,6 +127,8 @@ namespace RTE { UpdateMaxUnheldItemsTextbox(); } else if (guiEvent.GetControl() == m_CrabBombThresholdTextbox && guiEvent.GetMsg() == GUITextBox::Enter) { UpdateCrabBombThresholdTextbox(); + } else if (guiEvent.GetControl() == m_UnheldItemsHUDDisplayRangeSlider) { + UpdateUnheldItemsHUDDisplayRange(); // Update both textboxes when clicking the main CollectionBox, otherwise clicking off focused textboxes does not remove their focus or update the setting values and they will still capture keyboard input. } else if (guiEvent.GetControl() == m_GameplaySettingsBox && guiEvent.GetMsg() == GUICollectionBox::Clicked && !m_GameplaySettingsBox->HasFocus()) { UpdateMaxUnheldItemsTextbox(); diff --git a/Menus/SettingsGameplayGUI.h b/Menus/SettingsGameplayGUI.h index a030766dc..e47de823b 100644 --- a/Menus/SettingsGameplayGUI.h +++ b/Menus/SettingsGameplayGUI.h @@ -7,6 +7,8 @@ namespace RTE { class GUICollectionBox; class GUICheckbox; class GUITextBox; + class GUISlider; + class GUILabel; class GUIEvent; /// @@ -55,6 +57,8 @@ namespace RTE { GUICheckbox *m_EnableSmartBuyMenuNavigationCheckbox; GUITextBox *m_MaxUnheldItemsTextbox; GUITextBox *m_CrabBombThresholdTextbox; + GUISlider *m_UnheldItemsHUDDisplayRangeSlider; + GUILabel *m_UnheldItemsHUDDisplayRangeLabel; #pragma region Gameplay Settings Handling /// @@ -66,6 +70,11 @@ namespace RTE { /// Updates the CrabBombThreshold textbox to override any invalid input, applies the setting value and removes its focus. /// void UpdateCrabBombThresholdTextbox(); + + /// + /// Updates the UnheldItemsHUDDisplayRange setting and label according to the slider value. + /// + void UpdateUnheldItemsHUDDisplayRange(); #pragma endregion // Disallow the use of some implicit methods. diff --git a/System/Controller.cpp b/System/Controller.cpp index 2ac77148c..c4464d8de 100644 --- a/System/Controller.cpp +++ b/System/Controller.cpp @@ -331,7 +331,7 @@ namespace RTE { if (m_AnalogAim.GetMagnitude() > 0.1F && !m_ControlStates.at(PIE_MENU_ACTIVE)) { m_ControlStates.at(AIM_SHARP) = true; } // Disable sharp aim while moving - this also helps with keyboard vs mouse fighting when moving and aiming in opposite directions - if (m_ControlStates.at(PRESS_RIGHT) || m_ControlStates.at(PRESS_LEFT) || m_ControlStates.at(BODY_JUMPSTART) || (m_ControlStates.at(PIE_MENU_ACTIVE) && !m_ControlStates.at(SECONDARY_ACTION))) { + if (m_ControlStates.at(BODY_JUMP) || (m_ControlStates.at(PIE_MENU_ACTIVE) && !m_ControlStates.at(SECONDARY_ACTION))) { if (IsMouseControlled()) { g_UInputMan.SetMouseValueMagnitude(0.05F); } m_ControlStates.at(AIM_SHARP) = false; } diff --git a/System/Entity.cpp b/System/Entity.cpp index 0575ccf19..2726b65b4 100644 --- a/System/Entity.cpp +++ b/System/Entity.cpp @@ -158,6 +158,17 @@ namespace RTE { return dataModule->GetFileName() + "/" + GetPresetName(); } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::string Entity::GetModuleName() const { + if (m_DefinedInModule > 0) { + if (const DataModule *dataModule = g_PresetMan.GetDataModule(m_DefinedInModule)) { + return dataModule->GetFileName(); + } + } + return ""; + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Entity::MigrateToModule(int whichModule) { diff --git a/System/Entity.h b/System/Entity.h index 423661906..df08001e6 100644 --- a/System/Entity.h +++ b/System/Entity.h @@ -337,6 +337,12 @@ namespace RTE { /// A string with the module and instance name of this Entity. std::string GetModuleAndPresetName() const; + /// + /// Gets the name of this Entity's Data Module it was defined in. + /// + /// A string with the module of this Entity. + std::string GetModuleName() const; + /// /// Indicates whether this Entity was explicitly given a new instance name upon creation, or if it was just copied/inherited implicitly. /// diff --git a/System/Matrix.h b/System/Matrix.h index 294ede7ab..35e7d55bd 100644 --- a/System/Matrix.h +++ b/System/Matrix.h @@ -118,7 +118,7 @@ namespace RTE { /// /// Returns the angle difference between what this is currently representing, to another angle in radians. - /// It will wrap and normalize and give the shortest absolute distance between this and the passed in. + /// It will wrap and normalize and give the smallest angle difference between this and the passed in. /// /// A float with the angle to get the difference to from this, in radians. /// A float with the difference angle between this and the passed-in angle. @@ -126,7 +126,7 @@ namespace RTE { /// /// Returns the angle difference between what this is currently representing, to another angle in degrees. - /// It will wrap and normalize and give the shortest absolute distance between this and the passed in. + /// It will wrap and normalize and give the smallest angle difference between this and the passed in. /// /// A float with the angle to get the difference to from this, in degrees. /// A float with the difference angle between this and the passed-in angle. diff --git a/System/PieSlice.cpp b/System/PieSlice.cpp index 3dcdab066..3bf106605 100644 --- a/System/PieSlice.cpp +++ b/System/PieSlice.cpp @@ -55,7 +55,7 @@ namespace RTE { m_Icon = *dynamic_cast(g_PresetMan.GetEntityPreset("Icon", "Trade Star")); break; case PieSliceIndex::PSI_FULLINVENTORY: - m_Icon = *dynamic_cast(g_PresetMan.GetEntityPreset("Icon", "Unknown")); + m_Icon = *dynamic_cast(g_PresetMan.GetEntityPreset("Icon", "Inventory")); break; case PieSliceIndex::PSI_STATS: m_Icon = *dynamic_cast(g_PresetMan.GetEntityPreset("Icon", "Stats")); diff --git a/System/Vector.cpp b/System/Vector.cpp index 304a70cbc..f1fa68a1c 100644 --- a/System/Vector.cpp +++ b/System/Vector.cpp @@ -72,7 +72,7 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - Vector Vector::GetRadRotated(const float angle) { + Vector Vector::GetRadRotatedCopy(const float angle) { Vector returnVector = *this; const float adjustedAngle = -angle; returnVector.m_X = m_X * std::cos(adjustedAngle) - m_Y * std::sin(adjustedAngle); diff --git a/System/Vector.h b/System/Vector.h index d8f779b5b..adc0bd5de 100644 --- a/System/Vector.h +++ b/System/Vector.h @@ -199,32 +199,32 @@ namespace RTE { float GetAbsDegAngle() const { return GetAbsRadAngle() / c_PI * 180.0F; } /// - /// Returns a Vector rotated relatively by an angle in radians. + /// Returns a copy of this Vector, rotated relatively by an angle in radians. /// /// The angle in radians to rotate by. Positive angles rotate counter-clockwise, and negative angles clockwise. - /// a Vector rotated relatively to this Vector . - Vector GetRadRotated(const float angle); + /// A rotated copy of this Vector. + Vector GetRadRotatedCopy(const float angle); /// /// Rotate this Vector relatively by an angle in radians. /// /// The angle in radians to rotate by. Positive angles rotate counter-clockwise, and negative angles clockwise. /// Vector reference to this after the operation. - Vector & RadRotate(const float angle) { *this = GetRadRotated(angle); return *this; } + Vector & RadRotate(const float angle) { *this = GetRadRotatedCopy(angle); return *this; } /// - /// Returns a Vector rotated relatively by an angle in degrees. + /// Returns a copy of this Vector, rotated relatively by an angle in degrees. /// /// The angle in degrees to rotate by. Positive angles rotate counter-clockwise, and negative angles clockwise. - /// a Vector rotated relatively to this Vector . - Vector GetDegRotated(const float angle) { return GetRadRotated(angle * c_PI / 180.0F); }; + /// A rotated copy of this Vector. + Vector GetDegRotatedCopy(const float angle) { return GetRadRotatedCopy(angle * c_PI / 180.0F); }; /// /// Rotate this Vector relatively by an angle in degrees. /// /// The angle in degrees to rotate by. Positive angles rotate counter-clockwise, and negative angles clockwise. /// Vector reference to this after the operation. - Vector & DegRotate(const float angle) { *this = GetDegRotated(angle); return *this; } + Vector & DegRotate(const float angle) { *this = GetDegRotatedCopy(angle); return *this; } /// /// Set this Vector to an absolute rotation based on the absolute rotation of another Vector.