diff --git a/Activities/GATutorial.cpp b/Activities/GATutorial.cpp
index 0ebd51520..b414e42ee 100644
--- a/Activities/GATutorial.cpp
+++ b/Activities/GATutorial.cpp
@@ -87,6 +87,7 @@ void GATutorial::Clear()
for (int stage = 0; stage < FIGHTSTAGECOUNT; ++stage)
m_FightTriggers[stage].Reset();
+ m_EnemyCount = 0;
m_CurrentFightStage = NOFIGHT;
m_pCPUBrain = 0;
}
@@ -141,6 +142,7 @@ int GATutorial::Create(const GATutorial &reference)
for (int stage = 0; stage < FIGHTSTAGECOUNT; ++stage)
m_FightTriggers[stage] = reference.m_FightTriggers[stage];
+ m_EnemyCount = reference.m_EnemyCount;
m_CurrentFightStage = reference.m_CurrentFightStage;
// DOn't, owned and need to make deep copy in that case
// m_pCPUBrain;
@@ -293,6 +295,7 @@ int GATutorial::Start()
pOtherBrain = 0;
}
}
+ m_EnemyCount = g_MovableMan.GetTeamRoster(m_CPUTeam)->size();
}
// Give the player some scratch
else
@@ -767,23 +770,19 @@ void GATutorial::Update()
////////////////////////
// FIGHT LOGIC
- if (m_ControlledActor[m_TutorialPlayer])
- {
- // Triggered defending stage
- if (m_CurrentFightStage == NOFIGHT && m_FightTriggers[DEFENDING].IsWithinBox(m_ControlledActor[m_TutorialPlayer]->GetPos()))
- {
- // Take over control of screen messages
- m_MessageTimer[m_TutorialPlayer].Reset();
- // Display the text of the current step
- g_FrameMan.ClearScreenText(ScreenOfPlayer(m_TutorialPlayer));
- g_FrameMan.SetScreenText("DEFEND YOUR BRAIN AGAINST THE INCOMING FORCES!", ScreenOfPlayer(m_TutorialPlayer), 500, 8000, true);
- // This will make all the enemy team AI's go into brain hunt mode
- GameActivity::InitAIs();
- DisableAIs(false, Teams::TeamTwo);
-
- // Advance the stage
- m_CurrentFightStage = DEFENDING;
- }
+ // Triggered defending stage
+ if (m_CurrentFightStage == NOFIGHT && ((m_ControlledActor[m_TutorialPlayer] && m_FightTriggers[DEFENDING].IsWithinBox(m_ControlledActor[m_TutorialPlayer]->GetPos())) || g_MovableMan.GetTeamRoster(m_CPUTeam)->size() < m_EnemyCount)) {
+ // Take over control of screen messages
+ m_MessageTimer[m_TutorialPlayer].Reset();
+ // Display the text of the current step
+ g_FrameMan.ClearScreenText(ScreenOfPlayer(m_TutorialPlayer));
+ g_FrameMan.SetScreenText("DEFEND YOUR BRAIN AGAINST THE INCOMING FORCES!", ScreenOfPlayer(m_TutorialPlayer), 500, 8000, true);
+ // This will make all the enemy team AI's go into brain hunt mode
+ GameActivity::InitAIs();
+ DisableAIs(false, Teams::TeamTwo);
+
+ // Advance the stage
+ m_CurrentFightStage = DEFENDING;
}
///////////////////////////////////////////
@@ -1043,7 +1042,7 @@ void GATutorial::SetupAreas()
m_TutAreaSteps[ROOFTOP].push_back(TutStep("If you dig up gold, it is added to your team's funds", 4000, "Missions.rte/Objects/Tutorial/Funds.png", 2, 250));
m_TutAreaSteps[ROOFTOP].push_back(TutStep("Funds can be spent in the Buy Menu", 4000, "Missions.rte/Objects/Tutorial/Funds.png", 1, 333));
m_TutAreaSteps[ROOFTOP].push_back(TutStep("Which is opened through the Command Menu", 4000, "Missions.rte/Objects/Tutorial/MenuBuyMenu.png", 1, 500));
- m_TutAreaSteps[ROOFTOP].push_back(TutStep("Hold [" + PieName + "] and point up-left to 'Buy Menu'", 6000, "Missions.rte/Objects/Tutorial/MenuBuyMenu.png", 2, 500));
+ m_TutAreaSteps[ROOFTOP].push_back(TutStep("Hold [" + PieName + "] and point up and left to 'Buy Menu'", 6000, "Missions.rte/Objects/Tutorial/MenuBuyMenu.png", 2, 500));
m_TutAreaSteps[ROOFTOP].push_back(TutStep("The Buy Menu works like a shopping cart", 6000, "Missions.rte/Objects/Tutorial/BuyMenuCargo.png", 1, 500));
m_TutAreaSteps[ROOFTOP].push_back(TutStep("Add to the Cargo list the items you want delivered", 6000, "Missions.rte/Objects/Tutorial/BuyMenuCargo.png", 2, 500));
m_TutAreaSteps[ROOFTOP].push_back(TutStep("Then use the BUY button, or click outside the menu", 4000, "Missions.rte/Objects/Tutorial/BuyMenuBuy.png", 2, 500));
@@ -1062,12 +1061,12 @@ void GATutorial::SetupAreas()
m_TextOffsets[ROOFEAST].SetXY(m_apCommonScreens[0]->w / 2, -16);
// Set up the steps
m_TutAreaSteps[ROOFEAST].clear();
- m_TutAreaSteps[ROOFEAST].push_back(TutStep("Hold [" + PieName + "] and point up-right to 'Form Squad'", 4000, "Missions.rte/Objects/Tutorial/MenuTeam.png", 2, 500));
+ m_TutAreaSteps[ROOFEAST].push_back(TutStep("Hold [" + PieName + "] and point down and right to 'Form Squad'", 4000, "Missions.rte/Objects/Tutorial/MenuTeam.png", 2, 500));
m_TutAreaSteps[ROOFEAST].push_back(TutStep("Adjust selection circle to select nearby bodies", 4000, "Missions.rte/Objects/Tutorial/TeamSelect.png", 4, 500));
m_TutAreaSteps[ROOFEAST].push_back(TutStep("All selected units will follow you, and engage on their own", 4000, "Missions.rte/Objects/Tutorial/TeamFollow.png", 2, 500));
- m_TutAreaSteps[ROOFEAST].push_back(TutStep("Units with weapons similar to the leader's will fire in unison with him.", 4000, "Missions.rte/Objects/Tutorial/TeamFollow.png", 2, 500));
- m_TutAreaSteps[ROOFEAST].push_back(TutStep("Hold [" + PieName + "] and point up-right again to disband squad", 4000, "Missions.rte/Objects/Tutorial/MenuTeam.png", 2, 500));
- m_TutAreaSteps[ROOFEAST].push_back(TutStep("Next, you can go to the east for a TRIAL BATTLE!", 8000, "Missions.rte/Objects/Tutorial/ArrowRight.png", 2));
+ m_TutAreaSteps[ROOFEAST].push_back(TutStep("Units with similar weapons will fire in unison with the leader", 4000, "Missions.rte/Objects/Tutorial/TeamFollow.png", 2, 500));
+ m_TutAreaSteps[ROOFEAST].push_back(TutStep("Hold [" + PieName + "] and point down and right again to disband squad", 4000, "Missions.rte/Objects/Tutorial/MenuTeam.png", 2, 500));
+ m_TutAreaSteps[ROOFEAST].push_back(TutStep("Next, you can head east for a TRIAL BATTLE!", 8000, "Missions.rte/Objects/Tutorial/ArrowRight.png", 2));
m_AreaTimer.Reset();
m_StepTimer.Reset();
diff --git a/Activities/GATutorial.h b/Activities/GATutorial.h
index 5761b9357..a24197943 100644
--- a/Activities/GATutorial.h
+++ b/Activities/GATutorial.h
@@ -313,6 +313,7 @@ ClassInfoGetters;
TutorialRoom m_CurrentRoom;
// Trigger box for the subsequent fight
Box m_FightTriggers[FIGHTSTAGECOUNT];
+ int m_EnemyCount; //!< The amount of enemy actors at the start of the activity.
// The current fight stage
FightStage m_CurrentFightStage;
// The CPU opponent brain; not owned!
diff --git a/Activities/GameActivity.cpp b/Activities/GameActivity.cpp
index e4c8d8ca9..c9f6f3131 100644
--- a/Activities/GameActivity.cpp
+++ b/Activities/GameActivity.cpp
@@ -63,7 +63,6 @@ void GameActivity::Clear()
{
m_ObservationTarget[player].Reset();
m_DeathViewTarget[player].Reset();
- m_DeathTimer[player].Reset();
m_ActorSelectTimer[player].Reset();
m_ActorCursor[player].Reset();
m_pLastMarkedActor[player] = 0;
@@ -166,7 +165,6 @@ int GameActivity::Create(const GameActivity &reference)
{
m_ObservationTarget[player] = reference.m_ObservationTarget[player];
m_DeathViewTarget[player] = reference.m_DeathViewTarget[player];
-// m_DeathTimer[player] = reference.m_DeathTimer[player];
m_ActorCursor[player] = reference.m_ActorCursor[player];
m_pLastMarkedActor[player] = reference.m_pLastMarkedActor[player];
m_LandingZone[player] = reference.m_LandingZone[player];
@@ -1365,14 +1363,13 @@ void GameActivity::Update()
if (g_MovableMan.IsActor(m_ControlledActor[player]))
{
m_DeathViewTarget[player] = m_ControlledActor[player]->GetPos();
+ m_DeathTimer[player].Reset();
}
// Add delay after death before switching so the death comedy can be witnessed
// Died, so enter death watch mode
else
{
- m_ControlledActor[player] = 0;
- m_ViewState[player] = ViewState::DeathWatch;
- m_DeathTimer[player].Reset();
+ LoseControlOfActor(player);
}
}
// Ok, done watching death comedy, now automatically switch
@@ -1392,9 +1389,7 @@ void GameActivity::Update()
// Any other viewing mode and the actor died... go to deathwatch
else if (m_ControlledActor[player] && !g_MovableMan.IsActor(m_ControlledActor[player]))
{
- m_ControlledActor[player] = 0;
- m_ViewState[player] = ViewState::DeathWatch;
- m_DeathTimer[player].Reset();
+ LoseControlOfActor(player);
}
}
// Player brain is now gone! Remove any control he may have had
diff --git a/Activities/GameActivity.h b/Activities/GameActivity.h
index 1a5561b8e..6dcb0a8a4 100644
--- a/Activities/GameActivity.h
+++ b/Activities/GameActivity.h
@@ -1018,8 +1018,6 @@ class GameActivity : public Activity {
Vector m_ObservationTarget[Players::MaxPlayerCount];
// The player death sceneman scroll targets, for when a player-controlled actor dies and the view should go to his last position
Vector m_DeathViewTarget[Players::MaxPlayerCount];
- // Timers for measuring death cam delays.
- Timer m_DeathTimer[Players::MaxPlayerCount];
// Times the delay between regular actor swtich, and going into manual siwtch mode
Timer m_ActorSelectTimer[Players::MaxPlayerCount];
// The cursor for selecting new Actors
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1fa26204b..7ea180c26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -101,6 +101,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- New `Settings.ini` property `DisableFactionBuyMenuThemes = 0/1` which will cause custom faction theme definitions in all modules to be ignored and the default theme to be used instead.
+- New `Settings.ini` and `SceneMan` Lua (R/W) property `ScrapCompactingHeight` which determines the maximum height of a column of scrap terrain to collapse when the bottom pixel is knocked loose. 0 means no columns of terrain are ever collapsed, much like in old builds of CC.
+
- New `DataModule` INI and Lua (R/O) property `IsMerchant` which determines whether a module is an independent merchant. Defaults to false (0). ([Issue #401](https://github.com/cortex-command-community/Cortex-Command-Community-Project-Source/issues/401))
A module defined as a merchant will stop being playable (in Conquest, etc.) but will have its buyable content available for purchase/placement when playing as any other faction (like how base content is).
Only has a noticeable effect when the "Allow purchases from other factions" (`Settings.ini` `ShowForeignItems`) gameplay setting is disabled.
@@ -160,9 +162,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
`PieSlice`s with no preset name will always be added by this.
**`RemovePieSlice(pieSliceToRemove)`** - Removes the given `PieSlice` from the `PieMenu`, and returns it to Lua so you can add it to another `PieMenu` if you want.
- **`RemovePieSlicesByPresetName()`** - Removes any `PieSlice`s with the given preset name from the `PieMenu`. Note that, unlike `RemovePieSlice`, the `PieSlice` is not returned, since multiple `PieSlices` can be removed this way. Instead, this returns true if any `PieSlice`s were removed.
- **`RemovePieSlicesByType()`** - Removes any `PieSlice`s with the given `PieSlice` `Type` from the `PieMenu`. Note that, unlike `RemovePieSlice`, the `PieSlice` is not returned, since multiple `PieSlices` can be removed this way. Instead, this returns true if any `PieSlice`s were removed.
- **`RemovePieSlicesByOriginalSource()`** - Removes any `PieSlice`s with the original source from the `PieMenu`. Note that, unlike `RemovePieSlice`, the `PieSlice` is not returned, since multiple `PieSlices` can be removed this way. Instead, this returns true if any `PieSlice`s were removed.
+ **`RemovePieSlicesByPresetName(presetNameToRemoveBy)`** - Removes any `PieSlice`s with the given preset name from the `PieMenu`. Note that, unlike `RemovePieSlice`, the `PieSlice` is not returned, since multiple `PieSlices` can be removed this way. Instead, this returns true if any `PieSlice`s were removed.
+ **`RemovePieSlicesByType(pieSliceTypeToRemoveBy)`** - Removes any `PieSlice`s with the given `PieSlice` `Type` from the `PieMenu`. Note that, unlike `RemovePieSlice`, the `PieSlice` is not returned, since multiple `PieSlices` can be removed this way. Instead, this returns true if any `PieSlice`s were removed.
+ **`RemovePieSlicesByOriginalSource(originalSource)`** - Removes any `PieSlice`s with the original source from the `PieMenu`. Note that, unlike `RemovePieSlice`, the `PieSlice` is not returned, since multiple `PieSlices` can be removed this way. Instead, this returns true if any `PieSlice`s were removed.
+
+ **`ReplacePieSlice`(pieSliceToReplace, replacementPieSlice)`** - Replaces the specified `PieSlice` to replace, if it exists in the `PieMenu`, with the replacement `PieSlice` and returns the replaced `PieSlice` for use (e.g. for adding to a different `PieMenu`). The replacement `PieSlice` takes the replaced `PieSlice`'s original source, direction, middle slice eligibility, angles and slot count, so it seamlessly replaces it.
- `PieSlice`s have been modified to support `PieMenu`s being defined in INI. They have the following properties:
@@ -507,11 +511,15 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter
- Added Lua convenience function `RoundToNearestMultiple(num, multiple)` which returns a number rounded to the nearest specified multiple.
Note that this operates on integers, so fractional parts will be truncated towards zero by type conversion.
-- Added `AHuman` INI and Lua (R/W) property `DeviceArmSwayRate`, that defines how much `HeldDevices` will sway when walking. 0 is no sway, 1 directly couples sway with leg movement, >1 may be funny. Defaults to 0.75.
+- Added `Actor` INI and Lua property (R/W) `PlayerControllable`, that determines whether the `Actor` can be swapped to by human players. Note that Lua can probably break this, by forcing the `Controller`s of `Actor`s that aren't `PlayerControllable` to the `CIM_PLAYER` input mode.
-- Added `AHuman` INI and Lua (R/W) property `ReloadOffset`, that defines where `Hands` should move to when reloading, if they're not holding a supported `HeldDevice`. A non-zero value is reqiured for `OneHandedReloadAngle` to be used.
+- Added alternative `MovableMan:GetClosestTeamActor(team, player, scenePoint, maxRadius, getDistance, onlyPlayerControllableActors, actorToExclude)` that acts like the existing version, but allows you to specify whether or not to only get `Actors` that are `PlayerControllable`.
-- Added `AHuman` INI and Lua (R/W) property `OneHandedReloadAngleOffset`, that defines the angle in radians that should be added to `HeldDevice`s when reloading with only one hand (i.e. the `HeldDevice` is one-handed, or the `AHuman` no longer has their bg`Arm`), in addition to the `Arm`'s rotation. Note that this will only be used if the `AHuman` has a non-zero `ReloadOffset`.
+- New `Attachable` INI and Lua property `IgnoresParticlesWhileAttached`, which determines whether the `Attachable` should ignore collisions (and penetrations) with single-atom particles. Useful for preventing `HeldDevice`s from being destroyed by bullets while equipped.
+
+- Added `AHuman` INI and Lua (R/W) property `DeviceArmSwayRate`, that defines how much `HeldDevices` will sway when walking. 0 is no sway, 1 directly couples sway with leg movement, >1 may be funny. Defaults to 0.75.
+
+- Added `AHuman` INI and Lua (R/W) property `ReloadOffset`, that defines where `Hands` should move to when reloading, if they're not holding a supported `HeldDevice`.
- Added `AHuman` Lua function `FirearmsAreReloading(onlyIfAllFirearmsAreReloading)` which returns whether or not this `AHuman`'s `HeldDevices` are currently reloading. If the parameter is set to true and the `AHuman` is holding multiple `HeldDevices`, this will only return true if all of them are reloading.
@@ -529,8 +537,30 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter
- Added `Timer` Lua function `GetSimTimeLimitS()` that gets the sim time limit of the `Timer` in seconds.
+- New `SceneMan` Lua function `DislodgePixel(posX, posY)` that removes a pixel of terrain at the passed in coordinates and turns it into a `MOPixel`. Returns the dislodged pixel as a `MovableObject`, or `nil` if no pixel terrain was found at the passed in position.
+
+- New `HDFirearm` Lua property `CanFire` which accurately indicates whether the firearm is ready to fire off another round.
+
+- New `HDFirearm` Lua property `MSPerRound` which returns the minimum amount of MS in between shots, relative to `RateOfFire`.
+
+- New `HDFirearm` INI and Lua (R) properties `ReloadAngle` and `OneHandedReloadAngle` which determine the width of the reload animation angle, the latter being used when the device is held with no supporting arm available. 0 means the animation is disabled. In radians.
+
+- New `HDFirearm` Lua property `MSPerRound` which returns the minimum amount of MS in between shots, relative to`RateOfFire`.
+
+- New `Attachable` INI and Lua (R/W) property `GibWhenRemovedFromParent` which gibs the `Attachable` in question when it's removed from its parent. `DeleteWhenRemovedFromParent` will always override this.
+
+- New `Settings.ini` property `AutomaticGoldDeposit` which determines whether gold gathered by actors is automatically added into the team's funds. False means that gold needs to be manually transported into orbit via craft, the old school way. Enabled by default.
+ A noteworthy change in comparison to previous logic is that gold is no longer converted into objects, and `GoldCarried` is now automatically transferred into craft upon entering them. This effectively allows the same actor to resume prospecting without having to return to orbit with the craft.
+ Regardless of the setting, this behavior is always disabled for AI-only teams for the time being, until the actor AI is accommodated accordingly.
+
+- New `Actor` Lua function `AddGold(goldOz)` which adds the passed-in amount of gold either to the team's funds, or the `GoldCarried` of the actor in question, depending on whether automatic gold depositing is enabled. This effectively simulates the actor collecting gold.
+
+- New `Actor` Lua function `DropAllGold()` which converts all of the actor's `GoldCarried` into particles and spews them on the ground.
+
- Added `Alt + F2` key combination to reload all cached sprites. This allows you to see changes made to sprites immediately in-game.
+- New `MovableObject` Lua (R) property `DistanceTravelled` which returns the amount of pixels the object has travelled since its creation.
+
- Added `Activity` Lua function `ForceSetTeamAsActive(team)`, which forcefully sets a team as active. Necessary for `Activity`s that don't want to define/show all used teams, but still want `Actor`s of hidden teams to work properly.
- Added `GameActivity` INI property `DefaultGoldMaxDifficulty`, which lets you specify the default gold when the difficulty slider is maxed out.
@@ -711,7 +741,7 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter
```
MaxLength - The max length of the Arm in pixels.
MoveSpeed - How quickly the Arm moves between targets. 0 means no movement, 1 means instant movement.
- HandDefaultIdleOffset - The idle offset this Arm will move to if it has no targets, and nothing else affecting its idle offset (e.g. it's not holding or supporting a HeldDevice). IdleOffset is also allowed for compatibility.
+ HandIdleOffset - The idle offset this Arm's hand will move to if it has no targets, and nothing else affecting its idle offset (e.g. it's not holding or supporting a HeldDevice). IdleOffset is also allowed for compatibility.
HandSprite - The sprite file for this Arm's hand. Hand is also allowed for compatibility.
GripStrength - The Arm's grip strength when holding HeldDevices. Further described below, in the entry where it was added.
ThrowStrength - The Arm's throw strength when throwing ThrownDevices. Further described below, in the entry where it was added.
@@ -720,8 +750,8 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter
They now have the following Lua properties and functions:
**`MaxLength`** (R) - Allows getting the `Arm`'s maximum length.
**`MoveSpeed`** (R/W) - Allows getting and setting the `Arm`'s movement speed. 0 means no movement, 1 means instant movement.
- **`HandDefaultIdleOffset`** (R/W) - Allows getting and setting the `Arm`'s default idle hand offset, i.e. where the hand will go when it has no targets and isn't holding or supporting anything.
- **`HandCurrentPos`** (R/W) - Gets and sets the current position of the hand. Note that this will override any animations and move the hand to the position instantly, so it's generally not recommended.
+ **`HandIdleOffset`** (R/W) - Allows getting and setting the `Arm`'s default idle hand offset, i.e. where the hand will go when it has no targets and isn't holding or supporting anything.
+ **`HandPos`** (R/W) - Gets and sets the current position of the hand. Note that this will override any animations and move the hand to the position instantly, so it's generally not recommended.
**`HasAnyHandTargets`** (R) - Gets whether or not this `Arm` has any hand targets, i.e. any positions the `Arm` is supposed to try to move its hand to.
**`NumberOfHandTargets`** (R) - Gets the number of hand targets this `Arm` has.
**`NextHandTargetDescription`** (R/W) - Gets the description of the next target this `Arm`'s hand is moving to, or an empty string if there are no targets.
@@ -768,6 +798,11 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter
- Failing to create actor `FootGroup`s during loading will now crash with error message instead of straight to desktop.
+- `Gib` property `InheritsVel` now works as a `float` scalar from 0 to 1, defining the portion of velocity inherited from the parent object.
+
+- Jetpack burst fuel consumption is now scaled according to the total burst size instead of always being tenfold.
+ Bursts during downtime from burst spacing are now less punishing, scaling according to half of the burst size.
+
Fixed
@@ -793,6 +828,8 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter
- Fixed `Entity.ModuleName` returning an empty string for `Entities` defined in `Base.rte`. They now return "Base.rte", as they should.
+- Fixed `MOSRotating`s registering all penetrations in one frame even when exceeding gibbing conditions. They now omit all collisions after being flagged for deletion, allowing particles like grenade fragments to penetrate other objects.
+
Removed
@@ -944,7 +981,7 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter
**`ADoor`** - `DoorMoveStartSound`, `DoorMoveSound`, `DoorDirectionChangeSound`, `DoorMoveEndSound`
- Added Lua function `RoundFloatToPrecision`. Utility function to round and format floating point numbers for display in strings.
-`RoundFloatToPrecision(floatValue, digitsPastDecimal, roundingMode) -- Rounding mode 0 for system default, 1 for floored remainder, 2 for ceiled remainder.`
+`RoundFloatToPrecision(floatValue, digitsPastDecimal, roundingMode) -- Rounding mode 0 for system default, 1 for floored remainder, 2 for ceiled remainder, 3 for ceiled remainder with the last decimal place rounded to the nearest 0 or 5 (i.e. if option 2 gave 10.1, this would give 10.5, if it gave 10.369 this would give 10.370, etc.)`
- The Lua console (and all text boxes) now support using `Ctrl` to move the cursor around and select or delete text.
diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp
index 11c9be556..8d1da3565 100644
--- a/Entities/ACrab.cpp
+++ b/Entities/ACrab.cpp
@@ -75,7 +75,6 @@ void ACrab::Clear()
// m_StrideTimer[side].Reset();
}
m_Aiming = false;
- m_GoldInInventoryChunk = 0;
m_DeviceState = SCANNING;
m_SweepState = NOSWEEP;
@@ -239,8 +238,6 @@ int ACrab::Create(const ACrab &reference) {
}
}
- m_GoldInInventoryChunk = reference.m_GoldInInventoryChunk;
-
m_DeviceState = reference.m_DeviceState;
m_SweepState = reference.m_SweepState;
m_DigState = reference.m_DigState;
@@ -2152,7 +2149,7 @@ void ACrab::Update()
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);
+ m_JetTimeLeft = std::max(m_JetTimeLeft - g_TimerMan.GetDeltaTimeMS() * static_cast(std::max(m_pJetpack->GetTotalBurstSize(), 2)) * (m_pJetpack->CanTriggerBurst() ? 1.0F : 0.5F), 0.0F);
} else if (m_Controller.IsState(BODY_JUMP) && m_JetTimeLeft > 0 && m_Status != INACTIVE) {
m_pJetpack->EnableEmission(true);
// Jetpacks are noisy!
@@ -2165,8 +2162,7 @@ void ACrab::Update()
else {
m_pJetpack->EnableEmission(false);
if (m_MoveState == JUMP) { m_MoveState = STAND; }
-
- m_JetTimeLeft = std::min(m_JetTimeLeft + g_TimerMan.GetDeltaTimeMS() * 2.0F * m_JetReplenishRate, m_JetTimeTotal);
+ m_JetTimeLeft = std::min(m_JetTimeLeft + g_TimerMan.GetDeltaTimeMS() * m_JetReplenishRate, m_JetTimeTotal);
}
float maxAngle = c_HalfPI * m_JetAngleRange;
@@ -2393,6 +2389,7 @@ void ACrab::Update()
float RBGLegProg = m_Paths[RIGHTSIDE][BGROUND][WALK].GetRegularProgress();
bool restarted = false;
+ Matrix walkAngle(rotAngle * 0.5F);
// Make sure we are starting a stride if we're basically stopped.
if (isStill) { m_StrideStart[LEFTSIDE] = true; }
@@ -2402,14 +2399,14 @@ void ACrab::Update()
if (m_pLFGLeg && (!m_pLBGLeg || (!(m_Paths[LEFTSIDE][FGROUND][WALK].PathEnded() && LBGLegProg < 0.5F) || m_StrideStart[LEFTSIDE]))) {
m_StrideTimer[LEFTSIDE].Reset();
- m_pLFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pLFGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[LEFTSIDE][FGROUND][WALK], deltaTime, &restarted);
+ m_pLFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pLFGLeg->GetParentOffset()), m_Vel, walkAngle, m_Paths[LEFTSIDE][FGROUND][WALK], deltaTime, &restarted);
}
if (m_pLBGLeg) {
if (!m_pLFGLeg || !(m_Paths[LEFTSIDE][BGROUND][WALK].PathEnded() && LFGLegProg < 0.5F)) {
m_StrideStart[LEFTSIDE] = false;
m_StrideTimer[LEFTSIDE].Reset();
- m_pLBGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pLBGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[LEFTSIDE][BGROUND][WALK], deltaTime);
+ m_pLBGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pLBGLeg->GetParentOffset()), m_Vel, walkAngle, m_Paths[LEFTSIDE][BGROUND][WALK], deltaTime);
} else {
m_pLBGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pLBGLeg->GetParentOffset()), m_pLBGLeg->GetMaxLength(), m_PrevVel, m_AngularVel, m_pLBGLeg->GetMass(), deltaTime);
}
@@ -2425,7 +2422,7 @@ void ACrab::Update()
if (!m_pRBGLeg || !(m_Paths[RIGHTSIDE][FGROUND][WALK].PathEnded() && RBGLegProg < 0.5F)) {
m_StrideStart[RIGHTSIDE] = false;
m_StrideTimer[RIGHTSIDE].Reset();
- m_pRFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pRFGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[RIGHTSIDE][FGROUND][WALK], deltaTime, &restarted);
+ m_pRFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pRFGLeg->GetParentOffset()), m_Vel, walkAngle, m_Paths[RIGHTSIDE][FGROUND][WALK], deltaTime, &restarted);
} else {
m_pRFGFootGroup->FlailAsLimb(m_Pos, RotateOffset(m_pRFGLeg->GetParentOffset()), m_pRFGLeg->GetMaxLength(), m_PrevVel, m_AngularVel, m_pRFGLeg->GetMass(), deltaTime);
}
@@ -2433,7 +2430,7 @@ void ACrab::Update()
if (m_pRBGLeg && (!m_pRFGLeg || (!(m_Paths[RIGHTSIDE][BGROUND][WALK].PathEnded() && RFGLegProg < 0.5F) || m_StrideStart[RIGHTSIDE]))) {
m_StrideTimer[RIGHTSIDE].Reset();
- m_pRBGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pRBGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[RIGHTSIDE][BGROUND][WALK], deltaTime);
+ m_pRBGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pRBGLeg->GetParentOffset()), m_Vel, walkAngle, m_Paths[RIGHTSIDE][BGROUND][WALK], deltaTime);
}
// Reset the right-side walking stride if it's taking longer than it should.
@@ -2469,11 +2466,11 @@ void ACrab::Update()
m_Paths[side][layer][WALK].Terminate();
}
}
- if (m_pLFGLeg) { m_pLFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pLFGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[LEFTSIDE][FGROUND][STAND], deltaTime); }
+ if (m_pLFGLeg) { m_pLFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pLFGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[LEFTSIDE][FGROUND][STAND], deltaTime, nullptr, !m_pRFGLeg); }
if (m_pLBGLeg) { m_pLBGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pLBGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[LEFTSIDE][BGROUND][STAND], deltaTime); }
- if (m_pRFGLeg) { m_pRFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pRFGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[RIGHTSIDE][FGROUND][STAND], deltaTime); }
+ if (m_pRFGLeg) { m_pRFGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pRFGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[RIGHTSIDE][FGROUND][STAND], deltaTime, nullptr, !m_pLFGLeg); }
if (m_pRBGLeg) { m_pRBGFootGroup->PushAsLimb(m_Pos + RotateOffset(m_pRBGLeg->GetParentOffset()), m_Vel, m_Rotation, m_Paths[RIGHTSIDE][BGROUND][STAND], deltaTime); }
}
@@ -2767,31 +2764,8 @@ void ACrab::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr
}
}
- // Weight and jetpack energy
- if (m_pJetpack && m_pJetpack->IsAttached() && m_Controller.IsState(BODY_JUMP)) {
- float mass = GetMass();
- if (m_JetTimeLeft < 100) {
- // Draw empty fuel indicator
- str[0] = m_IconBlinkTimer.AlternateSim(100) ? -26 : -25;
- } else {
- // Display normal jet icons
- // TODO: Don't hardcode the mass indicator! Figure out how to calculate the jetpack threshold values
- str[0] = mass < 135 ? -31 : (mass < 150 ? -30 : (mass < 165 ? -29 : -28));
- // Do the blinky blink
- if ((str[0] == -28 || str[0] == -29) && m_IconBlinkTimer.AlternateSim(250)) { str[0] = -27; }
- }
- str[1] = 0;
- pSymbolFont->DrawAligned(&allegroBitmap, drawPos.m_X - 11, drawPos.m_Y + m_HUDStack, str, GUIFont::Centre);
-
- 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;
- }
// Held-related GUI stuff
- else if (m_pTurret && m_pTurret->IsAttached()) {
+ if (m_pTurret) {
std::string textString;
for (const HeldDevice *mountedDevice : m_pTurret->GetMountedDevices()) {
if (const HDFirearm *mountedFirearm = dynamic_cast(mountedDevice)) {
@@ -2810,15 +2784,50 @@ void ACrab::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr
str[0] = -56; str[1] = 0;
pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 10, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Left);
pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 0, drawPos.GetFloorIntY() + m_HUDStack + 3, textString, GUIFont::Left);
- m_HUDStack += -10;
+ m_HUDStack -= 9;
}
-
} else {
std::snprintf(str, sizeof(str), "NO TURRET!");
pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X + 2, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Centre);
m_HUDStack += -9;
}
+ if (m_pJetpack && m_Status != INACTIVE && !m_Controller.IsState(PIE_MENU_ACTIVE) && (m_Controller.IsState(BODY_JUMP) || m_JetTimeLeft < m_JetTimeTotal)) {
+ if (m_JetTimeLeft < 100.0F) {
+ str[0] = m_IconBlinkTimer.AlternateSim(100) ? -26 : -25;
+ } else if (m_pJetpack->IsEmitting()) {
+ float acceleration = m_pJetpack->EstimateImpulse(false) / std::max(GetMass(), 0.1F);
+ if (acceleration > 0.41F) {
+ str[0] = acceleration > 0.47F ? -31 : -30;
+ } else {
+ str[0] = acceleration > 0.35F ? -29 : -28;
+ if (m_IconBlinkTimer.AlternateSim(200)) { str[0] = -27; }
+ }
+ } else {
+ str[0] = -27;
+ }
+ str[1] = 0;
+ pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 7, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre);
+
+ rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 7, drawPos.GetFloorIntX() + 15, drawPos.GetFloorIntY() + m_HUDStack + 8, 245);
+ if (m_JetTimeTotal > 0) {
+ float jetTimeRatio = m_JetTimeLeft / m_JetTimeTotal;
+ int gaugeColor;
+ if (jetTimeRatio > 0.75F) {
+ gaugeColor = 149;
+ } else if (jetTimeRatio > 0.5F) {
+ gaugeColor = 133;
+ } else if (jetTimeRatio > 0.375F) {
+ gaugeColor = 77;
+ } else if (jetTimeRatio > 0.25F) {
+ gaugeColor = 48;
+ } else {
+ gaugeColor = 13;
+ }
+ rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 6, drawPos.GetFloorIntX() + static_cast(15.0F * jetTimeRatio), drawPos.GetFloorIntY() + m_HUDStack + 7, gaugeColor);
+ }
+ m_HUDStack -= 9;
+ }
// Print aim angle and rot angle stoff
/*{
diff --git a/Entities/ACrab.h b/Entities/ACrab.h
index 4c13c0420..3f5c57d54 100644
--- a/Entities/ACrab.h
+++ b/Entities/ACrab.h
@@ -135,15 +135,6 @@ class ACrab : public Actor {
void Destroy(bool notInherited = false) override;
-//////////////////////////////////////////////////////////////////////////////////////////
-// Method: GetGoldCarried
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Gets how many ounces of gold this Actor is carrying.
-// Arguments: None.
-// Return value: The current amount of carried gold, in Oz.
-
- float GetGoldCarried() const override { return m_GoldCarried + m_GoldInInventoryChunk; }
-
//////////////////////////////////////////////////////////////////////////////////////////
// Virtual method: GetEyePos
//////////////////////////////////////////////////////////////////////////////////////////
@@ -629,8 +620,6 @@ int FirearmActivationDelay() const;
bool m_StrideStart[SIDECOUNT];
// Times the strides to make sure they get restarted if they end up too long
Timer m_StrideTimer[SIDECOUNT];
- // How much gold is carried in an MovableObject in inventory, separate from the actor gold tally.
- int m_GoldInInventoryChunk;
// The maximum angle MountedMO can be aimed up, positive values only, in radians
float m_AimRangeUpperLimit;
// The maximum angle MountedMO can be aimed down, positive values only, in radians
diff --git a/Entities/ACraft.cpp b/Entities/ACraft.cpp
index 01db505ac..e8135edf7 100644
--- a/Entities/ACraft.cpp
+++ b/Entities/ACraft.cpp
@@ -627,11 +627,22 @@ void ACraft::AddInventoryItem(MovableObject *pItemToAdd)
{
// If the hatch is open, then only add the new item to the intermediate new inventory list
// so that it doesn't get chucked out right away again
- if (m_HatchState == OPEN || m_HatchState == OPENING)
- m_CollectedInventory.push_back(pItemToAdd);
- // If doors are already closed, it's safe to put the item directly the regular inventory
- else
- AddToInventoryBack(pItemToAdd);
+ if (m_HatchState == OPEN || m_HatchState == OPENING) {
+ m_CollectedInventory.push_back(pItemToAdd);
+ } else {
+ // If doors are already closed, it's safe to put the item directly the regular inventory
+ AddToInventoryBack(pItemToAdd);
+ }
+ if (Actor *itemAsActor = dynamic_cast(pItemToAdd); itemAsActor && itemAsActor->GetGoldCarried() > 0) {
+ m_GoldCarried += itemAsActor->GetGoldCarried();
+ itemAsActor->SetGoldCarried(0);
+ m_GoldPicked = true;
+ if (g_ActivityMan.GetActivity()->IsHumanTeam(m_Team)) {
+ for (int player = Players::PlayerOne; player < Players::MaxPlayerCount; player++) {
+ if (g_ActivityMan.GetActivity()->GetTeamOfPlayer(player) == m_Team) { g_GUISound.FundsChangedSound()->Play(player); }
+ }
+ }
+ }
}
}
diff --git a/Entities/AEmitter.cpp b/Entities/AEmitter.cpp
index 97a3b413a..44815c71b 100644
--- a/Entities/AEmitter.cpp
+++ b/Entities/AEmitter.cpp
@@ -392,7 +392,6 @@ void AEmitter::SetFlash(Attachable *newFlash) {
m_pFlash->SetDrawnNormallyByParent(false);
m_pFlash->SetInheritsRotAngle(false);
- m_pFlash->SetInheritsHFlipped(0);
m_pFlash->SetDeleteWhenRemovedFromParent(true);
m_pFlash->SetCollidesWithTerrainWhileAttached(false);
}
@@ -421,8 +420,7 @@ void AEmitter::Update()
// Update and show flash if there is one
if (m_pFlash && (!m_FlashOnlyOnBurst || m_BurstTriggered)) {
m_pFlash->SetParentOffset(m_EmissionOffset);
- // Don't set the flipping for the flash because that is wasting resources when drawing, just handle the flipping of the rotation here.
- m_pFlash->SetRotAngle(m_HFlipped ? c_PI + m_Rotation.GetRadAngle() - m_EmitAngle.GetRadAngle() : m_Rotation.GetRadAngle() + m_EmitAngle.GetRadAngle());
+ m_pFlash->SetRotAngle(m_Rotation.GetRadAngle() + (m_EmitAngle.GetRadAngle() * GetFlipFactor()));
m_pFlash->SetScale(m_FlashScale);
m_pFlash->SetNextFrame();
}
@@ -451,8 +449,7 @@ void AEmitter::Update()
float throttleFactor = GetThrottleFactor();
m_FlashScale = throttleFactor;
// Check burst triggering against whether the spacing is fulfilled
- if (m_BurstTriggered && (m_BurstSpacing <= 0 || m_BurstTimer.IsPastSimMS(m_BurstSpacing)))
- {
+ if (m_BurstTriggered && CanTriggerBurst()) {
// Play burst sound
if (m_BurstSound) { m_BurstSound->Play(m_Pos); }
// Start timing until next burst
diff --git a/Entities/AEmitter.h b/Entities/AEmitter.h
index d37bc813d..12ca6eca3 100644
--- a/Entities/AEmitter.h
+++ b/Entities/AEmitter.h
@@ -447,7 +447,7 @@ ClassInfoGetters;
// Arguments: None.
// Return value: If it is possible to trigger a burst.
- bool CanTriggerBurst() { if (m_BurstSpacing <= 0 || m_BurstTimer.IsPastSimMS(m_BurstSpacing)) return true; return false; }
+ bool CanTriggerBurst() { return m_BurstSpacing <= 0 || m_BurstTimer.IsPastSimMS(m_BurstSpacing); }
//////////////////////////////////////////////////////////////////////////////////////////
diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp
index e0ef45881..c24f5622c 100644
--- a/Entities/AHuman.cpp
+++ b/Entities/AHuman.cpp
@@ -74,9 +74,9 @@ void AHuman::Clear()
m_JetTimeLeft = 0.0;
m_JetReplenishRate = 1.0F;
m_JetAngleRange = 0.25F;
+ m_CanActivateBGItem = false;
+ m_TriggerPulled = false;
m_WaitingToReloadOffhand = false;
- m_OneHandedReloadAngleOffset = -0.4F;
- m_GoldInInventoryChunk = 0;
m_ThrowTmr.Reset();
m_ThrowPrepTime = 1000;
m_SharpAimRevertTimer.Reset();
@@ -85,7 +85,7 @@ void AHuman::Clear()
m_EquipHUDTimer.Reset();
m_WalkAngle.fill(Matrix());
m_ArmSwingRate = 1.0F;
- m_DeviceArmSwayRate = m_ArmSwingRate * 0.75F;
+ m_DeviceArmSwayRate = 0.5F;
m_DeviceState = SCANNING;
m_SweepState = NOSWEEP;
@@ -136,7 +136,7 @@ int AHuman::Create()
// If empty-handed, equip first thing in inventory
if (m_pFGArm && m_pFGArm->IsAttached() && !m_pFGArm->GetHeldDevice()) {
m_pFGArm->SetHeldDevice(dynamic_cast(SwapNextInventory(nullptr, true)));
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
}
// Initalize the jump time left
@@ -180,7 +180,6 @@ int AHuman::Create(const AHuman &reference) {
m_JetReplenishRate = reference.m_JetReplenishRate;
m_JetAngleRange = reference.m_JetAngleRange;
m_WaitingToReloadOffhand = reference.m_WaitingToReloadOffhand;
- m_OneHandedReloadAngleOffset = reference.m_OneHandedReloadAngleOffset;
m_FGArmFlailScalar = reference.m_FGArmFlailScalar;
m_BGArmFlailScalar = reference.m_BGArmFlailScalar;
m_ArmSwingRate = reference.m_ArmSwingRate;
@@ -222,8 +221,6 @@ int AHuman::Create(const AHuman &reference) {
m_RotAngleTargets[i] = reference.m_RotAngleTargets[i];
}
- m_GoldInInventoryChunk = reference.m_GoldInInventoryChunk;
-
m_DeviceState = reference.m_DeviceState;
m_SweepState = reference.m_SweepState;
m_DigState = reference.m_DigState;
@@ -264,8 +261,6 @@ int AHuman::ReadProperty(const std::string_view &propName, Reader &reader) {
reader >> m_JetReplenishRate;
} else if (propName == "JumpAngleRange" || propName == "JetAngleRange") {
reader >> m_JetAngleRange;
- } else if (propName == "OneHandedReloadAngleOffset") {
- reader >> m_OneHandedReloadAngleOffset;
} else if (propName == "FGArmFlailScalar") {
reader >> m_FGArmFlailScalar;
} else if (propName == "BGArmFlailScalar") {
@@ -369,7 +364,6 @@ int AHuman::Save(Writer &writer) const
writer << m_JetReplenishRate;
writer.NewProperty("JumpAngleRange");
writer << m_JetAngleRange;
- writer.NewPropertyWithValue("OneHandedReloadAngle", m_OneHandedReloadAngleOffset);
writer.NewProperty("FGArmFlailScalar");
writer << m_FGArmFlailScalar;
writer.NewProperty("BGArmFlailScalar");
@@ -701,42 +695,6 @@ bool AHuman::CollideAtPoint(HitData &hd)
*/
}
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// Method: ChunkGold
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Converts an appropriate amount of gold tracked by Actor, and puts it
-// in a MovableObject which is put into inventory.
-
-void AHuman::ChunkGold()
-{
- MovableObject *pGoldMO = 0;
- if (m_GoldCarried >= 24) {
- pGoldMO = dynamic_cast(
- g_PresetMan.GetEntityPreset("MOSParticle", "24 oz Gold Brick")->Clone());
- AddToInventoryFront(pGoldMO);
- m_GoldCarried -= 24;
- m_GoldInInventoryChunk = 24;
- }
- else if (m_GoldCarried >= 10) {
- pGoldMO = dynamic_cast(
-// g_PresetMan.GetEntityPreset("MOSRotating", "10 Gold Brick")->Clone());
- g_PresetMan.GetEntityPreset("MOSParticle", "10 oz Gold Brick")->Clone());
- AddToInventoryFront(pGoldMO);
- m_GoldCarried -= 10;
- m_GoldInInventoryChunk = 10;
- }
-/*
- else if (m_GoldCarried >= 1) {
- pGoldMO = dynamic_cast(
- g_PresetMan.GetEntityPreset("MOPixel", "Gold Particle")->Clone());
- AddToInventoryFront(pGoldMO);
- m_GoldCarried -= 1;
- m_GoldInInventoryChunk = 1;
- }
-*/
-}
-
/*
//////////////////////////////////////////////////////////////////////////////////////////
// Method: OnBounce
@@ -811,7 +769,7 @@ void AHuman::AddInventoryItem(MovableObject *pItemToAdd) {
// If we have nothing in inventory, and nothing in our hands, just grab this first thing added to us.
if (HeldDevice *itemToAddAsHeldDevice = dynamic_cast(pItemToAdd); itemToAddAsHeldDevice && m_Inventory.empty() && m_pFGArm && m_pFGArm->IsAttached() && !m_pFGArm->GetHeldDevice()) {
m_pFGArm->SetHeldDevice(itemToAddAsHeldDevice);
- m_pFGArm->SetHandCurrentPos(m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_HolsterOffset.GetXFlipped(m_HFlipped));
} else {
Actor::AddInventoryItem(pItemToAdd);
}
@@ -887,7 +845,7 @@ bool AHuman::EquipFirearm(bool doEquip)
// Now put the device we were looking for and found into the hand
m_pFGArm->SetHeldDevice(pWeapon);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
// Equip shield in BG arm if applicable
EquipShieldInBGArm();
@@ -956,7 +914,7 @@ bool AHuman::EquipDeviceInGroup(std::string group, bool doEquip)
// Now put the device we were looking for and found into the hand
m_pFGArm->SetHeldDevice(pDevice);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
// Equip shield in BG arm if applicable
EquipShieldInBGArm();
@@ -1013,7 +971,7 @@ bool AHuman::EquipLoadedFirearmInGroup(std::string group, std::string excludeGro
// Now put the device we were looking for and found into the hand
m_pFGArm->SetHeldDevice(pFirearm);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
// Equip shield in BG arm if applicable
EquipShieldInBGArm();
@@ -1070,7 +1028,7 @@ bool AHuman::EquipNamedDevice(const std::string &moduleName, const std::string &
// Now put the device we were looking for and found into the hand
m_pFGArm->SetHeldDevice(pDevice);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
// Equip shield in BG arm if applicable
EquipShieldInBGArm();
@@ -1128,7 +1086,7 @@ bool AHuman::EquipThrowable(bool doEquip)
// Now put the device we were looking for and found into the hand
m_pFGArm->SetHeldDevice(pThrown);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
// Equip shield in BG arm as applicable
EquipShieldInBGArm();
@@ -1185,7 +1143,7 @@ bool AHuman::EquipDiggingTool(bool doEquip)
// Now put the device we were looking for and found into the hand
m_pFGArm->SetHeldDevice(pTool);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
// Equip shield in BG arm is applicable
EquipShieldInBGArm();
@@ -1272,7 +1230,7 @@ bool AHuman::EquipShield()
// Now put the device we were looking for and found into the hand
m_pFGArm->SetHeldDevice(pShield);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pFGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pFGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
// Equip shield in BG arm is applicable
EquipShieldInBGArm();
@@ -1337,7 +1295,7 @@ bool AHuman::EquipShieldInBGArm()
// Now put the device we were looking for and found into the hand
m_pBGArm->SetHeldDevice(pShield);
// Move the hand to a poisition so it looks like the new device was drawn from inventory
- m_pBGArm->SetHandCurrentPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
+ m_pBGArm->SetHandPos(m_Pos + m_HolsterOffset.GetXFlipped(m_HFlipped));
if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); }
@@ -1355,7 +1313,7 @@ bool AHuman::UnequipFGArm() {
if (HeldDevice *heldDevice = m_pFGArm->GetHeldDevice()) {
heldDevice->Deactivate();
AddToInventoryBack(m_pFGArm->RemoveAttachable(heldDevice));
- m_pFGArm->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset));
+ m_pFGArm->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset));
return true;
}
}
@@ -1369,7 +1327,7 @@ bool AHuman::UnequipBGArm() {
if (HeldDevice *heldDevice = m_pBGArm->GetHeldDevice()) {
heldDevice->Deactivate();
AddToInventoryBack(m_pBGArm->RemoveAttachable(heldDevice));
- m_pBGArm->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset));
+ m_pBGArm->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset));
return true;
}
}
@@ -1542,23 +1500,19 @@ void AHuman::ReloadFirearms(bool onlyReloadEmptyFirearms) {
}
if (reloadHeldFirearm) {
- //TODO it would be nice to calculate this based on arm movement speed, so it accounts for moving the arm there and back but I couldn't figure out the maths. Alternatively, the reload time could be calculated based on this, instead of this trying to calculate from the reload time.
- float percentageOfReloadTimeToStayAtReloadOffset = 0.85F;
+ heldFirearm->Reload();
+ if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); }
bool otherArmIsAvailable = otherArm && !otherArm->GetHeldDevice();
- heldFirearm->Reload();
if (otherArmIsAvailable) {
+ float delayAtTarget = std::max(static_cast(heldFirearm->GetReloadTime() - 200), 0.0F);
otherArm->AddHandTarget("Magazine Pos", heldFirearm->GetMagazinePos());
if (!m_ReloadOffset.IsZero()) {
- otherArm->AddHandTarget("Reload Offset", m_Pos + RotateOffset(m_ReloadOffset), static_cast(heldFirearm->GetReloadTime()) * percentageOfReloadTimeToStayAtReloadOffset);
+ otherArm->AddHandTarget("Reload Offset", m_Pos + RotateOffset(m_ReloadOffset), delayAtTarget);
} else {
- otherArm->AddHandTarget("Holster Offset", m_Pos + RotateOffset(m_HolsterOffset), static_cast(heldFirearm->GetReloadTime()) * percentageOfReloadTimeToStayAtReloadOffset);
+ otherArm->AddHandTarget("Holster Offset", m_Pos + RotateOffset(m_HolsterOffset), delayAtTarget);
}
- otherArm->AddHandTarget("Magazine Pos", heldFirearm->GetMagazinePos());
- if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); }
- } else if (!m_ReloadOffset.IsZero()) {
- arm->AddHandTarget("Reload Offset", m_Pos + RotateOffset(m_ReloadOffset), static_cast(heldFirearm->GetReloadTime()) * percentageOfReloadTimeToStayAtReloadOffset);
- if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); }
+ otherArm->SetHandPos(heldFirearm->GetMagazinePos());
}
}
}
@@ -1872,6 +1826,11 @@ void AHuman::UpdateAI()
////////////////////////////////////////////////
// AI MODES
+ // Squad logic
+ if (m_AIMode == AIMODE_SQUAD) {
+ m_AIMode = AIMODE_GOTO;
+ }
+
// If alarmed, override all modes, look at the alarming point
if (!m_AlarmTimer.IsPastSimTimeLimit())
{
@@ -1970,14 +1929,12 @@ void AHuman::UpdateAI()
m_MoveVector = g_SceneMan.ShortestDistance(m_Pos, m_MoveTarget);
if ((m_MoveVector.m_X > 0 && m_LateralMoveState == LAT_LEFT) || (m_MoveVector.m_X < 0 && m_LateralMoveState == LAT_RIGHT) || (m_LateralMoveState == LAT_STILL && m_DeviceState != AIMING && m_DeviceState != FIRING))
{
- // If not following an MO, stay still and switch to sentry mode if we're close enough to final static destination
- if (!m_pMOMoveTarget && m_Waypoints.empty() && m_MovePath.empty() && fabs(m_MoveVector.m_X) <= 10)
- {
- // DONE MOVING TOWARD TARGET
- m_LateralMoveState = LAT_STILL;
- m_AIMode = AIMODE_SENTRY;
- m_DeviceState = SCANNING;
- }
+ // Stay still and switch to sentry mode if we're close enough to the final destination.
+ if (m_Waypoints.empty() && m_MovePath.empty() && std::abs(m_MoveVector.m_X) < 10.0F) {
+ m_LateralMoveState = LAT_STILL;
+ m_DeviceState = SCANNING;
+ if (!m_pMOMoveTarget) { m_AIMode = AIMODE_SENTRY; }
+ }
// Turns only after a delay to avoid getting stuck on switchback corners in corridors
else if (m_MoveOvershootTimer.IsPastSimMS(500) || m_LateralMoveState == LAT_STILL)
m_LateralMoveState = m_LateralMoveState == LAT_RIGHT ? LAT_LEFT : LAT_RIGHT;
@@ -2944,7 +2901,7 @@ void AHuman::UpdateAI()
Vector topHeadPos = m_Pos;
// Stack up the maximum height the top back of the head can have over the body's position
topHeadPos.m_X += m_HFlipped ? m_pHead->GetRadius() : -m_pHead->GetRadius();
- topHeadPos.m_Y += m_pHead->GetParentOffset().m_Y - m_pHead->GetJointOffset().m_Y + m_pHead->GetSpriteOffset().m_Y - 6;
+ topHeadPos.m_Y += m_pHead->GetParentOffset().m_Y - m_pHead->GetJointOffset().m_Y + m_pHead->GetSpriteOffset().m_Y - 3;
// First check up to the top of the head, and then from there forward
if (g_SceneMan.CastStrengthRay(m_Pos, topHeadPos - m_Pos, 5, obstaclePos, 4, g_MaterialDoor) ||
g_SceneMan.CastStrengthRay(topHeadPos, heading, 5, obstaclePos, 4, g_MaterialDoor))
@@ -3119,7 +3076,7 @@ void AHuman::Update()
m_pJetpack->TriggerBurst();
m_ForceDeepCheck = true;
m_pJetpack->EnableEmission(true);
- m_JetTimeLeft = std::max(m_JetTimeLeft - g_TimerMan.GetDeltaTimeMS() * 10.0F, 0.0F);
+ m_JetTimeLeft = std::max(m_JetTimeLeft - g_TimerMan.GetDeltaTimeMS() * static_cast(std::max(m_pJetpack->GetTotalBurstSize(), 2)) * (m_pJetpack->CanTriggerBurst() ? 1.0F : 0.5F), 0.0F);
} else if (m_Controller.IsState(BODY_JUMP) && m_JetTimeLeft > 0 && m_Status != INACTIVE) {
m_pJetpack->EnableEmission(true);
m_pJetpack->AlarmOnEmit(m_Team);
@@ -3130,7 +3087,7 @@ void AHuman::Update()
} else {
m_pJetpack->EnableEmission(false);
if (m_MoveState == JUMP) { m_MoveState = STAND; }
- m_JetTimeLeft = std::min(m_JetTimeLeft + g_TimerMan.GetDeltaTimeMS() * 2.0F * m_JetReplenishRate, m_JetTimeTotal);
+ m_JetTimeLeft = std::min(m_JetTimeLeft + g_TimerMan.GetDeltaTimeMS() * m_JetReplenishRate, m_JetTimeTotal);
}
float maxAngle = c_HalfPI * m_JetAngleRange;
@@ -3228,12 +3185,12 @@ void AHuman::Update()
////////////////////////////////////
// Standard Reloading
-
for (const Arm *arm : { m_pFGArm, m_pBGArm }) {
if (arm) {
if (HDFirearm *heldFirearm = dynamic_cast(arm->GetHeldDevice())) {
- const Arm *otherArm = arm == m_pFGArm ? m_pBGArm : m_pFGArm;
+ Arm *otherArm = arm == m_pFGArm ? m_pBGArm : m_pFGArm;
bool otherArmIsAvailable = otherArm && !otherArm->GetHeldDevice();
+ if (otherArmIsAvailable && heldFirearm->DoneReloading()) { otherArm->SetHandPos(heldFirearm->GetMagazinePos()); };
heldFirearm->SetSupportAvailable(otherArmIsAvailable);
}
}
@@ -3267,7 +3224,7 @@ void AHuman::Update()
m_pFGArm->SetHeldDevice(dynamic_cast(SwapPrevInventory(m_pFGArm->RemoveAttachable(m_pFGArm->GetHeldDevice()))));
}
EquipShieldInBGArm();
- m_pFGArm->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset));
+ m_pFGArm->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset));
}
m_EquipHUDTimer.Reset();
m_SharpAimProgress = 0;
@@ -3358,107 +3315,143 @@ void AHuman::Update()
// Handle firing/activating/throwing HeldDevices and ThrownDevices.
// Also deal with certain reload cases and setting sharp aim progress for HeldDevices.
- ThrownDevice *pThrown = nullptr;
- if (m_pFGArm && m_Status != INACTIVE) {
- if (HeldDevice *device = m_pFGArm->GetHeldDevice(); device && !dynamic_cast(device)) {
- // If reloading 2 guns one-at-a-time, the automatic reload when firing empty won't trigger, so this makes sure it happens automatically.
- if (device->IsEmpty()) {
- ReloadFirearms(true);
- }
-
+ ThrownDevice *thrownDevice = nullptr;
+ if (HeldDevice *device = GetEquippedItem(); device && m_Status != INACTIVE) {
+ if (!dynamic_cast(device)) {
device->SetSharpAim(m_SharpAimProgress);
- if (m_Controller.IsState(WEAPON_FIRE)) {
- device->Activate();
- if (device->IsEmpty()) {
- ReloadFirearms(true);
+
+ if (HDFirearm *deviceAsFirearm = dynamic_cast(device)) {
+ if (m_Controller.IsState(WEAPON_FIRE)) {
+ if (!m_CanActivateBGItem) {
+ if (deviceAsFirearm->IsFullAuto()) {
+ deviceAsFirearm->Activate();
+ m_CanActivateBGItem = deviceAsFirearm->FiredOnce() && deviceAsFirearm->HalfwayToNextRound();
+ } else if (!m_TriggerPulled) {
+ deviceAsFirearm->Activate();
+ if (deviceAsFirearm->FiredOnce()) {
+ m_CanActivateBGItem = true;
+ m_TriggerPulled = true;
+ }
+ }
+ }
+ } else {
+ deviceAsFirearm->Deactivate();
+ m_TriggerPulled = false;
}
} else {
- device->Deactivate();
+ m_CanActivateBGItem = true;
+ if (m_Controller.IsState(WEAPON_FIRE)) {
+ device->Activate();
+ if (device->IsEmpty()) {
+ ReloadFirearms(true);
+ }
+ } else {
+ device->Deactivate();
+ }
+ }
+ // If reloading 2 guns one-at-a-time, the automatic reload when firing empty won't trigger, so this makes sure it happens automatically.
+ if (device->IsEmpty()) {
+ ReloadFirearms(true);
}
if (device->IsReloading()) {
+ m_CanActivateBGItem = true;
m_SharpAimTimer.Reset();
m_SharpAimProgress = 0;
device->SetSharpAim(m_SharpAimProgress);
}
- } else if (pThrown = dynamic_cast(m_pFGArm->GetHeldDevice())) {
- pThrown->SetSharpAim(isSharpAiming ? 1.0F : 0);
- if (m_Controller.IsState(WEAPON_FIRE)) {
- if (m_ArmsState != THROWING_PREP) {
- m_ThrowTmr.Reset();
- if (!pThrown->ActivatesWhenReleased()) { pThrown->Activate(); }
- }
- float throwProgress = GetThrowProgress();
- m_ArmsState = THROWING_PREP;
- m_pFGArm->AddHandTarget("Start Throw Offset", m_pFGArm->GetJointPos() + pThrown->GetStartThrowOffset().GetXFlipped(m_HFlipped).RadRotate(adjustedAimAngle));
- } else if (m_ArmsState == THROWING_PREP) {
- m_ArmsState = THROWING_RELEASE;
- m_pFGArm->AddHandTarget("End Throw Offset", m_pFGArm->GetJointPos() + pThrown->GetEndThrowOffset().GetXFlipped(m_HFlipped).RadRotate(adjustedAimAngle));
-
- float maxThrowVel = pThrown->GetCalculatedMaxThrowVelIncludingArmThrowStrength();
- if (MovableObject *pMO = m_pFGArm->RemoveAttachable(pThrown)) {
- pMO->SetPos(m_pFGArm->GetJointPos() + Vector(m_pFGArm->GetMaxLength() * GetFlipFactor(), -m_pFGArm->GetMaxLength() * 0.5F).RadRotate(adjustedAimAngle));
- float minThrowVel = pThrown->GetMinThrowVel();
- if (minThrowVel == 0) { minThrowVel = maxThrowVel * 0.2F; }
-
- Vector tossVec(minThrowVel + (maxThrowVel - minThrowVel) * GetThrowProgress(), 0.5F * RandomNormalNum());
- pMO->SetVel(m_Vel * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped));
- pMO->SetAngularVel(m_AngularVel + RandomNum(-5.0F, 2.5F) * GetFlipFactor());
- pMO->SetRotAngle(adjustedAimAngle);
-
- if (HeldDevice *moAsHeldDevice = dynamic_cast(pMO)) {
- moAsHeldDevice->SetTeam(m_Team);
- moAsHeldDevice->SetIgnoresTeamHits(true);
- g_MovableMan.AddItem(moAsHeldDevice);
- } else {
- if (pMO->IsGold()) {
- m_GoldInInventoryChunk = 0;
- ChunkGold();
+ } else {
+ m_CanActivateBGItem = true;
+ if (thrownDevice = dynamic_cast(device)) {
+ thrownDevice->SetSharpAim(isSharpAiming ? 1.0F : 0);
+ if (m_Controller.IsState(WEAPON_FIRE)) {
+ if (m_ArmsState != THROWING_PREP) {
+ m_ThrowTmr.Reset();
+ if (!thrownDevice->ActivatesWhenReleased()) { thrownDevice->Activate(); }
+ }
+ float throwProgress = GetThrowProgress();
+ m_ArmsState = THROWING_PREP;
+ m_pFGArm->SetHandPos(m_pFGArm->GetJointPos() + (thrownDevice->GetStartThrowOffset().GetXFlipped(m_HFlipped) * throwProgress + thrownDevice->GetStanceOffset() * (1.0F - throwProgress)).RadRotate(adjustedAimAngle));
+ } else if (m_ArmsState == THROWING_PREP) {
+ m_ArmsState = THROWING_RELEASE;
+ m_pFGArm->SetHandPos(m_pFGArm->GetJointPos() + thrownDevice->GetEndThrowOffset().RadRotate(adjustedAimAngle).GetXFlipped(m_HFlipped));
+
+ float maxThrowVel = thrownDevice->GetCalculatedMaxThrowVelIncludingArmThrowStrength();
+ if (MovableObject *pMO = m_pFGArm->RemoveAttachable(thrownDevice)) {
+ pMO->SetPos(m_pFGArm->GetJointPos() + Vector(m_pFGArm->GetMaxLength() * GetFlipFactor(), -m_pFGArm->GetMaxLength() * 0.5F).RadRotate(adjustedAimAngle));
+ float minThrowVel = thrownDevice->GetMinThrowVel();
+ if (minThrowVel == 0) { minThrowVel = maxThrowVel * 0.2F; }
+
+ Vector tossVec(minThrowVel + (maxThrowVel - minThrowVel) * GetThrowProgress(), 0.5F * RandomNormalNum());
+ pMO->SetVel(m_Vel * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped));
+ pMO->SetAngularVel(m_AngularVel + RandomNum(-5.0F, 2.5F) * GetFlipFactor());
+ pMO->SetRotAngle(adjustedAimAngle);
+
+ if (HeldDevice *moAsHeldDevice = dynamic_cast(pMO)) {
+ moAsHeldDevice->SetTeam(m_Team);
+ moAsHeldDevice->SetIgnoresTeamHits(true);
+ g_MovableMan.AddItem(moAsHeldDevice);
}
- g_MovableMan.AddParticle(pMO);
+ pMO = 0;
}
- pMO = 0;
+ if (thrownDevice->ActivatesWhenReleased()) { thrownDevice->Activate(); }
+ m_ThrowTmr.Reset();
+ } else {
+ //m_pFGArm->SetHandIdleRotation(adjustedAimAngle);
+ //m_pFGArm->AddHandTarget("Stance Offset", m_pFGArm->GetJointPos() + thrownDevice->GetStanceOffset().RadRotate(adjustedAimAngle)); //TODO this can probably be replaced non-targeted handling, especially if I let you rotate idle offsets. Make sure to fix arm so TDs aren't excluded, to make this happen
}
- if (pThrown->ActivatesWhenReleased()) { pThrown->Activate(); }
- m_ThrowTmr.Reset();
- } else {
- //m_pFGArm->SetHandIdleRotation(adjustedAimAngle);
- //m_pFGArm->AddHandTarget("Stance Offset", m_pFGArm->GetJointPos() + pThrown->GetStanceOffset().RadRotate(adjustedAimAngle)); //TODO this can probably be replaced non-targeted handling, especially if I let you rotate idle offsets. Make sure to fix arm so TDs aren't excluded, to make this happen
+ } else if (m_ArmsState == THROWING_RELEASE && m_ThrowTmr.GetElapsedSimTimeMS() > 100) {
+ m_pFGArm->SetHeldDevice(dynamic_cast(SwapNextInventory()));
+ m_pFGArm->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset));
+ EquipShieldInBGArm();
+ m_ArmsState = WEAPON_READY;
+ } else if (m_ArmsState == THROWING_RELEASE) {
+ m_pFGArm->AddHandTarget("Adjusted Aim Angle", m_Pos + Vector(m_pFGArm->GetMaxLength() * GetFlipFactor(), -m_pFGArm->GetMaxLength() * 0.5F).RadRotate(adjustedAimAngle));
}
- } else if (m_ArmsState == THROWING_RELEASE && m_ThrowTmr.GetElapsedSimTimeMS() > 100) {
- m_pFGArm->SetHeldDevice(dynamic_cast(SwapNextInventory()));
- m_pFGArm->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset));
- EquipShieldInBGArm();
- m_ArmsState = WEAPON_READY;
- } else if (m_ArmsState == THROWING_RELEASE) {
- m_pFGArm->AddHandTarget("Adjusted Aim Angle", m_Pos + Vector(m_pFGArm->GetMaxLength() * GetFlipFactor(), -m_pFGArm->GetMaxLength() * 0.5F).RadRotate(adjustedAimAngle));
}
+ } else {
+ m_CanActivateBGItem = true;
}
if (HeldDevice *device = GetEquippedBGItem(); device && m_Status != INACTIVE) {
+ if (HDFirearm *deviceAsFirearm = dynamic_cast(device)) {
+ if (m_Controller.IsState(WEAPON_FIRE)) {
+ if (m_CanActivateBGItem && (!m_TriggerPulled || (deviceAsFirearm->IsFullAuto() && deviceAsFirearm->HalfwayToNextRound()))) {
+ deviceAsFirearm->Activate();
+ if (deviceAsFirearm->FiredOnce()) {
+ m_CanActivateBGItem = false;
+ m_TriggerPulled = true;
+ }
+ }
+ } else {
+ deviceAsFirearm->Deactivate();
+ m_TriggerPulled = false;
+ }
+ } else {
+ m_CanActivateBGItem = false;
+ if (m_Controller.IsState(WEAPON_FIRE)) {
+ device->Activate();
+ } else {
+ device->Deactivate();
+ }
+ }
// If reloading 2 guns one-at-a-time, the automatic reload when firing empty won't trigger, so this makes sure it happens automatically.
if (device->IsEmpty()) {
ReloadFirearms(true);
}
-
device->SetSharpAim(m_SharpAimProgress);
- if (m_Controller.IsState(WEAPON_FIRE)) {
- device->Activate();
- if (device->IsEmpty()) {
- ReloadFirearms(true);
- }
- } else {
- device->Deactivate();
- }
if (device->IsReloading()) {
+ m_CanActivateBGItem = false;
m_SharpAimTimer.Reset();
m_SharpAimProgress = 0;
device->SetSharpAim(m_SharpAimProgress);
}
+ } else {
+ m_CanActivateBGItem = false;
}
- if (m_ArmsState == THROWING_PREP && !pThrown) {
+ if (m_ArmsState == THROWING_PREP && !thrownDevice) {
m_ArmsState = WEAPON_READY;
}
@@ -3485,21 +3478,22 @@ void AHuman::Update()
heldDevice->SetPos(arm->GetJointPos() + Vector(arm->GetMaxLength() * GetFlipFactor(), 0).RadRotate(adjustedAimAngle));
Vector tossVec(1.0F + std::sqrt(std::abs(arm->GetThrowStrength()) / std::sqrt(std::abs(heldDevice->GetMass()) + 1.0F)), RandomNormalNum());
- heldDevice->SetVel(m_Vel * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped));
- heldDevice->SetAngularVel(m_AngularVel * 0.5F + 3.0F * RandomNormalNum());
+ heldDevice->SetVel(heldDevice->GetVel() * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped));
+ heldDevice->SetAngularVel(heldDevice->GetAngularVel() + m_AngularVel * 0.5F + 3.0F * RandomNormalNum());
- arm->AddHandTarget("HeldDevice Pos", heldDevice->GetPos());
+ arm->SetHandPos(heldDevice->GetPos());
if (!m_Inventory.empty()) {
arm->SetHeldDevice(dynamic_cast(SwapNextInventory()));
- arm->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset));
+ arm->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset));
}
anyDropped = true;
+ break;
}
}
- if (!anyDropped && !m_Inventory.empty()) {
+ if (!anyDropped && !m_Inventory.empty() && !m_pFGArm) {
DropAllInventory();
if (m_pBGArm) {
- m_pBGArm->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset));
+ m_pBGArm->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset));
}
}
EquipShieldInBGArm();
@@ -3518,7 +3512,7 @@ void AHuman::Update()
reach += m_pFGArm ? m_pFGArm->GetMaxLength() : m_pBGArm->GetMaxLength();
reachPoint = m_pFGArm ? m_pFGArm->GetJointPos() : m_pBGArm->GetJointPos();
- MOID itemMOID = g_SceneMan.CastMORay(reachPoint, Vector(reach * RandomNum(), 0).RadRotate(GetAimAngle(true) + (!m_pItemInReach ? RandomNum(-c_HalfPI, 0.0F) * GetFlipFactor() : 0)), m_MOID, Activity::NoTeam, g_MaterialGrass, true, 2);
+ MOID itemMOID = g_SceneMan.CastMORay(reachPoint, Vector(reach * RandomNum(0.5F, 1.0F) * GetFlipFactor(), 0).RadRotate(m_pItemInReach ? adjustedAimAngle : RandomNum(-(c_HalfPI + c_EighthPI), m_AimAngle * 0.75F + c_EighthPI) * GetFlipFactor()), m_MOID, Activity::NoTeam, g_MaterialGrass, true, 3);
if (MovableObject *foundMO = g_MovableMan.GetMOFromID(itemMOID)) {
if (HeldDevice *foundDevice = dynamic_cast(foundMO->GetRootParent())) {
@@ -3536,8 +3530,8 @@ void AHuman::Update()
Arm *armToUse = m_pFGArm ? m_pFGArm : m_pBGArm;
Attachable *pMO = armToUse->RemoveAttachable(armToUse->GetHeldDevice());
AddToInventoryBack(pMO);
+ armToUse->SetHandPos(m_pItemInReach->GetJointPos());
armToUse->SetHeldDevice(m_pItemInReach);
- armToUse->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset));
m_pItemInReach = nullptr;
if (armToUse != m_pBGArm) {
@@ -3621,7 +3615,7 @@ void AHuman::Update()
// Slightly negative BGArmProg makes sense because any progress on the starting segments are reported as negative,
// and there's many starting segments on properly formed climbing paths
if (climbing) {
- if (m_pFGArm && !(m_Paths[FGROUND][CLIMB].PathEnded() && BGArmProg > 0.1F)) { // < 0.5F
+ if (m_pFGArm && !m_pFGArm->GetHeldDevice() && !(m_Paths[FGROUND][CLIMB].PathEnded() && BGArmProg > 0.1F)) { // < 0.5F
m_ArmClimbing[FGROUND] = true;
m_Paths[FGROUND][WALK].Terminate();
m_StrideStart = true;
@@ -3679,7 +3673,8 @@ void AHuman::Update()
if (m_pBGArm) {
m_ArmClimbing[BGROUND] = true;
m_pBGHandGroup->PushAsLimb(m_Pos + RotateOffset(Vector(0, m_pBGArm->GetParentOffset().m_Y)), m_Vel, m_Rotation, m_Paths[BGROUND][ARMCRAWL], deltaTime);
- } else if (m_pFGArm && !m_pFGArm->GetHeldDevice()) {
+ }
+ if (m_pFGArm && !m_pFGArm->GetHeldDevice() && !(m_Paths[FGROUND][ARMCRAWL].PathEnded() && m_Paths[BGROUND][ARMCRAWL].GetRegularProgress() < 0.5F)) {
m_ArmClimbing[FGROUND] = true;
m_pFGHandGroup->PushAsLimb(m_Pos + RotateOffset(Vector(0, m_pFGArm->GetParentOffset().m_Y)), m_Vel, m_Rotation, m_Paths[FGROUND][ARMCRAWL], deltaTime);
}
@@ -3759,11 +3754,10 @@ void AHuman::Update()
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);
- }
+ toRotate = m_pHead->GetRotMatrix().GetRadAngleTo(rot) * m_pHead->GetJointStiffness() * c_QuarterPI;
+ }
+ m_pHead->SetRotAngle(m_pHead->GetRotAngle() + toRotate);
+ }
if (m_pFGLeg) {
m_pFGLeg->EnableIdle(m_ProneState == NOTPRONE && m_Status != UNSTABLE);
@@ -3777,13 +3771,13 @@ void AHuman::Update()
// FG Arm rotating and climbing
if (m_pFGArm) {
- float affectingBodyAngle = 0.0F;
- if (m_FGArmFlailScalar != 0 && m_SharpAimDelay != 0) {
+ float affectingBodyAngle = m_Status < INACTIVE ? m_FGArmFlailScalar : 1.0F;
+ if (affectingBodyAngle != 0 && m_SharpAimDelay != 0) {
float aimScalar = std::min(static_cast(m_SharpAimTimer.GetElapsedSimTimeMS()) / static_cast(m_SharpAimDelay), 1.0F);
float revertScalar = std::min(static_cast(m_SharpAimRevertTimer.GetElapsedSimTimeMS()) / static_cast(m_SharpAimDelay), 1.0F);
aimScalar = (aimScalar > revertScalar) ? aimScalar : 1.0F - revertScalar;
- affectingBodyAngle = std::abs(std::sin(rot)) * rot * m_FGArmFlailScalar * (1.0F - aimScalar);
+ affectingBodyAngle *= std::abs(std::sin(rot)) * rot * (1.0F - aimScalar);
}
m_pFGArm->SetRotAngle(affectingBodyAngle + adjustedAimAngle);
@@ -3799,12 +3793,13 @@ void AHuman::Update()
// BG Arm rotating, climbing, throw animations, supporting fg weapon
if (m_pBGArm) {
- m_pBGArm->SetRotAngle(std::abs(std::sin(rot)) * rot * m_BGArmFlailScalar + (adjustedAimAngle));
+ float affectingBodyAngle = m_Status < INACTIVE ? m_BGArmFlailScalar : 1.0F;
+ m_pBGArm->SetRotAngle(std::abs(std::sin(rot)) * rot * affectingBodyAngle + adjustedAimAngle);
if (m_Status == STABLE) {
if (m_ArmClimbing[BGROUND]) {
// Can't climb or crawl with the shield
- if (m_MoveState == CLIMB || (m_MoveState == CRAWL && m_ProneState == PRONE)) {
+ if (m_MoveState != CRAWL || m_ProneState == PRONE) {
UnequipBGArm();
}
m_pBGArm->AddHandTarget("Hand AtomGroup Limb Pos", m_pBGHandGroup->GetLimbPos(m_HFlipped));
@@ -3845,23 +3840,19 @@ void AHuman::Update()
/////////////////////////////////
// Arm swinging or device swaying walking animations
- if (m_MoveState == MovementState::WALK && (m_ArmSwingRate > 0 || m_DeviceArmSwayRate > 0)) {
+ if (m_MoveState != MovementState::STAND && (m_ArmSwingRate != 0 || m_DeviceArmSwayRate != 0)) {
for (Arm *arm : { m_pFGArm, m_pBGArm }) {
if (arm && !arm->GetHeldDeviceThisArmIsTryingToSupport()) {
Leg *legToSwingWith = arm == m_pFGArm ? m_pBGLeg : m_pFGLeg;
Leg *otherLeg = legToSwingWith == m_pBGLeg ? m_pFGLeg : m_pBGLeg;
- if (!legToSwingWith) {
+ if (!legToSwingWith || m_MoveState == JUMP || m_MoveState == CROUCH) {
std::swap(legToSwingWith, otherLeg);
}
if (legToSwingWith) {
float armMovementRateToUse = m_ArmSwingRate;
if (HeldDevice *heldDevice = arm->GetHeldDevice()) {
- // For device sway, the Leg doesn't matter, but both HeldDevices (if there are 2) need to use the same Arm or it looks silly!
- if (arm == m_pBGArm && otherLeg) {
- std::swap(legToSwingWith, otherLeg);
- }
- armMovementRateToUse = m_DeviceArmSwayRate * (heldDevice->IsOneHanded() ? 0.5F : 1.0F);
+ armMovementRateToUse = m_DeviceArmSwayRate * (1.0F - m_SharpAimProgress) * std::sin(std::abs(heldDevice->GetStanceOffset().GetAbsRadAngle()));
}
float angleToSwingTo = std::sin(legToSwingWith->GetRotAngle() + (c_HalfPI * GetFlipFactor()));
arm->SetHandIdleRotation(angleToSwingTo * armMovementRateToUse);
@@ -3870,21 +3861,6 @@ void AHuman::Update()
}
}
- // Point HeldDevices that are trying to to a one-handed reload towards the one handed reload angle.
- for (Arm *arm : { m_pFGArm, m_pBGArm }) {
- if (arm) {
- if (HeldDevice *heldDevice = arm->GetHeldDevice(); heldDevice && heldDevice->IsReloading() && arm->GetNextHandTargetDescription() == "Reload Offset") {
- float currentForearmAngle = (arm->GetHandCurrentOffset().GetAbsRadAngle() - (m_HFlipped ? c_PI : 0)) * GetFlipFactor();
- heldDevice->SetInheritedRotAngleOffset(currentForearmAngle - m_OneHandedReloadAngleOffset);
- } else if (heldDevice && heldDevice->DoneReloading()) {
- heldDevice->SetInheritedRotAngleOffset(0);
- if (arm->GetNextHandTargetDescription() == "Reload Offset") {
- arm->RemoveNextHandTarget();
- }
- }
- }
- }
-
/////////////////////////////////////////////////
// Update MovableObject, adds on the forces etc
// NOTE: this also updates the controller, so any setstates of it will be wiped!
@@ -3949,14 +3925,6 @@ void AHuman::Update()
// Add velocity also so the viewpoint moves ahead at high speeds
if (m_Vel.MagnitudeIsGreaterThan(10.0F)) { m_ViewPoint += m_Vel * std::sqrt(m_Vel.GetMagnitude() * 0.1F); }
- /////////////////////////////////////////
- // Gold Chunk inventroy management
-
- if (m_GoldInInventoryChunk <= 0) {
- ChunkGold();
- }
-
-
////////////////////////////////////////
// Balance stuff
@@ -4230,104 +4198,74 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc
}
}
- // Weight and jetpack energy
- if (m_pJetpack && m_Controller.IsState(BODY_JUMP) && m_Status != INACTIVE)
- {
- // Draw empty fuel indicator
- if (m_JetTimeLeft < 100)
- str[0] = m_IconBlinkTimer.AlternateSim(100) ? -26 : -25;
- // Display normal jet icons
- else
- {
- float acceleration = m_pJetpack->EstimateImpulse(false) / std::max(GetMass(), 0.1F);
- if (acceleration > 0.47F) {
- str[0] = -31;
- } else {
- str[0] = acceleration > 0.41F ? -30 : (acceleration > 0.35F ? -29 : -28);
- }
- // Do the blinky blink
- if ((str[0] == -28 || str[0] == -29) && m_IconBlinkTimer.AlternateSim(250)) { str[0] = -27; }
- }
- // null-terminate
- str[1] = 0;
- pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 9, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre);
-
- 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;
- if (m_pFGArm && !m_EquipHUDTimer.IsPastRealMS(500)) {
- std::string equippedItemsString = (m_pFGArm->GetHeldDevice() ? m_pFGArm->GetHeldDevice()->GetPresetName() : "EMPTY") + (m_pBGArm && m_pBGArm->GetHeldDevice() ? " | " + m_pBGArm->GetHeldDevice()->GetPresetName() : "");
- pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 3, equippedItemsString, GUIFont::Centre);
- m_HUDStack -= 9;
- }
- }
- // Held-related GUI stuff
- else if (m_pFGArm || m_pBGArm) {
+ if (m_pFGArm || m_pBGArm) {
+ // Held-related GUI stuff
HDFirearm *fgHeldFirearm = dynamic_cast(GetEquippedItem());
HDFirearm *bgHeldFirearm = dynamic_cast(GetEquippedBGItem());
if (fgHeldFirearm || bgHeldFirearm) {
- str[0] = -56; str[1] = 0;
- pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 10, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Left);
+ str[0] = -56;
+ str[1] = 0;
std::string fgWeaponString = "EMPTY";
if (fgHeldFirearm) {
if (fgHeldFirearm->IsReloading()) {
fgWeaponString = "Reloading";
+ int barColorIndex = 77;
+ if (!fgHeldFirearm->GetSupportAvailable()) {
+ float reloadMultiplier = fgHeldFirearm->GetOneHandedReloadTimeMultiplier();
+ if (reloadMultiplier != 1.0F) {
+ // Add a hand icon next to the ammo icon when reloading without supporting hand.
+ str[0] = -37; str[1] = -49; str[2] = -56; str[3] = 0;
+ if (reloadMultiplier > 1.0F) {
+ if (m_IconBlinkTimer.AlternateSim(250)) { barColorIndex = 13; }
+ } else {
+ barColorIndex = 133;
+ }
+ }
+ }
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 * fgHeldFirearm->GetReloadProgress() + 0.5F), drawPos.GetFloorIntY() + m_HUDStack + 13, 77);
+ rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 12, drawPos.GetFloorIntX() + static_cast(28.0F * fgHeldFirearm->GetReloadProgress() + 0.5F), drawPos.GetFloorIntY() + m_HUDStack + 13, barColorIndex);
} else {
fgWeaponString = fgHeldFirearm->GetRoundInMagCount() < 0 ? "Infinite" : std::to_string(fgHeldFirearm->GetRoundInMagCount());
}
}
+ std::string bgWeaponString;
if (bgHeldFirearm) {
- std::string bgWeaponString;
if (bgHeldFirearm->IsReloading()) {
bgWeaponString = "Reloading";
+ int barColorIndex = 77;
+ if (!bgHeldFirearm->GetSupportAvailable()) {
+ float reloadMultiplier = bgHeldFirearm->GetOneHandedReloadTimeMultiplier();
+ if (reloadMultiplier != 1.0F) {
+ // Add a hand icon next to the ammo icon when reloading without supporting hand.
+ str[0] = -37; str[1] = -49; str[2] = -56; str[3] = 0;
+ if (reloadMultiplier > 1.0F) {
+ if (m_IconBlinkTimer.AlternateSim(250)) { barColorIndex = 13; }
+ } else {
+ barColorIndex = 133;
+ }
+ }
+ }
int totalTextWidth = pSmallFont->CalculateWidth(fgWeaponString) + 6;
rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1 + totalTextWidth, drawPos.GetFloorIntY() + m_HUDStack + 13, drawPos.GetFloorIntX() + 29 + totalTextWidth, drawPos.GetFloorIntY() + m_HUDStack + 14, 245);
- rectfill(pTargetBitmap, drawPos.GetFloorIntX() + totalTextWidth, drawPos.GetFloorIntY() + m_HUDStack + 12, drawPos.GetFloorIntX() + static_cast(28.0F * bgHeldFirearm->GetReloadProgress() + 0.5F) + totalTextWidth, drawPos.GetFloorIntY() + m_HUDStack + 13, 77);
+ rectfill(pTargetBitmap, drawPos.GetFloorIntX() + totalTextWidth, drawPos.GetFloorIntY() + m_HUDStack + 12, drawPos.GetFloorIntX() + static_cast(28.0F * bgHeldFirearm->GetReloadProgress() + 0.5F) + totalTextWidth, drawPos.GetFloorIntY() + m_HUDStack + 13, barColorIndex);
} 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());
}
+ pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - pSymbolFont->CalculateWidth(str) - 3, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Left);
+ std::snprintf(str, sizeof(str), bgHeldFirearm ? "%s | %s" : "%s", fgWeaponString.c_str(), bgWeaponString.c_str());
pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 3, str, GUIFont::Left);
- m_HUDStack -= 10;
+ m_HUDStack -= 9;
}
-
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)
- {
- str[0] = m_GoldPicked ? -57 : -58; str[1] = 0;
- pSymbolFont->DrawAligned(&allegroBitmap, drawPos.m_X - 11, drawPos.m_Y + m_HUDStack, str, GUIFont::Left);
- 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;
- }
-*/
- std::string equippedItemsString = (m_pFGArm && m_pFGArm->GetHeldDevice() ? m_pFGArm->GetHeldDevice()->GetPresetName() : "EMPTY") + (m_pBGArm && m_pBGArm->GetHeldDevice() ? " | " + m_pBGArm->GetHeldDevice()->GetPresetName() : "");
+ std::string equippedItemsString = (fgHeldFirearm ? fgHeldFirearm->GetPresetName() : "EMPTY") + (bgHeldFirearm ? " | " + bgHeldFirearm->GetPresetName() : "");
pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 3, equippedItemsString, 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())
- {
- std::snprintf(str, sizeof(str), " Å“ Reload", pHeldFirearm);
- pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 12, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Left);
- }
-*/
- }
+ }
}
else
{
@@ -4336,6 +4274,43 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc
m_HUDStack -= 9;
}
+ if (m_pJetpack && m_Status != INACTIVE && !m_Controller.IsState(PIE_MENU_ACTIVE) && (m_Controller.IsState(BODY_JUMP) || m_JetTimeLeft < m_JetTimeTotal)) {
+ if (m_JetTimeLeft < 100.0F) {
+ str[0] = m_IconBlinkTimer.AlternateSim(100) ? -26 : -25;
+ } else if (m_pJetpack->IsEmitting()) {
+ float acceleration = m_pJetpack->EstimateImpulse(false) / std::max(GetMass(), 0.1F);
+ if (acceleration > 0.41F) {
+ str[0] = acceleration > 0.47F ? -31 : -30;
+ } else {
+ str[0] = acceleration > 0.35F ? -29 : -28;
+ if (m_IconBlinkTimer.AlternateSim(200)) { str[0] = -27; }
+ }
+ } else {
+ str[0] = -27;
+ }
+ str[1] = 0;
+ pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 7, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre);
+
+ rectfill(pTargetBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 7, drawPos.GetFloorIntX() + 15, drawPos.GetFloorIntY() + m_HUDStack + 8, 245);
+ if (m_JetTimeTotal > 0) {
+ float jetTimeRatio = m_JetTimeLeft / m_JetTimeTotal;
+ int gaugeColor;
+ if (jetTimeRatio > 0.75F) {
+ gaugeColor = 149;
+ } else if (jetTimeRatio > 0.5F) {
+ gaugeColor = 133;
+ } else if (jetTimeRatio > 0.375F) {
+ gaugeColor = 77;
+ } else if (jetTimeRatio > 0.25F) {
+ gaugeColor = 48;
+ } else {
+ gaugeColor = 13;
+ }
+ rectfill(pTargetBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 6, drawPos.GetFloorIntX() + static_cast(15.0F * jetTimeRatio), drawPos.GetFloorIntY() + m_HUDStack + 7, gaugeColor);
+ }
+ m_HUDStack -= 9;
+ }
+
// Pickup GUI
if (!m_Controller.IsState(PIE_MENU_ACTIVE) && m_pItemInReach) {
std::snprintf(str, sizeof(str), " %c %s", -49, m_pItemInReach->GetPresetName().c_str());
@@ -4517,7 +4492,7 @@ int AHuman::WhilePieMenuOpenListener(const PieMenu *pieMenu) {
if (pieSlice->GetType() == PieSlice::SliceType::Pickup) {
if (m_pFGArm || (m_pBGArm && m_pItemInReach->IsOneHanded())) {
- pieSlice->SetEnabled(true);
+ pieSlice->SetEnabled(m_Status != INACTIVE);
pieSlice->SetDescription("Pick Up " + m_pItemInReach->GetPresetName());
} else {
pieSlice->SetEnabled(false);
@@ -4527,7 +4502,7 @@ int AHuman::WhilePieMenuOpenListener(const PieMenu *pieMenu) {
const HeldDevice *fgHeldDevice = dynamic_cast(GetEquippedItem());
const HeldDevice *bgHeldDevice = dynamic_cast(GetEquippedBGItem());
if (fgHeldDevice || bgHeldDevice) {
- pieSlice->SetEnabled((fgHeldDevice && !fgHeldDevice->IsFull()) || (bgHeldDevice && !bgHeldDevice->IsFull()));
+ pieSlice->SetEnabled(m_Status != INACTIVE && ((fgHeldDevice && !fgHeldDevice->IsFull()) || (bgHeldDevice && !bgHeldDevice->IsFull())));
pieSlice->SetDescription("Reload");
} else {
pieSlice->SetEnabled(false);
@@ -4537,7 +4512,7 @@ int AHuman::WhilePieMenuOpenListener(const PieMenu *pieMenu) {
break;
case PieSlice::SliceType::NextItem:
if (!IsInventoryEmpty() && m_pFGArm) {
- pieSlice->SetEnabled(true);
+ pieSlice->SetEnabled(m_Status != INACTIVE);
pieSlice->SetDescription("Next Item");
} else {
pieSlice->SetEnabled(false);
@@ -4546,7 +4521,7 @@ int AHuman::WhilePieMenuOpenListener(const PieMenu *pieMenu) {
break;
case PieSlice::SliceType::PreviousItem:
if (!IsInventoryEmpty() && m_pFGArm) {
- pieSlice->SetEnabled(true);
+ pieSlice->SetEnabled(m_Status != INACTIVE);
pieSlice->SetDescription("Prev Item");
} else {
pieSlice->SetEnabled(false);
@@ -4555,13 +4530,13 @@ int AHuman::WhilePieMenuOpenListener(const PieMenu *pieMenu) {
break;
case PieSlice::SliceType::Drop:
if (const MovableObject *equippedFGItem = GetEquippedItem()) {
- pieSlice->SetEnabled(true);
+ pieSlice->SetEnabled(m_Status != INACTIVE);
pieSlice->SetDescription("Drop " + equippedFGItem->GetPresetName());
} else if (const MovableObject *equippedBGItem = GetEquippedBGItem()) {
pieSlice->SetDescription("Drop " + equippedBGItem->GetPresetName());
- pieSlice->SetEnabled(true);
- } else if (!IsInventoryEmpty()) {
- pieSlice->SetEnabled(true);
+ pieSlice->SetEnabled(m_Status != INACTIVE);
+ } else if (!IsInventoryEmpty() && !m_pFGArm) {
+ pieSlice->SetEnabled(m_Status != INACTIVE);
pieSlice->SetDescription("Drop Inventory");
} else {
pieSlice->SetEnabled(false);
diff --git a/Entities/AHuman.h b/Entities/AHuman.h
index 056de1894..7ced26754 100644
--- a/Entities/AHuman.h
+++ b/Entities/AHuman.h
@@ -156,16 +156,6 @@ DefaultPieMenuNameGetter("Default Human Pie Menu");
void Destroy(bool notInherited = false) override;
-//////////////////////////////////////////////////////////////////////////////////////////
-// Method: GetGoldCarried
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Gets how many ounces of gold this Actor is carrying.
-// Arguments: None.
-// Return value: The current amount of carried gold, in Oz.
-
- float GetGoldCarried() const override { return m_GoldCarried + m_GoldInInventoryChunk; }
-
-
//////////////////////////////////////////////////////////////////////////////////////////
// Method: GetTotalValue
//////////////////////////////////////////////////////////////////////////////////////////
@@ -388,18 +378,6 @@ DefaultPieMenuNameGetter("Default Human Pie Menu");
/// The ratio at which this jetpack follows the aim angle of the user.
void SetJetAngleRange(float newValue) { m_JetAngleRange = newValue; }
- ///
- /// Gets the angle offset that should be added to this AHuman's HDFirearms when they're being reloaded one-handed, in addition to them rotating with the Arm holding them.
- ///
- /// The angle offset that should be added to this AHuman's HDFirearms when they're being reloaded one-handed.
- float GetOneHandedReloadAngleOffset() const { return m_OneHandedReloadAngleOffset; }
-
- ///
- /// Sets the angle offset that should be added to this AHuman's HDFirearms when they're being reloaded one-handed, in addition to them rotating with the Arm holding them.
- ///
- /// The new angle offset that should be added to this AHuman's HDFirearms when they're being reloaded one-handed.
- void SetOneHandedReloadAngleOffset(float newValue) { m_OneHandedReloadAngleOffset = newValue; }
-
/// Gets this AHuman's UpperBodyState.
///
/// This AHuman's UpperBodyState.
@@ -615,7 +593,7 @@ DefaultPieMenuNameGetter("Default Human Pie Menu");
///
/// Unequips whatever is in either of the arms and puts them into the inventory.
///
- void UnequipArms() { UnequipFGArm(); UnequipBGArm(); }
+ void UnequipArms() { UnequipBGArm(); UnequipFGArm(); }
///
/// Gets the FG Arm's HeldDevice. Ownership is NOT transferred.
@@ -991,17 +969,6 @@ DefaultPieMenuNameGetter("Default Human Pie Menu");
protected:
-//////////////////////////////////////////////////////////////////////////////////////////
-// Method: ChunkGold
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Converts an appropriate amount of gold tracked by Actor, and puts it
-// in a MovableObject which is put into inventory.
-// Arguments: None.
-// Return value: None.
-
- void ChunkGold();
-
-
///
/// Draws an aiming aid in front of this AHuman for throwing.
///
@@ -1043,8 +1010,9 @@ DefaultPieMenuNameGetter("Default Human Pie Menu");
float m_JetReplenishRate; //!< A multiplier affecting how fast the jetpack fuel will replenish when not in use. 1 means that jet time replenishes at 2x speed in relation to depletion.
// Ratio at which the jetpack angle follows aim angle
float m_JetAngleRange;
+ bool m_CanActivateBGItem; //!< A flag for whether or not the BG item is waiting to be activated separately. Used for dual-wielding. TODO: Should this be able to be toggled off per actor, device, or controller?
+ bool m_TriggerPulled; //!< Internal flag for whether this AHuman is currently holding down the trigger of a HDFirearm. Used for dual-wielding.
bool m_WaitingToReloadOffhand; //!< A flag for whether or not the offhand HeldDevice is waiting to be reloaded.
- float m_OneHandedReloadAngleOffset; //!< The angle offset that should be added to this AHuman's HDFirearms when they're being reloaded one-handed, in addition to them rotating with the Arm holding them.
// Blink timer
Timer m_IconBlinkTimer;
// Current upper body state.
@@ -1069,8 +1037,6 @@ DefaultPieMenuNameGetter("Default Human Pie Menu");
bool m_StrideStart;
// Times the stride to see if it is taking too long and needs restart
Timer m_StrideTimer;
- // How much gold is carried in an MovableObject in inventory, separate from the actor gold tally.
- int m_GoldInInventoryChunk;
// For timing throws
Timer m_ThrowTmr;
// The duration it takes this AHuman to fully charge a throw.
diff --git a/Entities/Activity.cpp b/Entities/Activity.cpp
index f7afdbc03..fd9c4b6cc 100644
--- a/Entities/Activity.cpp
+++ b/Entities/Activity.cpp
@@ -39,6 +39,7 @@ void Activity::Clear() {
m_IsHuman[player] = player == Players::PlayerOne;
m_PlayerScreen[player] = (player == Players::PlayerOne) ? Players::PlayerOne : Players::NoPlayer;
m_ViewState[player] = ViewState::Normal;
+ m_DeathTimer[player].Reset();
m_Team[player] = Teams::TeamOne;
m_TeamFundsShare[player] = 1.0F;
m_FundsContribution[player] = 0;
@@ -761,6 +762,20 @@ void Activity::Clear() {
return true;
}
+//////////////////////////////////////////////////////////////////////////////////////////
+
+ void Activity::LoseControlOfActor(int player) {
+ if (player >= Players::PlayerOne && player < Players::MaxPlayerCount) {
+ if (Actor *actor = m_ControlledActor[player]; actor && g_MovableMan.IsActor(actor)) {
+ actor->SetControllerMode(Controller::CIM_AI);
+ actor->GetController()->SetDisabled(false);
+ }
+ m_ControlledActor[player] = nullptr;
+ m_ViewState[player] = ViewState::DeathWatch;
+ m_DeathTimer[player].Reset();
+ }
+ }
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Activity::HandleCraftEnteringOrbit(ACraft *orbitedCraft) {
diff --git a/Entities/Activity.h b/Entities/Activity.h
index 51bd81d35..df417ef0b 100644
--- a/Entities/Activity.h
+++ b/Entities/Activity.h
@@ -663,6 +663,12 @@ namespace RTE {
/// An Actor pointer to skip in the sequence.
virtual void SwitchToNextActor(int player, int team, Actor *actorToSkip = 0) { SwitchToPrevOrNextActor(true, player, team, actorToSkip); }
+ ///
+ /// Forces player to lose control of the currently selected Actor, as if it had died.
+ ///
+ /// Which player to lose control of their selected Actor.
+ virtual void LoseControlOfActor(int player);
+
///
/// Handles when an ACraft has left the game scene and entered orbit, though does not delete it. Ownership is NOT transferred, as the ACraft's inventory is just 'unloaded'.
///
@@ -721,6 +727,7 @@ namespace RTE {
int m_PlayerScreen[Players::MaxPlayerCount]; //!< The screen index of each player - only applicable to human players. -1 if AI or other.
ViewState m_ViewState[Players::MaxPlayerCount]; //!< What to be viewing for each player.
+ Timer m_DeathTimer[Players::MaxPlayerCount]; //!< Timers for measuring death view delays.
std::string m_TeamNames[Teams::MaxTeamCount]; //!< Names for each team.
Icon m_TeamIcons[Teams::MaxTeamCount]; //!< Icons for each team.
diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp
index def726c86..96f33037a 100644
--- a/Entities/Actor.cpp
+++ b/Entities/Actor.cpp
@@ -709,36 +709,37 @@ bool Actor::Look(float FOVSpread, float range)
return g_SceneMan.CastSeeRay(m_Team, aimPos, lookVector, ignored, 25, g_SceneMan.GetUnseenResolution(m_Team).GetSmallest() / 2);
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////
-// Method: AddGold
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Adds a certain amount of ounces of gold to this' team's total funds.
-
-void Actor::AddGold(float goldOz)
-{
- g_ActivityMan.GetActivity()->ChangeTeamFunds(goldOz, m_Team);
+void Actor::AddGold(float goldOz) {
+ bool isHumanTeam = g_ActivityMan.GetActivity()->IsHumanTeam(m_Team);
+ if (g_SettingsMan.GetAutomaticGoldDeposit() || !isHumanTeam) {
+ // TODO: Allow AI to reliably deliver gold via craft
+ g_ActivityMan.GetActivity()->ChangeTeamFunds(goldOz, m_Team);
+ } else {
+ m_GoldCarried += goldOz;
+ m_GoldPicked = true;
+ if (isHumanTeam) {
+ for (int player = Players::PlayerOne; player < Players::MaxPlayerCount; player++) {
+ if (g_ActivityMan.GetActivity()->GetTeamOfPlayer(player) == m_Team) { g_GUISound.FundsChangedSound()->Play(player); }
+ }
+ }
+ }
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-
-void Actor::RestDetection()
-{
- MOSRotating::RestDetection();
+void Actor::RestDetection() {
+ MOSRotating::RestDetection();
- if (m_Status != DEAD) {
- m_RestTimer.Reset();
- m_ToSettle = false;
- }
+ if (m_Status != DEAD) {
+ m_AngOscillations = 0;
+ m_VelOscillations = 0;
+ m_RestTimer.Reset();
+ m_ToSettle = false;
+ }
}
-
//////////////////////////////////////////////////////////////////////////////////////////
// Virtual method: AddAIMOWaypoint
//////////////////////////////////////////////////////////////////////////////////////////
@@ -888,7 +889,8 @@ void Actor::DropAllInventory()
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());
+ gibROffset.SetXY(m_SpriteRadius * 0.35F * RandomNum(), 0);
+ gibROffset.RadRotate(c_PI * RandomNormalNum());
// Set up its position and velocity according to the parameters of this AEmitter.
pObject->SetPos(m_Pos + gibROffset);
pObject->SetRotAngle(m_Rotation.GetRadAngle() + pObject->GetRotMatrix().GetRadAngle());
@@ -906,18 +908,17 @@ void Actor::DropAllInventory()
// Gib is too close to center to always make it rotate in one direction, so give it a baseline rotation and then randomize
else
{
- pObject->SetAngularVel((pObject->GetAngularVel() * 0.5F + pObject->GetAngularVel() * RandomNum()) * (RandomNormalNum() > 0.0F ? 1.0F : -1.0F));
+ pObject->SetAngularVel((pObject->GetAngularVel() * RandomNum(0.5F, 1.5F)) * (RandomNum() < 0.5F ? 1.0F : -1.0F));
}
// TODO: Optimize making the random angles!")
gibVel = gibROffset;
if (gibVel.IsZero()) {
gibVel.SetXY(RandomNum(velMin, velMax), 0.0F);
+ gibVel.RadRotate(c_PI * RandomNormalNum());
} 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
// gibVel += (impactImpulse / m_Gibs.size()) / pObject->GetMass();
pObject->SetVel(m_Vel + gibVel);
@@ -948,6 +949,29 @@ void Actor::DropAllInventory()
//////////////////////////////////////////////////////////////////////////////////////////
+void Actor::DropAllGold() {
+ const Material *goldMaterial = g_SceneMan.GetMaterialFromID(g_MaterialGold);
+ float velMin = 3.0F;
+ float velMax = velMin + std::sqrt(m_SpriteRadius);
+
+ for (int i = 0; i < static_cast(std::floor(m_GoldCarried)); i++) {
+ Vector dropOffset(m_SpriteRadius * 0.3F * RandomNum(), 0);
+ dropOffset.RadRotate(c_PI * RandomNormalNum());
+
+ Vector dropVelocity(dropOffset);
+ dropVelocity.SetMagnitude(RandomNum(velMin, velMax));
+
+ Atom *goldMOPixelAtom = new Atom(Vector(), g_MaterialGold, nullptr, goldMaterial->GetColor(), 2);
+
+ MOPixel *goldMOPixel = new MOPixel(goldMaterial->GetColor(), goldMaterial->GetPixelDensity(), m_Pos + dropOffset, dropVelocity, goldMOPixelAtom);
+ goldMOPixel->SetToHitMOs(false);
+ g_MovableMan.AddParticle(goldMOPixel);
+ }
+ m_GoldCarried = 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+
bool Actor::AddToInventoryFront(MovableObject *itemToAdd) {
// This function is called often to add stuff we just removed from our hands, which may be set to delete so we need to guard against that lest we crash.
if (!itemToAdd || itemToAdd->IsSetToDelete()) {
@@ -1516,33 +1540,7 @@ void Actor::Update()
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(); }
-
- Material const * AuMat = g_SceneMan.GetMaterial(std::string("Gold"));
- int goldCount = m_GoldCarried/*std::floor(GetGoldCarried())*/;
- for (int i = 0; i < goldCount; i++)
- {
-/*
- MOPixel *pixelMO = dynamic_cast(MOPixel::InstanceFromPool());
- pixelMO->Create(AuMat.color,
- AuMat.pixelDensity,
- Vector(m_Pos.m_X, m_Pos.m_Y - 10),
- Vector(4 * NormalRand(), RandomNum(-5, -7)),
- new Atom(Vector(), AuMat, 0, AuMat.color, 2),
- 0);
-*/
- MOPixel *pixelMO = new MOPixel(AuMat->GetColor(),
- AuMat->GetPixelDensity(),
- Vector(m_Pos.m_X, m_Pos.m_Y - 10),
- Vector(4.0F * RandomNormalNum(), RandomNum(-5.0F, -7.0F)),
- new Atom(Vector(), AuMat->GetIndex(), 0, AuMat->GetColor(), 2),
- 0);
-
- pixelMO->SetToHitMOs(false);
- pixelMO->SetToGetHitByMOs(false);
- g_MovableMan.AddParticle(pixelMO);
- pixelMO = 0;
- }
- m_GoldCarried = 0;
+ if (m_GoldCarried > 0) { DropAllGold(); }
}
////////////////////////////////
@@ -1815,24 +1813,21 @@ void Actor::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr
m_HUDStack += -12;
- // Gold
- if (GetGoldCarried() > 0) {
- str[0] = m_GoldPicked ? -57 : -58; str[1] = 0;
- pSymbolFont->DrawAligned(&bitmapInt, drawPos.m_X - 11, drawPos.m_Y + m_HUDStack, str, GUIFont::Left);
- std::snprintf(str, sizeof(str), "%.0f oz", GetGoldCarried());
- pSmallFont->DrawAligned(&bitmapInt, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack + 2, str, GUIFont::Left);
-
- m_HUDStack += -11;
- }
+ if (IsPlayerControlled()) {
+ if (GetGoldCarried() > 0) {
+ str[0] = m_GoldPicked ? -57 : -58; str[1] = 0;
+ pSymbolFont->DrawAligned(&bitmapInt, drawPos.GetFloorIntX() - 11, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Left);
+ std::snprintf(str, sizeof(str), "%.0f oz", GetGoldCarried());
+ pSmallFont->DrawAligned(&bitmapInt, drawPos.GetFloorIntX() - 0, drawPos.GetFloorIntY() + m_HUDStack + 2, str, GUIFont::Left);
- // Player name
- if (IsPlayerControlled() && g_FrameMan.IsInMultiplayerMode())
- {
- GameActivity * pGameActivity = dynamic_cast(g_ActivityMan.GetActivity());
- if (pGameActivity)
- {
- pSmallFont->DrawAligned(&bitmapInt, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack + 2, pGameActivity->GetNetworkPlayerName(m_Controller.GetPlayer()).c_str(), GUIFont::Centre);
- m_HUDStack += -11;
+ m_HUDStack -= 11;
+ }
+ // Player name
+ if (g_FrameMan.IsInMultiplayerMode()) {
+ if (GameActivity * gameActivity = dynamic_cast(g_ActivityMan.GetActivity())) {
+ pSmallFont->DrawAligned(&bitmapInt, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 2, gameActivity->GetNetworkPlayerName(m_Controller.GetPlayer()).c_str(), GUIFont::Centre);
+ m_HUDStack -= 11;
+ }
}
}
/* Obsolete
diff --git a/Entities/Actor.h b/Entities/Actor.h
index 0c65f2e2a..d0d1be07e 100644
--- a/Entities/Actor.h
+++ b/Entities/Actor.h
@@ -264,7 +264,7 @@ ClassInfoGetters;
// Arguments: None.
// Return value: The current amount of carried gold, in Oz.
- virtual float GetGoldCarried() const { return m_GoldCarried; }
+ float GetGoldCarried() const { return m_GoldCarried; }
//////////////////////////////////////////////////////////////////////////////////////////
@@ -458,7 +458,7 @@ ClassInfoGetters;
// Arguments: A Status enumeration.
// Return value: None.
- void SetStatus(Actor::Status newStatus) { m_Status = newStatus; }
+ void SetStatus(Actor::Status newStatus) { m_Status = newStatus; if (newStatus == Actor::Status::UNSTABLE) { m_StableRecoverTimer.Reset(); } }
//////////////////////////////////////////////////////////////////////////////////////////
@@ -546,18 +546,6 @@ ClassInfoGetters;
virtual bool Look(float FOVSpread, float range);
-/* Old version, we don't let the actors carry gold anymore, goes directly to the team funds instead
-//////////////////////////////////////////////////////////////////////////////////////////
-// Method: AddGold
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Adds a certain amount of ounces of gold to teh currently carried
-// amount.
-// Arguments: The amount in Oz with which to change the current gol dtally of this
-// Actor.
-// Return value: None.
-
- void AddGold(float goldOz) { m_GoldCarried += std::ceil(goldOz); m_GoldPicked = true; }
-*/
//////////////////////////////////////////////////////////////////////////////////////////
// Method: AddGold
@@ -569,17 +557,10 @@ ClassInfoGetters;
void AddGold(float goldOz);
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-// Arguments: None.
-// Return value: None.
-
- void RestDetection() override;
-
+ ///
+ /// Does the calculations necessary to detect whether this Actor is at rest or not. IsAtRest() retrieves the answer.
+ ///
+ void RestDetection() override;
///
/// Adds health points to this Actor's current health value.
@@ -953,6 +934,10 @@ ClassInfoGetters;
virtual void DropAllInventory();
+ ///
+ /// Converts all of the Gold carried by this Actor into MovableObjects and ejects them into the Scene.
+ ///
+ virtual void DropAllGold();
//////////////////////////////////////////////////////////////////////////////////////////
// Method: GetInventorySize
diff --git a/Entities/Arm.cpp b/Entities/Arm.cpp
index 8d3b8790c..7c4ffa128 100644
--- a/Entities/Arm.cpp
+++ b/Entities/Arm.cpp
@@ -15,7 +15,7 @@ namespace RTE {
m_MaxLength = 0;
m_MoveSpeed = 0;
- m_HandDefaultIdleOffset.Reset();
+ m_HandIdleOffset.Reset();
m_HandIdleRotation = 0;
m_HandCurrentOffset.Reset();
@@ -63,7 +63,7 @@ namespace RTE {
m_MaxLength = reference.m_MaxLength;
m_MoveSpeed = reference.m_MoveSpeed;
- m_HandDefaultIdleOffset = reference.m_HandDefaultIdleOffset;
+ m_HandIdleOffset = reference.m_HandIdleOffset;
m_HandIdleRotation = reference.m_HandIdleRotation;
m_HandCurrentOffset = reference.m_HandCurrentOffset;
@@ -93,8 +93,8 @@ namespace RTE {
reader >> m_MaxLength;
} else if (propName == "MoveSpeed") {
reader >> m_MoveSpeed;
- } else if (propName == "HandDefaultIdleOffset" || propName == "IdleOffset") {
- reader >> m_HandDefaultIdleOffset;
+ } else if (propName == "HandIdleOffset" || propName == "IdleOffset") {
+ reader >> m_HandIdleOffset;
} else if (propName == "HandSprite" || propName == "Hand") {
reader >> m_HandSpriteFile;
m_HandSpriteBitmap = m_HandSpriteFile.GetAsBitmap();
@@ -118,7 +118,7 @@ namespace RTE {
writer.NewPropertyWithValue("MaxLength", m_MaxLength);
writer.NewPropertyWithValue("MoveSpeed", m_MoveSpeed);
- writer.NewPropertyWithValue("HandDefaultIdleOffset", m_HandDefaultIdleOffset);
+ writer.NewPropertyWithValue("HandIdleOffset", m_HandIdleOffset);
writer.NewPropertyWithValue("HandSprite", m_HandSpriteFile);
writer.NewPropertyWithValue("GripStrength", m_GripStrength);
writer.NewPropertyWithValue("ThrowStrength", m_ThrowStrength);
@@ -129,7 +129,7 @@ namespace RTE {
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- void Arm::SetHandCurrentPos(const Vector &newHandPos) {
+ void Arm::SetHandPos(const Vector &newHandPos) {
SetHandCurrentOffset(g_SceneMan.ShortestDistance(m_JointPos, newHandPos, g_SceneMan.SceneWrapsX() || g_SceneMan.SceneWrapsY()));
}
@@ -195,7 +195,7 @@ namespace RTE {
m_AngularVel = 0.0F;
}
- if (m_HeldDeviceThisArmIsTryingToSupport && !m_HeldDeviceThisArmIsTryingToSupport->IsSupportable()) {
+ if (m_HeldDeviceThisArmIsTryingToSupport && (!m_HeldDeviceThisArmIsTryingToSupport->IsSupportable() || m_HeldDeviceThisArmIsTryingToSupport->GetSupportOffset().MagnitudeIsGreaterThan(m_MaxLength * 2.0F))) {
m_HeldDeviceThisArmIsTryingToSupport = nullptr;
}
@@ -238,12 +238,30 @@ namespace RTE {
if (armHasParent) {
Vector targetOffset;
if (m_HandTargets.empty()) {
- if (bool parentIsStable = dynamic_cast(m_Parent)->IsStatus(Actor::Status::STABLE); parentIsStable && m_HeldDevice) {
+ if (m_HeldDevice) {
targetOffset = m_HeldDevice->GetStanceOffset();
- } else if (parentIsStable && m_HeldDeviceThisArmIsTryingToSupport) {
+ if (HDFirearm *heldFirearm = dynamic_cast(m_HeldDevice); heldFirearm && heldFirearm->GetReloadAngle() != 0) {
+ if (heldFirearm->IsReloading()) {
+ float reloadProgressSin = std::sin(heldFirearm->GetReloadProgress() * c_PI);
+ // TODO: There are a few values available for customization here, but they need clear property names. The following plays out well as a default.
+ // Currently, non-supported always move to the same angle relative to the body. Supported items move halfway between the aim angle and body rotation.
+ // What needs to be decided upon is the property name(s) for the rate at which both the two-handed and one-handed reload angles move between the aim angle and body rotation.
+ float noSupportFactor = std::min(std::abs(heldFirearm->GetReloadAngle()), 1.0F);
+ float inheritedBodyAngle = m_Rotation.GetRadAngle() * noSupportFactor;
+ // m_Rotation corresponds to the aim angle here, since the arm hasn't been adjusted yet.
+ float reloadAngle = (heldFirearm->GetReloadAngle() - inheritedBodyAngle * GetFlipFactor()) * reloadProgressSin;
+ heldFirearm->SetInheritedRotAngleOffset(reloadAngle);
+ targetOffset.RadRotate(reloadAngle * GetFlipFactor());
+ float retractionRate = 0.5F * noSupportFactor; // Another value potentially open for customization.
+ targetOffset.SetMagnitude(targetOffset.GetMagnitude() * (1.0F - reloadProgressSin * retractionRate));
+ } else if (heldFirearm->DoneReloading()) {
+ heldFirearm->SetInheritedRotAngleOffset(0);
+ }
+ }
+ } else if (bool parentIsStable = dynamic_cast(m_Parent)->IsStatus(Actor::Status::STABLE); parentIsStable && m_HeldDeviceThisArmIsTryingToSupport) {
targetOffset = g_SceneMan.ShortestDistance(m_JointPos, m_HeldDeviceThisArmIsTryingToSupport->GetSupportPos(), g_SceneMan.SceneWrapsX() || g_SceneMan.SceneWrapsY());
} else {
- targetOffset = m_HandDefaultIdleOffset.GetXFlipped(m_Parent->IsHFlipped()).GetRadRotatedCopy(m_Parent->GetRotAngle());
+ targetOffset = m_HandIdleOffset.GetXFlipped(m_Parent->IsHFlipped()).GetRadRotatedCopy(m_Parent->GetRotAngle());
}
if (m_HandIdleRotation != 0) {
targetOffset.RadRotate(m_HandIdleRotation);
@@ -288,18 +306,17 @@ namespace RTE {
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Arm::AccountForHeldDeviceRecoil(const HeldDevice *heldDevice, Vector &targetOffset) {
- // TODO: Fine-tune this if needed.
if (!heldDevice->GetRecoilForce().IsZero()) {
float totalGripStrength = m_GripStrength * heldDevice->GetGripStrengthMultiplier();
if (totalGripStrength == 0) { totalGripStrength = heldDevice->GetJointStrength(); }
if (heldDevice->GetSupported()) {
const AHuman *rootParentAsAHuman = dynamic_cast(GetRootParent());
- const Arm *rootParentAsHumanBGArm = rootParentAsAHuman ? rootParentAsAHuman->GetBGArm() : nullptr;
- if (rootParentAsHumanBGArm) {
- if (rootParentAsHumanBGArm->GetGripStrength() < 0) {
+ const Arm *supportingArm = rootParentAsAHuman ? rootParentAsAHuman->GetBGArm() : nullptr;
+ if (supportingArm) {
+ if (supportingArm->GetGripStrength() < 0) {
totalGripStrength = -1.0F;
- } else if (rootParentAsHumanBGArm->GetGripStrength() > 0) {
- totalGripStrength += (rootParentAsHumanBGArm->GetGripStrength() * heldDevice->GetGripStrengthMultiplier());
+ } else if (supportingArm->GetGripStrength() > 0) {
+ totalGripStrength += (supportingArm->GetGripStrength() * 0.5F * heldDevice->GetGripStrengthMultiplier());
} else {
totalGripStrength *= 1.5F;
}
@@ -308,7 +325,7 @@ namespace RTE {
if (totalGripStrength > 0) {
// 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;
+ float recoilScalar = std::sqrt(std::min(heldDevice->GetRecoilForce().GetMagnitude() / totalGripStrength, 0.7F)) * 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.
diff --git a/Entities/Arm.h b/Entities/Arm.h
index a137da209..c9a983980 100644
--- a/Entities/Arm.h
+++ b/Entities/Arm.h
@@ -79,13 +79,13 @@ namespace RTE {
/// Gets the default idle offset of this Arm's hand, i.e. the default offset from the joint position that this Arm will try to move to when not moving towards a position.
///
/// The idle offset of this Arm's hand.
- Vector GetHandDefaultIdleOffset() const { return m_HandDefaultIdleOffset; }
+ Vector GetHandIdleOffset() const { return m_HandIdleOffset; }
///
/// Sets the default idle offset of this Arm's hand, i.e. the default offset from the joint position that this Arm will try to move to when not moving towards a position.
///
/// The new idle offset of this Arm's hand.
- void SetHandDefaultIdleOffset(const Vector &newDefaultIdleOffset) { m_HandDefaultIdleOffset = newDefaultIdleOffset; }
+ void SetHandIdleOffset(const Vector &newDefaultIdleOffset) { m_HandIdleOffset = newDefaultIdleOffset; }
///
/// Gets the rotation that is being applied to this Arm's hand, if it's using an idle offset.
@@ -116,13 +116,13 @@ namespace RTE {
/// Gets the current position of this Arm's hand in absolute Scene coordinates.
///
/// The current position of this Arm's hand in absolute Scene coordinates.
- Vector GetHandCurrentPos() const { return m_JointPos + m_HandCurrentOffset; }
+ Vector GetHandPos() const { return m_JointPos + m_HandCurrentOffset; }
///
/// Sets the current position of this Arm's hand to an absolute scene coordinate. If needed, the set position is modified so its distance from the joint position of the Arm is capped to the max length of the Arm.
///
/// The new current position of this Arm's hand as absolute scene coordinate.
- void SetHandCurrentPos(const Vector &newHandPos);
+ void SetHandPos(const Vector &newHandPos);
///
/// Gets the the strength with which this Arm will grip its HeldDevice.
@@ -293,7 +293,7 @@ namespace RTE {
float m_MaxLength; //!< The maximum length of this Arm when fully extended, i.e. the length of the straight Arm sprite.
float m_MoveSpeed; //!< How quickly this Arm moves between targets. 0.0 means it doesn't move at all, 1.0 means it moves instantly.
- Vector m_HandDefaultIdleOffset; //!< The default offset that this Arm's hand should move to when not moving towards anything else, relative to its joint position. Other offsets are used under certain circumstances.
+ Vector m_HandIdleOffset; //!< The default offset that this Arm's hand should move to when not moving towards anything else, relative to its joint position. Other offsets are used under certain circumstances.
float m_HandIdleRotation; //!< The rotation to be applied to the idle offset, when it's being used. Resets every update to avoid locking it.
Vector m_HandCurrentOffset; //!< The current offset of this Arm's hand, relative to its joint position.
diff --git a/Entities/Attachable.cpp b/Entities/Attachable.cpp
index 7218d16aa..30b300d52 100644
--- a/Entities/Attachable.cpp
+++ b/Entities/Attachable.cpp
@@ -17,6 +17,7 @@ namespace RTE {
m_DrawAfterParent = true;
m_DrawnNormallyByParent = true;
m_DeleteWhenRemovedFromParent = false;
+ m_GibWhenRemovedFromParent = false;
m_ApplyTransferredForcesAtOffset = true;
m_GibWithParentChance = 0.0F;
@@ -40,6 +41,7 @@ namespace RTE {
m_AtomSubgroupID = -1L;
m_CollidesWithTerrainWhileAttached = true;
+ m_IgnoresParticlesWhileAttached = false;
m_PieSlices.clear();
@@ -68,6 +70,7 @@ namespace RTE {
m_DrawAfterParent = reference.m_DrawAfterParent;
m_DrawnNormallyByParent = reference.m_DrawnNormallyByParent;
m_DeleteWhenRemovedFromParent = reference.m_DeleteWhenRemovedFromParent;
+ m_GibWhenRemovedFromParent = reference.m_GibWhenRemovedFromParent;
m_ApplyTransferredForcesAtOffset = reference.m_ApplyTransferredForcesAtOffset;
m_GibWithParentChance = reference.m_GibWithParentChance;
@@ -91,6 +94,7 @@ namespace RTE {
m_AtomSubgroupID = GetUniqueID();
m_CollidesWithTerrainWhileAttached = reference.m_CollidesWithTerrainWhileAttached;
+ m_IgnoresParticlesWhileAttached = reference.m_IgnoresParticlesWhileAttached;
for (const std::unique_ptr &pieSlice : reference.m_PieSlices) {
m_PieSlices.emplace_back(std::unique_ptr(dynamic_cast(pieSlice->Clone())));
@@ -110,6 +114,8 @@ namespace RTE {
reader >> m_DrawAfterParent;
} else if (propName == "DeleteWhenRemovedFromParent") {
reader >> m_DeleteWhenRemovedFromParent;
+ } else if (propName == "GibWhenRemovedFromParent") {
+ reader >> m_GibWhenRemovedFromParent;
} else if (propName == "ApplyTransferredForcesAtOffset") {
reader >> m_ApplyTransferredForcesAtOffset;
} else if (propName == "GibWithParentChance") {
@@ -142,6 +148,8 @@ namespace RTE {
reader >> m_InheritsFrame;
} else if (propName == "CollidesWithTerrainWhileAttached") {
reader >> m_CollidesWithTerrainWhileAttached;
+ } else if (propName == "IgnoresParticlesWhileAttached") {
+ reader >> m_IgnoresParticlesWhileAttached;
} else if (propName == "AddPieSlice") {
m_PieSlices.emplace_back(std::unique_ptr(dynamic_cast(g_PresetMan.ReadReflectedPreset(reader))));
} else {
@@ -159,6 +167,7 @@ namespace RTE {
writer.NewPropertyWithValue("ParentOffset", m_ParentOffset);
writer.NewPropertyWithValue("DrawAfterParent", m_DrawAfterParent);
writer.NewPropertyWithValue("DeleteWhenRemovedFromParent", m_DeleteWhenRemovedFromParent);
+ writer.NewPropertyWithValue("GibWhenRemovedFromParent", m_GibWhenRemovedFromParent);
writer.NewPropertyWithValue("ApplyTransferredForcesAtOffset", m_ApplyTransferredForcesAtOffset);
writer.NewPropertyWithValue("JointStrength", m_JointStrength);
@@ -173,6 +182,7 @@ namespace RTE {
writer.NewPropertyWithValue("InheritedRotAngleOffset", m_InheritedRotAngleOffset);
writer.NewPropertyWithValue("CollidesWithTerrainWhileAttached", m_CollidesWithTerrainWhileAttached);
+ writer.NewPropertyWithValue("IgnoresParticlesWhileAttached", m_IgnoresParticlesWhileAttached);
for (const std::unique_ptr &pieSlice : m_PieSlices) {
writer.NewPropertyWithValue("AddPieSlice", pieSlice.get());
@@ -191,12 +201,7 @@ namespace RTE {
return true;
}
- Vector totalForce;
- for (const auto &[force, forceOffset] : m_Forces) {
- totalForce += force;
- }
-
- jointForces += totalForce;
+ jointForces += GetTotalForce();
m_Forces.clear();
return true;
}
@@ -287,6 +292,15 @@ namespace RTE {
return m_CollidesWithTerrainWhileAttached;
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ bool Attachable::CollideAtPoint(HitData &hd) {
+ if (m_IgnoresParticlesWhileAttached && m_Parent && !m_Parent->ToDelete() && !dynamic_cast(hd.Body[HITOR])) {
+ return false;
+ }
+ return MOSRotating::CollideAtPoint(hd);
+ }
+
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool Attachable::ParticlePenetration(HitData &hd) {
diff --git a/Entities/Attachable.h b/Entities/Attachable.h
index 82c43c713..7c8403fbb 100644
--- a/Entities/Attachable.h
+++ b/Entities/Attachable.h
@@ -136,16 +136,28 @@ namespace RTE {
void SetDrawnNormallyByParent(bool drawnNormallyByParent) { m_DrawnNormallyByParent = drawnNormallyByParent; }
///
- /// Gets whether this Attachable will be deleted when it's removed from its parent. Has no effect until the Attachable is added to a parent.
+ /// Gets whether this Attachable will be deleted when removed from its parent. Has no effect until the Attachable has been added to a parent.
///
- /// Whether this Attachable is marked to be deleted when it's removed from its parent or not.
+ /// Whether this Attachable is marked to be deleted when removed from its parent or not.
bool GetDeleteWhenRemovedFromParent() const { return m_DeleteWhenRemovedFromParent; }
///
- /// Sets whether this Attachable will be deleted when it's removed from its parent.
+ /// Sets whether this Attachable will be deleted when removed from its parent.
///
- /// Whether this Attachable should be deleted when it's removed from its parent.
- virtual void SetDeleteWhenRemovedFromParent(bool deleteWhenRemovedFromParent) { m_DeleteWhenRemovedFromParent = deleteWhenRemovedFromParent; }
+ /// Whether this Attachable should be deleted when removed from its parent.
+ void SetDeleteWhenRemovedFromParent(bool deleteWhenRemovedFromParent) { m_DeleteWhenRemovedFromParent = deleteWhenRemovedFromParent; }
+
+ ///
+ /// Gets whether this Attachable will gib when removed from its parent. Has no effect until the Attachable has been added to a parent.
+ ///
+ /// Whether this Attachable is marked to gib when removed from its parent or not.
+ bool GetGibWhenRemovedFromParent() const { return m_GibWhenRemovedFromParent; }
+
+ ///
+ /// Sets whether this Attachable will gib when removed from its parent.
+ ///
+ /// Whether this Attachable should gib when removed from its parent.
+ void SetGibWhenRemovedFromParent(bool gibWhenRemovedFromParent) { m_GibWhenRemovedFromParent = gibWhenRemovedFromParent; }
///
/// Gets whether forces transferred from this Attachable should be applied at its parent's offset (rotated to match the parent) where they will produce torque, or directly at its parent's position.
@@ -391,9 +403,29 @@ namespace RTE {
///
/// Whether this Attachable is currently able to collide with terrain, taking into account its terrain collision settings and those of its parent and so on.
bool CanCollideWithTerrain() const;
+
+ ///
+ /// Gets whether this Attachable currently ignores collisions with single-atom particles.
+ ///
+ /// >Whether this attachable ignores collisions with single-atom particles.
+ bool GetIgnoresParticlesWhileAttached() const { return m_IgnoresParticlesWhileAttached; }
+
+ ///
+ /// Sets whether this Attachable currently ignores collisions with single-atom particles.
+ ///
+ /// Whether this attachable ignores collisions with single-atom particles.
+ void SetIgnoresParticlesWhileAttached(bool ignoresParticlesWhileAttached) { m_IgnoresParticlesWhileAttached = ignoresParticlesWhileAttached; }
#pragma endregion
#pragma region Override Methods
+ ///
+ /// Calculates the collision response when another MO's Atom collides with this MO's physical representation.
+ /// The effects will be applied directly to this MO, and also represented in the passed in HitData.
+ ///
+ /// Reference to the HitData struct which describes the collision. This will be modified to represent the results of the collision.
+ /// Whether the collision has been deemed valid. If false, then disregard any impulses in the HitData.
+ bool CollideAtPoint(HitData &hitData) override;
+
///
/// Determines whether a particle which has hit this MO will penetrate, and if so, whether it gets lodged or exits on the other side of this MO.
/// Appropriate effects will be determined and applied ONLY IF there was penetration! If not, nothing will be affected.
@@ -529,7 +561,8 @@ namespace RTE {
Vector m_ParentOffset; //!< The offset from the parent's Pos to the joint point this Attachable is attached with.
bool m_DrawAfterParent; //!< Whether to draw this Attachable after (in front of) or before (behind) the parent.
bool m_DrawnNormallyByParent; //!< Whether this Attachable will be drawn normally when attached, or will require special handling by some non-MOSR parent type.
- bool m_DeleteWhenRemovedFromParent; //!< Whether this Attachable should be deleted when it's removed from its parent.
+ bool m_DeleteWhenRemovedFromParent; //!< Whether this Attachable should be deleted when removed from its parent.
+ bool m_GibWhenRemovedFromParent; //!< Whether this Attachable should gib when removed from its parent.
bool m_ApplyTransferredForcesAtOffset; //!< Whether forces transferred from this Attachable should be applied at the rotated parent offset (which will produce torque), or directly at the parent's position. Mostly useful to make jetpacks and similar emitters viable.
float m_GibWithParentChance; //!< The percentage chance that this Attachable will gib when its parent does. 0 means never, 1 means always.
@@ -554,6 +587,7 @@ namespace RTE {
long m_AtomSubgroupID; //!< The Atom IDs this' atoms will have when attached and added to a parent's AtomGroup.
bool m_CollidesWithTerrainWhileAttached; //!< Whether this attachable currently has terrain collisions enabled while it's attached to a parent.
+ bool m_IgnoresParticlesWhileAttached; //!< Whether this Attachable should ignore collisions with single-atom MOs while attached.
std::vector> m_PieSlices; //!< The vector of PieSlices belonging to this Attachable. Added to and removed from the RootParent as appropriate, when a parent is set.
diff --git a/Entities/Gib.cpp b/Entities/Gib.cpp
index 5f14258b9..93e37a6f5 100644
--- a/Entities/Gib.cpp
+++ b/Entities/Gib.cpp
@@ -16,7 +16,7 @@ namespace RTE {
m_MinVelocity = 0;
m_MaxVelocity = 0;
m_LifeVariation = 0.1F;
- m_InheritsVel = true;
+ m_InheritsVel = 1.0F;
m_IgnoresTeamHits = false;
m_SpreadMode = SpreadMode::SpreadRandom;
}
diff --git a/Entities/Gib.h b/Entities/Gib.h
index 9d241b31f..23ddf10f2 100644
--- a/Entities/Gib.h
+++ b/Entities/Gib.h
@@ -113,10 +113,10 @@ namespace RTE {
float GetLifeVariation() const { return m_LifeVariation; }
///
- /// Gets whether this Gib's GibParticles should inherit the velocity of the gibbing parent.
+ /// Gets how much of the gibbing parent's velocity this Gib's GibParticles should inherit.
///
- /// Whether this Gib's GibParticles should inherit the velocity of the gibbing parent.
- bool InheritsVelocity() const { return m_InheritsVel; }
+ /// The proportion of inherited velocity as a scalar from 0 to 1.
+ float InheritsVelocity() const { return m_InheritsVel; }
///
/// Gets whether this Gib's GibParticles should ignore hits with the team of the gibbing parent.
@@ -146,7 +146,7 @@ namespace RTE {
float m_MinVelocity; //!< The minimum velocity a GibParticle object can have when spawned.
float m_MaxVelocity; //!< The maximum velocity a GibParticle object can have when spawned.
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.
+ float m_InheritsVel; //!< How much of the exploding parent's velocity this Gib should inherit.
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.
diff --git a/Entities/HDFirearm.cpp b/Entities/HDFirearm.cpp
index 995e25c63..90e3146e3 100644
--- a/Entities/HDFirearm.cpp
+++ b/Entities/HDFirearm.cpp
@@ -58,7 +58,9 @@ void HDFirearm::Clear()
m_FireIgnoresThis = true;
m_Reloadable = true;
m_DualReloadable = false;
- m_OneHandedReloadTimeMultiplier = 1.0F;
+ m_OneHandedReloadTimeMultiplier = 1.5F;
+ m_ReloadAngle = -0.5F;
+ m_OneHandedReloadAngle = -1.0F;
m_ShakeRange = 0;
m_SharpShakeRange = 0;
m_NoSupportFactor = 0;
@@ -140,6 +142,8 @@ int HDFirearm::Create(const HDFirearm &reference) {
m_Reloadable = reference.m_Reloadable;
m_DualReloadable = reference.m_DualReloadable;
m_OneHandedReloadTimeMultiplier = reference.m_OneHandedReloadTimeMultiplier;
+ m_ReloadAngle = reference.m_ReloadAngle;
+ m_OneHandedReloadAngle = reference.m_OneHandedReloadAngle;
m_ShakeRange = reference.m_ShakeRange;
m_SharpShakeRange = reference.m_SharpShakeRange;
m_NoSupportFactor = reference.m_NoSupportFactor;
@@ -217,6 +221,10 @@ int HDFirearm::ReadProperty(const std::string_view &propName, Reader &reader) {
reader >> m_DualReloadable;
} else if (propName == "OneHandedReloadTimeMultiplier") {
reader >> m_OneHandedReloadTimeMultiplier;
+ } else if (propName == "ReloadAngle") {
+ reader >> m_ReloadAngle;
+ } else if (propName == "OneHandedReloadAngle") {
+ reader >> m_OneHandedReloadAngle;
} else if (propName == "RecoilTransmission") {
reader >> m_JointStiffness;
} else if (propName == "IsAnimatedManually") {
@@ -303,6 +311,8 @@ int HDFirearm::Save(Writer &writer) const
writer.NewProperty("Reloadable");
writer.NewPropertyWithValue("DualReloadable", m_DualReloadable);
writer.NewPropertyWithValue("OneHandedReloadTimeMultiplier", m_OneHandedReloadTimeMultiplier);
+ writer.NewPropertyWithValue("ReloadAngle", m_ReloadAngle);
+ writer.NewPropertyWithValue("OneHandedReloadAngle", m_OneHandedReloadAngle);
writer << m_Reloadable;
writer.NewProperty("RecoilTransmission");
writer << m_JointStiffness;
@@ -625,23 +635,14 @@ Vector HDFirearm::GetMuzzlePos() const
return m_Pos + RotateOffset(m_MuzzleOff);
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-
-void HDFirearm::RestDetection()
-{
- HeldDevice::RestDetection();
+void HDFirearm::RestDetection() {
+ HeldDevice::RestDetection();
- if (m_FiredOnce)
- m_RestTimer.Reset();
+ if (m_FiredOnce) { m_RestTimer.Reset(); }
}
-
//////////////////////////////////////////////////////////////////////////////////////////
// Virtual method: Activate
//////////////////////////////////////////////////////////////////////////////////////////
@@ -808,34 +809,24 @@ void HDFirearm::Update()
{
if (m_Activated && !(m_PreFireSound && m_PreFireSound->IsBeingPlayed())) {
- // Full auto
- if (m_FullAuto)
- {
- // ms per Round.
- double mspr = (long double)60000 / (long double)m_RateOfFire;
- // First round should fly as soon as activated and the delays are taken into account
- if (!m_FiredOnce && (m_LastFireTmr.GetElapsedSimTimeMS() - m_DeactivationDelay - m_ActivationDelay) > mspr)
- {
- roundsFired = 1;
- // Wind back the last fire timer appropriately for the first round, but not farther back than 0
- m_LastFireTmr.SetElapsedSimTimeMS(MAX(m_LastFireTmr.GetElapsedSimTimeMS() - mspr, 0));
- }
- // How many rounds are going to fly since holding down activation. Make sure gun can't be fired faster by tapping activation fast
- if (m_LastFireTmr.GetElapsedSimTimeMS() > (m_ActivationTimer.GetElapsedSimTimeMS() - m_ActivationDelay))
- roundsFired += (m_ActivationTimer.GetElapsedSimTimeMS() - m_ActivationDelay) / mspr;
- else
- roundsFired += m_LastFireTmr.GetElapsedSimTimeMS() / mspr;
- }
- // Semi-auto
- else
- {
- double mspr = (long double)60000 / (long double)m_RateOfFire;
-// TODO: Confirm that the delays work properly in semi-auto!
- if (!m_FiredOnce && (m_LastFireTmr.GetElapsedSimTimeMS() - m_ActivationDelay - m_DeactivationDelay) > mspr)
- roundsFired = 1;
- else
- roundsFired = 0;
- }
+ double msPerRound = GetMSPerRound();
+ if (m_FullAuto) {
+ // First round should fly as soon as activated and the delays are taken into account
+ if (!m_FiredOnce && (m_LastFireTmr.GetElapsedSimTimeMS() - m_DeactivationDelay - m_ActivationDelay) > msPerRound) {
+ roundsFired = 1;
+ // Wind back the last fire timer appropriately for the first round, but not farther back than 0
+ m_LastFireTmr.SetElapsedSimTimeMS(std::max(m_LastFireTmr.GetElapsedSimTimeMS() - msPerRound, 0.0));
+ }
+ // How many rounds are going to fly since holding down activation. Make sure gun can't be fired faster by tapping activation fast
+ if (m_LastFireTmr.GetElapsedSimTimeMS() > (m_ActivationTimer.GetElapsedSimTimeMS() - m_ActivationDelay)) {
+ roundsFired += (m_ActivationTimer.GetElapsedSimTimeMS() - m_ActivationDelay) / msPerRound;
+ } else {
+ roundsFired += m_LastFireTmr.GetElapsedSimTimeMS() / msPerRound;
+ }
+ } else {
+ // TODO: Confirm that the delays work properly in semi-auto!
+ roundsFired = !m_FiredOnce && (m_LastFireTmr.GetElapsedSimTimeMS() - m_ActivationDelay - m_DeactivationDelay) > msPerRound ? 1 : 0;
+ }
if (roundsFired >= 1)
{
@@ -1015,6 +1006,9 @@ void HDFirearm::Update()
//////////////////////////////////////////////
// Recoil and other activation effects logic.
+ // TODO: don't use arbitrary numbers?
+ m_RecoilForce.SetMagnitude(std::max(m_RecoilForce.GetMagnitude() * 0.7F - 1.0F, 0.0F));
+
if (roundsFired > 0) {
// Alternate to get that shake effect!
m_Recoiled = !m_Recoiled;
@@ -1028,7 +1022,7 @@ void HDFirearm::Update()
// Set up the recoil shake offset
m_RecoilOffset = m_RecoilForce;
- m_RecoilOffset.SetMagnitude(std::min(m_RecoilOffset.GetMagnitude(), 1.2F));
+ m_RecoilOffset.SetMagnitude(std::min(m_RecoilOffset.GetMagnitude(), 1.0F));
}
// Screen shake
@@ -1066,12 +1060,6 @@ void HDFirearm::Update()
if (m_Loudness > 0) { g_MovableMan.RegisterAlarmEvent(AlarmEvent(m_Pos, m_Team, m_Loudness)); }
} else {
m_Recoiled = false;
- // TODO: don't use arbitrary numbers? (see Arm.cpp)
- if (m_RecoilForce.MagnitudeIsGreaterThan(0.01F)) {
- m_RecoilForce *= 0.6F;
- } else {
- m_RecoilForce.Reset();
- }
if (!m_IsAnimatedManually) { m_Frame = 0; }
}
diff --git a/Entities/HDFirearm.h b/Entities/HDFirearm.h
index 8f82bda12..80b35ef87 100644
--- a/Entities/HDFirearm.h
+++ b/Entities/HDFirearm.h
@@ -130,6 +130,12 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload");
void SetRateOfFire(int newRate) { m_RateOfFire = newRate; }
+ ///
+ /// Gets the minimum time in between shots, in MS.
+ ///
+ /// The minimum time in between shots, in MS.
+ double GetMSPerRound() const { return 60000.0 / static_cast(m_RateOfFire); }
+
///
/// Gets the Magazine of this HDFirearm.
///
@@ -271,6 +277,12 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload");
/// The new multiplier to be applied to reload time when this HDFirearm is being reloaded one-handed.
void SetOneHandedReloadTimeMultiplier(float newOneHandedReloadTimeMultiplier) { m_OneHandedReloadTimeMultiplier = newOneHandedReloadTimeMultiplier; }
+ ///
+ /// Gets the default reload angle offset, if support is available, or the one handed reload angle offset, if not.
+ ///
+ /// The appropriate reload angle to use, in radians.
+ float GetReloadAngle() const { return m_SupportAvailable ? m_ReloadAngle : m_OneHandedReloadAngle; }
+
//////////////////////////////////////////////////////////////////////////////////////////
// Method: GetShakeRange
@@ -591,16 +603,10 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload");
/// The reload progress as a scalar from 0 to 1.
float GetReloadProgress() const { return IsReloading() && m_BaseReloadTime > 0 ? static_cast(m_ReloadTmr.SimTimeLimitProgress()) : 1.0F; }
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-// Arguments: None.
-// Return value: None.
-
- void RestDetection() override;
+ ///
+ /// Does the calculations necessary to detect whether this HDFirearm is at rest or not. IsAtRest() retrieves the answer.
+ ///
+ void RestDetection() override;
//////////////////////////////////////////////////////////////////////////////////////////
@@ -787,6 +793,17 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload");
bool FiredFrame() const { return m_FireFrame; }
+ ///
+ /// Gets whether this HDFirearm is ready to be fired.
+ ///
+ /// Whether this HDFirearm is ready to pop another Round.
+ bool CanFire() const { return m_ActivationTimer.IsPastSimMS(GetMSPerRound()); }
+
+ ///
+ /// Gets whether this HDFirearm is halfway to be fired. Used for evenly spacing out dual-wielded fire.
+ ///
+ /// Whether this HDFirearm is halfway to pop another Round.
+ bool HalfwayToNextRound() const { return m_LastFireTmr.IsPastSimMS(GetMSPerRound() / 2.0); }
//////////////////////////////////////////////////////////////////////////////////////////
// Method: RoundsFired
@@ -876,6 +893,8 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload");
bool m_Reloadable; //!< Whether this HDFirearm is reloadable by normal means.
float m_OneHandedReloadTimeMultiplier; //!< The multiplier for how long this weapon takes to reload when being used one-handed. Only relevant for one-handed weapons.
bool m_DualReloadable; //!< Whether or not this weapon can be dual-reloaded, i.e. both guns can reload at once instead of having to wait til the other dual-wielded gun isn't being reloaded. Only relevant for one-handed weapons.
+ float m_ReloadAngle; //!< The angle offset for the default reload animation, in radians.
+ float m_OneHandedReloadAngle; //!< The angle offset for one-handed reload animation, in radians.
// Timer for timing how long ago the last round was fired.
Timer m_LastFireTmr;
diff --git a/Entities/HeldDevice.cpp b/Entities/HeldDevice.cpp
index a9cc86db6..5050c1b12 100644
--- a/Entities/HeldDevice.cpp
+++ b/Entities/HeldDevice.cpp
@@ -569,11 +569,13 @@ void HeldDevice::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whi
const GameActivity *gameActivity = dynamic_cast(activity);
if (gameActivity && gameActivity->GetViewState(viewingPlayer) == GameActivity::ViewState::ActorSelect) { unheldItemDisplayRange = -1.0F; }
}
- // Note - to avoid item HUDs flickering in and out, we need to add a little leeway when hiding them if they're already displayed.
- if (m_SeenByPlayer.at(viewingPlayer) && unheldItemDisplayRange > 0) { unheldItemDisplayRange += 3.0F; }
- m_SeenByPlayer.at(viewingPlayer) = unheldItemDisplayRange < 0 || (unheldItemDisplayRange > 0 && g_SceneMan.ShortestDistance(m_Pos, g_CameraMan.GetScrollTarget(whichScreen), g_SceneMan.SceneWrapsX()).MagnitudeIsLessThan(unheldItemDisplayRange));
+ if (!m_SeenByPlayer[viewingPlayer]) {
+ m_SeenByPlayer[viewingPlayer] = unheldItemDisplayRange < 0 || (unheldItemDisplayRange > 0 && m_Vel.MagnitudeIsLessThan(2.0F) && g_SceneMan.ShortestDistance(m_Pos, g_CameraMan.GetScrollTarget(whichScreen), g_SceneMan.SceneWrapsX()).MagnitudeIsLessThan(unheldItemDisplayRange));
+ } else {
+ // Note - to avoid item HUDs flickering in and out, we need to add a little leeway when hiding them if they're already displayed.
+ if (unheldItemDisplayRange > 0) { unheldItemDisplayRange += 4.0F; }
+ m_SeenByPlayer.at(viewingPlayer) = unheldItemDisplayRange < 0 || (unheldItemDisplayRange > 0 && g_SceneMan.ShortestDistance(m_Pos, g_CameraMan.GetScrollTarget(whichScreen), g_SceneMan.SceneWrapsX()).MagnitudeIsLessThan(unheldItemDisplayRange));
- if (m_SeenByPlayer.at(viewingPlayer)) {
char pickupArrowString[64];
pickupArrowString[0] = 0;
if (m_BlinkTimer.GetElapsedSimTimeMS() < 250) {
diff --git a/Entities/MOPixel.cpp b/Entities/MOPixel.cpp
index 0f3061017..59d5fe190 100644
--- a/Entities/MOPixel.cpp
+++ b/Entities/MOPixel.cpp
@@ -12,8 +12,7 @@ namespace RTE {
void MOPixel::Clear() {
m_Atom = 0;
m_Color.Reset();
- m_DistanceTraveled = 0;
- m_LethalRange = std::max(g_FrameMan.GetPlayerScreenWidth(), g_FrameMan.GetPlayerScreenHeight()) / c_MPP;
+ m_LethalRange = std::max(g_FrameMan.GetPlayerScreenWidth(), g_FrameMan.GetPlayerScreenHeight());
m_MinLethalRange = 1;
m_MaxLethalRange = 1;
m_LethalSharpness = 1;
@@ -128,9 +127,6 @@ namespace RTE {
void MOPixel::SetLethalRange(float range) {
m_LethalRange = range;
if (m_MinLethalRange < m_MaxLethalRange) { m_LethalRange *= RandomNum(m_MinLethalRange, m_MaxLethalRange); }
-
- // convert to meters
- m_LethalRange /= c_PPM;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -194,6 +190,7 @@ namespace RTE {
// If we seem to be about to settle, make sure we're not still flying in the air
if ((m_ToSettle || IsAtRest()) && g_SceneMan.OverAltitude(m_Pos, 2, 0)) {
+ m_VelOscillations = 0;
m_RestTimer.Reset();
m_ToSettle = false;
}
@@ -206,8 +203,7 @@ namespace RTE {
// TODO: Rework this once we figure out how we want to handle it
if (m_HitsMOs && m_Sharpness > 0) {
- m_DistanceTraveled += m_Vel.GetLargest() * g_TimerMan.GetDeltaTimeSecs();
- if (m_DistanceTraveled > m_LethalRange) {
+ if (m_DistanceTravelled > m_LethalRange) {
if (m_Sharpness < m_LethalSharpness) {
m_Sharpness = std::max(m_Sharpness * (1.0F - (20.0F * g_TimerMan.GetDeltaTimeSecs())) - 0.1F, 0.0F);
if (m_LethalRange > 0) {
diff --git a/Entities/MOPixel.h b/Entities/MOPixel.h
index dca346ac7..2d9150d2c 100644
--- a/Entities/MOPixel.h
+++ b/Entities/MOPixel.h
@@ -31,7 +31,7 @@ namespace RTE {
/// A float specifying the object's mass in Kilograms (kg).
/// A Vector specifying the initial position.
/// A Vector specifying the initial velocity.
- /// An Atom that will collide with the terrain.
+ /// An Atom that will collide with the terrain. Ownership IS transferred!
/// The amount of time in ms this MOPixel will exist. 0 means unlimited.
MOPixel(Color color, const float mass, const Vector &position, const Vector &velocity, Atom *atom, const unsigned long lifetime = 0) { Clear(); Create(color, mass, position, velocity, atom, lifetime); }
@@ -156,7 +156,7 @@ namespace RTE {
bool CollideAtPoint(HitData &hitData) override;
///
- /// Does the calculations necessary to detect whether this MO appears to have has settled in the world and is at rest or not. IsAtRest() retrieves the answer.
+ /// Does the calculations necessary to detect whether this MOPixel is at rest or not. IsAtRest() retrieves the answer.
///
void RestDetection() override;
@@ -196,8 +196,6 @@ namespace RTE {
Atom *m_Atom; //!< The single Atom that is responsible for collisions of this MOPixel.
Color m_Color; //!< Color representation of this MOPixel.
- float m_DistanceTraveled; //!< An estimate of how far this MO has traveled since its creation.
-
float m_LethalRange; //!< After this distance in meters, the MO has a chance to no longer hit MOs, and its Lifetime decreases. Defaults to the length of a player's screen.
float m_MinLethalRange; //!< Lower bound multiplier for setting LethalRange at random. By default, 1.0 equals one screen.
float m_MaxLethalRange; //!< Upper bound multiplier for setting LethalRange at random. By default, 1.0 equals one screen.
diff --git a/Entities/MOSParticle.cpp b/Entities/MOSParticle.cpp
index aa5abef76..3ff8c0550 100644
--- a/Entities/MOSParticle.cpp
+++ b/Entities/MOSParticle.cpp
@@ -93,6 +93,7 @@ namespace RTE {
// If we seem to be about to settle, make sure we're not still flying in the air
if ((m_ToSettle || IsAtRest()) && g_SceneMan.OverAltitude(m_Pos, (m_aSprite[m_Frame]->h / 2) + 3, 2)) {
+ m_VelOscillations = 0;
m_RestTimer.Reset();
m_ToSettle = false;
}
diff --git a/Entities/MOSParticle.h b/Entities/MOSParticle.h
index 4ed2a25b9..5b8f8d35c 100644
--- a/Entities/MOSParticle.h
+++ b/Entities/MOSParticle.h
@@ -112,7 +112,7 @@ namespace RTE {
bool CollideAtPoint(HitData &hitData) override { return true; }
///
- /// Does the calculations necessary to detect whether this MO appears to have has settled in the world and is at rest or not. IsAtRest() retrieves the answer.
+ /// Does the calculations necessary to detect whether this MOSParticle is at rest or not. IsAtRest() retrieves the answer.
///
void RestDetection() override;
diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp
index 7ff861753..b20854103 100644
--- a/Entities/MOSRotating.cpp
+++ b/Entities/MOSRotating.cpp
@@ -287,14 +287,6 @@ int MOSRotating::Create(const MOSRotating &reference) {
m_DamageMultiplier = reference.m_DamageMultiplier;
m_NoSetDamageMultiplier = reference.m_NoSetDamageMultiplier;
-/* Allocated in lazy fashion as needed when drawing flipped
- if (!m_pFlipBitmap && m_aSprite[0])
- m_pFlipBitmap = create_bitmap_ex(8, m_aSprite[0]->w, m_aSprite[0]->h);
-*/
-/* Not anymore; points to shared static bitmaps
- if (!m_pTempBitmap && m_aSprite[0])
- m_pTempBitmap = create_bitmap_ex(8, m_aSprite[0]->w, m_aSprite[0]->h);
-*/
m_pTempBitmap = reference.m_pTempBitmap;
m_pTempBitmapS = reference.m_pTempBitmapS;
@@ -507,13 +499,13 @@ int MOSRotating::GetWoundCount(bool includePositiveDamageAttachables, bool inclu
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-Attachable * MOSRotating::GetNearestAttachableToOffset(const Vector &offset) const {
+Attachable * MOSRotating::GetNearestDetachableAttachableToOffset(const Vector &offset) const {
Attachable *nearestAttachable = nullptr;
- float closestRadius = -1.0F;
+ float closestRadius = m_SpriteRadius;
for (Attachable *attachable : m_Attachables) {
- if (attachable->GetsHitByMOs() && attachable->GetJointStrength() > 0 && attachable->GetDamageMultiplier() > 0) {
+ if (attachable->GetsHitByMOs() && attachable->GetGibImpulseLimit() > 0 && attachable->GetJointStrength() > 0 && attachable->GetDamageMultiplier() > 0 && offset.Dot(attachable->GetParentOffset()) > 0) {
float radius = (offset - attachable->GetParentOffset()).GetMagnitude();
- if (closestRadius < 0 || radius < closestRadius) {
+ if (radius < closestRadius) {
closestRadius = radius;
nearestAttachable = attachable;
}
@@ -524,14 +516,36 @@ Attachable * MOSRotating::GetNearestAttachableToOffset(const Vector &offset) con
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+void MOSRotating::DetachAttachablesFromImpulse(Vector &impulseVector) {
+ float impulseRemainder = impulseVector.GetMagnitude();
+ // Find the attachable closest to the impact point by using an inverted impulse vector.
+ Vector invertedImpulseOffset = Vector(impulseVector.GetX(), impulseVector.GetY()).SetMagnitude(-GetRadius()) * -m_Rotation;
+ Attachable *nearestAttachableToImpulse = GetNearestDetachableAttachableToOffset(invertedImpulseOffset);
+ while (nearestAttachableToImpulse) {
+ float attachableImpulseLimit = nearestAttachableToImpulse->GetGibImpulseLimit();
+ float attachableJointStrength = nearestAttachableToImpulse->GetJointStrength();
+ if (impulseRemainder > attachableImpulseLimit) {
+ nearestAttachableToImpulse->GibThis(impulseVector.SetMagnitude(attachableImpulseLimit));
+ impulseRemainder -= attachableImpulseLimit;
+ } else if (impulseRemainder > attachableJointStrength) {
+ RemoveAttachable(nearestAttachableToImpulse, true, true);
+ impulseRemainder -= attachableJointStrength;
+ } else {
+ break;
+ }
+ nearestAttachableToImpulse = GetNearestDetachableAttachableToOffset(invertedImpulseOffset);
+ }
+ impulseVector.SetMagnitude(impulseRemainder);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
void MOSRotating::AddWound(AEmitter *woundToAdd, const Vector &parentOffsetToSet, bool checkGibWoundLimit) {
- if (woundToAdd && !ToDelete()) {
+ if (woundToAdd && !m_ToDelete) {
if (checkGibWoundLimit && m_GibWoundLimit > 0 && m_Wounds.size() + 1 >= m_GibWoundLimit) {
- // Find and detach an attachable near the new wound before gibbing the object itself.
- if (m_DetachAttachablesBeforeGibbingFromWounds && RandomNum() < 0.5F) {
- if (Attachable *attachableToDetach = GetNearestAttachableToOffset(parentOffsetToSet)) {
- RemoveAttachable(attachableToDetach, true, true);
- }
+ // Find and detach an attachable near the new wound before gibbing the object itself. TODO: Perhaps move this to Actor, since it's more relevant there?
+ if (Attachable *attachableToDetach = GetNearestDetachableAttachableToOffset(parentOffsetToSet); attachableToDetach && m_DetachAttachablesBeforeGibbingFromWounds) {
+ RemoveAttachable(attachableToDetach, true, true);
} else {
// TODO: Don't hardcode the blast strength!
GibThis(Vector(-5.0F, 0).RadRotate(woundToAdd->GetEmitAngle()));
@@ -541,7 +555,6 @@ void MOSRotating::AddWound(AEmitter *woundToAdd, const Vector &parentOffsetToSet
}
woundToAdd->SetCollidesWithTerrainWhileAttached(false);
woundToAdd->SetParentOffset(parentOffsetToSet);
- woundToAdd->SetInheritsHFlipped(false);
woundToAdd->SetParent(this);
woundToAdd->SetIsWound(true);
if (woundToAdd->HasNoSetDamageMultiplier()) { woundToAdd->SetDamageMultiplier(1.0F); }
@@ -738,6 +751,10 @@ void MOSRotating::AddRecoil()
bool MOSRotating::CollideAtPoint(HitData &hd)
{
+ if (m_ToDelete) {
+ return false; // TODO: Add a settings flag to enable old school particle sponges!
+ }
+
hd.ResImpulse[HITOR].Reset();
hd.ResImpulse[HITEE].Reset();
@@ -967,11 +984,11 @@ bool MOSRotating::ParticlePenetration(HitData &hd)
{
// Add entry wound AEmitter to actor where the particle penetrated.
AEmitter *pEntryWound = dynamic_cast(m_pEntryWound->Clone());
- pEntryWound->SetEmitAngle(dir.GetXFlipped(m_HFlipped).GetAbsRadAngle() + c_PI);
+ pEntryWound->SetInheritedRotAngleOffset(dir.GetAbsRadAngle() + c_PI);
float damageMultiplier = pEntryWound->HasNoSetDamageMultiplier() ? 1.0F : pEntryWound->GetDamageMultiplier();
pEntryWound->SetDamageMultiplier(damageMultiplier * hd.Body[HITOR]->WoundDamageMultiplier());
// Adjust position so that it looks like the hole is actually *on* the Hitee.
- entryPos[dom] += increment[dom] * (pEntryWound->GetSpriteFrame()->w / 2);
+ entryPos[dom] += increment[dom] * (pEntryWound->GetSpriteWidth() / 2);
AddWound(pEntryWound, entryPos + m_SpriteOffset);
pEntryWound = 0;
}
@@ -986,8 +1003,8 @@ bool MOSRotating::ParticlePenetration(HitData &hd)
{
AEmitter *pExitWound = dynamic_cast(m_pExitWound->Clone());
// Adjust position so that it looks like the hole is actually *on* the Hitee.
- exitPos[dom] -= increment[dom] * (pExitWound->GetSpriteFrame()->w / 2);
- pExitWound->SetEmitAngle(dir.GetXFlipped(m_HFlipped).GetAbsRadAngle());
+ exitPos[dom] -= increment[dom] * (pExitWound->GetSpriteWidth() / 2);
+ pExitWound->SetInheritedRotAngleOffset(dir.GetAbsRadAngle());
float damageMultiplier = pExitWound->HasNoSetDamageMultiplier() ? 1.0F : pExitWound->GetDamageMultiplier();
pExitWound->SetDamageMultiplier(damageMultiplier * hd.Body[HITOR]->WoundDamageMultiplier());
AddWound(pExitWound, exitPos + m_SpriteOffset);
@@ -1103,7 +1120,7 @@ void MOSRotating::CreateGibsWhenGibbing(const Vector &impactImpulse, MovableObje
}
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()));
+ gibParticleClone->SetVel(gibVelocity + ((m_PrevVel + m_Vel) / 2) * gibSettingsObject.InheritsVelocity());
if (movableObjectToIgnore) { gibParticleClone->SetWhichMOToNotHit(movableObjectToIgnore); }
if (gibSettingsObject.IgnoresTeamHits()) {
gibParticleClone->SetTeam(m_Team);
@@ -1136,14 +1153,14 @@ void MOSRotating::CreateGibsWhenGibbing(const Vector &impactImpulse, MovableObje
// 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));
+ gibVelocity.RadRotate(gibSettingsObject.InheritsVelocity() > 0 ? 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()));
+ gibParticleClone->SetVel(gibVelocity + ((m_PrevVel + m_Vel) / 2) * gibSettingsObject.InheritsVelocity());
if (movableObjectToIgnore) { gibParticleClone->SetWhichMOToNotHit(movableObjectToIgnore); }
if (gibSettingsObject.IgnoresTeamHits()) {
gibParticleClone->SetTeam(m_Team);
@@ -1163,7 +1180,7 @@ void MOSRotating::RemoveAttachablesWhenGibbing(const Vector &impactImpulse, Mova
for (Attachable *attachable : nonVolatileAttachablesVectorForLuaSafety) {
RTEAssert(attachable, "Broken Attachable when Gibbing!");
- if (RandomNum() < attachable->GetGibWithParentChance()) {
+ if (RandomNum() < attachable->GetGibWithParentChance() || attachable->GetGibWhenRemovedFromParent()) {
attachable->GibThis();
continue;
}
@@ -1229,15 +1246,9 @@ void MOSRotating::ApplyImpulses() {
impulseLimit *= 1.0F - (static_cast(m_Wounds.size()) / static_cast(m_GibWoundLimit)) * m_WoundCountAffectsImpulseLimitRatio;
}
if (totalImpulse.MagnitudeIsGreaterThan(impulseLimit)) {
- for (Attachable *attachable : m_Attachables) {
- if (attachable->GetGibWithParentChance() == 0 && totalImpulse.MagnitudeIsGreaterThan(attachable->GetGibImpulseLimit())) {
- attachable->SetGibWithParentChance(0.33F);
- }
- }
- if (Attachable *nearestAttachableToImpulse = GetNearestAttachableToOffset(averagedImpulseForceOffset.SetMagnitude(-GetRadius()) * -m_Rotation); nearestAttachableToImpulse && totalImpulse.MagnitudeIsGreaterThan(nearestAttachableToImpulse->GetGibImpulseLimit())) {
- nearestAttachableToImpulse->SetGibWithParentChance(1.0F);
- }
- GibThis(totalImpulse);
+ DetachAttachablesFromImpulse(totalImpulse);
+ // Use the remainder of the impulses left over from detaching to gib the parent object.
+ if (totalImpulse.MagnitudeIsGreaterThan(impulseLimit)) { GibThis(totalImpulse); }
}
}
@@ -1265,47 +1276,53 @@ void MOSRotating::ResetAllTimers()
(*attachable)->ResetAllTimers();
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-
-void MOSRotating::RestDetection()
-{
- MOSprite::RestDetection();
-
- // Rotational settling detection.
- if (((m_AngularVel > 0 && m_PrevAngVel < 0) || (m_AngularVel < 0 && m_PrevAngVel > 0)) && m_RestThreshold >= 0) {
- if (m_AngOscillations >= 2)
- m_ToSettle = true;
- else
- ++m_AngOscillations;
- }
- else
- m_AngOscillations = 0;
+void MOSRotating::RestDetection() {
+ MOSprite::RestDetection();
-// if (fabs(m_AngularVel) >= 1.0)
-// m_RestTimer.Reset();
+ // Rotational settling detection.
+ if ((m_AngularVel > 0 && m_PrevAngVel < 0) || (m_AngularVel < 0 && m_PrevAngVel > 0)) {
+ ++m_AngOscillations;
+ } else {
+ m_AngOscillations = 0;
+ }
- if (fabs(m_Rotation.GetRadAngle() - m_PrevRotation.GetRadAngle()) >= 0.01)
- m_RestTimer.Reset();
+ if (std::abs(m_Rotation.GetRadAngle() - m_PrevRotation.GetRadAngle()) >= 0.01) { m_RestTimer.Reset(); }
+
+ // If about to settle, make sure the object isn't flying in the air.
+ // Note that this uses sprite radius to avoid possibly settling when it shouldn't (e.g. if there's a lopsided attachable enlarging the radius, using GetRadius might make it settle in the air).
+ if (m_ToSettle || IsAtRest()) {
+ bool resting = true;
+ if (g_SceneMan.OverAltitude(m_Pos, static_cast(m_SpriteRadius) + 4, 3)) {
+ resting = false;
+ for (const Attachable *attachable : m_Attachables) {
+ if (attachable->GetCollidesWithTerrainWhileAttached() && !g_SceneMan.OverAltitude(attachable->GetPos(), static_cast(attachable->GetIndividualRadius()) + 2, 3)) {
+ resting = true;
+ break;
+ }
+ }
+ }
+ if (!resting) {
+ m_VelOscillations = 0;
+ m_AngOscillations = 0;
+ m_RestTimer.Reset();
+ m_ToSettle = false;
+ }
+ }
+ m_PrevRotation = m_Rotation;
+ m_PrevAngVel = m_AngularVel;
+}
- // If we seem to be about to settle, make sure we're not flying in the air still.
- // Note that this uses sprite radius to avoid possibly settling when it shouldn't (e.g. if there's a lopsided attachable enlarging the radius, using GetRadius might make it settle in the air).
- if (m_ToSettle || IsAtRest())
- {
- if (g_SceneMan.OverAltitude(m_Pos, m_SpriteRadius + 4, 3))
- {
- m_RestTimer.Reset();
- m_ToSettle = false;
- }
- }
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- m_PrevRotation = m_Rotation;
- m_PrevAngVel = m_AngularVel;
+bool MOSRotating::IsAtRest() {
+ if (m_RestThreshold < 0 || m_PinStrength != 0) {
+ return false;
+ } else if (m_VelOscillations > 2 || m_AngOscillations > 2) {
+ return true;
+ }
+ return m_RestTimer.IsPastSimMS(m_RestThreshold);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1503,8 +1520,7 @@ void MOSRotating::Travel()
void MOSRotating::PostTravel()
{
// Check for stupid velocities to gib instead of outright deletion that MOSprite::PostTravel() will do
- if (IsTooFast())
- GibThis();
+ if (IsTooFast()) { GibThis(); }
// For some reason MovableObject lifetime death is in post travel rather than update, so this is done here too
if (m_GibAtEndOfLifetime && m_Lifetime && m_AgeTimer.GetElapsedSimTimeMS() > m_Lifetime) { GibThis(); }
@@ -1518,15 +1534,10 @@ void MOSRotating::PostTravel()
impulseLimit *= 1.0F - (static_cast(m_Wounds.size()) / static_cast(m_GibWoundLimit)) * m_WoundCountAffectsImpulseLimitRatio;
}
if (m_TravelImpulse.MagnitudeIsGreaterThan(impulseLimit)) {
- for (Attachable *attachable : m_Attachables) {
- if (attachable->GetGibWithParentChance() == 0 && m_TravelImpulse.MagnitudeIsGreaterThan(attachable->GetGibImpulseLimit())) {
- attachable->SetGibWithParentChance(0.33F);
- }
- }
- if (Attachable *nearestAttachableToImpulse = GetNearestAttachableToOffset(Vector(m_TravelImpulse.GetX(), m_TravelImpulse.GetY()).SetMagnitude(-GetRadius()) * -m_Rotation); nearestAttachableToImpulse && m_TravelImpulse.MagnitudeIsGreaterThan(nearestAttachableToImpulse->GetGibImpulseLimit())) {
- nearestAttachableToImpulse->SetGibWithParentChance(1.0F);
- }
- GibThis();
+ Vector totalImpulse(m_TravelImpulse.GetX(), m_TravelImpulse.GetY());
+ DetachAttachablesFromImpulse(totalImpulse);
+ // Use the remainder of the impulses left over from detaching to gib the parent object.
+ if (totalImpulse.MagnitudeIsGreaterThan(impulseLimit)) { GibThis(); }
}
}
// Reset
@@ -1712,7 +1723,7 @@ Attachable * MOSRotating::RemoveAttachable(Attachable *attachable, bool addToMov
AEmitter *parentBreakWound = dynamic_cast(attachable->GetParentBreakWound()->Clone());
if (parentBreakWound) {
parentBreakWound->SetDrawnAfterParent(attachable->IsDrawnAfterParent());
- parentBreakWound->SetEmitAngle((attachable->GetParentOffset() * m_Rotation).GetAbsRadAngle());
+ parentBreakWound->SetInheritedRotAngleOffset((attachable->GetParentOffset() * m_Rotation).GetAbsRadAngle());
AddWound(parentBreakWound, attachable->GetParentOffset(), false);
parentBreakWound = nullptr;
}
@@ -1720,7 +1731,7 @@ Attachable * MOSRotating::RemoveAttachable(Attachable *attachable, bool addToMov
if (!attachable->IsSetToDelete() && attachable->GetBreakWound()) {
AEmitter *childBreakWound = dynamic_cast(attachable->GetBreakWound()->Clone());
if (childBreakWound) {
- childBreakWound->SetEmitAngle(attachable->GetJointOffset().GetAbsRadAngle());
+ childBreakWound->SetInheritedRotAngleOffset(attachable->GetJointOffset().GetAbsRadAngle());
attachable->AddWound(childBreakWound, attachable->GetJointOffset());
childBreakWound = nullptr;
}
@@ -1741,6 +1752,7 @@ Attachable * MOSRotating::RemoveAttachable(Attachable *attachable, bool addToMov
if (attachable->GetDeleteWhenRemovedFromParent()) { attachable->SetToDelete(); }
if (addToMovableMan || attachable->IsSetToDelete()) {
g_MovableMan.AddMO(attachable);
+ if (attachable->GetGibWhenRemovedFromParent()) { attachable->GibThis(); }
return nullptr;
}
diff --git a/Entities/MOSRotating.h b/Entities/MOSRotating.h
index 53656c8bf..e69c1fb53 100644
--- a/Entities/MOSRotating.h
+++ b/Entities/MOSRotating.h
@@ -483,11 +483,17 @@ ClassInfoGetters;
void RemoveOrDestroyAllAttachables(bool destroy);
///
- /// Gets the Attachable nearest to the passed in offset.
+ /// Gets a damage-transferring, impulse-vulnerable Attachable nearest to the passed in offset.
///
/// The offset that will be compared to each Attachable's ParentOffset.
- /// The nearest damage-transferring Attachable, or nullptr if none was found.
- Attachable * GetNearestAttachableToOffset(const Vector &offset) const;
+ /// The nearest detachable Attachable, or nullptr if none was found.
+ Attachable * GetNearestDetachableAttachableToOffset(const Vector &offset) const;
+
+ ///
+ /// Gibs or detaches any Attachables that would normally gib or detach from the passed in impulses.
+ ///
+ /// The impulse vector which determines the Attachables to gib or detach. Will be filled out with the remainder of impulses.
+ void DetachAttachablesFromImpulse(Vector &impulseVector);
//////////////////////////////////////////////////////////////////////////////////////////
@@ -501,18 +507,15 @@ ClassInfoGetters;
void ResetAllTimers() override;
+ ///
+ /// Does the calculations necessary to detect whether this MOSRotating is at rest or not. IsAtRest() retrieves the answer.
+ ///
+ void RestDetection() override;
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-// Arguments: None.
-// Return value: None.
-
- void RestDetection() override;
-
+ ///
+ /// Indicates whether this MOSRotating has been at rest with no movement for longer than its RestThreshold.
+ ///
+ bool IsAtRest() override;
///
/// Indicates whether this MOSRotating's current graphical representation, including its Attachables, overlaps a point in absolute scene coordinates.
diff --git a/Entities/MOSprite.cpp b/Entities/MOSprite.cpp
index a3155cf04..988be29c2 100644
--- a/Entities/MOSprite.cpp
+++ b/Entities/MOSprite.cpp
@@ -47,7 +47,7 @@ void MOSprite::Clear()
m_PrevRotation.Reset();
m_AngularVel = 0;
m_PrevAngVel = 0;
- m_AngOscillations = 0;
+ m_AngOscillations = 0;
m_SettleMaterialDisabled = false;
m_pEntryWound = 0;
m_pExitWound = 0;
diff --git a/Entities/MOSprite.h b/Entities/MOSprite.h
index e088d9249..08adf7554 100644
--- a/Entities/MOSprite.h
+++ b/Entities/MOSprite.h
@@ -391,6 +391,11 @@ class MOSprite : public MovableObject {
/// The height of the GUI icon bitmap.
int GetIconHeight() const { return GetGraphicalIcon()->h; }
+ ///
+ /// Forces this MOSprite out of resting conditions.
+ ///
+ void NotResting() override { MovableObject::NotResting(); m_AngOscillations = 0; }
+
//////////////////////////////////////////////////////////////////////////////////////////
// Virtual method: IsTooFast
@@ -584,8 +589,7 @@ class MOSprite : public MovableObject {
// The precalculated maximum possible radius and diameter of this, in pixels
float m_SpriteRadius;
float m_SpriteDiameter;
- // A counter to count the oscillations in rotation, in order to detect settling.
- int m_AngOscillations;
+ int m_AngOscillations; //!< A counter for oscillations in rotation, in order to detect settling.
// Whether to disable the settle material ID when this gets drawn as material
bool m_SettleMaterialDisabled;
// Entry wound template
diff --git a/Entities/MovableObject.cpp b/Entities/MovableObject.cpp
index af7fe76ff..3c2785939 100644
--- a/Entities/MovableObject.cpp
+++ b/Entities/MovableObject.cpp
@@ -41,6 +41,7 @@ void MovableObject::Clear()
m_Vel.Reset();
m_PrevPos.Reset();
m_PrevVel.Reset();
+ m_DistanceTravelled = 0;
m_Scale = 1.0;
m_GlobalAccScalar = 1.0;
m_AirResistance = 0;
@@ -72,7 +73,7 @@ void MovableObject::Clear()
m_HasEverBeenAddedToMovableMan = false;
m_MOIDFootprint = 0;
m_AlreadyHitBy.clear();
- m_VelOscillations = 0;
+ m_VelOscillations = 0;
m_ToSettle = false;
m_ToDelete = false;
m_HUDVisible = true;
@@ -701,55 +702,31 @@ float MovableObject::GetAltitude(int max, int accuracy)
return g_SceneMan.FindAltitude(m_Pos, max, accuracy);
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-
-void MovableObject::RestDetection()
-{
- if (m_PinStrength)
- return;
-
- // Translational settling detection
- if ((m_Vel.Dot(m_PrevVel) < 0)) {
- if (m_VelOscillations >= 2 && m_RestThreshold >= 0)
- m_ToSettle = true;
- else
- ++m_VelOscillations;
- }
- else
- m_VelOscillations = 0;
-
-// if (fabs(m_Vel.m_X) >= 0.25 || fabs(m_Vel.m_Y) >= 0.25)
-// m_RestTimer.Reset();
-
- if (fabs(m_Pos.m_X - m_PrevPos.m_X) >= 1.0f || fabs(m_Pos.m_Y - m_PrevPos.m_Y) >= 1.0f)
- m_RestTimer.Reset();
+void MovableObject::RestDetection() {
+ // Translational settling detection.
+ if (m_Vel.Dot(m_PrevVel) < 0) {
+ ++m_VelOscillations;
+ } else {
+ m_VelOscillations = 0;
+ }
+ if ((m_Pos - m_PrevPos).MagnitudeIsGreaterThan(1.0F)) { m_RestTimer.Reset(); }
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: IsAtRest
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Indicates wheter the MovableObject has been at rest (no velocity) for
-// more than one (1) second.
-
-bool MovableObject::IsAtRest()
-{
- if (m_PinStrength)
- return false;
-
- if (m_RestThreshold < 0)
- return false;
- else
- return m_RestTimer.IsPastSimMS(m_RestThreshold);
+bool MovableObject::IsAtRest() {
+ if (m_RestThreshold < 0 || m_PinStrength) {
+ return false;
+ } else {
+ if (m_VelOscillations > 2) {
+ return true;
+ }
+ return m_RestTimer.IsPastSimMS(m_RestThreshold);
+ }
}
-
//////////////////////////////////////////////////////////////////////////////////////////
// Virtual method: OnMOHit
//////////////////////////////////////////////////////////////////////////////////////////
@@ -776,6 +753,15 @@ void MovableObject::SetHitWhatTerrMaterial(unsigned char matID) {
RunScriptedFunctionInAppropriateScripts("OnCollideWithTerrain", false, false, {}, {std::to_string(m_TerrainMatHit)});
}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+Vector MovableObject::GetTotalForce() {
+ Vector totalForceVector;
+ for (const auto &[force, forceOffset] : m_Forces) {
+ totalForceVector += force;
+ }
+ return totalForceVector;
+}
//////////////////////////////////////////////////////////////////////////////////////////
// Virtual method: ApplyForces
@@ -804,13 +790,11 @@ void MovableObject::ApplyForces()
if (m_AirResistance > 0 && m_Vel.GetLargest() >= m_AirThreshold)
m_Vel *= 1.0 - (m_AirResistance * deltaTime);
- // Apply the translational effects of all the forces accumulated during the Update()
- for (auto fItr = m_Forces.begin(); fItr != m_Forces.end(); ++fItr)
- {
- // Continuous force application to transformational velocity.
- // (F = m * a -> a = F / m).
- m_Vel += ((*fItr).first / (GetMass() != 0 ? GetMass() : 0.0001F) * deltaTime);
- }
+ // Apply the translational effects of all the forces accumulated during the Update().
+ if (m_Forces.size() > 0) {
+ // Continuous force application to transformational velocity (F = m * a -> a = F / m).
+ m_Vel += GetTotalForce() / (GetMass() != 0 ? GetMass() : 0.0001F) * deltaTime;
+ }
// Clear out the forces list
m_Forces.clear();
@@ -932,6 +916,8 @@ void MovableObject::PostTravel()
// Reset the terrain intersection warning
m_CheckTerrIntersection = false;
+
+ m_DistanceTravelled += m_Vel.GetMagnitude() * c_PPM * g_TimerMan.GetDeltaTimeSecs();
}
/*
diff --git a/Entities/MovableObject.h b/Entities/MovableObject.h
index 0d1c20a3b..7d4da1a40 100644
--- a/Entities/MovableObject.h
+++ b/Entities/MovableObject.h
@@ -271,6 +271,12 @@ enum MOType
/// A Vector describing the previous velocity vector.
const Vector & GetPrevVel() const { return m_PrevVel; }
+ ///
+ /// Gets the amount of distance this MO has travelled since its creation, in pixels.
+ ///
+ /// The amount of distance this MO has travelled, in pixels.
+ float GetDistanceTravelled() const { return m_DistanceTravelled; }
+
//////////////////////////////////////////////////////////////////////////////////////////
// Method: GetAngularVel
@@ -1160,40 +1166,20 @@ enum MOType
virtual void ResetAllTimers() {}
+ ///
+ /// Does the calculations necessary to detect whether this MovableObject is at rest or not. IsAtRest() retrieves the answer.
+ ///
+ virtual void RestDetection();
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: RestDetection
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Does the calculations necessary to detect whether this MO appears to
-// have has settled in the world and is at rest or not. IsAtRest()
-// retreves the answer.
-// Arguments: None.
-// Return value: None.
-
- virtual void RestDetection();
-
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: NotResting
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Makes this MO reset its tiemr that keeps track of how long it's been
-// at rest, effectively delaying it.
-// Arguments: None.
-// Return value: None.
-
- void NotResting() { m_RestTimer.Reset(); m_ToSettle = false; }
-
-
-//////////////////////////////////////////////////////////////////////////////////////////
-// Virtual method: IsAtRest
-//////////////////////////////////////////////////////////////////////////////////////////
-// Description: Indicates wheter the MovableObject has been at rest (no velocity) for more
-// than one (1) second.
-// Arguments: None.
-// Return value: Wheter the MovableObject has been at rest for more than one full second.
-
- bool IsAtRest();
+ ///
+ /// Forces this MovableObject out of resting conditions.
+ ///
+ virtual void NotResting() { m_RestTimer.Reset(); m_ToSettle = false; m_VelOscillations = 0; }
+ ///
+ /// Indicates whether this MovableObject has been at rest with no movement for longer than its RestThreshold.
+ ///
+ virtual bool IsAtRest();
//////////////////////////////////////////////////////////////////////////////////////////
// Method: IsUpdated
@@ -1382,6 +1368,11 @@ enum MOType
Vector GetForceVector(int n) { if (n > 0 && n < m_Forces.size()) return m_Forces[n].first; else return Vector(0, 0); }
+ ///
+ /// Gets the total sum of all forces applied to this MovableObject in a single Vector.
+ ///
+ /// The total sum of all forces applied to this MovableObject.
+ virtual Vector GetTotalForce();
//////////////////////////////////////////////////////////////////////////////////////////
// Virtual method: GetForceOffset()
@@ -1474,7 +1465,7 @@ enum MOType
int GetSimUpdatesBetweenScriptedUpdates() const { return m_SimUpdatesBetweenScriptedUpdates; }
///
- /// sets the number of Sim updates that run between each script update for this MovableObject.
+ /// Sets the number of Sim updates that run between each script update for this MovableObject.
///
/// The new number of Sim updates that run between each script update for this MovableObject.
void SetSimUpdatesBetweenScriptedUpdates(int newSimUpdatesBetweenScriptedUpdates) { m_SimUpdatesBetweenScriptedUpdates = std::max(1, newSimUpdatesBetweenScriptedUpdates); }
@@ -1875,6 +1866,7 @@ enum MOType
Vector m_Vel; // In meters per second (m/s).
Vector m_PrevPos; // Previous frame's position.
Vector m_PrevVel; // Previous frame's velocity.
+ float m_DistanceTravelled; //!< An estimate of how many pixels this MO has travelled since its creation.
float m_Scale; // The scale that this MovableObject's representation will be drawn in. 1.0 being 1:1;
// How this is affected by global effects, from +1.0 to -1.0. Something with a negative value will 'float' upward
float m_GlobalAccScalar;
@@ -1939,8 +1931,7 @@ enum MOType
bool m_HasEverBeenAddedToMovableMan;
// A set of ID:s of MO:s that already have collided with this MO during this frame.
std::set m_AlreadyHitBy;
- // A counter to count the oscillations in translational velocity, in order to detect settling.
- int m_VelOscillations;
+ int m_VelOscillations; //!< A counter for oscillations in translational velocity, in order to detect settling.
// Mark to have the MovableMan copy this the terrain layers at the end
// of update.
bool m_ToSettle;
diff --git a/Entities/PieMenu.cpp b/Entities/PieMenu.cpp
index 775740cf1..65106b096 100644
--- a/Entities/PieMenu.cpp
+++ b/Entities/PieMenu.cpp
@@ -355,7 +355,9 @@ namespace RTE {
leastFullPieQuadrant = &pieQuadrant;
}
}
- sliceWasAdded = leastFullPieQuadrant->AddPieSlice(pieSliceToAdd);
+ if (leastFullPieQuadrant) {
+ sliceWasAdded = leastFullPieQuadrant->AddPieSlice(pieSliceToAdd);
+ }
} else {
int desiredQuadrantIndex = static_cast(pieSliceDirection);
sliceWasAdded = m_PieQuadrants.at(desiredQuadrantIndex).AddPieSlice(pieSliceToAdd);
@@ -489,8 +491,77 @@ namespace RTE {
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- void PieMenu::Update() {
- Controller *controller = GetController();
+ PieSlice * PieMenu::ReplacePieSlice(const PieSlice *pieSliceToReplace, PieSlice *replacementPieSlice) {
+ if (pieSliceToReplace == nullptr) {
+ return nullptr;
+ }
+ if (replacementPieSlice == nullptr) {
+ return RemovePieSlice(pieSliceToReplace);
+ }
+
+ PieSlice *replacedPieSlice = nullptr;
+
+ auto DoPieSliceReplacementInPieQuadrant = [&replacedPieSlice, &pieSliceToReplace, &replacementPieSlice](PieQuadrant &pieQuadrant) {
+ if (pieSliceToReplace == pieQuadrant.m_MiddlePieSlice.get()) {
+ replacedPieSlice = pieQuadrant.m_MiddlePieSlice.release();
+ pieQuadrant.m_MiddlePieSlice = std::unique_ptr(replacementPieSlice);
+ } else if (pieSliceToReplace == pieQuadrant.m_LeftPieSlices[0].get()) {
+ replacedPieSlice = pieQuadrant.m_LeftPieSlices[0].release();
+ pieQuadrant.m_LeftPieSlices[0] = std::unique_ptr(replacementPieSlice);
+ } else if (pieSliceToReplace == pieQuadrant.m_LeftPieSlices[1].get()) {
+ replacedPieSlice = pieQuadrant.m_LeftPieSlices[1].release();
+ pieQuadrant.m_LeftPieSlices[1] = std::unique_ptr(replacementPieSlice);
+ } else if (pieSliceToReplace == pieQuadrant.m_RightPieSlices[0].get()) {
+ replacedPieSlice = pieQuadrant.m_RightPieSlices[0].release();
+ pieQuadrant.m_RightPieSlices[0] = std::unique_ptr(replacementPieSlice);
+ } else if (pieSliceToReplace == pieQuadrant.m_RightPieSlices[1].get()) {
+ replacedPieSlice = pieQuadrant.m_RightPieSlices[1].release();
+ pieQuadrant.m_RightPieSlices[1] = std::unique_ptr(replacementPieSlice);
+ }
+ RTEAssert(replacedPieSlice, "Tried to do PieSlice replacement in PieQuadrant, but PieSlice to replace was not found and removed from any PieQuadrant.");
+ };
+
+ if (Directions sliceDirection = pieSliceToReplace->GetDirection(); sliceDirection > Directions::None) {
+ if (sliceDirection == Directions::Any) {
+ for (PieQuadrant &pieQuadrant : m_PieQuadrants) {
+ if (pieQuadrant.ContainsPieSlice(pieSliceToReplace)) {
+ DoPieSliceReplacementInPieQuadrant(pieQuadrant);
+ break;
+ }
+ }
+ } else if (PieQuadrant &pieQuadrant = m_PieQuadrants[sliceDirection]; pieQuadrant.ContainsPieSlice(pieSliceToReplace)) {
+ DoPieSliceReplacementInPieQuadrant(pieQuadrant);
+ }
+ }
+
+ if (replacedPieSlice) {
+ replacementPieSlice->SetOriginalSource(pieSliceToReplace->GetOriginalSource());
+ replacementPieSlice->SetDirection(pieSliceToReplace->GetDirection());
+ replacementPieSlice->SetCanBeMiddleSlice(pieSliceToReplace->GetCanBeMiddleSlice());
+ replacementPieSlice->SetStartAngle(pieSliceToReplace->GetStartAngle());
+ replacementPieSlice->SetSlotCount(pieSliceToReplace->GetSlotCount());
+ replacementPieSlice->SetMidAngle(pieSliceToReplace->GetMidAngle());
+
+ if (m_HoveredPieSlice == pieSliceToReplace) {
+ m_HoveredPieSlice = replacementPieSlice;
+ }
+ if (m_ActivatedPieSlice == pieSliceToReplace) {
+ m_ActivatedPieSlice = replacedPieSlice;
+ }
+ if (m_AlreadyActivatedPieSlice == pieSliceToReplace) {
+ m_AlreadyActivatedPieSlice = replacementPieSlice;
+ }
+
+ RepopulateAndRealignCurrentPieSlices();
+ }
+
+ return replacedPieSlice;
+ }
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ void PieMenu::Update() {
+ const Controller *controller = GetController();
m_ActivatedPieSlice = nullptr;
diff --git a/Entities/PieMenu.h b/Entities/PieMenu.h
index 5fb27e64d..712106af4 100644
--- a/Entities/PieMenu.h
+++ b/Entities/PieMenu.h
@@ -317,6 +317,15 @@ namespace RTE {
/// The original source whose PieSlices should be removed.
/// Whether or not any PieSlices were removed from this PieMenu.
bool RemovePieSlicesByOriginalSource(const Entity *originalSource);
+
+ ///
+ /// Replaces the first PieSlice with the second, ensuring original source, direction, middle slice eligibility, angles and slot count are maintained.
+ /// The existing PieSlice is returned, and ownership IS transferred both ways!
+ ///
+ /// The PieSlice that will be replaced.
+ /// The PieSlice that will replace the existing one. If this is nullptr, the existing one will just be removed.
+ /// The removed PieSlice, if there is one. Ownership IS transferred!
+ PieSlice * ReplacePieSlice(const PieSlice *pieSliceToReplace, PieSlice *replacementPieSlice);
#pragma endregion
#pragma region Updating
diff --git a/Entities/Scene.cpp b/Entities/Scene.cpp
index 9dac036be..f669be29e 100644
--- a/Entities/Scene.cpp
+++ b/Entities/Scene.cpp
@@ -1568,6 +1568,7 @@ void Scene::SaveSceneObject(Writer &writer, const SceneObject *sceneObjectToSave
writer.NewPropertyWithValue("ParentOffset", attachableToSave->GetParentOffset());
writer.NewPropertyWithValue("DrawAfterParent", attachableToSave->IsDrawnAfterParent());
writer.NewPropertyWithValue("DeleteWhenRemovedFromParent", attachableToSave->GetDeleteWhenRemovedFromParent());
+ writer.NewPropertyWithValue("GibWhenRemovedFromParent", attachableToSave->GetGibWhenRemovedFromParent());
writer.NewPropertyWithValue("JointStrength", attachableToSave->GetJointStrength());
writer.NewPropertyWithValue("JointStiffness", attachableToSave->GetJointStiffness());
writer.NewPropertyWithValue("JointOffset", attachableToSave->GetJointOffset());
diff --git a/Entities/ThrownDevice.h b/Entities/ThrownDevice.h
index 752975437..af6fa43b2 100644
--- a/Entities/ThrownDevice.h
+++ b/Entities/ThrownDevice.h
@@ -129,7 +129,7 @@ namespace RTE {
void Activate() override;
///
- /// Does the calculations necessary to detect whether this MO appears to have has settled in the world and is at rest or not. IsAtRest() retrieves the answer.
+ /// Does the calculations necessary to detect whether this ThrownDevice is at rest or not. IsAtRest() retrieves the answer.
///
void RestDetection() override { HeldDevice::RestDetection(); if (m_Activated) { m_RestTimer.Reset(); } }
#pragma endregion
diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp
index 94b4509a1..42c0574f6 100644
--- a/Lua/LuaBindingsEntities.cpp
+++ b/Lua/LuaBindingsEntities.cpp
@@ -263,6 +263,7 @@ namespace RTE {
.def("HasObject", &Actor::HasObject)
.def("HasObjectInGroup", &Actor::HasObjectInGroup)
.def("IsWithinRange", &Actor::IsWithinRange)
+ .def("AddGold", &Actor::AddGold)
.def("AddHealth", &Actor::AddHealth)
.def("IsStatus", &Actor::IsStatus)
.def("IsDead", &Actor::IsDead)
@@ -283,6 +284,7 @@ namespace RTE {
.def("SwapNextInventory", &Actor::SwapNextInventory)
.def("SwapPrevInventory", &Actor::SwapPrevInventory)
.def("DropAllInventory", &Actor::DropAllInventory)
+ .def("DropAllGold", &Actor::DropAllGold)
.def("IsInventoryEmpty", &Actor::IsInventoryEmpty)
.def("FlashWhite", &Actor::FlashWhite)
.def("DrawWaypoints", &Actor::DrawWaypoints)
@@ -432,7 +434,6 @@ namespace RTE {
.property("JetTimeLeft", &AHuman::GetJetTimeLeft, &AHuman::SetJetTimeLeft)
.property("JetReplenishRate", &AHuman::GetJetReplenishRate, &AHuman::SetJetReplenishRate)
.property("JetAngleRange", &AHuman::GetJetAngleRange, &AHuman::SetJetAngleRange)
- .property("OneHandedReloadAngleOffset", &AHuman::GetOneHandedReloadAngleOffset, &AHuman::SetOneHandedReloadAngleOffset)
.property("UpperBodyState", &AHuman::GetUpperBodyState, &AHuman::SetUpperBodyState)
.property("ThrowPrepTime", &AHuman::GetThrowPrepTime, &AHuman::SetThrowPrepTime)
.property("ThrowProgress", &AHuman::GetThrowProgress)
@@ -551,9 +552,9 @@ namespace RTE {
.property("MaxLength", &Arm::GetMaxLength)
.property("MoveSpeed", &Arm::GetMoveSpeed, &Arm::SetMoveSpeed)
- .property("HandDefaultIdleOffset", &Arm::GetHandDefaultIdleOffset, &Arm::SetHandDefaultIdleOffset)
+ .property("HandIdleOffset", &Arm::GetHandIdleOffset, &Arm::SetHandIdleOffset)
- .property("HandCurrentPos", &Arm::GetHandCurrentPos, &Arm::SetHandCurrentPos)
+ .property("HandPos", &Arm::GetHandPos, &Arm::SetHandPos)
.property("HasAnyHandTargets", &Arm::HasAnyHandTargets)
.property("NumberOfHandTargets", &Arm::GetNumberOfHandTargets)
.property("NextHandTargetDescription", &Arm::GetNextHandTargetDescription)
@@ -583,6 +584,7 @@ namespace RTE {
.property("JointOffset", &Attachable::GetJointOffset, &Attachable::SetJointOffset)
.property("JointPos", &Attachable::GetJointPos)
.property("DeleteWhenRemovedFromParent", &Attachable::GetDeleteWhenRemovedFromParent, &Attachable::SetDeleteWhenRemovedFromParent)
+ .property("GibWhenRemovedFromParent", &Attachable::GetGibWhenRemovedFromParent, &Attachable::SetGibWhenRemovedFromParent)
.property("ApplyTransferredForcesAtOffset", &Attachable::GetApplyTransferredForcesAtOffset, &Attachable::SetApplyTransferredForcesAtOffset)
.property("BreakWound", &Attachable::GetBreakWound, &LuaAdaptersPropertyOwnershipSafetyFaker::AttachableSetBreakWound)
.property("ParentBreakWound", &Attachable::GetParentBreakWound, &LuaAdaptersPropertyOwnershipSafetyFaker::AttachableSetParentBreakWound)
@@ -591,6 +593,7 @@ namespace RTE {
.property("InheritedRotAngleOffset", &Attachable::GetInheritedRotAngleOffset, &Attachable::SetInheritedRotAngleOffset)
.property("AtomSubgroupID", &Attachable::GetAtomSubgroupID)
.property("CollidesWithTerrainWhileAttached", &Attachable::GetCollidesWithTerrainWhileAttached, &Attachable::SetCollidesWithTerrainWhileAttached)
+ .property("IgnoresParticlesWhileAttached", &Attachable::GetIgnoresParticlesWhileAttached, &Attachable::SetIgnoresParticlesWhileAttached)
.property("CanCollideWithTerrain", &Attachable::CanCollideWithTerrain)
.property("DrawnAfterParent", &Attachable::IsDrawnAfterParent, &Attachable::SetDrawnAfterParent)
.property("InheritsFrame", &Attachable::InheritsFrame, &Attachable::SetInheritsFrame)
@@ -671,6 +674,7 @@ namespace RTE {
return ConcreteTypeLuaClassDefinition(HDFirearm, HeldDevice)
.property("RateOfFire", &HDFirearm::GetRateOfFire, &HDFirearm::SetRateOfFire)
+ .property("MSPerRound", &HDFirearm::GetMSPerRound)
.property("FullAuto", &HDFirearm::IsFullAuto, &HDFirearm::SetFullAuto)
.property("Reloadable", &HDFirearm::IsReloadable, &HDFirearm::SetReloadable)
.property("DualReloadable", &HDFirearm::IsDualReloadable, &HDFirearm::SetDualReloadable)
@@ -699,6 +703,7 @@ namespace RTE {
.property("ShellVelVariation", &HDFirearm::GetShellVelVariation, &HDFirearm::SetShellVelVariation)
.property("FiredOnce", &HDFirearm::FiredOnce)
.property("FiredFrame", &HDFirearm::FiredFrame)
+ .property("CanFire", &HDFirearm::CanFire)
.property("RoundsFired", &HDFirearm::RoundsFired)
.property("IsAnimatedManually", &HDFirearm::IsAnimatedManually, &HDFirearm::SetAnimatedManually)
.property("RecoilTransmission", &HDFirearm::GetJointStiffness, &HDFirearm::SetJointStiffness)
@@ -965,6 +970,7 @@ namespace RTE {
.property("Vel", &MovableObject::GetVel, &MovableObject::SetVel)
.property("PrevPos", &MovableObject::GetPrevPos)
.property("PrevVel", &MovableObject::GetPrevVel)
+ .property("DistanceTravelled", &MovableObject::GetDistanceTravelled)
.property("AngularVel", &MovableObject::GetAngularVel, &MovableObject::SetAngularVel)
.property("Radius", &MovableObject::GetRadius)
.property("Diameter", &MovableObject::GetDiameter)
@@ -1173,7 +1179,8 @@ namespace RTE {
.def("RemovePieSlice", &PieMenu::RemovePieSlice, luabind::adopt(luabind::return_value))
.def("RemovePieSlicesByPresetName", &PieMenu::RemovePieSlicesByPresetName)
.def("RemovePieSlicesByType", &PieMenu::RemovePieSlicesByType)
- .def("RemovePieSlicesByOriginalSource", &PieMenu::RemovePieSlicesByOriginalSource);
+ .def("RemovePieSlicesByOriginalSource", &PieMenu::RemovePieSlicesByOriginalSource)
+ .def("ReplacePieSlice", &PieMenu::ReplacePieSlice, luabind::adopt(luabind::result) + luabind::adopt(_3));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/Lua/LuaBindingsManagers.cpp b/Lua/LuaBindingsManagers.cpp
index fd3fb2a48..6a5cd84e8 100644
--- a/Lua/LuaBindingsManagers.cpp
+++ b/Lua/LuaBindingsManagers.cpp
@@ -278,6 +278,7 @@ namespace RTE {
.property("GlobalAcc", &SceneMan::GetGlobalAcc)
.property("OzPerKg", &SceneMan::GetOzPerKg)
.property("KgPerOz", &SceneMan::GetKgPerOz)
+ .property("ScrapCompactingHeight", &SceneMan::GetScrapCompactingHeight, &SceneMan::SetScrapCompactingHeight)
.def("LoadScene", (int (SceneMan::*)(std::string, bool, bool))&SceneMan::LoadScene)
.def("LoadScene", (int (SceneMan::*)(std::string, bool))&SceneMan::LoadScene)
@@ -327,7 +328,8 @@ namespace RTE {
.def("ObscuredPoint", (bool (SceneMan::*)(Vector &, int))&SceneMan::ObscuredPoint)//, out_value(_2))
.def("ObscuredPoint", (bool (SceneMan::*)(int, int, int))&SceneMan::ObscuredPoint)
.def("AddSceneObject", &SceneMan::AddSceneObject, luabind::adopt(_2))
- .def("CheckAndRemoveOrphans", (int (SceneMan::*)(int, int, int, int, bool))&SceneMan::RemoveOrphans);
+ .def("CheckAndRemoveOrphans", (int (SceneMan::*)(int, int, int, int, bool))&SceneMan::RemoveOrphans)
+ .def("DislodgePixel", &SceneMan::DislodgePixel);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -356,7 +358,8 @@ namespace RTE {
.property("PrintDebugInfo", &SettingsMan::PrintDebugInfo, &SettingsMan::SetPrintDebugInfo)
.property("RecommendedMOIDCount", &SettingsMan::RecommendedMOIDCount)
.property("AIUpdateInterval", &SettingsMan::GetAIUpdateInterval, &SettingsMan::SetAIUpdateInterval)
- .property("ShowEnemyHUD", &SettingsMan::ShowEnemyHUD);
+ .property("ShowEnemyHUD", &SettingsMan::ShowEnemyHUD)
+ .property("AutomaticGoldDeposit", &SettingsMan::GetAutomaticGoldDeposit);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/Managers/CameraMan.cpp b/Managers/CameraMan.cpp
index 53dbf30a2..c527b1ced 100644
--- a/Managers/CameraMan.cpp
+++ b/Managers/CameraMan.cpp
@@ -206,22 +206,6 @@ namespace RTE {
Screen &screen = m_Screens[screenId];
const SLTerrain *terrain = g_SceneMan.GetScene()->GetTerrain();
- // Don't let our screen shake beyond our max.
- screen.ScreenShakeMagnitude = std::min(screen.ScreenShakeMagnitude, m_ScreenShakeDecay * m_MaxScreenShakeTime);
-
- // Reduce screen shake over time.
- screen.ScreenShakeMagnitude -= m_ScreenShakeDecay * static_cast(screen.ScrollTimer.GetElapsedRealTimeS());
- screen.ScreenShakeMagnitude = std::max(screen.ScreenShakeMagnitude, 0.0F);
-
- // Feedback was that the best screen-shake strength was between 25% and 40% of default.
- // As such, we want the default setting to reflect that, instead the default setting being 30%.
- // So just hard-coded multiply to make 100% in settings correspond to 30% here (much easier than rebalancing everything).
- const float screenShakeScale = 0.3F;
-
- Vector screenShakeOffset(1.0F, 0.0F);
- screenShakeOffset.RadRotate(RandomNormalNum() * c_PI);
- screenShakeOffset *= screen.ScreenShakeMagnitude * m_ScreenShakeStrength * screenShakeScale;
-
if (g_TimerMan.DrawnSimUpdate()) {
// Adjust for wrapping if the scroll target jumped a seam this frame, as reported by whatever screen set it (the scroll target) this frame. This is to avoid big, scene-wide jumps in scrolling when traversing the seam.
if (screen.TargetWrapped) {
@@ -259,7 +243,26 @@ namespace RTE {
newOffset += scrollVec * scrollProgress;
}
- newOffset += screenShakeOffset;
+ if (g_ActivityMan.GetActivity()->GetActivityState() == Activity::ActivityState::Running) {
+ // Don't let our screen shake beyond our max.
+ screen.ScreenShakeMagnitude = std::min(screen.ScreenShakeMagnitude, m_ScreenShakeDecay * m_MaxScreenShakeTime);
+
+ // Reduce screen shake over time.
+ screen.ScreenShakeMagnitude -= m_ScreenShakeDecay * static_cast(screen.ScrollTimer.GetElapsedRealTimeS());
+ screen.ScreenShakeMagnitude = std::max(screen.ScreenShakeMagnitude, 0.0F);
+
+ // Feedback was that the best screen-shake strength was between 25% and 40% of default.
+ // As such, we want the default setting to reflect that, instead the default setting being 30%.
+ // So just hard-coded multiply to make 100% in settings correspond to 30% here (much easier than rebalancing everything).
+ const float screenShakeScale = 0.3F;
+
+ Vector screenShakeOffset(1.0F, 0.0F);
+ screenShakeOffset.RadRotate(RandomNormalNum() * c_PI);
+ screenShakeOffset *= screen.ScreenShakeMagnitude * m_ScreenShakeStrength * screenShakeScale;
+
+ newOffset += screenShakeOffset;
+ }
+
SetOffset(newOffset, screenId);
screen.DeltaOffset = screen.Offset - oldOffset;
diff --git a/Managers/MovableMan.cpp b/Managers/MovableMan.cpp
index a2034adc1..08b4058db 100644
--- a/Managers/MovableMan.cpp
+++ b/Managers/MovableMan.cpp
@@ -716,7 +716,7 @@ Actor * MovableMan::GetClosestActor(const Vector &scenePoint, int maxRadius, Vec
Actor * MovableMan::GetClosestBrainActor(int team, const Vector &scenePoint) const
{
- if (team < Activity::TeamOne || team >= Activity::MaxTeamCount || m_Actors.empty() || m_ActorRoster[team].empty())
+ if (team < Activity::TeamOne || team >= Activity::MaxTeamCount || m_ActorRoster[team].empty())
return 0;
float sqrShortestDistance = std::numeric_limits::infinity();
@@ -1027,6 +1027,8 @@ void MovableMan::ChangeActorTeam(Actor * pActor, int team)
if (!pActor)
return;
+ if (pActor->IsPlayerControlled()) { g_ActivityMan.GetActivity()->LoseControlOfActor(pActor->GetController()->GetPlayer()); }
+
RemoveActorFromTeamRoster(pActor);
pActor->SetTeam(team);
AddActorToTeamRoster(pActor);
diff --git a/Managers/MovableMan.h b/Managers/MovableMan.h
index 084d3198d..0b77b5306 100644
--- a/Managers/MovableMan.h
+++ b/Managers/MovableMan.h
@@ -264,7 +264,7 @@ class MovableMan : public Singleton, public Serializable {
/// The player to get the Actor for. This affects which brain can be marked.
/// The Scene point to search for the closest to.
/// The maximum radius around that scene point to search.
- /// A float to be filled out with the distance of the returned closest to the search point. Will be unaltered if no object was found within radius.
+ /// A Vector to be filled out with the distance of the returned closest to the search point. Will be unaltered if no object was found within radius.
/// An Actor to exclude from the search. OWNERSHIP IS NOT TRANSFERRED!
/// An Actor pointer to the requested team's Actor closest to the Scene point, but not outside the max radius. If no Actor other than the excluded one was found within the radius of the point, nullptr is returned.
Actor * GetClosestTeamActor(int team, int player, const Vector &scenePoint, int maxRadius, Vector &getDistance, const Actor *excludeThis = nullptr) { return GetClosestTeamActor(team, player, scenePoint, maxRadius, getDistance, false, excludeThis); }
@@ -276,7 +276,7 @@ class MovableMan : public Singleton, public Serializable {
/// The player to get the Actor for. This affects which brain can be marked.
/// The Scene point to search for the closest to.
/// The maximum radius around that scene point to search.
- /// A float to be filled out with the distance of the returned closest to the search point. Will be unaltered if no object was found within radius.
+ /// A Vector to be filled out with the distance of the returned closest to the search point. Will be unaltered if no object was found within radius.
/// Whether to only get Actors that are flagged as player controllable.
/// An Actor to exclude from the search. OWNERSHIP IS NOT TRANSFERRED!
/// An Actor pointer to the requested team's Actor closest to the Scene point, but not outside the max radius. If no Actor other than the excluded one was found within the radius of the point, nullptr is returned.
diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp
index cfadecb88..bf2420019 100644
--- a/Managers/SceneMan.cpp
+++ b/Managers/SceneMan.cpp
@@ -40,11 +40,12 @@ namespace RTE
{
#define CLEANAIRINTERVAL 200000
-#define COMPACTINGHEIGHT 25
const std::string SceneMan::c_ClassName = "SceneMan";
std::vector> SceneMan::m_IntermediateSettlingBitmaps;
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
void SceneMan::Clear()
{
m_DefaultSceneName = "Tutorial Bunker";
@@ -76,6 +77,8 @@ void SceneMan::Clear()
if (m_pOrphanSearchBitmap)
destroy_bitmap(m_pOrphanSearchBitmap);
m_pOrphanSearchBitmap = create_bitmap_ex(8, MAXORPHANRADIUS , MAXORPHANRADIUS);
+
+ m_ScrapCompactingHeight = 25;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -579,7 +582,7 @@ bool SceneMan::SceneIsLocked() const
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SceneMan::RegisterDrawing(const BITMAP *bitmap, int moid, int left, int top, int right, int bottom) {
- if (m_pMOColorLayer && m_pMOColorLayer->GetBitmap() == bitmap) {
+ if (m_pMOColorLayer && m_pMOColorLayer->GetBitmap() == bitmap) {
m_pMOColorLayer->RegisterDrawing(left, top, right, bottom);
} else if (m_pMOIDLayer && m_pMOIDLayer->GetBitmap() == bitmap) {
#ifdef DRAW_MOID_LAYER
@@ -871,9 +874,8 @@ bool SceneMan::TryPenetrate(int posX,
Material const * sceneMat = GetMaterialFromID(materialID);
Material const * spawnMat;
- float sprayScale = 0.1;
-// float spraySpread = 10.0;
- float sqrImpMag = impulse.GetSqrMagnitude();
+ float sprayScale = 0.1F;
+ float sqrImpMag = impulse.GetSqrMagnitude();
// Test if impulse force is enough to penetrate
if (sqrImpMag >= (sceneMat->GetIntegrity() * sceneMat->GetIntegrity()))
@@ -939,9 +941,8 @@ bool SceneMan::TryPenetrate(int posX,
// retardation = -sceneMat.density;
retardation = -(sceneMat->GetIntegrity() / std::sqrt(sqrImpMag));
- // If this is a scrap pixel, or there is no background pixel 'supporting' the knocked-loose pixel, make the column above also turn into particles
- if (sceneMat->IsScrap() || _getpixel(m_pCurrentScene->GetTerrain()->GetBGColorBitmap(), posX, posY) == g_MaskColor)
- {
+ // If this is a scrap pixel, or there is no background pixel 'supporting' the knocked-loose pixel, make the column above also turn into particles.
+ if (m_ScrapCompactingHeight > 0 && (sceneMat->IsScrap() || _getpixel(m_pCurrentScene->GetTerrain()->GetBGColorBitmap(), posX, posY) == g_MaskColor)) {
// Get quicker direct access to bitmaps
BITMAP *pFGColor = m_pCurrentScene->GetTerrain()->GetFGColorBitmap();
BITMAP *pBGColor = m_pCurrentScene->GetTerrain()->GetBGColorBitmap();
@@ -950,58 +951,42 @@ bool SceneMan::TryPenetrate(int posX,
int testMaterialID = g_MaterialAir;
MOPixel *pixelMO = 0;
Color spawnColor;
- float sprayMag = velocity.GetLargest() * sprayScale;
- Vector sprayVel;
-
- // Look at pixel above to see if it isn't air and has support, or should fall down
- for (int testY = posY - 1; testY > posY - COMPACTINGHEIGHT && testY >= 0; --testY)
- {
- // Check if there is a material pixel above
- if ((testMaterialID = _getpixel(pMaterial, posX, testY)) != g_MaterialAir)
- {
- sceneMat = GetMaterialFromID(testMaterialID);
+ float sprayMag = std::sqrt(velocity.GetMagnitude() * sprayScale);
+ Vector sprayVel;
+
+ for (int testY = posY - 1; testY > posY - m_ScrapCompactingHeight && testY >= 0; --testY) {
+ if ((testMaterialID = _getpixel(pMaterial, posX, testY)) != g_MaterialAir) {
+ sceneMat = GetMaterialFromID(testMaterialID);
+
+ if (sceneMat->IsScrap() || _getpixel(pBGColor, posX, testY) == g_MaskColor) {
+ if (RandomNum() < 0.7F) {
+ spawnMat = sceneMat->GetSpawnMaterial() ? GetMaterialFromID(sceneMat->GetSpawnMaterial()) : sceneMat;
+ if (spawnMat->UsesOwnColor()) {
+ spawnColor = spawnMat->GetColor();
+ } else {
+ spawnColor.SetRGBWithIndex(m_pCurrentScene->GetTerrain()->GetFGColorPixel(posX, testY));
+ }
+ if (spawnColor.GetIndex() != g_MaskColor) {
+ // Send terrain pixels flying at a diminishing rate the higher the column goes.
+ sprayVel.SetXY(0, -sprayMag * (1.0F - (static_cast(posY - testY) / static_cast(m_ScrapCompactingHeight))));
+ sprayVel.RadRotate(RandomNum(-c_HalfPI, c_HalfPI));
- // No support in the background layer, or is scrap material, so make particle of some of them
- if (sceneMat->IsScrap() || _getpixel(pBGColor, posX, testY) == g_MaskColor)
- {
- // Only generate particles of some of 'em
- if (RandomNum() > 0.75F)
- {
- // Figure out the mateiral and color of the new spray particle
- spawnMat = sceneMat->GetSpawnMaterial() ? GetMaterialFromID(sceneMat->GetSpawnMaterial()) : sceneMat;
- if (spawnMat->UsesOwnColor())
- spawnColor = spawnMat->GetColor();
- else
- spawnColor.SetRGBWithIndex(m_pCurrentScene->GetTerrain()->GetFGColorPixel(posX, testY));
-
- // No point generating a key-colored MOPixel
- if (spawnColor.GetIndex() != g_MaskColor)
- {
- // Figure out the randomized velocity the spray should have upward
- sprayVel.SetXY(sprayMag* RandomNormalNum() * 0.5F, (-sprayMag * 0.5F) + (-sprayMag * RandomNum(0.0F, 0.5F)));
-
- // Create the new spray pixel
pixelMO = new MOPixel(spawnColor, spawnMat->GetPixelDensity(), Vector(posX, testY), sprayVel, new Atom(Vector(), spawnMat->GetIndex(), 0, spawnColor, 2), 0);
- // Let it loose into the world
- pixelMO->SetToHitMOs(spawnMat->GetIndex() == c_GoldMaterialID);
- pixelMO->SetToGetHitByMOs(false);
- g_MovableMan.AddParticle(pixelMO);
- pixelMO = 0;
- }
-
- // Remove orphaned terrain left from hits and scrap damage
- RemoveOrphans(posX + testY%2 ? -1 : 1, testY, 5, 25, true);
+ pixelMO->SetToHitMOs(spawnMat->GetIndex() == c_GoldMaterialID);
+ pixelMO->SetToGetHitByMOs(false);
+ g_MovableMan.AddParticle(pixelMO);
+ pixelMO = 0;
+ }
+ RemoveOrphans(posX + testY % 2 ? -1 : 1, testY, removeOrphansRadius + 5, removeOrphansMaxArea + 10, true);
}
- // Clear the terrain pixel now when the particle has been generated from it
RegisterTerrainChange(posX, testY, 1, 1, g_MaskColor, false);
- _putpixel(pFGColor, posX, testY, g_MaskColor);
- _putpixel(pMaterial, posX, testY, g_MaterialAir);
- }
- // There is support, so stop checking
- else
- break;
+ _putpixel(pFGColor, posX, testY, g_MaskColor);
+ _putpixel(pMaterial, posX, testY, g_MaterialAir);
+ } else {
+ break;
+ }
}
}
}
@@ -1022,6 +1007,38 @@ bool SceneMan::TryPenetrate(int posX,
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+MovableObject * SceneMan::DislodgePixel(int posX, int posY) {
+ int materialID = getpixel(m_pCurrentScene->GetTerrain()->GetMaterialBitmap(), posX, posY);
+ if (materialID <= MaterialColorKeys::g_MaterialAir) {
+ return nullptr;
+ }
+ const Material *sceneMat = GetMaterialFromID(static_cast(materialID));
+ const Material *spawnMat = sceneMat->GetSpawnMaterial() ? GetMaterialFromID(sceneMat->GetSpawnMaterial()) : sceneMat;
+
+ Color spawnColor;
+ if (spawnMat->UsesOwnColor()) {
+ spawnColor = spawnMat->GetColor();
+ } else {
+ spawnColor.SetRGBWithIndex(m_pCurrentScene->GetTerrain()->GetFGColorPixel(posX, posY));
+ }
+ // No point generating a key-colored MOPixel.
+ if (spawnColor.GetIndex() == ColorKeys::g_MaskColor) {
+ return nullptr;
+ }
+ Atom *pixelAtom = new Atom(Vector(), spawnMat->GetIndex(), nullptr, spawnColor, 2);
+ MOPixel *pixelMO = new MOPixel(spawnColor, spawnMat->GetPixelDensity(), Vector(static_cast(posX), static_cast(posY)), Vector(), pixelAtom, 0);
+ pixelMO->SetToHitMOs(spawnMat->GetIndex() == c_GoldMaterialID);
+ g_MovableMan.AddParticle(pixelMO);
+
+ m_pCurrentScene->GetTerrain()->SetFGColorPixel(posX, posY, ColorKeys::g_MaskColor);
+ RegisterTerrainChange(posX, posY, 1, 1, ColorKeys::g_MaskColor, false);
+ m_pCurrentScene->GetTerrain()->SetMaterialPixel(posX, posY, MaterialColorKeys::g_MaterialAir);
+
+ return pixelMO;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
void SceneMan::MakeAllUnseen(Vector pixelSize, const int team)
{
RTEAssert(m_pCurrentScene, "Messing with scene before the scene exists!");
@@ -2701,7 +2718,7 @@ void SceneMan::Update(int screenId) {
// Update the scene, only if doing the first screen, since it only needs done once per update.
if (screenId == 0) {
- m_pCurrentScene->Update();
+ m_pCurrentScene->Update();
}
g_CameraMan.Update(screenId);
@@ -2709,7 +2726,7 @@ void SceneMan::Update(int screenId) {
const Vector &offset = g_CameraMan.GetOffset(screenId);
m_pMOColorLayer->SetOffset(offset);
m_pMOIDLayer->SetOffset(offset);
- if (m_pDebugLayer) {
+ if (m_pDebugLayer) {
m_pDebugLayer->SetOffset(offset);
}
@@ -2783,8 +2800,8 @@ void SceneMan::Draw(BITMAP *targetBitmap, BITMAP *targetGUIBitmap, const Vector
}
if (!g_FrameMan.IsInMultiplayerMode()) {
int teamId = g_CameraMan.GetScreenTeam(m_LastUpdatedScreen);
- if (SceneLayer *unseenLayer = (teamId != Activity::NoTeam) ? m_pCurrentScene->GetUnseenLayer(teamId) : nullptr) {
- unseenLayer->Draw(targetBitmap, targetBox);
+ if (SceneLayer *unseenLayer = (teamId != Activity::NoTeam) ? m_pCurrentScene->GetUnseenLayer(teamId) : nullptr) {
+ unseenLayer->Draw(targetBitmap, targetBox);
}
}
@@ -2792,8 +2809,8 @@ void SceneMan::Draw(BITMAP *targetBitmap, BITMAP *targetGUIBitmap, const Vector
g_PrimitiveMan.DrawPrimitives(m_LastUpdatedScreen, targetGUIBitmap, targetPos);
g_ActivityMan.GetActivity()->DrawGUI(targetGUIBitmap, targetPos, m_LastUpdatedScreen);
- if (m_pDebugLayer) {
- m_pDebugLayer->Draw(targetBitmap, targetBox);
+ if (m_pDebugLayer) {
+ m_pDebugLayer->Draw(targetBitmap, targetBox);
}
break;
diff --git a/Managers/SceneMan.h b/Managers/SceneMan.h
index da2cd0d53..0b86c33b1 100644
--- a/Managers/SceneMan.h
+++ b/Managers/SceneMan.h
@@ -668,6 +668,14 @@ class SceneMan : public Singleton, public Serializable {
int maxArea,
bool remove = false);
+ ///
+ /// Removes a pixel from the terrain and adds it to MovableMan.
+ ///
+ /// The X coordinate of the terrain pixel.
+ /// The Y coordinate of the terrain pixel.
+ /// The newly dislodged pixel, if one was found.
+ MovableObject * DislodgePixel(int posX, int posY);
+
//////////////////////////////////////////////////////////////////////////////////////////
// Method: MakeAllUnseen
//////////////////////////////////////////////////////////////////////////////////////////
@@ -1399,6 +1407,18 @@ class SceneMan : public Singleton, public Serializable {
///
void ClearCurrentScene();
+ ///
+ /// Gets the maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose.
+ ///
+ /// The compacting height of scrap terrain.
+ int GetScrapCompactingHeight() const { return m_ScrapCompactingHeight; }
+
+ ///
+ /// Sets the maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose.
+ ///
+ /// The new compacting height, in pixels.
+ void SetScrapCompactingHeight(int newHeight) { m_ScrapCompactingHeight = newHeight; }
+
//////////////////////////////////////////////////////////////////////////////////////////
// Protected member variable and method declarations
@@ -1463,6 +1483,8 @@ class SceneMan : public Singleton, public Serializable {
// Bitmap to look for orphaned regions
BITMAP * m_pOrphanSearchBitmap;
+ int m_ScrapCompactingHeight; //!< The maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose.
+
//////////////////////////////////////////////////////////////////////////////////////////
// Private member variable and method declarations
diff --git a/Managers/SettingsMan.cpp b/Managers/SettingsMan.cpp
index 2096d6634..20d4aeb34 100644
--- a/Managers/SettingsMan.cpp
+++ b/Managers/SettingsMan.cpp
@@ -31,6 +31,7 @@ namespace RTE {
m_CrabBombThreshold = 42;
m_ShowEnemyHUD = true;
m_EnableSmartBuyMenuNavigation = true;
+ m_AutomaticGoldDeposit = true;
m_NetworkServerAddress = "127.0.0.1:8000";
m_PlayerNetworkName = "Dummy";
@@ -160,6 +161,10 @@ namespace RTE {
reader >> m_ShowEnemyHUD;
} else if (propName == "SmartBuyMenuNavigation") {
reader >> m_EnableSmartBuyMenuNavigation;
+ } else if (propName == "ScrapCompactingHeight") {
+ reader >> g_SceneMan.m_ScrapCompactingHeight;
+ } else if (propName == "AutomaticGoldDeposit") {
+ reader >> m_AutomaticGoldDeposit;
} else if (propName == "ScreenShakeStrength") {
reader >> g_CameraMan.m_ScreenShakeStrength;
} else if (propName == "ScreenShakeDecay") {
@@ -351,6 +356,8 @@ namespace RTE {
writer.NewPropertyWithValue("CrabBombThreshold", m_CrabBombThreshold);
writer.NewPropertyWithValue("ShowEnemyHUD", m_ShowEnemyHUD);
writer.NewPropertyWithValue("SmartBuyMenuNavigation", m_EnableSmartBuyMenuNavigation);
+ writer.NewPropertyWithValue("ScrapCompactingHeight", g_SceneMan.m_ScrapCompactingHeight);
+ writer.NewPropertyWithValue("AutomaticGoldDeposit", m_AutomaticGoldDeposit);
writer.NewLine(false, 2);
writer.NewDivider(false);
diff --git a/Managers/SettingsMan.h b/Managers/SettingsMan.h
index 729702ab9..f97b8ce94 100644
--- a/Managers/SettingsMan.h
+++ b/Managers/SettingsMan.h
@@ -244,6 +244,12 @@ namespace RTE {
///
/// Whether to enable smart BuyMenu navigation or not.
void SetSmartBuyMenuNavigation(bool enable) { m_EnableSmartBuyMenuNavigation = enable; }
+
+ ///
+ /// Gets whether gold gathered by Actors is automatically added into team funds.
+ ///
+ /// Whether gold gathered by Actors is automatically added into team funds.
+ bool GetAutomaticGoldDeposit() const { return m_AutomaticGoldDeposit; }
#pragma endregion
#pragma region Network Settings
@@ -499,6 +505,7 @@ namespace RTE {
int m_CrabBombThreshold; //!< The number of crabs needed to be released at once to trigger the crab bomb effect.
bool m_ShowEnemyHUD; //!< Whether the HUD of enemy actors should be visible to the player.
bool m_EnableSmartBuyMenuNavigation; //!< Whether swapping to equipment mode and back should change active tabs in the BuyMenu.
+ bool m_AutomaticGoldDeposit; //!< Whether gold gathered by Actors is automatically added into team funds. False means that gold needs to be manually transported into orbit via Craft.
std::string m_PlayerNetworkName; //!< Player name used in network multiplayer matches.
std::string m_NetworkServerAddress; //!< LAN server address to connect to.
diff --git a/Menus/BuyMenuGUI.cpp b/Menus/BuyMenuGUI.cpp
index cf7bb50e3..004095943 100644
--- a/Menus/BuyMenuGUI.cpp
+++ b/Menus/BuyMenuGUI.cpp
@@ -1384,14 +1384,14 @@ void BuyMenuGUI::Update()
if (craftMaxMass == 0) {
description += "\nNO CARGO SPACE!";
} else if (craftMaxMass > 0) {
- description += "\nMax Mass: " + RoundFloatToPrecision(craftMaxMass, 1) + " kg";
+ description += "\nMax Mass: " + RoundFloatToPrecision(craftMaxMass, craftMaxMass < 50.0F ? 1 : 0, 3) + " kg";
}
if (craftMaxPassengers >= 0 && craftMaxMass != 0) { description += (craftMaxPassengers == 0) ? "\nNO PASSENGER SPACE!" : "\nMax Passengers: " + std::to_string(craftMaxPassengers); }
} else {
// Items in the BuyMenu always have any remainder rounded up in their masses.
const Actor *itemAsActor = dynamic_cast(currentItem);
if (itemAsActor) {
- description += "\nMass: " + RoundFloatToPrecision(itemAsActor->GetMass(), 1, 2) + " kg";
+ description += "\nMass: " + (itemAsActor->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsActor->GetMass(), itemAsActor->GetMass() < 50.0F ? 1 : 0, 3) + " kg");
int passengerSlotsTaken = itemAsActor->GetPassengerSlots();
if (passengerSlotsTaken > 1) {
description += "\nPassenger Slots: " + std::to_string(passengerSlotsTaken);
@@ -1412,7 +1412,7 @@ void BuyMenuGUI::Update()
extraMass = itemAsMOSRotating->GetNumberValue("Belt Mass");
}
}
- description += "\nMass: " + RoundFloatToPrecision(itemAsMO->GetMass() + extraMass, 1, 2) + " kg";
+ description += "\nMass: " + (itemAsMO->GetMass() + extraMass < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsMO->GetMass() + extraMass, itemAsMO->GetMass() + extraMass < 50.0F ? 1 : 0, 3) + " kg");
}
}
}
@@ -1576,7 +1576,7 @@ void BuyMenuGUI::Update()
const Entity *currentItem = pItem->m_pEntity;
const Actor *itemAsActor = dynamic_cast(currentItem);
if (itemAsActor) {
- description += "\nMass: " + RoundFloatToPrecision(itemAsActor->GetMass(), 1, 2) + " kg";
+ description += "\nMass: " + (itemAsActor->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsActor->GetMass(), itemAsActor->GetMass() < 50.0F ? 1 : 0, 3) + " kg");
int passengerSlotsTaken = itemAsActor->GetPassengerSlots();
if (passengerSlotsTaken > 1) {
@@ -1585,7 +1585,7 @@ void BuyMenuGUI::Update()
} else {
const MovableObject *itemAsMO = dynamic_cast(currentItem);
if (itemAsMO) {
- description += "\nMass: " + RoundFloatToPrecision(itemAsMO->GetMass(), 1, 2) + " kg";
+ description += "\nMass: " + (itemAsMO->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsMO->GetMass(), itemAsMO->GetMass() < 50.0F ? 1 : 0, 3) + " kg");
}
}
}
@@ -2522,7 +2522,7 @@ void BuyMenuGUI::AddPresetsToItemList()
// Add the ship's cost, if there is one defined
if ((*lItr).GetDeliveryCraft())
{
- loadoutLabel += " on " + (*lItr).GetDeliveryCraft()->GetPresetName();
+ loadoutLabel += " via " + (*lItr).GetDeliveryCraft()->GetPresetName();
// Adjust price for foreignness of the ship to this player
loadoutCost += (*lItr).GetDeliveryCraft()->GetGoldValue(m_NativeTechModule, m_ForeignCostMult);
}
diff --git a/Menus/InventoryMenuGUI.cpp b/Menus/InventoryMenuGUI.cpp
index 197c2e308..8b6cba937 100644
--- a/Menus/InventoryMenuGUI.cpp
+++ b/Menus/InventoryMenuGUI.cpp
@@ -1242,10 +1242,10 @@ namespace RTE {
Arm *equippedItemArm = equippedItemIndex == 0 ? inventoryActorAsAHuman->GetFGArm() : inventoryActorAsAHuman->GetBGArm();
equippedItemArm->SetHeldDevice(dynamic_cast(m_InventoryActor->SetInventoryItemAtIndex(equippedItemArm->RemoveAttachable(equippedItemArm->GetHeldDevice()), inventoryItemIndex)));
- equippedItemArm->SetHandCurrentPos(m_InventoryActor->GetPos() + m_InventoryActor->GetHolsterOffset().GetXFlipped(m_InventoryActor->IsHFlipped()));
+ equippedItemArm->SetHandPos(m_InventoryActor->GetPos() + m_InventoryActor->GetHolsterOffset().GetXFlipped(m_InventoryActor->IsHFlipped()));
if (!inventoryItemCanGoInOffhand && offhandEquippedItem) {
m_InventoryActor->AddInventoryItem(inventoryActorAsAHuman->GetBGArm()->RemoveAttachable(inventoryActorAsAHuman->GetBGArm()->GetHeldDevice()));
- inventoryActorAsAHuman->GetBGArm()->SetHandCurrentPos(m_InventoryActor->GetPos() + m_InventoryActor->GetHolsterOffset().GetXFlipped(m_InventoryActor->IsHFlipped()));
+ inventoryActorAsAHuman->GetBGArm()->SetHandPos(m_InventoryActor->GetPos() + m_InventoryActor->GetHolsterOffset().GetXFlipped(m_InventoryActor->IsHFlipped()));
}
m_InventoryActor->GetDeviceSwitchSound()->Play(m_MenuController->GetPlayer());
return true;
@@ -1413,7 +1413,7 @@ namespace RTE {
}
});
- std::string massString = RoundFloatToPrecision(std::fminf(999, totalItemMass), 0) + (totalItemMass > 999 ? "+ " : " ") + "KG";
+ std::string massString = totalItemMass < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(std::fminf(999, totalItemMass), (totalItemMass < 9.95F ? 1 : 0)) + (totalItemMass > 999 ? "+ " : " ") + "kg";
m_SmallFont->DrawAligned(carouselAllegroBitmap, itemBoxToDraw.IconCenterPosition.GetFloorIntX(), itemBoxToDraw.IconCenterPosition.GetFloorIntY() - ((itemBoxToDraw.CurrentSize.GetFloorIntY() + m_SmallFont->GetFontHeight()) / 2) + 1, massString.c_str(), GUIFont::Centre);
}
diff --git a/System/Atom.cpp b/System/Atom.cpp
index 02ed328c8..b3ac8f1b4 100644
--- a/System/Atom.cpp
+++ b/System/Atom.cpp
@@ -800,9 +800,8 @@ namespace RTE {
// Gold special collection case!
// TODO: Make material IDs more robust!")
if (m_Material->GetIndex() == c_GoldMaterialID && g_MovableMan.IsOfActor(m_MOIDHit)) {
- Actor *pActor = dynamic_cast(g_MovableMan.GetMOFromID(m_LastHit.Body[HITEE]->GetRootID()));
- if (pActor) {
- pActor->AddGold(m_OwnerMO->GetMass() * g_SceneMan.GetOzPerKg() * removeOrphansRadius ? 1.25F : 1.0F);
+ if (Actor *actor = dynamic_cast(g_MovableMan.GetMOFromID(m_LastHit.Body[HITEE]->GetRootID())); actor && !actor->IsDead()) {
+ actor->AddGold(m_OwnerMO->GetMass() * g_SceneMan.GetOzPerKg() * removeOrphansRadius ? 1.25F : 1.0F);
m_OwnerMO->SetToDelete(true);
// This is to break out of the do-while and the function properly.
m_LastHit.Terminate[HITOR] = hit[dom] = hit[sub] = true;
diff --git a/System/RTETools.cpp b/System/RTETools.cpp
index 36e2381e5..e2583684d 100644
--- a/System/RTETools.cpp
+++ b/System/RTETools.cpp
@@ -185,6 +185,12 @@ namespace RTE {
case 2:
roundingBuffer = std::ceil(roundingBuffer);
break;
+ case 3:
+ roundingBuffer = std::ceil(roundingBuffer);
+ if (int remainder = static_cast(roundingBuffer) % 10; remainder > 0) {
+ roundingBuffer = roundingBuffer - static_cast(remainder) + (remainder <= 5 ? 5.0F : 10.0F);
+ }
+ break;
default:
RTEAbort("Error in RoundFloatToPrecision: INVALID ROUNDING MODE");
break;