From f75edf5e8968b34aa352e9622664ded5edcffcf0 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 22:02:25 +0200 Subject: [PATCH 01/76] Add dedicated `Activity` function `LoseControlOfActor` that needs to be run in tandem with `MovableMan` function `ChangeActorTeam` so that the player won't control an actor of the wrong team. (TODO: Allow modders to change actor team without losing control?) --- Activities/GameActivity.cpp | 9 +++------ Entities/Activity.cpp | 13 +++++++++++++ Entities/Activity.h | 6 ++++++ Managers/MovableMan.cpp | 2 ++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Activities/GameActivity.cpp b/Activities/GameActivity.cpp index a237d1a19..140a5a7c5 100644 --- a/Activities/GameActivity.cpp +++ b/Activities/GameActivity.cpp @@ -1346,14 +1346,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 @@ -1373,9 +1372,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/Entities/Activity.cpp b/Entities/Activity.cpp index ae2aa8a3d..bd8b44c63 100644 --- a/Entities/Activity.cpp +++ b/Entities/Activity.cpp @@ -754,6 +754,19 @@ void Activity::Clear() { return true; } +////////////////////////////////////////////////////////////////////////////////////////// + + void Activity::LoseControlOfActor(int player) { + if (player >= Players::PlayerOne && player < Players::MaxPlayerCount) { + if (Actor *actor = m_ControlledActor[player]) { + actor->SetControllerMode(Controller::CIM_AI); + actor->GetController()->SetDisabled(false); + } + m_ControlledActor[player] = nullptr; + m_ViewState[player] = ViewState::DeathWatch; + } + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void Activity::HandleCraftEnteringOrbit(ACraft *orbitedCraft) { diff --git a/Entities/Activity.h b/Entities/Activity.h index bb0ed1034..767397717 100644 --- a/Entities/Activity.h +++ b/Entities/Activity.h @@ -657,6 +657,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'. /// diff --git a/Managers/MovableMan.cpp b/Managers/MovableMan.cpp index b17e3bf39..0b28e3879 100644 --- a/Managers/MovableMan.cpp +++ b/Managers/MovableMan.cpp @@ -979,6 +979,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); From 51882150a6a89e353b2b0f0cae3ef4a2f83a8359 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 22:12:39 +0200 Subject: [PATCH 02/76] Change the preposition "on" to "via" in Preset descriptions to better describe any method of delivery, such as teleportation --- Menus/BuyMenuGUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Menus/BuyMenuGUI.cpp b/Menus/BuyMenuGUI.cpp index 22ad2e71b..221f35f52 100644 --- a/Menus/BuyMenuGUI.cpp +++ b/Menus/BuyMenuGUI.cpp @@ -2375,7 +2375,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); } From 6626b1f77b4106cfb466eaeb4775f35ab5a57fed Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 22:51:29 +0200 Subject: [PATCH 03/76] Crutches for the terminally ill cpp AI (sorry Causeless) --- Entities/AHuman.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 8b4370fc2..ac9c841c3 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1832,6 +1832,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()) { @@ -1930,14 +1935,13 @@ 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; @@ -2904,7 +2908,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)) From 4c0403aed26c213b9fccc8545dd7ea333d2b3caf Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 23:00:37 +0200 Subject: [PATCH 04/76] Refer to the Velocity of the item when it gets dropped (otherwise inherited from the parent actor), so that `OnDetach` can run assign scripted velocities to the dropped item (Uzira sword) --- Entities/AHuman.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index ac9c841c3..9b8da8a79 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3448,8 +3448,8 @@ void AHuman::Update() heldMO->SetPos(m_pFGArm->GetJointPos() + Vector(m_pFGArm->GetMaxLength() * GetFlipFactor(), 0).RadRotate(adjustedAimAngle)); m_pFGArm->SetHandPos(heldMO->GetPos()); Vector tossVec(1.0F + std::sqrt(std::abs(m_pFGArm->GetThrowStrength()) / std::sqrt(std::abs(heldMO->GetMass()) + 1.0F)), RandomNormalNum()); - heldMO->SetVel(m_Vel * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped)); - heldMO->SetAngularVel(m_AngularVel * 0.5F + 3.0F * RandomNormalNum()); + heldMO->SetVel(heldMO->GetVel() * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped)); + heldMO->SetAngularVel(heldMO->GetAngularVel() + m_AngularVel * 0.5F + 3.0F * RandomNormalNum()); if (HeldDevice *heldMOAsHeldDevice = dynamic_cast(heldMO)) { g_MovableMan.AddItem(heldMOAsHeldDevice); } else { @@ -3469,8 +3469,8 @@ void AHuman::Update() heldMO->SetPos(m_pBGArm->GetJointPos() + Vector(m_pBGArm->GetMaxLength() * GetFlipFactor(), 0).RadRotate(adjustedAimAngle)); m_pBGArm->SetHandPos(heldMO->GetPos()); Vector tossVec(1.0F + std::sqrt(std::abs(m_pBGArm->GetThrowStrength()) / std::sqrt(std::abs(heldMO->GetMass()) + 1.0F)), RandomNormalNum()); - heldMO->SetVel(m_Vel * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped)); - heldMO->SetAngularVel(m_AngularVel * 0.5F + 3.0F * RandomNormalNum()); + heldMO->SetVel(heldMO->GetVel() * 0.5F + tossVec.RadRotate(m_AimAngle).GetXFlipped(m_HFlipped)); + heldMO->SetAngularVel(heldMO->GetAngularVel() + m_AngularVel * 0.5F + 3.0F * RandomNormalNum()); if (HeldDevice *heldMOAsHeldDevice = dynamic_cast(heldMO)) { g_MovableMan.AddItem(heldMOAsHeldDevice); } else { From 505cc40fcd05658c69579389fe889299ddccc98e Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 23:10:08 +0200 Subject: [PATCH 05/76] Allow penetrating particles to omit the hitee MOSR if it has already been flagged for deletion (TODO: settings flag?) --- CHANGELOG.md | 2 ++ Entities/MOSRotating.cpp | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c45a549c..7f2fa6eaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -633,6 +633,8 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - Fixed `Entity.ModuleName` returning and 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 diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index 5a5f3fb8b..90e1fb1bd 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -501,7 +501,7 @@ Attachable * MOSRotating::GetNearestAttachableToOffset(const Vector &offset) con ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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) { @@ -714,6 +714,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(); From b940fd6f69d13ba99b1576ce6b9320315bf82480 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 23:14:35 +0200 Subject: [PATCH 06/76] Use flip `AEmitter` Flashes instead of rotating them --- Entities/AEmitter.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Entities/AEmitter.cpp b/Entities/AEmitter.cpp index 97a3b413a..ab8d70170 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_HFlipped ? -m_EmitAngle.GetRadAngle() : m_EmitAngle.GetRadAngle())); m_pFlash->SetScale(m_FlashScale); m_pFlash->SetNextFrame(); } From 89aa378c5bf293b96e71ed036b660c2cc96fdb71 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 23:22:00 +0200 Subject: [PATCH 07/76] `AHuman` head rotation fixes and HUD fix where item in pickup reach properly raises the `AboveHUDPos` --- Entities/AHuman.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 9b8da8a79..e6eaf8bc8 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3734,18 +3734,16 @@ void AHuman::Update() ///////////////////////////////// // Manage Attachable:s - if (m_pHead) { - float toRotate = 0; - // Only rotate the head to match the aim angle if body is stable and upright - if (m_Status == STABLE && std::abs(rot) < (c_HalfPI + c_QuarterPI)) { - toRotate = m_pHead->GetRotMatrix().GetRadAngleTo((adjustedAimAngle) * m_LookToAimRatio + rot * (0.9F - m_LookToAimRatio)) * 0.15F; + if (m_pHead) { + float toRotate = 0; + if (m_Status == STABLE && std::abs(rot) < (c_HalfPI + c_QuarterPI)) { + 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); @@ -4281,6 +4279,7 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc if (!m_Controller.IsState(PIE_MENU_ACTIVE) && m_pItemInReach) { std::snprintf(str, sizeof(str), " %c %s", -49, m_pItemInReach->GetPresetName().c_str()); pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX(), drawPos.GetFloorIntY() + m_HUDStack + 3, str, GUIFont::Centre); + m_HUDStack -= 9; } /* // AI Mode select GUI HUD From 03acb75db51e05943c85228dc6219a558e4d3804 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 12 Jan 2023 23:42:37 +0200 Subject: [PATCH 08/76] Added settings property for `CompactingHeight`, i.e. the maximum height of a column of scrap terrain that can get collapsed. Also cleaned up around compacting height code, and made the pixels falling from the column less erratic. --- CHANGELOG.md | 2 + Managers/SceneMan.cpp | 88 ++++++++++++++++------------------------ Managers/SettingsMan.cpp | 4 ++ Managers/SettingsMan.h | 7 ++++ 4 files changed, 48 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f2fa6eaf..0e139e95e 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` property `CompactingHeight` 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. diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp index 49a52adc2..a732ac2d4 100644 --- a/Managers/SceneMan.cpp +++ b/Managers/SceneMan.cpp @@ -39,7 +39,6 @@ namespace RTE { #define CLEANAIRINTERVAL 200000 -#define COMPACTINGHEIGHT 25 const std::string SceneMan::c_ClassName = "SceneMan"; std::vector> SceneMan::m_IntermediateSettlingBitmaps; @@ -1073,9 +1072,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())) @@ -1140,10 +1138,10 @@ bool SceneMan::TryPenetrate(int posX, // Save the impulse force effects of the penetrating particle. // retardation = -sceneMat.density; retardation = -(sceneMat->GetIntegrity() / std::sqrt(sqrImpMag)); + int compactingHeight = g_SettingsMan.GetCompactingHeight(); - // 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 (compactingHeight > 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(); @@ -1152,58 +1150,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 - compactingHeight && 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(compactingHeight)))); + 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; + } } } } diff --git a/Managers/SettingsMan.cpp b/Managers/SettingsMan.cpp index 63323eeed..c1ddcf624 100644 --- a/Managers/SettingsMan.cpp +++ b/Managers/SettingsMan.cpp @@ -29,6 +29,7 @@ namespace RTE { m_CrabBombThreshold = 42; m_ShowEnemyHUD = true; m_EnableSmartBuyMenuNavigation = true; + m_CompactingHeight = 25; m_NetworkServerAddress = "127.0.0.1:8000"; m_PlayerNetworkName = "Dummy"; @@ -164,6 +165,8 @@ namespace RTE { reader >> m_ShowEnemyHUD; } else if (propName == "SmartBuyMenuNavigation") { reader >> m_EnableSmartBuyMenuNavigation; + } else if (propName == "CompactingHeight") { + reader >> m_CompactingHeight; } else if (propName == "LaunchIntoActivity") { reader >> g_ActivityMan.m_LaunchIntoActivity; } else if (propName == "DefaultActivityType") { @@ -346,6 +349,7 @@ namespace RTE { writer.NewPropertyWithValue("CrabBombThreshold", m_CrabBombThreshold); writer.NewPropertyWithValue("ShowEnemyHUD", m_ShowEnemyHUD); writer.NewPropertyWithValue("SmartBuyMenuNavigation", m_EnableSmartBuyMenuNavigation); + writer.NewPropertyWithValue("CompactingHeight", m_CompactingHeight); writer.NewLine(false, 2); writer.NewDivider(false); diff --git a/Managers/SettingsMan.h b/Managers/SettingsMan.h index 729702ab9..644aec412 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 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 GetCompactingHeight() const { return m_CompactingHeight; } #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. + int m_CompactingHeight; //!< The maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose. std::string m_PlayerNetworkName; //!< Player name used in network multiplayer matches. std::string m_NetworkServerAddress; //!< LAN server address to connect to. From 7cbb83b8b0501fdd7f8ae059ca39cbdb28a8388b Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 13 Jan 2023 00:04:34 +0200 Subject: [PATCH 09/76] Settling logic cleanup, namely removing seemingly prehistoric `VelOscillations` and `AngOscillations` which are frankly crap methods of detecting stillness, when pos and angle shifts are already being read. --- Entities/Actor.cpp | 21 +++++---------- Entities/Actor.h | 15 +++-------- Entities/HDFirearm.cpp | 17 +++--------- Entities/HDFirearm.h | 14 +++------- Entities/MOPixel.h | 2 +- Entities/MOSParticle.h | 2 +- Entities/MOSRotating.cpp | 55 +++++++++++--------------------------- Entities/MOSRotating.h | 16 +++-------- Entities/MOSprite.cpp | 1 - Entities/MOSprite.h | 2 -- Entities/MovableObject.cpp | 35 +++++------------------- Entities/MovableObject.h | 19 ++++--------- Entities/ThrownDevice.h | 2 +- 13 files changed, 53 insertions(+), 148 deletions(-) diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp index b311d0cd3..b2a08e2e6 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -703,22 +703,15 @@ void Actor::AddGold(float goldOz) g_ActivityMan.GetActivity()->ChangeTeamFunds(goldOz, m_Team); } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////// -// 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_RestTimer.Reset(); + m_ToSettle = false; + } } diff --git a/Entities/Actor.h b/Entities/Actor.h index 09048fe2d..fbe492b42 100644 --- a/Entities/Actor.h +++ b/Entities/Actor.h @@ -545,17 +545,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. diff --git a/Entities/HDFirearm.cpp b/Entities/HDFirearm.cpp index 516bdaeeb..080876ad2 100644 --- a/Entities/HDFirearm.cpp +++ b/Entities/HDFirearm.cpp @@ -602,23 +602,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 ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Entities/HDFirearm.h b/Entities/HDFirearm.h index 8510c718b..c3cedd92c 100644 --- a/Entities/HDFirearm.h +++ b/Entities/HDFirearm.h @@ -569,16 +569,10 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload"); /// The reload progress as a scalar from 0 to 1. float GetReloadProgress() const { return IsReloading() && m_ReloadTime > 0 ? std::min(static_cast(m_ReloadTmr.GetElapsedSimTimeMS() / m_ReloadTime), 1.0F) : 1.0F; } -////////////////////////////////////////////////////////////////////////////////////////// -// Virtual method: RestDetection -////////////////////////////////////////////////////////////////////////////////////////// -// 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; ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Entities/MOPixel.h b/Entities/MOPixel.h index dca346ac7..60c4f52a7 100644 --- a/Entities/MOPixel.h +++ b/Entities/MOPixel.h @@ -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; 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 90e1fb1bd..bbca8ed35 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -1233,47 +1233,24 @@ 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; - -// if (fabs(m_AngularVel) >= 1.0) -// m_RestTimer.Reset(); +void MOSRotating::RestDetection() { + MOSprite::RestDetection(); - 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 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; - } - } + // 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()) { + if (g_SceneMan.OverAltitude(m_Pos, static_cast(m_SpriteRadius) + 4, 3) && m_pAtomGroup->RatioInTerrain() < 0.5F) { + m_RestTimer.Reset(); + m_ToSettle = false; + } + } - m_PrevRotation = m_Rotation; - m_PrevAngVel = m_AngularVel; + m_PrevRotation = m_Rotation; + m_PrevAngVel = m_AngularVel; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1469,8 +1446,8 @@ 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(); } diff --git a/Entities/MOSRotating.h b/Entities/MOSRotating.h index d7ed222be..2ca0206e6 100644 --- a/Entities/MOSRotating.h +++ b/Entities/MOSRotating.h @@ -501,18 +501,10 @@ ClassInfoGetters; void ResetAllTimers() 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; - + /// + /// Does the calculations necessary to detect whether this MOSRotating is at rest or not. IsAtRest() retrieves the answer. + /// + void RestDetection() 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 9a5ce4ea8..5a0018a87 100644 --- a/Entities/MOSprite.cpp +++ b/Entities/MOSprite.cpp @@ -46,7 +46,6 @@ void MOSprite::Clear() m_PrevRotation.Reset(); m_AngularVel = 0; m_PrevAngVel = 0; - m_AngOscillations = 0; m_SettleMaterialDisabled = false; m_pEntryWound = 0; m_pExitWound = 0; diff --git a/Entities/MOSprite.h b/Entities/MOSprite.h index c5539605c..643c39213 100644 --- a/Entities/MOSprite.h +++ b/Entities/MOSprite.h @@ -585,8 +585,6 @@ 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; // 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 742286775..af685a5ab 100644 --- a/Entities/MovableObject.cpp +++ b/Entities/MovableObject.cpp @@ -71,7 +71,6 @@ void MovableObject::Clear() m_HasEverBeenAddedToMovableMan = false; m_MOIDFootprint = 0; m_AlreadyHitBy.clear(); - m_VelOscillations = 0; m_ToSettle = false; m_ToDelete = false; m_HUDVisible = true; @@ -698,37 +697,15 @@ 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() { + if (m_PinStrength) { + return; + } + if (std::abs(m_Pos.m_X - m_PrevPos.m_X) > 1.0F || std::abs(m_Pos.m_Y - m_PrevPos.m_Y) > 1.0F) { m_RestTimer.Reset(); } } - ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: IsAtRest ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Entities/MovableObject.h b/Entities/MovableObject.h index a73f704bc..9f47e94d3 100644 --- a/Entities/MovableObject.h +++ b/Entities/MovableObject.h @@ -1160,17 +1160,10 @@ enum MOType virtual void 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. -// Arguments: None. -// Return value: None. - - virtual void RestDetection(); + /// + /// Does the calculations necessary to detect whether this MovableObject is at rest or not. IsAtRest() retrieves the answer. + /// + virtual void RestDetection(); ////////////////////////////////////////////////////////////////////////////////////////// @@ -1474,7 +1467,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); } @@ -1931,8 +1924,6 @@ 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; // Mark to have the MovableMan copy this the terrain layers at the end // of update. bool m_ToSettle; 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 From c51c7b92f9d1cfa8e7e298db5a199ed227d3fbeb Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 13 Jan 2023 16:28:35 +0200 Subject: [PATCH 10/76] Review changes + reformatting --- Activities/GameActivity.cpp | 2 -- Activities/GameActivity.h | 2 -- CHANGELOG.md | 2 +- Entities/Activity.cpp | 2 ++ Entities/Activity.h | 1 + Entities/MOSRotating.cpp | 3 +-- Entities/MovableObject.cpp | 27 ++++++++------------------- Entities/MovableObject.h | 25 ++++++------------------- Lua/LuaBindingsManagers.cpp | 3 ++- Managers/SceneMan.cpp | 2 +- Managers/SettingsMan.cpp | 8 ++++---- Managers/SettingsMan.h | 10 ++++++++-- 12 files changed, 34 insertions(+), 53 deletions(-) diff --git a/Activities/GameActivity.cpp b/Activities/GameActivity.cpp index 140a5a7c5..b9614bc4d 100644 --- a/Activities/GameActivity.cpp +++ b/Activities/GameActivity.cpp @@ -60,7 +60,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; @@ -162,7 +161,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]; diff --git a/Activities/GameActivity.h b/Activities/GameActivity.h index aa4455641..eab0c13d4 100644 --- a/Activities/GameActivity.h +++ b/Activities/GameActivity.h @@ -1011,8 +1011,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 0e139e95e..9235e0d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,7 +101,7 @@ 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` property `CompactingHeight` 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 `Settings.ini` and `SettingsMan` 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). diff --git a/Entities/Activity.cpp b/Entities/Activity.cpp index bd8b44c63..ac3a49d65 100644 --- a/Entities/Activity.cpp +++ b/Entities/Activity.cpp @@ -37,6 +37,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; @@ -764,6 +765,7 @@ void Activity::Clear() { } m_ControlledActor[player] = nullptr; m_ViewState[player] = ViewState::DeathWatch; + m_DeathTimer[player].Reset(); } } diff --git a/Entities/Activity.h b/Entities/Activity.h index 767397717..d487eba3a 100644 --- a/Entities/Activity.h +++ b/Entities/Activity.h @@ -721,6 +721,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/MOSRotating.cpp b/Entities/MOSRotating.cpp index bbca8ed35..5ae5e1a20 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -1446,8 +1446,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(); } diff --git a/Entities/MovableObject.cpp b/Entities/MovableObject.cpp index af685a5ab..ebe777f61 100644 --- a/Entities/MovableObject.cpp +++ b/Entities/MovableObject.cpp @@ -700,30 +700,19 @@ float MovableObject::GetAltitude(int max, int accuracy) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void MovableObject::RestDetection() { - if (m_PinStrength) { - return; - } - if (std::abs(m_Pos.m_X - m_PrevPos.m_X) > 1.0F || std::abs(m_Pos.m_Y - m_PrevPos.m_Y) > 1.0F) { m_RestTimer.Reset(); } + if (m_PinStrength || (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) { + return false; + } else { + return m_RestTimer.IsPastSimMS(m_RestThreshold); + } } - ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: OnMOHit ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Entities/MovableObject.h b/Entities/MovableObject.h index 9f47e94d3..5b6577fbc 100644 --- a/Entities/MovableObject.h +++ b/Entities/MovableObject.h @@ -1165,29 +1165,16 @@ enum MOType /// 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. - + /// + /// Forces this MovableObject out of resting conditions. + /// 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. - + /// + /// Indicates whether this MovableObject has been at rest with no movement for longer than its RestThreshold. + /// bool IsAtRest(); - ////////////////////////////////////////////////////////////////////////////////////////// // Method: IsUpdated ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Lua/LuaBindingsManagers.cpp b/Lua/LuaBindingsManagers.cpp index 0867c7597..45ba1a06d 100644 --- a/Lua/LuaBindingsManagers.cpp +++ b/Lua/LuaBindingsManagers.cpp @@ -338,7 +338,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("ScrapCompactingheight", &SettingsMan::GetScrapCompactingHeight, &SettingsMan::SetScrapCompactingHeight); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp index a732ac2d4..667337ec1 100644 --- a/Managers/SceneMan.cpp +++ b/Managers/SceneMan.cpp @@ -1138,7 +1138,7 @@ bool SceneMan::TryPenetrate(int posX, // Save the impulse force effects of the penetrating particle. // retardation = -sceneMat.density; retardation = -(sceneMat->GetIntegrity() / std::sqrt(sqrImpMag)); - int compactingHeight = g_SettingsMan.GetCompactingHeight(); + int compactingHeight = g_SettingsMan.GetScrapCompactingHeight(); // 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 (compactingHeight > 0 && (sceneMat->IsScrap() || _getpixel(m_pCurrentScene->GetTerrain()->GetBGColorBitmap(), posX, posY) == g_MaskColor)) { diff --git a/Managers/SettingsMan.cpp b/Managers/SettingsMan.cpp index c1ddcf624..6e1fd5f0d 100644 --- a/Managers/SettingsMan.cpp +++ b/Managers/SettingsMan.cpp @@ -29,7 +29,7 @@ namespace RTE { m_CrabBombThreshold = 42; m_ShowEnemyHUD = true; m_EnableSmartBuyMenuNavigation = true; - m_CompactingHeight = 25; + m_ScrapCompactingHeight = 25; m_NetworkServerAddress = "127.0.0.1:8000"; m_PlayerNetworkName = "Dummy"; @@ -165,8 +165,8 @@ namespace RTE { reader >> m_ShowEnemyHUD; } else if (propName == "SmartBuyMenuNavigation") { reader >> m_EnableSmartBuyMenuNavigation; - } else if (propName == "CompactingHeight") { - reader >> m_CompactingHeight; + } else if (propName == "ScrapCompactingHeight") { + reader >> m_ScrapCompactingHeight; } else if (propName == "LaunchIntoActivity") { reader >> g_ActivityMan.m_LaunchIntoActivity; } else if (propName == "DefaultActivityType") { @@ -349,7 +349,7 @@ namespace RTE { writer.NewPropertyWithValue("CrabBombThreshold", m_CrabBombThreshold); writer.NewPropertyWithValue("ShowEnemyHUD", m_ShowEnemyHUD); writer.NewPropertyWithValue("SmartBuyMenuNavigation", m_EnableSmartBuyMenuNavigation); - writer.NewPropertyWithValue("CompactingHeight", m_CompactingHeight); + writer.NewPropertyWithValue("ScrapCompactingHeight", m_ScrapCompactingHeight); writer.NewLine(false, 2); writer.NewDivider(false); diff --git a/Managers/SettingsMan.h b/Managers/SettingsMan.h index 644aec412..c96f2bab7 100644 --- a/Managers/SettingsMan.h +++ b/Managers/SettingsMan.h @@ -249,7 +249,13 @@ namespace RTE { /// 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 GetCompactingHeight() const { return m_CompactingHeight; } + 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; } #pragma endregion #pragma region Network Settings @@ -505,7 +511,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. - int m_CompactingHeight; //!< The maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose. + int m_ScrapCompactingHeight; //!< The maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose. std::string m_PlayerNetworkName; //!< Player name used in network multiplayer matches. std::string m_NetworkServerAddress; //!< LAN server address to connect to. From 0a32cf87de1e683e65d2a55531ee5fa7a1472596 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sat, 14 Jan 2023 22:46:56 +0200 Subject: [PATCH 11/76] Reverting and reformatting settling logic --- Entities/Actor.cpp | 3 ++- Entities/MOPixel.cpp | 1 + Entities/MOSParticle.cpp | 1 + Entities/MOSRotating.cpp | 35 +++++++++++++++++++++++++++++++++-- Entities/MOSRotating.h | 5 +++++ Entities/MOSprite.cpp | 1 + Entities/MOSprite.h | 1 + Entities/MovableObject.cpp | 14 ++++++++++++-- Entities/MovableObject.h | 3 ++- 9 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp index b2a08e2e6..d7d5084bd 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -709,12 +709,13 @@ void Actor::RestDetection() { MOSRotating::RestDetection(); if (m_Status != DEAD) { + m_AngOscillations = 0; + m_VelOscillations = 0; m_RestTimer.Reset(); m_ToSettle = false; } } - ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: AddAIMOWaypoint ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Entities/MOPixel.cpp b/Entities/MOPixel.cpp index 1c6ad5783..a4ac37f64 100644 --- a/Entities/MOPixel.cpp +++ b/Entities/MOPixel.cpp @@ -193,6 +193,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; } diff --git a/Entities/MOSParticle.cpp b/Entities/MOSParticle.cpp index b4389dfdc..271fcf06f 100644 --- a/Entities/MOSParticle.cpp +++ b/Entities/MOSParticle.cpp @@ -92,6 +92,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/MOSRotating.cpp b/Entities/MOSRotating.cpp index 5ae5e1a20..e55355c58 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -1238,23 +1238,54 @@ void MOSRotating::ResetAllTimers() void MOSRotating::RestDetection() { MOSprite::RestDetection(); + // Rotational settling detection. + if ((m_AngularVel > 0 && m_PrevAngVel < 0) || (m_AngularVel < 0 && m_PrevAngVel > 0)) { + ++m_AngOscillations; + } else { + m_AngOscillations = 0; + } + 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()) { - if (g_SceneMan.OverAltitude(m_Pos, static_cast(m_SpriteRadius) + 4, 3) && m_pAtomGroup->RatioInTerrain() < 0.5F) { + 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; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool MOSRotating::IsAtRest() { + if (m_RestThreshold < 0 || m_PinStrength) { + return false; + } else { + if (m_VelOscillations > 2 || m_AngOscillations > 2) { + return true; + } + return m_RestTimer.IsPastSimMS(m_RestThreshold); + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool MOSRotating::IsOnScenePoint(Vector &scenePoint) const { if (!m_aSprite[m_Frame]) { return false; diff --git a/Entities/MOSRotating.h b/Entities/MOSRotating.h index 2ca0206e6..2764cc0b0 100644 --- a/Entities/MOSRotating.h +++ b/Entities/MOSRotating.h @@ -506,6 +506,11 @@ ClassInfoGetters; /// 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 5a0018a87..3319a5310 100644 --- a/Entities/MOSprite.cpp +++ b/Entities/MOSprite.cpp @@ -46,6 +46,7 @@ void MOSprite::Clear() m_PrevRotation.Reset(); m_AngularVel = 0; m_PrevAngVel = 0; + m_AngOscillations = 0; m_SettleMaterialDisabled = false; m_pEntryWound = 0; m_pExitWound = 0; diff --git a/Entities/MOSprite.h b/Entities/MOSprite.h index 643c39213..05f71a004 100644 --- a/Entities/MOSprite.h +++ b/Entities/MOSprite.h @@ -585,6 +585,7 @@ class MOSprite : public MovableObject { // The precalculated maximum possible radius and diameter of this, in pixels float m_SpriteRadius; float m_SpriteDiameter; + 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 ebe777f61..ced8cca74 100644 --- a/Entities/MovableObject.cpp +++ b/Entities/MovableObject.cpp @@ -71,6 +71,7 @@ void MovableObject::Clear() m_HasEverBeenAddedToMovableMan = false; m_MOIDFootprint = 0; m_AlreadyHitBy.clear(); + m_VelOscillations = 0; m_ToSettle = false; m_ToDelete = false; m_HUDVisible = true; @@ -700,15 +701,24 @@ float MovableObject::GetAltitude(int max, int accuracy) ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void MovableObject::RestDetection() { - if (m_PinStrength || (m_Pos - m_PrevPos).MagnitudeIsGreaterThan(1.0F)) { m_RestTimer.Reset(); } + // 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(); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool MovableObject::IsAtRest() { - if (m_RestThreshold < 0) { + if (m_RestThreshold < 0 || m_PinStrength) { return false; } else { + if (m_VelOscillations > 2) { + return true; + } return m_RestTimer.IsPastSimMS(m_RestThreshold); } } diff --git a/Entities/MovableObject.h b/Entities/MovableObject.h index 5b6577fbc..edf8ee462 100644 --- a/Entities/MovableObject.h +++ b/Entities/MovableObject.h @@ -1173,7 +1173,7 @@ enum MOType /// /// Indicates whether this MovableObject has been at rest with no movement for longer than its RestThreshold. /// - bool IsAtRest(); + virtual bool IsAtRest(); ////////////////////////////////////////////////////////////////////////////////////////// // Method: IsUpdated @@ -1911,6 +1911,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; + 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; From cae0dc7f7efcdacf027050c3bb6825f7fe36f1f9 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sat, 14 Jan 2023 22:50:42 +0200 Subject: [PATCH 12/76] Important changes regarding arms and dropped items; Dropped items now need to be still in order for the HUD to show, making actor deaths less noisy visually --- Entities/AHuman.cpp | 9 +++++---- Entities/Actor.cpp | 8 ++++---- Entities/Arm.cpp | 25 ++++++++++++------------- Entities/HeldDevice.cpp | 8 +++++--- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index e6eaf8bc8..772c053ba 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3756,13 +3756,13 @@ void AHuman::Update() } 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); @@ -3780,7 +3780,8 @@ void AHuman::Update() } 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 diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp index d7d5084bd..536239d54 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -862,7 +862,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()); @@ -880,18 +881,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); diff --git a/Entities/Arm.cpp b/Entities/Arm.cpp index b56c51776..5747d25b2 100644 --- a/Entities/Arm.cpp +++ b/Entities/Arm.cpp @@ -259,19 +259,18 @@ MovableObject * Arm::ReleaseHeldMO() // MovableMan. Ownership is transferred to MovableMan. // Arguments: None. -MovableObject * Arm::DropEverything() -{ - MovableObject *pReturnMO = m_pHeldMO; - - if (m_pHeldMO && m_pHeldMO->IsDevice()) { - RemoveAttachable(dynamic_cast(m_pHeldMO), true, false); - } - else if (m_pHeldMO) - g_MovableMan.AddParticle(m_pHeldMO); - - m_pHeldMO = 0; - - return pReturnMO; +MovableObject * Arm::DropEverything() { + MovableObject *heldMO = ReleaseHeldMO(); + + if (heldMO) { + Vector tossVec = m_HandOffset; + tossVec.SetMagnitude(RandomNum(3.0F, 6.0F)); + heldMO->SetVel((heldMO->GetVel() + m_Vel) * 0.5F + tossVec.RadRotate(c_QuarterPI * RandomNormalNum())); + heldMO->SetAngularVel(heldMO->GetAngularVel() + RandomNormalNum()); + g_MovableMan.AddMO(heldMO); + } + + return heldMO; } diff --git a/Entities/HeldDevice.cpp b/Entities/HeldDevice.cpp index cebf3ff19..b5c774594 100644 --- a/Entities/HeldDevice.cpp +++ b/Entities/HeldDevice.cpp @@ -551,10 +551,12 @@ void HeldDevice::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whi 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_SceneMan.GetScrollTarget(whichScreen), g_SceneMan.SceneWrapsX()).MagnitudeIsLessThan(unheldItemDisplayRange)); + if (!m_SeenByPlayer.at(viewingPlayer)) { + m_SeenByPlayer.at(viewingPlayer) = unheldItemDisplayRange < 0 || (unheldItemDisplayRange > 0 && m_Vel.MagnitudeIsLessThan(2.0F) && g_SceneMan.ShortestDistance(m_Pos, g_SceneMan.GetScrollTarget(whichScreen), g_SceneMan.SceneWrapsX()).MagnitudeIsLessThan(unheldItemDisplayRange)); + } else { + if (unheldItemDisplayRange > 0) { unheldItemDisplayRange += 4.0F; } + m_SeenByPlayer.at(viewingPlayer) = unheldItemDisplayRange < 0 || (unheldItemDisplayRange > 0 && g_SceneMan.ShortestDistance(m_Pos, g_SceneMan.GetScrollTarget(whichScreen), g_SceneMan.SceneWrapsX()).MagnitudeIsLessThan(unheldItemDisplayRange)); - if (m_SeenByPlayer.at(viewingPlayer)) { char pickupArrowString[64]; pickupArrowString[0] = 0; if (m_BlinkTimer.GetElapsedSimTimeMS() < 250) { From 3c351d56c1eb831b9f9dc0075f8ba783428fcd67 Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Sun, 15 Jan 2023 01:17:22 -0400 Subject: [PATCH 13/76] Minor refactor --- Entities/MOSRotating.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index e55355c58..64cf0148b 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -1274,14 +1274,12 @@ void MOSRotating::RestDetection() { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool MOSRotating::IsAtRest() { - if (m_RestThreshold < 0 || m_PinStrength) { + 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); + } else if (m_VelOscillations > 2 || m_AngOscillations > 2) { + return true; } + return m_RestTimer.IsPastSimMS(m_RestThreshold); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 787ee0a0110c653ad9a00384d97bf8b734459b30 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 15 Jan 2023 20:58:51 +0200 Subject: [PATCH 14/76] Fix `MovableMan::GetClosestBrainActor` early return due to not checking `AddedActors`, however removing the `Actors.empty()` check is enough since `ActorRoster` is the only relevant thing in the function --- Managers/MovableMan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Managers/MovableMan.cpp b/Managers/MovableMan.cpp index 0b28e3879..f01a4ba17 100644 --- a/Managers/MovableMan.cpp +++ b/Managers/MovableMan.cpp @@ -668,7 +668,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(); From a42f383e8634327f6914e713ffa476e7747f051a Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 15 Jan 2023 21:37:35 +0200 Subject: [PATCH 15/76] Tutorial mission edits --- Activities/GATutorial.cpp | 43 +++++++++++++++++++-------------------- Activities/GATutorial.h | 1 + 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Activities/GATutorial.cpp b/Activities/GATutorial.cpp index 0ebd51520..9d54afbd6 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 upper-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 bottom-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 bottom-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! From 036fc4bf4f22071a325bdb1cfef8c9f749ec056d Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 15 Jan 2023 21:38:04 +0200 Subject: [PATCH 16/76] Reset actor stable recovery timer when made unstable --- Entities/Actor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/Actor.h b/Entities/Actor.h index fbe492b42..3ec44cb9a 100644 --- a/Entities/Actor.h +++ b/Entities/Actor.h @@ -434,7 +434,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(); } } ////////////////////////////////////////////////////////////////////////////////////////// From 0fdd25a4787db73354bc64bdc7f5d015abff1c2d Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Tue, 17 Jan 2023 01:37:26 -0400 Subject: [PATCH 17/76] Added Actor PlayerControllable flag that can make Actors not able to be selected by player (unless they cheat with lua I guess) Added support and lua bindings for this flag and an alternative MovableMan::GetClosestTeamActor --- Activities/GameActivity.cpp | 2 +- CHANGELOG.md | 4 ++++ Entities/Activity.cpp | 2 +- Entities/Actor.cpp | 11 +++++++--- Entities/Actor.h | 13 ++++++++++++ Lua/LuaBindingsEntities.cpp | 1 + Lua/LuaBindingsManagers.cpp | 3 ++- Managers/MovableMan.cpp | 6 +++--- Managers/MovableMan.h | 40 +++++++++++++++++++++---------------- 9 files changed, 56 insertions(+), 26 deletions(-) diff --git a/Activities/GameActivity.cpp b/Activities/GameActivity.cpp index 11fdf6e95..34afc5dc3 100644 --- a/Activities/GameActivity.cpp +++ b/Activities/GameActivity.cpp @@ -1470,7 +1470,7 @@ void GameActivity::Update() // Find the actor closest to the cursor, if any within the radius Vector markedDistance; - Actor *pMarkedActor = g_MovableMan.GetClosestTeamActor(team, player, m_ActorCursor[player], g_SceneMan.GetSceneWidth(), markedDistance); + Actor *pMarkedActor = g_MovableMan.GetClosestTeamActor(team, player, m_ActorCursor[player], g_SceneMan.GetSceneWidth(), markedDistance, true); // Actor *pMarkedActor = g_MovableMan.GetClosestTeamActor(team, player, m_ActorCursor[player], g_FrameMan.GetPlayerScreenWidth() / 4); // Player canceled selection of actor diff --git a/CHANGELOG.md b/CHANGELOG.md index 727179077..57d5fc204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -497,6 +497,10 @@ 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 `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 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`. +
Changed diff --git a/Entities/Activity.cpp b/Entities/Activity.cpp index 34b2fa2f8..92051a327 100644 --- a/Entities/Activity.cpp +++ b/Entities/Activity.cpp @@ -726,7 +726,7 @@ void Activity::Clear() { if (team < Teams::TeamOne || team >= Teams::MaxTeamCount || player < Players::PlayerOne || player >= Players::MaxPlayerCount || !m_IsHuman[player]) { return false; } - if (!actor || !g_MovableMan.IsActor(actor)) { + if (!actor || !g_MovableMan.IsActor(actor) || !actor->IsPlayerControllable()) { return false; } if ((actor != m_Brain[player] && actor->IsPlayerControlled()) || IsOtherPlayerBrain(actor, player)) { diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp index a24daaa84..322187c7e 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -62,6 +62,7 @@ bool Actor::m_sIconsLoaded = false; void Actor::Clear() { m_Controller.Reset(); + m_PlayerControllable = true; m_BodyHitSound = nullptr; m_AlarmSound = nullptr; m_PainSound = nullptr; @@ -197,6 +198,7 @@ int Actor::Create(const Actor &reference) m_Controller = reference.m_Controller; m_Controller.SetInputMode(Controller::CIM_AI); m_Controller.SetControlledActor(this); + m_PlayerControllable = reference.m_PlayerControllable; if (reference.m_BodyHitSound) { m_BodyHitSound = dynamic_cast(reference.m_BodyHitSound->Clone()); } if (reference.m_AlarmSound) { m_AlarmSound = dynamic_cast(reference.m_AlarmSound->Clone()); } @@ -308,7 +310,9 @@ int Actor::Create(const Actor &reference) int Actor::ReadProperty(const std::string_view &propName, Reader &reader) { - if (propName == "BodyHitSound") { + if (propName == "PlayerControllable") { + reader >> m_PlayerControllable; + } else if (propName == "BodyHitSound") { m_BodyHitSound = new SoundContainer; reader >> m_BodyHitSound; } else if (propName == "AlarmSound") { @@ -410,6 +414,7 @@ int Actor::Save(Writer &writer) const { MOSRotating::Save(writer); + writer.NewPropertyWithValue("PlayerControllable", m_PlayerControllable); writer.NewProperty("BodyHitSound"); writer << m_BodyHitSound; writer.NewProperty("AlarmSound"); @@ -1930,7 +1935,7 @@ void Actor::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr if ((*prevItr) == (*selfItr)) break; } - while((*prevItr)->GetController()->IsPlayerControlled() || + while(!(*prevItr)->IsPlayerControllable() || (*prevItr)->GetController()->IsPlayerControlled() || g_ActivityMan.GetActivity()->IsOtherPlayerBrain((*prevItr), m_Controller.GetPlayer())); // Get the next actor in the list (not controlled by another player) @@ -1942,7 +1947,7 @@ void Actor::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr if ((*nextItr) == (*selfItr)) break; } - while((*nextItr)->GetController()->IsPlayerControlled() || + while(!(*nextItr)->IsPlayerControllable() || (*nextItr)->GetController()->IsPlayerControlled() || g_ActivityMan.GetActivity()->IsOtherPlayerBrain((*prevItr), m_Controller.GetPlayer())); Vector iconPos = cpuPos; diff --git a/Entities/Actor.h b/Entities/Actor.h index 3ec44cb9a..62e27fda6 100644 --- a/Entities/Actor.h +++ b/Entities/Actor.h @@ -184,6 +184,18 @@ ClassInfoGetters; virtual bool IsControllable() const { return true; } + /// + /// Gets whether or not this Actor can be controlled by human players. Note that this does not protect the Actor's Controller from having its input mode forced to CIM_PLAYER (e.g. via Lua). + /// + /// Whether or not this Actor can be controlled by human players. + bool IsPlayerControllable() const { return m_PlayerControllable; } + + /// + /// Sets whether or not this Actor can be controlled by human players. + /// + /// Whether or not this Actor should be able to be controlled by human players. + void SetPlayerControllable(bool playerControllable) { m_PlayerControllable = playerControllable; } + ////////////////////////////////////////////////////////////////////////////////////////// // Method: GetStatus @@ -1373,6 +1385,7 @@ ClassInfoGetters; AtomGroup *m_pHitBody; Controller m_Controller; + bool m_PlayerControllable; //!< Whether or not this Actor can be controlled by human players. // Sounds SoundContainer *m_BodyHitSound; diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 46bf013f5..ca6340fc6 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -206,6 +206,7 @@ namespace RTE { .def(luabind::constructor<>()) + .property("PlayerControllable", &Actor::IsPlayerControllable, &Actor::SetPlayerControllable) .property("BodyHitSound", &Actor::GetBodyHitSound, &LuaAdaptersPropertyOwnershipSafetyFaker::ActorSetBodyHitSound) .property("AlarmSound", &Actor::GetAlarmSound, &LuaAdaptersPropertyOwnershipSafetyFaker::ActorSetAlarmSound) .property("PainSound", &Actor::GetPainSound, &LuaAdaptersPropertyOwnershipSafetyFaker::ActorSetPainSound) diff --git a/Lua/LuaBindingsManagers.cpp b/Lua/LuaBindingsManagers.cpp index 18845a84a..54b98a2f8 100644 --- a/Lua/LuaBindingsManagers.cpp +++ b/Lua/LuaBindingsManagers.cpp @@ -125,7 +125,8 @@ namespace RTE { .def("GetPrevActorInGroup", &MovableMan::GetPrevActorInGroup) .def("GetNextTeamActor", &MovableMan::GetNextTeamActor) .def("GetPrevTeamActor", &MovableMan::GetPrevTeamActor) - .def("GetClosestTeamActor", &MovableMan::GetClosestTeamActor) + .def("GetClosestTeamActor", (Actor * (MovableMan::*)(int team, int player, const Vector &scenePoint, int maxRadius, Vector &getDistance, const Actor *excludeThis))&MovableMan::GetClosestTeamActor) + .def("GetClosestTeamActor", (Actor * (MovableMan::*)(int team, int player, const Vector &scenePoint, int maxRadius, Vector &getDistance, bool onlyPlayerControllableActors, const Actor *excludeThis))&MovableMan::GetClosestTeamActor) .def("GetClosestEnemyActor", &MovableMan::GetClosestEnemyActor) .def("GetFirstTeamActor", &MovableMan::GetFirstTeamActor) .def("GetClosestActor", &MovableMan::GetClosestActor) diff --git a/Managers/MovableMan.cpp b/Managers/MovableMan.cpp index f01a4ba17..d4dbcdda1 100644 --- a/Managers/MovableMan.cpp +++ b/Managers/MovableMan.cpp @@ -532,7 +532,7 @@ Actor * MovableMan::GetPrevTeamActor(int team, Actor *pBeforeThis) // Description: Get a pointer to an Actor in the internal Actor list that is of a // specifc team and closest to a specific scene point. -Actor * MovableMan::GetClosestTeamActor(int team, int player, const Vector &scenePoint, int maxRadius, Vector &getDistance, const Actor *pExcludeThis) +Actor * MovableMan::GetClosestTeamActor(int team, int player, const Vector &scenePoint, int maxRadius, Vector &getDistance, bool onlyPlayerControllableActors, const Actor *excludeThis) { if (team < Activity::NoTeam || team >= Activity::MaxTeamCount || m_Actors.empty() || m_ActorRoster[team].empty()) return 0; @@ -547,7 +547,7 @@ Actor * MovableMan::GetClosestTeamActor(int team, int player, const Vector &scen { for (std::deque::iterator aIt = m_Actors.begin(); aIt != m_Actors.end(); ++aIt) { - if ((*aIt) == pExcludeThis || (*aIt)->GetTeam() != Activity::NoTeam) { + if ((*aIt) == excludeThis || (*aIt)->GetTeam() != Activity::NoTeam || (onlyPlayerControllableActors && !(*aIt)->IsPlayerControllable())) { continue; } @@ -565,7 +565,7 @@ Actor * MovableMan::GetClosestTeamActor(int team, int player, const Vector &scen { for (std::list::iterator aIt = m_ActorRoster[team].begin(); aIt != m_ActorRoster[team].end(); ++aIt) { - if ((*aIt) == pExcludeThis || (player != NoPlayer && ((*aIt)->GetController()->IsPlayerControlled(player) || (pActivity && pActivity->IsOtherPlayerBrain(*aIt, player))))) { + if ((*aIt) == excludeThis || (onlyPlayerControllableActors && !(*aIt)->IsPlayerControllable()) || (player != NoPlayer && ((*aIt)->GetController()->IsPlayerControlled(player) || (pActivity && pActivity->IsOtherPlayerBrain(*aIt, player))))) { continue; } diff --git a/Managers/MovableMan.h b/Managers/MovableMan.h index c26cb8c0a..e6502a819 100644 --- a/Managers/MovableMan.h +++ b/Managers/MovableMan.h @@ -239,24 +239,30 @@ class MovableMan : public Singleton, public Serializable { Actor * GetPrevTeamActor(int team = 0, Actor *pBeforeThis = 0); + /// + /// Get a pointer to an Actor in the internal Actor list that is of a specifc team and closest to a specific scene point. + /// + /// Which team to try to get an Actor for. 0 means first team, 1 means 2nd. + /// 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. + /// 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); } -////////////////////////////////////////////////////////////////////////////////////////// -// Method: GetClosestTeamActor -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Get a pointer to an Actor in the internal Actor list that is of a -// specifc team and closest to a specific scene point. -// Arguments: Which team to try to get an Actor for. 0 means first team, 1 means 2nd. -// 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. -// An Actor to exclude from the search. OWNERSHIP IS NOT TRANSFERRED! -// Return value: 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, 0 is returned. - - Actor * GetClosestTeamActor(int team, int player, const Vector &scenePoint, int maxRadius, Vector &getDistance, const Actor *pExcludeThis = 0); + /// + /// Get a pointer to an Actor in the internal Actor list that is of a specifc team and closest to a specific scene point. + /// + /// Which team to try to get an Actor for. 0 means first team, 1 means 2nd. + /// 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. + /// 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. + Actor * GetClosestTeamActor(int team, int player, const Vector &scenePoint, int maxRadius, Vector &getDistance, bool onlyPlayerControllableActors, const Actor *excludeThis = nullptr); ////////////////////////////////////////////////////////////////////////////////////////// From 576c27b305935332fcc464dcbec38c481ba5cf80 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 17 Jan 2023 22:40:03 +0200 Subject: [PATCH 18/76] New `Attachable` property `IgnoresParticlesWhileAttached` --- CHANGELOG.md | 2 ++ Entities/Attachable.cpp | 17 +++++++++++++++++ Entities/Attachable.h | 21 +++++++++++++++++++++ Lua/LuaBindingsEntities.cpp | 1 + 4 files changed, 41 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d5fc204..ad694c42d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -501,6 +501,8 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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`. +- 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. +
Changed diff --git a/Entities/Attachable.cpp b/Entities/Attachable.cpp index 7218d16aa..60607fb81 100644 --- a/Entities/Attachable.cpp +++ b/Entities/Attachable.cpp @@ -40,6 +40,7 @@ namespace RTE { m_AtomSubgroupID = -1L; m_CollidesWithTerrainWhileAttached = true; + m_IgnoresParticlesWhileAttached = true; m_PieSlices.clear(); @@ -91,6 +92,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()))); @@ -142,6 +144,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 { @@ -173,6 +177,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()); @@ -287,6 +292,18 @@ namespace RTE { return m_CollidesWithTerrainWhileAttached; } +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool Attachable::CollideAtPoint(HitData &hd) { + if (m_IgnoresParticlesWhileAttached && m_Parent && !m_Parent->ToDelete()) { + MOSRotating *hitorAsMOSR = dynamic_cast(hd.Body[HITOR]); + if (!hitorAsMOSR) { + return false; + } + } + return MOSRotating::CollideAtPoint(hd); + } + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Attachable::ParticlePenetration(HitData &hd) { diff --git a/Entities/Attachable.h b/Entities/Attachable.h index 82c43c713..810984e5d 100644 --- a/Entities/Attachable.h +++ b/Entities/Attachable.h @@ -391,9 +391,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. @@ -554,6 +574,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/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index ca6340fc6..40519f1f8 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -566,6 +566,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) From e2f6a8fec6113e5a17df097e392729ceaa56a164 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 17 Jan 2023 22:41:25 +0200 Subject: [PATCH 19/76] `AHuman` item pickup detection improvements --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 772c053ba..8d82ec5d0 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3501,7 +3501,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())) { From 7b8153e463f5a6079df8c4b4d4419720b31ea436 Mon Sep 17 00:00:00 2001 From: fourZK Date: Wed, 18 Jan 2023 10:49:46 +0200 Subject: [PATCH 20/76] Whoops lol --- Entities/Attachable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/Attachable.cpp b/Entities/Attachable.cpp index 60607fb81..0fa874336 100644 --- a/Entities/Attachable.cpp +++ b/Entities/Attachable.cpp @@ -40,7 +40,7 @@ namespace RTE { m_AtomSubgroupID = -1L; m_CollidesWithTerrainWhileAttached = true; - m_IgnoresParticlesWhileAttached = true; + m_IgnoresParticlesWhileAttached = false; m_PieSlices.clear(); From 0d5ac57dbe85254518559d877ccb1540352a801e Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Wed, 18 Jan 2023 22:52:18 -0400 Subject: [PATCH 21/76] Added the ability to replace one PieSlice in a PieMenu with another (also a bit of cleanup in PieMenu and with piemenu changelog entries) --- CHANGELOG.md | 8 +++-- Entities/PieMenu.cpp | 68 +++++++++++++++++++++++++++++++++++-- Entities/PieMenu.h | 9 +++++ Lua/LuaBindingsEntities.cpp | 3 +- 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad694c42d..9b85ae648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -162,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: diff --git a/Entities/PieMenu.cpp b/Entities/PieMenu.cpp index afbba3f16..fa00b51a9 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,68 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void PieMenu::Update() { - Controller *controller = GetController(); + PieSlice * PieMenu::ReplacePieSlice(const PieSlice *pieSliceToReplace, PieSlice *replacementPieSlice) { + 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); + } + }; + + if (Directions sliceDirection = pieSliceToReplace->GetDirection(); sliceDirection > Directions::None) { + if (sliceDirection == Directions::Any) { + for (PieQuadrant &pieQuadrant : m_PieQuadrants) { + if (pieQuadrant.ContainsPieSlice(pieSliceToReplace)) { + replacementPieSlice->SetOriginalSource(pieSliceToReplace->GetOriginalSource()); + replacementPieSlice->SetDirection(pieSliceToReplace->GetDirection()); + replacementPieSlice->SetCanBeMiddleSlice(pieSliceToReplace->GetCanBeMiddleSlice()); + replacementPieSlice->SetStartAngle(pieSliceToReplace->GetStartAngle()); + replacementPieSlice->SetSlotCount(pieSliceToReplace->GetSlotCount()); + replacementPieSlice->SetMidAngle(pieSliceToReplace->GetMidAngle()); + + DoPieSliceReplacementInPieQuadrant(pieQuadrant); + break; + } + } + } else if (PieQuadrant &pieQuadrant = m_PieQuadrants[sliceDirection]; pieQuadrant.ContainsPieSlice(pieSliceToReplace)) { + DoPieSliceReplacementInPieQuadrant(pieQuadrant); + } + } + + if (replacedPieSlice) { + 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 0b9c3dd34..189c1e32c 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. + /// 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/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 40519f1f8..0e0c622f9 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -1143,7 +1143,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)); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 40087e64988e9c2cf44eac3f859d8dff11b6ecea Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Wed, 18 Jan 2023 23:15:30 -0400 Subject: [PATCH 22/76] Fixed a bug caused by carelessness Added safety assert that should never trigger --- Entities/PieMenu.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Entities/PieMenu.cpp b/Entities/PieMenu.cpp index fa00b51a9..84018ae13 100644 --- a/Entities/PieMenu.cpp +++ b/Entities/PieMenu.cpp @@ -511,19 +511,13 @@ namespace RTE { 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)) { - replacementPieSlice->SetOriginalSource(pieSliceToReplace->GetOriginalSource()); - replacementPieSlice->SetDirection(pieSliceToReplace->GetDirection()); - replacementPieSlice->SetCanBeMiddleSlice(pieSliceToReplace->GetCanBeMiddleSlice()); - replacementPieSlice->SetStartAngle(pieSliceToReplace->GetStartAngle()); - replacementPieSlice->SetSlotCount(pieSliceToReplace->GetSlotCount()); - replacementPieSlice->SetMidAngle(pieSliceToReplace->GetMidAngle()); - DoPieSliceReplacementInPieQuadrant(pieQuadrant); break; } @@ -534,6 +528,13 @@ namespace RTE { } 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; } @@ -543,6 +544,7 @@ namespace RTE { if (m_AlreadyActivatedPieSlice == pieSliceToReplace) { m_AlreadyActivatedPieSlice = replacementPieSlice; } + RepopulateAndRealignCurrentPieSlices(); } From 70361fd15418d36ba8419b2d5af856f045ce0007 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 24 Jan 2023 13:18:12 +0200 Subject: [PATCH 23/76] AHuman hand reaches out to picked up item instead of going to HolsterOffset, like when switching weapons. This is fine because hand offset is immediately clamped per Arm MaxLength. --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 8d82ec5d0..9620c6c47 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3519,8 +3519,8 @@ void AHuman::Update() Arm *armToUse = m_pFGArm ? m_pFGArm : m_pBGArm; MovableObject *pMO = armToUse->ReleaseHeldMO(); if (pMO) { m_Inventory.push_back(pMO); } + armToUse->SetHandPos(m_pItemInReach->GetJointPos()); armToUse->SetHeldMO(m_pItemInReach); - armToUse->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset)); m_pItemInReach = nullptr; if (armToUse != m_pBGArm) { From cd0502451166a14bcbc2609aa5f9e9b342a4fdad Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 24 Jan 2023 13:20:01 +0200 Subject: [PATCH 24/76] Enable AHuman FGArm crawl when not holding any items --- Entities/AHuman.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 9620c6c47..b3009c6a7 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3662,7 +3662,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->HoldsSomething()) { + } + if (m_pFGArm && !m_pFGArm->HoldsSomething() && !(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); } From 12ac3e3ebdf531d55d4552cd662b1300f738d524 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 24 Jan 2023 13:21:10 +0200 Subject: [PATCH 25/76] Fix unequipping BGArm when climbing; MoveState is in fact never set to CLIMB, but ArmClimbing is enough to signify arm-related LimbPath activity --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index b3009c6a7..7806bbb75 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3786,7 +3786,7 @@ void AHuman::Update() 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)) { UnequipBGArm(); } + if (m_MoveState != CRAWL && m_ProneState == PRONE) { UnequipBGArm(); } m_pBGArm->ReachToward(m_pBGHandGroup->GetLimbPos(m_HFlipped)); } else { From 08cd550bf735bafc2c25939d211b1bd4f0645374 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 24 Jan 2023 13:23:57 +0200 Subject: [PATCH 26/76] Use InheritedRotAngleOffset for wound angles instead of EmitAngle. This allows for more variety with sprites and emission offsets rotating properly. --- Entities/MOSRotating.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index 8aeda662e..7497cbcfd 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -522,7 +522,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); } @@ -952,11 +951,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; } @@ -971,8 +970,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); @@ -1714,7 +1713,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; } @@ -1722,7 +1721,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; } From 5c1924b5df1a36c3ff388a392c9111d5e218a10a Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 2 Feb 2023 22:42:19 +0200 Subject: [PATCH 27/76] New `SceneMan` function `DislodgePixel` --- CHANGELOG.md | 2 ++ Lua/LuaBindingsManagers.cpp | 3 ++- Managers/SceneMan.cpp | 31 +++++++++++++++++++++++++++++++ Managers/SceneMan.h | 8 ++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ae692f9a..a885eb71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -527,6 +527,8 @@ 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. +
Changed diff --git a/Lua/LuaBindingsManagers.cpp b/Lua/LuaBindingsManagers.cpp index 54b98a2f8..e4732ea6f 100644 --- a/Lua/LuaBindingsManagers.cpp +++ b/Lua/LuaBindingsManagers.cpp @@ -318,7 +318,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); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp index e561a2d88..7d186915c 100644 --- a/Managers/SceneMan.cpp +++ b/Managers/SceneMan.cpp @@ -1044,6 +1044,37 @@ bool SceneMan::TryPenetrate(int posX, ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +MovableObject * SceneMan::DislodgePixel(int posX, int posY) { + + unsigned char materialID = _getpixel(m_pCurrentScene->GetTerrain()->GetMaterialBitmap(), posX, posY); + if (materialID == g_MaterialAir) { + return nullptr; + } + Material const * sceneMat = GetMaterialFromID(materialID); + Material const * 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() == g_MaskColor) { + return nullptr; + } + MOPixel *pixelMO = new MOPixel(spawnColor, spawnMat->GetPixelDensity(), Vector(posX, posY), Vector(), new Atom(Vector(), spawnMat->GetIndex(), 0, spawnColor, 2), 0); + pixelMO->SetToHitMOs(spawnMat->GetIndex() == c_GoldMaterialID); + g_MovableMan.AddParticle(pixelMO); + + m_pCurrentScene->GetTerrain()->SetFGColorPixel(posX, posY, g_MaskColor); + RegisterTerrainChange(posX, posY, 1, 1, g_MaskColor, false); + m_pCurrentScene->GetTerrain()->SetMaterialPixel(posX, posY, g_MaterialAir); + + return pixelMO; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void SceneMan::MakeAllUnseen(Vector pixelSize, const int team) { RTEAssert(m_pCurrentScene, "Messing with scene before the scene exists!"); diff --git a/Managers/SceneMan.h b/Managers/SceneMan.h index b27d31c76..7013010c4 100644 --- a/Managers/SceneMan.h +++ b/Managers/SceneMan.h @@ -698,6 +698,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 ////////////////////////////////////////////////////////////////////////////////////////// From 462ec8d77329b21aa70698256c63c3c00fcd307f Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 2 Feb 2023 22:43:57 +0200 Subject: [PATCH 28/76] `NotResting` function to reset oscillation checks --- Entities/MOSprite.h | 5 +++++ Entities/MovableObject.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Entities/MOSprite.h b/Entities/MOSprite.h index 05f71a004..bd47254c3 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 diff --git a/Entities/MovableObject.h b/Entities/MovableObject.h index edf8ee462..a6b7c8dc9 100644 --- a/Entities/MovableObject.h +++ b/Entities/MovableObject.h @@ -1168,7 +1168,7 @@ enum MOType /// /// Forces this MovableObject out of resting conditions. /// - void NotResting() { m_RestTimer.Reset(); m_ToSettle = false; } + 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. From f1681da0fccb1f208c59ac9ab931e7e6c494a4a7 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 2 Feb 2023 22:49:40 +0200 Subject: [PATCH 29/76] `Gib` property `InheritsVel` is now float instead of bool, similar to `Emission` --- CHANGELOG.md | 2 ++ Entities/Gib.cpp | 2 +- Entities/Gib.h | 8 ++++---- Entities/MOSRotating.cpp | 6 +++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a885eb71b..a34fecc86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -712,6 +712,8 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter SetScroll(center, screenId); ``` +- `Gib` property `InheritsVel` now works as a `float` scalar from 0 to 1, defining the portion of velocity inherited from the parent object. +
Fixed 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/MOSRotating.cpp b/Entities/MOSRotating.cpp index 7497cbcfd..ccaefece3 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -1087,7 +1087,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); @@ -1120,14 +1120,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); From e7d5d9cb232376141668f8beb58eb7d508f42871 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 2 Feb 2023 23:14:26 +0200 Subject: [PATCH 30/76] Move camera shake under `ActivityState::Running` condition so that it doesn't interfere in the Gib Editor --- Managers/CameraMan.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Managers/CameraMan.cpp b/Managers/CameraMan.cpp index 3b053ba70..b03b13dad 100644 --- a/Managers/CameraMan.cpp +++ b/Managers/CameraMan.cpp @@ -205,22 +205,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) { @@ -258,7 +242,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.GetElapsedSimTimeS()); + 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.2F; + + 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; From fbb07dfd70d45b916e5082963623f5153eee4acf Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 2 Feb 2023 23:57:47 +0200 Subject: [PATCH 31/76] Have BG arm unequip first so that the FG weapon is always the first one available. --- Entities/AHuman.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 056de1894..a0285ef3c 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -615,7 +615,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. From 17e8abb609f92b060f8a8ec19ce156145a46e0b6 Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 3 Feb 2023 00:00:09 +0200 Subject: [PATCH 32/76] Arm sway improvements --- Entities/AHuman.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index c6651ca31..524de4803 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -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; @@ -3841,7 +3841,7 @@ 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; @@ -3853,11 +3853,7 @@ void AHuman::Update() 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); From 3a2c27ca31dd39c85da1521cb7ee955194c1d028 Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 3 Feb 2023 00:29:35 +0200 Subject: [PATCH 33/76] `AHuman` dropping logic fixes; Inventory drop now only possible when main arm isn't available --- Entities/AHuman.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 524de4803..b1054b8a3 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3483,15 +3483,16 @@ void AHuman::Update() 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->SetHandCurrentPos(heldDevice->GetPos()); if (!m_Inventory.empty()) { arm->SetHeldDevice(dynamic_cast(SwapNextInventory())); arm->SetHandCurrentPos(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)); @@ -4547,13 +4548,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); From 8aa18bde12527170114a5b36b32763f73b9ec409 Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 3 Feb 2023 00:30:19 +0200 Subject: [PATCH 34/76] `INACTIVE` checks on `AHuman` Pie Slices --- Entities/AHuman.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index b1054b8a3..9597f201b 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -4510,7 +4510,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); @@ -4520,7 +4520,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); @@ -4530,7 +4530,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); @@ -4539,7 +4539,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); From fbf5997ce5d6fbc4f890430fa92e386fe09dcbd6 Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 3 Feb 2023 00:31:18 +0200 Subject: [PATCH 35/76] FG Arm needs to be empty to climb --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 9597f201b..815e68744 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3617,7 +3617,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; From fb36ddf4b5162a9bed4a021dad8e5f28fa08ea22 Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 3 Feb 2023 00:49:11 +0200 Subject: [PATCH 36/76] Change a couple `Arm` property names for clarity --- CHANGELOG.md | 6 +++--- Entities/AHuman.cpp | 36 ++++++++++++++++++------------------ Entities/Arm.cpp | 14 +++++++------- Entities/Arm.h | 10 +++++----- Lua/LuaBindingsEntities.cpp | 4 ++-- Menus/InventoryMenuGUI.cpp | 4 ++-- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a34fecc86..8955a7a31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -675,7 +675,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. @@ -684,8 +684,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. diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 815e68744..f111b343a 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -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 @@ -802,7 +802,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); } @@ -878,7 +878,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(); @@ -947,7 +947,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(); @@ -1004,7 +1004,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(); @@ -1061,7 +1061,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(); @@ -1119,7 +1119,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(); @@ -1176,7 +1176,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(); @@ -1263,7 +1263,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(); @@ -1328,7 +1328,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); } @@ -1346,7 +1346,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; } } @@ -1360,7 +1360,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; } } @@ -3262,7 +3262,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; @@ -3422,7 +3422,7 @@ void AHuman::Update() } } else if (m_ArmsState == THROWING_RELEASE && m_ThrowTmr.GetElapsedSimTimeMS() > 100) { m_pFGArm->SetHeldDevice(dynamic_cast(SwapNextInventory())); - m_pFGArm->SetHandCurrentPos(m_Pos + RotateOffset(m_HolsterOffset)); + m_pFGArm->SetHandPos(m_Pos + RotateOffset(m_HolsterOffset)); EquipShieldInBGArm(); m_ArmsState = WEAPON_READY; } else if (m_ArmsState == THROWING_RELEASE) { @@ -3483,10 +3483,10 @@ void AHuman::Update() 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->SetHandCurrentPos(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; @@ -3495,7 +3495,7 @@ void AHuman::Update() 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(); @@ -3532,7 +3532,7 @@ void AHuman::Update() Arm *armToUse = m_pFGArm ? m_pFGArm : m_pBGArm; Attachable *pMO = armToUse->RemoveAttachable(armToUse->GetHeldDevice()); AddToInventoryBack(pMO); - armToUse->SetHandCurrentPos(m_pItemInReach->GetJointPos()); + armToUse->SetHandPos(m_pItemInReach->GetJointPos()); armToUse->SetHeldDevice(m_pItemInReach); m_pItemInReach = nullptr; diff --git a/Entities/Arm.cpp b/Entities/Arm.cpp index 8d3b8790c..29e653d25 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())); } @@ -243,7 +243,7 @@ namespace RTE { } else if (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); 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/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 9408ecdce..fd65f4372 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -548,9 +548,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) diff --git a/Menus/InventoryMenuGUI.cpp b/Menus/InventoryMenuGUI.cpp index e3e262e43..d5eb37347 100644 --- a/Menus/InventoryMenuGUI.cpp +++ b/Menus/InventoryMenuGUI.cpp @@ -1233,10 +1233,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()); } From 9f56e22113287cef85b538057e8a6d9122e49449 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 22:16:40 +0200 Subject: [PATCH 37/76] Remove `AHuman`-side reload angle stuff --- Entities/AHuman.cpp | 20 -------------------- Entities/AHuman.h | 1 - 2 files changed, 21 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index a10682af2..01e27875c 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -75,7 +75,6 @@ void AHuman::Clear() m_JetReplenishRate = 1.0F; m_JetAngleRange = 0.25F; m_WaitingToReloadOffhand = false; - m_OneHandedReloadAngleOffset = -0.4F; m_GoldInInventoryChunk = 0; m_ThrowTmr.Reset(); m_ThrowPrepTime = 1000; @@ -180,7 +179,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; @@ -255,8 +253,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") { @@ -360,7 +356,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"); @@ -3863,21 +3858,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! diff --git a/Entities/AHuman.h b/Entities/AHuman.h index a0285ef3c..8c59c29a9 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -1044,7 +1044,6 @@ DefaultPieMenuNameGetter("Default Human Pie Menu"); // Ratio at which the jetpack angle follows aim angle float m_JetAngleRange; 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. From f055e12e6dc80d948055ebe843afbcb90184ec89 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 22:30:41 +0200 Subject: [PATCH 38/76] Visual recoil fine-tuning --- Entities/Arm.cpp | 13 ++++++------- Entities/HDFirearm.cpp | 11 ++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Entities/Arm.cpp b/Entities/Arm.cpp index 29e653d25..794c036f8 100644 --- a/Entities/Arm.cpp +++ b/Entities/Arm.cpp @@ -288,18 +288,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 +307,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/HDFirearm.cpp b/Entities/HDFirearm.cpp index 4a12ea38c..36dfaa556 100644 --- a/Entities/HDFirearm.cpp +++ b/Entities/HDFirearm.cpp @@ -1006,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; @@ -1019,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 @@ -1057,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; } } From b080018c33949e454453a484e22c960dacc3e288 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 22:37:40 +0200 Subject: [PATCH 39/76] Remove reload angle getters and setters, too --- Entities/AHuman.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 8c59c29a9..048e6ac00 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -388,18 +388,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. From cd446ca67b1dfd05a04b03765f56353169650147 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:07:03 +0200 Subject: [PATCH 40/76] Spaced out `AHuman` dual-wielded fire + some reformatting along with `CanFire` and `MSPerRound` properties --- CHANGELOG.md | 4 + Entities/AHuman.cpp | 183 ++++++++++++++++++++++-------------- Entities/AHuman.h | 2 + Entities/HDFirearm.cpp | 46 ++++----- Entities/HDFirearm.h | 17 ++++ Lua/LuaBindingsEntities.cpp | 2 + 6 files changed, 157 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63e7f84ea..d9c098839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -533,6 +533,10 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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`. +
Changed diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 01e27875c..0d19d2176 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -74,6 +74,8 @@ void AHuman::Clear() m_JetTimeLeft = 0.0; m_JetReplenishRate = 1.0F; m_JetAngleRange = 0.25F; + m_ActivateBGItem = false; + m_TriggerPulled = false; m_WaitingToReloadOffhand = false; m_GoldInInventoryChunk = 0; m_ThrowTmr.Reset(); @@ -3348,107 +3350,150 @@ 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_ActivateBGItem) { + if (deviceAsFirearm->IsFullAuto()) { + deviceAsFirearm->Activate(); + m_ActivateBGItem = (deviceAsFirearm->FiredOnce() || deviceAsFirearm->IsReloading()) && deviceAsFirearm->HalfwayToNextRound(); + } else if (!m_TriggerPulled) { + deviceAsFirearm->Activate(); + if (deviceAsFirearm->FiredOnce()) { + m_ActivateBGItem = true; + m_TriggerPulled = true; + } + } + } + } else { + deviceAsFirearm->Deactivate(); + m_TriggerPulled = false; } } else { - device->Deactivate(); + m_ActivateBGItem = 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_ActivateBGItem = 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_ActivateBGItem = 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->AddHandTarget("Start Throw Offset", m_pFGArm->GetJointPos() + thrownDevice->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() + thrownDevice->GetEndThrowOffset().GetXFlipped(m_HFlipped).RadRotate(adjustedAimAngle)); + + 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); + } else { + if (pMO->IsGold()) { + m_GoldInInventoryChunk = 0; + ChunkGold(); + } + g_MovableMan.AddParticle(pMO); } - 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->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 { + m_ActivateBGItem = true; } if (HeldDevice *device = GetEquippedBGItem(); device && m_Status != INACTIVE) { + if (HDFirearm *deviceAsFirearm = dynamic_cast(device)) { + if (m_Controller.IsState(WEAPON_FIRE)) { + if (m_ActivateBGItem && (!m_TriggerPulled || (deviceAsFirearm->IsFullAuto() && deviceAsFirearm->HalfwayToNextRound()))) { + deviceAsFirearm->Activate(); + if (deviceAsFirearm->FiredOnce()) { + m_ActivateBGItem = false; + m_TriggerPulled = true; + } + } + } else { + deviceAsFirearm->Deactivate(); + m_TriggerPulled = false; + } + } else { + m_ActivateBGItem = 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_ActivateBGItem = false; m_SharpAimTimer.Reset(); m_SharpAimProgress = 0; device->SetSharpAim(m_SharpAimProgress); } + } else { + m_ActivateBGItem = false; } - if (m_ArmsState == THROWING_PREP && !pThrown) { + if (m_ArmsState == THROWING_PREP && !thrownDevice) { m_ArmsState = WEAPON_READY; } diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 048e6ac00..65d2dc196 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -1031,6 +1031,8 @@ 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_ActivateBGItem; //!< 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. // Blink timer Timer m_IconBlinkTimer; diff --git a/Entities/HDFirearm.cpp b/Entities/HDFirearm.cpp index 36dfaa556..a3e63f67a 100644 --- a/Entities/HDFirearm.cpp +++ b/Entities/HDFirearm.cpp @@ -799,34 +799,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, (double)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) { diff --git a/Entities/HDFirearm.h b/Entities/HDFirearm.h index 0c400050d..72a1770a0 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 (double)60000 / (double)m_RateOfFire; } + /// /// Gets the Magazine of this HDFirearm. /// @@ -775,6 +781,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() / (double)2); } ////////////////////////////////////////////////////////////////////////////////////////// // Method: RoundsFired diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index fd65f4372..43476b018 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -669,6 +669,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) @@ -696,6 +697,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) From c6129ff77a912f56d4d79b6fd3f21348af7e369e Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:08:24 +0200 Subject: [PATCH 41/76] Forgot to commit the removal of `OneHandedReloadAngleOffset` luabind --- Lua/LuaBindingsEntities.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 43476b018..f11d5b9d9 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -428,7 +428,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) From 9a6b7e1521016e98a854ee2119fdad6e03ea4cf3 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:20:24 +0200 Subject: [PATCH 42/76] `AHuman` reload animation rework, new properties `ReloadAngle` and `OneHandedReloadAngle` --- CHANGELOG.md | 8 +++++--- Entities/AHuman.cpp | 19 ++++++++----------- Entities/Arm.cpp | 22 ++++++++++++++++++++-- Entities/HDFirearm.cpp | 12 +++++++++++- Entities/HDFirearm.h | 8 ++++++++ 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c098839..15d8b61ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -511,9 +511,7 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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`. A non-zero value is reqiured for `OneHandedReloadAngle` to be used. - -- 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`. +- 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. @@ -537,6 +535,10 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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`. +
Changed diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 0d19d2176..31992a224 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1530,23 +1530,20 @@ void AHuman::ReloadFirearms(bool onlyReloadEmptyFirearms) { } if (reloadHeldFirearm) { + heldFirearm->Reload(); + if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } //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; 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()); } } } @@ -3220,12 +3217,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); } } diff --git a/Entities/Arm.cpp b/Entities/Arm.cpp index 794c036f8..b685fb88c 100644 --- a/Entities/Arm.cpp +++ b/Entities/Arm.cpp @@ -238,9 +238,27 @@ 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_HandIdleOffset.GetXFlipped(m_Parent->IsHFlipped()).GetRadRotatedCopy(m_Parent->GetRotAngle()); diff --git a/Entities/HDFirearm.cpp b/Entities/HDFirearm.cpp index a3e63f67a..57c972d1c 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; diff --git a/Entities/HDFirearm.h b/Entities/HDFirearm.h index 72a1770a0..7c0ebc7ec 100644 --- a/Entities/HDFirearm.h +++ b/Entities/HDFirearm.h @@ -271,6 +271,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, if support is available, or the one handed reload angle, if not. + /// + /// The appropriate reload angle to use, in radians. + float GetReloadAngle() const { return m_SupportAvailable ? m_ReloadAngle : m_OneHandedReloadAngle; } + ////////////////////////////////////////////////////////////////////////////////////////// // Method: GetShakeRange @@ -881,6 +887,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; //!< Reload angle width for the default reload animation, in radians. + float m_OneHandedReloadAngle; //!< Reload angle width for one-handed reload animation, in radians. // Timer for timing how long ago the last round was fired. Timer m_LastFireTmr; From 5af7501c05df3875ab1bfbaeadf927c3b364e8c6 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:22:50 +0200 Subject: [PATCH 43/76] Rotate arms according to the leg of their own layer for a better pose while crouching and jumping --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 31992a224..9c8b5ad27 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3884,7 +3884,7 @@ void AHuman::Update() 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); } From a7240818e5f9a069e415ec3918742dc5cde7be39 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:23:51 +0200 Subject: [PATCH 44/76] Treat very high `SupportOffset`s as non-reachable --- Entities/Arm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/Arm.cpp b/Entities/Arm.cpp index b685fb88c..7c4ffa128 100644 --- a/Entities/Arm.cpp +++ b/Entities/Arm.cpp @@ -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; } From 0c5037fa533e29b6158e7725f3a3cc1de1117ed8 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:25:29 +0200 Subject: [PATCH 45/76] Update `AHuman` HUD to indicate being affected by reload time multipliers --- Entities/AHuman.cpp | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 9c8b5ad27..f2c0afe79 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -4285,34 +4285,54 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc 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) { + str[0] = -37; str[1] = -49; str[2] = -56; str[3] = 0; + barColorIndex = reloadMultiplier > 1.0F ? (m_IconBlinkTimer.AlternateSim(250) ? 13 : 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) { + str[0] = -37; str[1] = -49; str[2] = -56; str[3] = 0; + barColorIndex = reloadMultiplier > 1.0F ? (m_IconBlinkTimer.AlternateSim(250) ? 13 : barColorIndex) : 133; + /* + if (m_IconBlinkTimer.AlternateSim(250)) { + barColorIndex = reloadMultiplier > 1.0F ? (reloadMultiplier > 1.5F ? (reloadMultiplier > 2.0F ? 47 : 48) : 86) : 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; From 2f0323ac9481900da452e68fcdb9e094e69954ae Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:28:33 +0200 Subject: [PATCH 46/76] Remove unused commented block --- Entities/AHuman.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index f2c0afe79..de080946c 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -4317,11 +4317,6 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc if (reloadMultiplier != 1.0F) { str[0] = -37; str[1] = -49; str[2] = -56; str[3] = 0; barColorIndex = reloadMultiplier > 1.0F ? (m_IconBlinkTimer.AlternateSim(250) ? 13 : barColorIndex) : 133; - /* - if (m_IconBlinkTimer.AlternateSim(250)) { - barColorIndex = reloadMultiplier > 1.0F ? (reloadMultiplier > 1.5F ? (reloadMultiplier > 2.0F ? 47 : 48) : 86) : 133; - } - */ } } int totalTextWidth = pSmallFont->CalculateWidth(fgWeaponString) + 6; From ff95582477822975b8132c16dbafefb1d35cc965 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 7 Feb 2023 23:50:14 +0200 Subject: [PATCH 47/76] Doubles --- Entities/HDFirearm.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Entities/HDFirearm.h b/Entities/HDFirearm.h index 7c0ebc7ec..d8db4c0b6 100644 --- a/Entities/HDFirearm.h +++ b/Entities/HDFirearm.h @@ -134,7 +134,7 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload"); /// Gets the minimum time in between shots, in MS. /// /// The minimum time in between shots, in MS. - double GetMSPerRound() const { return (double)60000 / (double)m_RateOfFire; } + double GetMSPerRound() const { return 60000.0 / static_cast(m_RateOfFire); } /// /// Gets the Magazine of this HDFirearm. @@ -797,7 +797,7 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload"); /// 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() / (double)2); } + bool HalfwayToNextRound() const { return m_LastFireTmr.IsPastSimMS(GetMSPerRound() / 2.0); } ////////////////////////////////////////////////////////////////////////////////////////// // Method: RoundsFired From b7fc461ff012a0132d03fa0f903b0116023faaa1 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 9 Feb 2023 10:10:55 +0200 Subject: [PATCH 48/76] Review changes --- Entities/AHuman.cpp | 15 ++++++++++++--- Entities/HDFirearm.h | 6 +++--- Lua/LuaBindingsManagers.cpp | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index de080946c..a9512efa2 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1532,7 +1532,6 @@ void AHuman::ReloadFirearms(bool onlyReloadEmptyFirearms) { if (reloadHeldFirearm) { heldFirearm->Reload(); if (m_DeviceSwitchSound) { m_DeviceSwitchSound->Play(m_Pos); } - //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. bool otherArmIsAvailable = otherArm && !otherArm->GetHeldDevice(); if (otherArmIsAvailable) { @@ -4296,8 +4295,13 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc 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; - barColorIndex = reloadMultiplier > 1.0F ? (m_IconBlinkTimer.AlternateSim(250) ? 13 : barColorIndex) : 133; + 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); @@ -4315,8 +4319,13 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc 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; - barColorIndex = reloadMultiplier > 1.0F ? (m_IconBlinkTimer.AlternateSim(250) ? 13 : barColorIndex) : 133; + if (reloadMultiplier > 1.0F) { + if (m_IconBlinkTimer.AlternateSim(250)) { barColorIndex = 13; } + } else { + barColorIndex = 133; + } } } int totalTextWidth = pSmallFont->CalculateWidth(fgWeaponString) + 6; diff --git a/Entities/HDFirearm.h b/Entities/HDFirearm.h index d8db4c0b6..e791d0ec6 100644 --- a/Entities/HDFirearm.h +++ b/Entities/HDFirearm.h @@ -272,7 +272,7 @@ AddScriptFunctionNames(HeldDevice, "OnFire", "OnReload"); void SetOneHandedReloadTimeMultiplier(float newOneHandedReloadTimeMultiplier) { m_OneHandedReloadTimeMultiplier = newOneHandedReloadTimeMultiplier; } /// - /// Gets the default reload angle, if support is available, or the one handed reload angle, if not. + /// 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; } @@ -887,8 +887,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; //!< Reload angle width for the default reload animation, in radians. - float m_OneHandedReloadAngle; //!< Reload angle width for one-handed reload animation, in radians. + 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/Lua/LuaBindingsManagers.cpp b/Lua/LuaBindingsManagers.cpp index 47ec417e6..955c5c91e 100644 --- a/Lua/LuaBindingsManagers.cpp +++ b/Lua/LuaBindingsManagers.cpp @@ -354,7 +354,7 @@ namespace RTE { .property("RecommendedMOIDCount", &SettingsMan::RecommendedMOIDCount) .property("AIUpdateInterval", &SettingsMan::GetAIUpdateInterval, &SettingsMan::SetAIUpdateInterval) .property("ShowEnemyHUD", &SettingsMan::ShowEnemyHUD) - .property("ScrapCompactingheight", &SettingsMan::GetScrapCompactingHeight, &SettingsMan::SetScrapCompactingHeight); + .property("ScrapCompactingHeight", &SettingsMan::GetScrapCompactingHeight, &SettingsMan::SetScrapCompactingHeight); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 58202aad0b444b38eea69fec1e47e9310fff09d2 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 9 Feb 2023 10:11:37 +0200 Subject: [PATCH 49/76] Fix crash in `Activity::LoseControlOfActor` --- Entities/Activity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/Activity.cpp b/Entities/Activity.cpp index 92051a327..56f78a269 100644 --- a/Entities/Activity.cpp +++ b/Entities/Activity.cpp @@ -764,7 +764,7 @@ void Activity::Clear() { void Activity::LoseControlOfActor(int player) { if (player >= Players::PlayerOne && player < Players::MaxPlayerCount) { - if (Actor *actor = m_ControlledActor[player]) { + if (Actor *actor = m_ControlledActor[player]; actor && g_MovableMan.IsActor(actor)) { actor->SetControllerMode(Controller::CIM_AI); actor->GetController()->SetDisabled(false); } From 9d41193fd77529b78ebd4eb6d41f9057a288c29a Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 9 Feb 2023 10:13:47 +0200 Subject: [PATCH 50/76] Tweaks to gib limit to attachable interaction logic --- Entities/MOSRotating.cpp | 56 ++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index 542b1ca77..d9f2b248c 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -493,7 +493,7 @@ Attachable * MOSRotating::GetNearestAttachableToOffset(const Vector &offset) con Attachable *nearestAttachable = nullptr; float closestRadius = -1.0F; 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) { closestRadius = radius; @@ -509,11 +509,9 @@ Attachable * MOSRotating::GetNearestAttachableToOffset(const Vector &offset) con void MOSRotating::AddWound(AEmitter *woundToAdd, const Vector &parentOffsetToSet, bool checkGibWoundLimit) { 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 = GetNearestAttachableToOffset(parentOffsetToSet); attachableToDetach && m_DetachAttachablesBeforeGibbingFromWounds) { + RemoveAttachable(attachableToDetach, true, true); } else { // TODO: Don't hardcode the blast strength! GibThis(Vector(-5.0F, 0).RadRotate(woundToAdd->GetEmitAngle())); @@ -1214,15 +1212,24 @@ 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); + float impulseRemainder = totalImpulse.GetMagnitude(); + Vector invertedImpulseOffset = Vector(totalImpulse.GetX(), totalImpulse.GetY()).SetMagnitude(-GetRadius()) * -m_Rotation; + Attachable *nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); + while (nearestAttachableToImpulse) { + float attachableImpulseLimit = nearestAttachableToImpulse->GetGibImpulseLimit(); + float attachableJointStrength = nearestAttachableToImpulse->GetJointStrength(); + if (impulseRemainder > attachableImpulseLimit) { + nearestAttachableToImpulse->GibThis(totalImpulse.SetMagnitude(attachableImpulseLimit)); + impulseRemainder -= attachableImpulseLimit; + } else if (impulseRemainder > attachableJointStrength) { + RemoveAttachable(nearestAttachableToImpulse, true, true); + impulseRemainder -= attachableJointStrength; + } else { + break; } + nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); } - if (Attachable *nearestAttachableToImpulse = GetNearestAttachableToOffset(averagedImpulseForceOffset.SetMagnitude(-GetRadius()) * -m_Rotation); nearestAttachableToImpulse && totalImpulse.MagnitudeIsGreaterThan(nearestAttachableToImpulse->GetGibImpulseLimit())) { - nearestAttachableToImpulse->SetGibWithParentChance(1.0F); - } - GibThis(totalImpulse); + if (impulseRemainder > impulseLimit) { GibThis(totalImpulse.SetMagnitude(impulseRemainder)); } } } @@ -1508,15 +1515,24 @@ 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); + float impulseRemainder = m_TravelImpulse.GetMagnitude(); + Vector invertedImpulseOffset = Vector(m_TravelImpulse.GetX(), m_TravelImpulse.GetY()).SetMagnitude(-GetRadius()) * -m_Rotation; + Attachable *nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); + while (nearestAttachableToImpulse) { + float attachableImpulseLimit = nearestAttachableToImpulse->GetGibImpulseLimit(); + float attachableJointStrength = nearestAttachableToImpulse->GetJointStrength(); + if (impulseRemainder > attachableImpulseLimit) { + nearestAttachableToImpulse->GibThis(); + impulseRemainder -= attachableImpulseLimit; + } else if (impulseRemainder > attachableJointStrength) { + RemoveAttachable(nearestAttachableToImpulse, true, true); + impulseRemainder -= attachableJointStrength; + } else { + break; } + nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); } - 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(); + if (impulseRemainder > impulseLimit) { GibThis(); } } } // Reset From c21c73730ca0903fe38ba153ba12708d7189f28f Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 9 Feb 2023 10:21:43 +0200 Subject: [PATCH 51/76] Add `Attachable` property `GibWhenRemovedFromParent` --- CHANGELOG.md | 2 ++ Entities/Attachable.cpp | 5 +++++ Entities/Attachable.h | 23 ++++++++++++++++++----- Entities/MOSRotating.cpp | 3 ++- Entities/Scene.cpp | 1 + Lua/LuaBindingsEntities.cpp | 1 + 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d8b61ca..fe4c5b1e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -539,6 +539,8 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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. +
Changed diff --git a/Entities/Attachable.cpp b/Entities/Attachable.cpp index 0fa874336..1ed7d3452 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; @@ -69,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; @@ -112,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") { @@ -163,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); diff --git a/Entities/Attachable.h b/Entities/Attachable.h index 810984e5d..1b80bafbe 100644 --- a/Entities/Attachable.h +++ b/Entities/Attachable.h @@ -136,17 +136,29 @@ 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. + /// Whether this Attachable should be deleted when removed from its parent. virtual 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. + virtual 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. /// @@ -549,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. diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index d9f2b248c..13bc29e14 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -1146,7 +1146,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; } @@ -1754,6 +1754,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/Scene.cpp b/Entities/Scene.cpp index 45b8a7a1c..3ba512647 100644 --- a/Entities/Scene.cpp +++ b/Entities/Scene.cpp @@ -1545,6 +1545,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/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index f11d5b9d9..a76a28d3b 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -579,6 +579,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) From 960f3ce7ff7212e0a11ca3f3a2d62f132a7d1878 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 9 Feb 2023 10:26:28 +0200 Subject: [PATCH 52/76] Default `JetReplenishRate` to correspond to 1:1 instead of always being doubled, for continuity purposes, while also making it easier to balance the actual jetpack power output (Data tweaks will follow) --- Entities/ACrab.cpp | 3 +-- Entities/AHuman.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp index 0c5fa2eee..84028e2ab 100644 --- a/Entities/ACrab.cpp +++ b/Entities/ACrab.cpp @@ -2149,8 +2149,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; diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index a9512efa2..401e1f403 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3118,7 +3118,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; From 4c0bd3a3bd2221c9409ba81315e8da04bef4c730 Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Thu, 9 Feb 2023 14:48:02 -0400 Subject: [PATCH 53/76] Added safety checks when replacing pie slices --- Entities/PieMenu.cpp | 7 +++++++ Entities/PieMenu.h | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Entities/PieMenu.cpp b/Entities/PieMenu.cpp index 84018ae13..18abe8bf0 100644 --- a/Entities/PieMenu.cpp +++ b/Entities/PieMenu.cpp @@ -492,6 +492,13 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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) { diff --git a/Entities/PieMenu.h b/Entities/PieMenu.h index 189c1e32c..87281a723 100644 --- a/Entities/PieMenu.h +++ b/Entities/PieMenu.h @@ -323,7 +323,7 @@ namespace RTE { /// 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. + /// 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 From 55a69623f7b71deed7b4e425540cbfa3576c2327 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 12 Feb 2023 22:16:26 +0200 Subject: [PATCH 54/76] Added internal `MovableObject` function `GetTotalForce` --- Entities/Attachable.cpp | 7 +------ Entities/MovableObject.cpp | 21 ++++++++++++++------- Entities/MovableObject.h | 5 +++++ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Entities/Attachable.cpp b/Entities/Attachable.cpp index 1ed7d3452..775ce974c 100644 --- a/Entities/Attachable.cpp +++ b/Entities/Attachable.cpp @@ -201,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; } diff --git a/Entities/MovableObject.cpp b/Entities/MovableObject.cpp index b59db6f3f..1b90e7d1d 100644 --- a/Entities/MovableObject.cpp +++ b/Entities/MovableObject.cpp @@ -752,6 +752,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 @@ -780,13 +789,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(); diff --git a/Entities/MovableObject.h b/Entities/MovableObject.h index 7bbafbea0..9b4f0ac31 100644 --- a/Entities/MovableObject.h +++ b/Entities/MovableObject.h @@ -1362,6 +1362,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() From f67add02eab8135374ebe600f23a92f0b98bf102 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 12 Feb 2023 23:38:20 +0200 Subject: [PATCH 55/76] Tweak jetpack icon offsets and make the fuel gauge colors more robust --- Entities/ACrab.cpp | 15 +++++++++++++-- Entities/AHuman.cpp | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp index 84028e2ab..f240053a2 100644 --- a/Entities/ACrab.cpp +++ b/Entities/ACrab.cpp @@ -2764,10 +2764,21 @@ void ACrab::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichScr 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); + pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 8, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre); float jetTimeRatio = m_JetTimeLeft / m_JetTimeTotal; - int gaugeColor = jetTimeRatio > 0.6F ? 149 : (jetTimeRatio > 0.3F ? 77 : 13); + 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() + 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); diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 401e1f403..b43ee5bfc 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -4264,10 +4264,21 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc } // null-terminate str[1] = 0; - pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 9, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre); + pSymbolFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() - 8, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre); float jetTimeRatio = m_JetTimeLeft / m_JetTimeTotal; - int gaugeColor = jetTimeRatio > 0.6F ? 149 : (jetTimeRatio > 0.3F ? 77 : 13); + 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() + 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); From f27bef9488a0508baca23572192fdb29692f4a66 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 19 Feb 2023 16:35:41 +0200 Subject: [PATCH 56/76] Re-enabled `Actor` `GoldCarried` logic as an optional gameplay mutator; Cleaned up deprecated junk related to old gold logic. --- CHANGELOG.md | 4 ++ Entities/ACrab.cpp | 3 -- Entities/ACrab.h | 11 ---- Entities/ACraft.cpp | 21 ++++++-- Entities/AHuman.cpp | 64 ----------------------- Entities/AHuman.h | 23 --------- Entities/Actor.cpp | 100 +++++++++++++++++------------------- Entities/Actor.h | 18 ++----- Lua/LuaBindingsEntities.cpp | 2 + Lua/LuaBindingsManagers.cpp | 3 +- Managers/SettingsMan.cpp | 4 ++ Managers/SettingsMan.h | 7 +++ System/Atom.cpp | 5 +- 13 files changed, 90 insertions(+), 175 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4c5b1e8..3d7f507b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -541,6 +541,10 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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. +
Changed diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp index f240053a2..8f0412dd8 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; @@ -223,8 +222,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; diff --git a/Entities/ACrab.h b/Entities/ACrab.h index cb9030db6..4ba597c2c 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 ////////////////////////////////////////////////////////////////////////////////////////// @@ -605,8 +596,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..bf14e76be 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()->IsBeingPlayed()) { g_GUISound.FundsChangedSound()->Play(player); } + } + } + } } } diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index b43ee5bfc..e9b31bab6 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -77,7 +77,6 @@ void AHuman::Clear() m_ActivateBGItem = false; m_TriggerPulled = false; m_WaitingToReloadOffhand = false; - m_GoldInInventoryChunk = 0; m_ThrowTmr.Reset(); m_ThrowPrepTime = 1000; m_SharpAimRevertTimer.Reset(); @@ -213,8 +212,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; @@ -689,42 +686,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 @@ -3423,12 +3384,6 @@ void AHuman::Update() moAsHeldDevice->SetTeam(m_Team); moAsHeldDevice->SetIgnoresTeamHits(true); g_MovableMan.AddItem(moAsHeldDevice); - } else { - if (pMO->IsGold()) { - m_GoldInInventoryChunk = 0; - ChunkGold(); - } - g_MovableMan.AddParticle(pMO); } pMO = 0; } @@ -3963,14 +3918,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 @@ -4354,18 +4301,7 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc } 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() : ""); pSmallFont->DrawAligned(&allegroBitmap, drawPos.GetFloorIntX() + 1, drawPos.GetFloorIntY() + m_HUDStack + 3, equippedItemsString, GUIFont::Centre); m_HUDStack -= 9; diff --git a/Entities/AHuman.h b/Entities/AHuman.h index 65d2dc196..c9dbd96d4 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 ////////////////////////////////////////////////////////////////////////////////////////// @@ -979,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. /// @@ -1058,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/Actor.cpp b/Entities/Actor.cpp index 4b111674b..7c859d681 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -703,15 +703,22 @@ 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()->IsBeingPlayed()) { g_GUISound.FundsChangedSound()->Play(player); } + } + } + } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -936,6 +943,24 @@ void Actor::DropAllInventory() ////////////////////////////////////////////////////////////////////////////////////////// +void Actor::DropAllGold() { + Material const *goldMaterial = g_SceneMan.GetMaterialFromID(g_MaterialGold); + float velMin = 3.0F; + float velMax = velMin + std::sqrt(m_SpriteRadius); + for (int i = 0; i < m_GoldCarried; i++) { + Vector dropOffset(m_SpriteRadius * 0.3F * RandomNum(), 0); + dropOffset.RadRotate(c_PI * RandomNormalNum()); + + MOPixel *pixelMO = new MOPixel(goldMaterial->GetColor(), goldMaterial->GetPixelDensity(), m_Pos + dropOffset, dropOffset.SetMagnitude(RandomNum(velMin, velMax)), new Atom(Vector(), g_MaterialGold, 0, goldMaterial->GetColor(), 2), 0); + pixelMO->SetToHitMOs(false); + g_MovableMan.AddParticle(pixelMO); + pixelMO = nullptr; + } + 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()) { @@ -1493,33 +1518,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(); } } //////////////////////////////// @@ -1792,24 +1791,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 9030aeabc..27e3d7c0c 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; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -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 @@ -923,6 +911,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/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index a76a28d3b..43421ad8e 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -259,6 +259,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) @@ -279,6 +280,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) diff --git a/Lua/LuaBindingsManagers.cpp b/Lua/LuaBindingsManagers.cpp index 955c5c91e..deff7b653 100644 --- a/Lua/LuaBindingsManagers.cpp +++ b/Lua/LuaBindingsManagers.cpp @@ -354,7 +354,8 @@ namespace RTE { .property("RecommendedMOIDCount", &SettingsMan::RecommendedMOIDCount) .property("AIUpdateInterval", &SettingsMan::GetAIUpdateInterval, &SettingsMan::SetAIUpdateInterval) .property("ShowEnemyHUD", &SettingsMan::ShowEnemyHUD) - .property("ScrapCompactingHeight", &SettingsMan::GetScrapCompactingHeight, &SettingsMan::SetScrapCompactingHeight); + .property("ScrapCompactingHeight", &SettingsMan::GetScrapCompactingHeight, &SettingsMan::SetScrapCompactingHeight) + .property("AutomaticGoldDeposit", &SettingsMan::GetAutomaticGoldDeposit); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Managers/SettingsMan.cpp b/Managers/SettingsMan.cpp index 95be5b4ee..bee58801c 100644 --- a/Managers/SettingsMan.cpp +++ b/Managers/SettingsMan.cpp @@ -31,6 +31,7 @@ namespace RTE { m_ShowEnemyHUD = true; m_EnableSmartBuyMenuNavigation = true; m_ScrapCompactingHeight = 25; + m_AutomaticGoldDeposit = true; m_NetworkServerAddress = "127.0.0.1:8000"; m_PlayerNetworkName = "Dummy"; @@ -164,6 +165,8 @@ namespace RTE { reader >> m_EnableSmartBuyMenuNavigation; } else if (propName == "ScrapCompactingHeight") { reader >> m_ScrapCompactingHeight; + } else if (propName == "AutomaticGoldDeposit") { + reader >> m_AutomaticGoldDeposit; } else if (propName == "ScreenShakeStrength") { reader >> g_CameraMan.m_ScreenShakeStrength; } else if (propName == "ScreenShakeDecay") { @@ -357,6 +360,7 @@ namespace RTE { writer.NewPropertyWithValue("ShowEnemyHUD", m_ShowEnemyHUD); writer.NewPropertyWithValue("SmartBuyMenuNavigation", m_EnableSmartBuyMenuNavigation); writer.NewPropertyWithValue("ScrapCompactingHeight", 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 c96f2bab7..c36cca1cb 100644 --- a/Managers/SettingsMan.h +++ b/Managers/SettingsMan.h @@ -256,6 +256,12 @@ namespace RTE { /// /// The new compacting height, in pixels. void SetScrapCompactingHeight(int newHeight) { m_ScrapCompactingHeight = newHeight; } + + /// + /// 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 @@ -512,6 +518,7 @@ namespace RTE { 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. int m_ScrapCompactingHeight; //!< The maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose. + 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/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; From 044ad688b1647d87a9b2a6d68b2024928e1875f1 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 19 Feb 2023 16:48:03 +0200 Subject: [PATCH 57/76] Allow inverse arm swing and device sway rates for `AHuman` --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index e9b31bab6..f385c90b2 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3833,7 +3833,7 @@ void AHuman::Update() ///////////////////////////////// // Arm swinging or device swaying walking animations - if (m_MoveState != MovementState::STAND && (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; From 55414a5b6db7de5e7a13c4e5ba3f4b84c2e704f8 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 19 Feb 2023 17:27:38 +0200 Subject: [PATCH 58/76] Use `GetTotalBurstSize` for jetpack fuel consumption in order to match the power output of bursts ever so slightly better. Does not take to account `BurstScale` in order to sustain some level of compatibility in comparison to previous code, as the average burst size of vanilla jetpacks is around 10. Jetpack bursts during burst spacing downtime are now less punishing. Cleanup in AEmitter --- CHANGELOG.md | 3 +++ Entities/ACrab.cpp | 2 +- Entities/AEmitter.cpp | 3 +-- Entities/AEmitter.h | 2 +- Entities/AHuman.cpp | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7f507b3..7a0fcdc43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -732,6 +732,9 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - `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 diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp index 8f0412dd8..876ca85df 100644 --- a/Entities/ACrab.cpp +++ b/Entities/ACrab.cpp @@ -2133,7 +2133,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! diff --git a/Entities/AEmitter.cpp b/Entities/AEmitter.cpp index ab8d70170..aab69e23d 100644 --- a/Entities/AEmitter.cpp +++ b/Entities/AEmitter.cpp @@ -449,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 642f5c9ee..131d19b4d 100644 --- a/Entities/AEmitter.h +++ b/Entities/AEmitter.h @@ -445,7 +445,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 f385c90b2..284ffec9a 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3068,7 +3068,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); From 3f48d47c8a10a54318f0b7fa0fc68a4e20f9a515 Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Sun, 19 Feb 2023 13:50:30 -0400 Subject: [PATCH 59/76] Minor cleanup --- Entities/Actor.cpp | 17 +++++++++++------ Entities/MOPixel.h | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp index 7c859d681..81a101108 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -944,17 +944,22 @@ void Actor::DropAllInventory() ////////////////////////////////////////////////////////////////////////////////////////// void Actor::DropAllGold() { - Material const *goldMaterial = g_SceneMan.GetMaterialFromID(g_MaterialGold); + const Material *goldMaterial = g_SceneMan.GetMaterialFromID(g_MaterialGold); float velMin = 3.0F; float velMax = velMin + std::sqrt(m_SpriteRadius); - for (int i = 0; i < m_GoldCarried; i++) { + + 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 *pixelMO = new MOPixel(goldMaterial->GetColor(), goldMaterial->GetPixelDensity(), m_Pos + dropOffset, dropOffset.SetMagnitude(RandomNum(velMin, velMax)), new Atom(Vector(), g_MaterialGold, 0, goldMaterial->GetColor(), 2), 0); - pixelMO->SetToHitMOs(false); - g_MovableMan.AddParticle(pixelMO); - pixelMO = nullptr; + MOPixel *goldMOPixel = new MOPixel(goldMaterial->GetColor(), goldMaterial->GetPixelDensity(), m_Pos + dropOffset, dropVelocity, goldMOPixelAtom); + goldMOPixel->SetToHitMOs(false); + g_MovableMan.AddParticle(goldMOPixel); } m_GoldCarried = 0; } diff --git a/Entities/MOPixel.h b/Entities/MOPixel.h index 60c4f52a7..b3b31c277 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); } From ea46ae96c0496a4bef93b507e461021c3032eb03 Mon Sep 17 00:00:00 2001 From: fourZK Date: Sun, 19 Feb 2023 20:08:20 +0200 Subject: [PATCH 60/76] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a0fcdc43..96fa5517f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -545,6 +545,10 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter 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. +
Changed From 4fd026d1a57dd8b29859ce05013cc380c9b122c9 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 21 Feb 2023 11:09:50 +0200 Subject: [PATCH 61/76] Move attachable-detaching impulse logic into a dedicated function + refactor and renaming for a little more clarity --- Entities/MOSRotating.cpp | 75 +++++++++++++++++++--------------------- Entities/MOSRotating.h | 12 +++++-- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index 13bc29e14..5d8b06fcc 100644 --- a/Entities/MOSRotating.cpp +++ b/Entities/MOSRotating.cpp @@ -489,13 +489,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->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; } @@ -506,11 +506,35 @@ 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 && !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. TODO: Perhaps move this to Actor, since it's more relevant there? - if (Attachable *attachableToDetach = GetNearestAttachableToOffset(parentOffsetToSet); attachableToDetach && m_DetachAttachablesBeforeGibbingFromWounds) { + if (Attachable *attachableToDetach = GetNearestDetachableAttachableToOffset(parentOffsetToSet); attachableToDetach && m_DetachAttachablesBeforeGibbingFromWounds) { RemoveAttachable(attachableToDetach, true, true); } else { // TODO: Don't hardcode the blast strength! @@ -1212,24 +1236,9 @@ void MOSRotating::ApplyImpulses() { impulseLimit *= 1.0F - (static_cast(m_Wounds.size()) / static_cast(m_GibWoundLimit)) * m_WoundCountAffectsImpulseLimitRatio; } if (totalImpulse.MagnitudeIsGreaterThan(impulseLimit)) { - float impulseRemainder = totalImpulse.GetMagnitude(); - Vector invertedImpulseOffset = Vector(totalImpulse.GetX(), totalImpulse.GetY()).SetMagnitude(-GetRadius()) * -m_Rotation; - Attachable *nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); - while (nearestAttachableToImpulse) { - float attachableImpulseLimit = nearestAttachableToImpulse->GetGibImpulseLimit(); - float attachableJointStrength = nearestAttachableToImpulse->GetJointStrength(); - if (impulseRemainder > attachableImpulseLimit) { - nearestAttachableToImpulse->GibThis(totalImpulse.SetMagnitude(attachableImpulseLimit)); - impulseRemainder -= attachableImpulseLimit; - } else if (impulseRemainder > attachableJointStrength) { - RemoveAttachable(nearestAttachableToImpulse, true, true); - impulseRemainder -= attachableJointStrength; - } else { - break; - } - nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); - } - if (impulseRemainder > impulseLimit) { GibThis(totalImpulse.SetMagnitude(impulseRemainder)); } + DetachAttachablesFromImpulse(totalImpulse); + // Use the remainder of the impulses left over from detaching to gib the parent object. + if (totalImpulse.MagnitudeIsGreaterThan(impulseLimit)) { GibThis(totalImpulse); } } } @@ -1515,24 +1524,10 @@ void MOSRotating::PostTravel() impulseLimit *= 1.0F - (static_cast(m_Wounds.size()) / static_cast(m_GibWoundLimit)) * m_WoundCountAffectsImpulseLimitRatio; } if (m_TravelImpulse.MagnitudeIsGreaterThan(impulseLimit)) { - float impulseRemainder = m_TravelImpulse.GetMagnitude(); - Vector invertedImpulseOffset = Vector(m_TravelImpulse.GetX(), m_TravelImpulse.GetY()).SetMagnitude(-GetRadius()) * -m_Rotation; - Attachable *nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); - while (nearestAttachableToImpulse) { - float attachableImpulseLimit = nearestAttachableToImpulse->GetGibImpulseLimit(); - float attachableJointStrength = nearestAttachableToImpulse->GetJointStrength(); - if (impulseRemainder > attachableImpulseLimit) { - nearestAttachableToImpulse->GibThis(); - impulseRemainder -= attachableImpulseLimit; - } else if (impulseRemainder > attachableJointStrength) { - RemoveAttachable(nearestAttachableToImpulse, true, true); - impulseRemainder -= attachableJointStrength; - } else { - break; - } - nearestAttachableToImpulse = GetNearestAttachableToOffset(invertedImpulseOffset); - } - if (impulseRemainder > impulseLimit) { 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 diff --git a/Entities/MOSRotating.h b/Entities/MOSRotating.h index 34420cbc4..e7049bc71 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); ////////////////////////////////////////////////////////////////////////////////////////// From 8a02596c32524b43fdb52256db1c22a918e383a2 Mon Sep 17 00:00:00 2001 From: fourZK Date: Tue, 21 Feb 2023 11:10:33 +0200 Subject: [PATCH 62/76] Fix some faulty parameter descriptions in ClosestWhatever MovableMan functions --- Managers/MovableMan.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Managers/MovableMan.h b/Managers/MovableMan.h index 5118ef9b8..26c080741 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. From 591521c79597ea2bb7a5a6d4ef1e439ace4a10bd Mon Sep 17 00:00:00 2001 From: fourZK Date: Wed, 22 Feb 2023 20:12:29 +0200 Subject: [PATCH 63/76] Improve crab walking slightly by halving the rate at which the paths rotate. ACrabs tend to be slanted more often than AHumans due to PushAsLimb affecting rotation, so don't use a zero limbpath angle for them. Also reducing weird wobble by having FG stand paths not affect rotation. Some wobble is required for orienting on a slanted surface. --- Entities/ACrab.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp index 876ca85df..37fcab8bb 100644 --- a/Entities/ACrab.cpp +++ b/Entities/ACrab.cpp @@ -2373,6 +2373,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; } @@ -2382,14 +2383,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); } @@ -2405,7 +2406,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); } @@ -2413,7 +2414,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. @@ -2449,11 +2450,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); } } From 4acab231808c7bf7cd5158fc4ca20ef945eebef0 Mon Sep 17 00:00:00 2001 From: fourZK Date: Wed, 22 Feb 2023 20:14:23 +0200 Subject: [PATCH 64/76] Use decimals for weights in buy and inventory menus when weight is under 10. Also show `<0.1` when weight is minimal. --- Menus/BuyMenuGUI.cpp | 10 +++++----- Menus/InventoryMenuGUI.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Menus/BuyMenuGUI.cpp b/Menus/BuyMenuGUI.cpp index ba792305b..b15254d3c 100644 --- a/Menus/BuyMenuGUI.cpp +++ b/Menus/BuyMenuGUI.cpp @@ -1320,14 +1320,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 < 9.95F ? 1 : 0) + " 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() < 10.0F ? 1 : 0, 2) + " kg"); int passengerSlotsTaken = itemAsActor->GetPassengerSlots(); if (passengerSlotsTaken > 1) { description += "\nPassenger Slots: " + std::to_string(passengerSlotsTaken); @@ -1348,7 +1348,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 < 10.0F ? 1 : 0, 2) + " kg"); } } } @@ -1488,7 +1488,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() < 10.0F ? 1 : 0, 2) + " kg"); int passengerSlotsTaken = itemAsActor->GetPassengerSlots(); if (passengerSlotsTaken > 1) { @@ -1497,7 +1497,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() < 10.0F ? 1 : 0, 2) + " kg"); } } } diff --git a/Menus/InventoryMenuGUI.cpp b/Menus/InventoryMenuGUI.cpp index 3adc81d76..6f04c1a1c 100644 --- a/Menus/InventoryMenuGUI.cpp +++ b/Menus/InventoryMenuGUI.cpp @@ -1412,7 +1412,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); } From 9459db6f1084ec837b39c2687aec7a4c514fffc3 Mon Sep 17 00:00:00 2001 From: fourZK Date: Thu, 9 Mar 2023 15:34:27 +0200 Subject: [PATCH 65/76] Fix to AHuman throwing animation --- Entities/AHuman.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 284ffec9a..be3cca805 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3364,10 +3364,10 @@ void AHuman::Update() } float throwProgress = GetThrowProgress(); m_ArmsState = THROWING_PREP; - m_pFGArm->AddHandTarget("Start Throw Offset", m_pFGArm->GetJointPos() + thrownDevice->GetStartThrowOffset().GetXFlipped(m_HFlipped).RadRotate(adjustedAimAngle)); + 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->AddHandTarget("End Throw Offset", m_pFGArm->GetJointPos() + thrownDevice->GetEndThrowOffset().GetXFlipped(m_HFlipped).RadRotate(adjustedAimAngle)); + m_pFGArm->SetHandPos(m_pFGArm->GetJointPos() + thrownDevice->GetEndThrowOffset().RadRotate(adjustedAimAngle).GetXFlipped(m_HFlipped)); float maxThrowVel = thrownDevice->GetCalculatedMaxThrowVelIncludingArmThrowStrength(); if (MovableObject *pMO = m_pFGArm->RemoveAttachable(thrownDevice)) { From ca41b5d50314a87285345c1a3d5deb15791949df Mon Sep 17 00:00:00 2001 From: fourZK Date: Wed, 15 Mar 2023 15:24:02 +0200 Subject: [PATCH 66/76] Revert screen shake related changes --- Managers/CameraMan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Managers/CameraMan.cpp b/Managers/CameraMan.cpp index b03b13dad..353e8363e 100644 --- a/Managers/CameraMan.cpp +++ b/Managers/CameraMan.cpp @@ -247,13 +247,13 @@ namespace RTE { screen.ScreenShakeMagnitude = std::min(screen.ScreenShakeMagnitude, m_ScreenShakeDecay * m_MaxScreenShakeTime); // Reduce screen shake over time. - screen.ScreenShakeMagnitude -= m_ScreenShakeDecay * static_cast(screen.ScrollTimer.GetElapsedSimTimeS()); + 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.2F; + const float screenShakeScale = 0.3F; Vector screenShakeOffset(1.0F, 0.0F); screenShakeOffset.RadRotate(RandomNormalNum() * c_PI); From a423804620e723efea011f0cb9f92cb68f11ca9a Mon Sep 17 00:00:00 2001 From: fourZK Date: Wed, 15 Mar 2023 19:18:41 +0200 Subject: [PATCH 67/76] Move `DistanceTraveled` from `MOPixel` to `MovableObject`, remove meter conversion, fix spelling, add Lua getter --- Entities/MOPixel.cpp | 9 ++------- Entities/MOPixel.h | 2 -- Entities/MovableObject.cpp | 3 +++ Entities/MovableObject.h | 7 +++++++ Lua/LuaBindingsEntities.cpp | 1 + 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Entities/MOPixel.cpp b/Entities/MOPixel.cpp index bfc933546..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; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -207,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 b3b31c277..2d9150d2c 100644 --- a/Entities/MOPixel.h +++ b/Entities/MOPixel.h @@ -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/MovableObject.cpp b/Entities/MovableObject.cpp index db997545c..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; @@ -915,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 9b4f0ac31..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 @@ -1860,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; diff --git a/Lua/LuaBindingsEntities.cpp b/Lua/LuaBindingsEntities.cpp index 43421ad8e..f7a61055f 100644 --- a/Lua/LuaBindingsEntities.cpp +++ b/Lua/LuaBindingsEntities.cpp @@ -964,6 +964,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) From a954502420bcf9041a8cea3094c4f4bb126a731e Mon Sep 17 00:00:00 2001 From: fourZK Date: Wed, 15 Mar 2023 19:24:35 +0200 Subject: [PATCH 68/76] Changelog entry for `DistanceTravelled` --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5e7a9e61..d987634d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -551,6 +551,8 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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. +
Changed From bf3456caa488708ce6b7b6c1088f18b74b7e2915 Mon Sep 17 00:00:00 2001 From: fourZK Date: Fri, 17 Mar 2023 01:10:53 +0200 Subject: [PATCH 69/76] Allow Jetpack fuel indicator to persist until full --- Entities/ACrab.cpp | 75 ++++++++++++++++---------------- Entities/AHuman.cpp | 102 ++++++++++++++++++-------------------------- 2 files changed, 80 insertions(+), 97 deletions(-) diff --git a/Entities/ACrab.cpp b/Entities/ACrab.cpp index 37fcab8bb..644a91e06 100644 --- a/Entities/ACrab.cpp +++ b/Entities/ACrab.cpp @@ -2748,42 +2748,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.GetFloorIntX() - 8, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre); - - 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() + 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)) { @@ -2802,15 +2768,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/AHuman.cpp b/Entities/AHuman.cpp index be3cca805..6538a7931 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -4191,53 +4191,8 @@ 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() - 8, drawPos.GetFloorIntY() + m_HUDStack, str, GUIFont::Centre); - - 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() + 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()); @@ -4297,23 +4252,13 @@ void AHuman::DrawHUD(BITMAP *pTargetBitmap, const Vector &targetPos, int whichSc 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)) { - - 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 { @@ -4322,6 +4267,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()); From 9c155a24176f97dc763fc962612241f46f04e6b1 Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Sun, 19 Mar 2023 01:24:43 -0300 Subject: [PATCH 70/76] Various bits of minor cleanup --- Activities/GATutorial.cpp | 6 +++--- CHANGELOG.md | 4 ++-- Entities/ACraft.cpp | 2 +- Entities/AHuman.cpp | 2 -- Entities/Actor.cpp | 2 +- Entities/Attachable.cpp | 7 ++----- Entities/Attachable.h | 4 ++-- Entities/HDFirearm.cpp | 2 +- Entities/HeldDevice.cpp | 4 ++-- Entities/MOSRotating.cpp | 8 -------- Managers/SceneMan.cpp | 3 +-- 11 files changed, 15 insertions(+), 29 deletions(-) diff --git a/Activities/GATutorial.cpp b/Activities/GATutorial.cpp index 9d54afbd6..b414e42ee 100644 --- a/Activities/GATutorial.cpp +++ b/Activities/GATutorial.cpp @@ -1042,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 upper-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)); @@ -1061,11 +1061,11 @@ 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 bottom-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 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 bottom-right again to disband squad", 4000, "Missions.rte/Objects/Tutorial/MenuTeam.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(); diff --git a/CHANGELOG.md b/CHANGELOG.md index e40a5d492..ab4dc123a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -533,7 +533,7 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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` 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. @@ -552,7 +552,7 @@ This can be accessed via the new Lua (R/W) `SettingsMan` property `AIUpdateInter - 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. diff --git a/Entities/ACraft.cpp b/Entities/ACraft.cpp index bf14e76be..e8135edf7 100644 --- a/Entities/ACraft.cpp +++ b/Entities/ACraft.cpp @@ -639,7 +639,7 @@ void ACraft::AddInventoryItem(MovableObject *pItemToAdd) 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()->IsBeingPlayed()) { g_GUISound.FundsChangedSound()->Play(player); } + if (g_ActivityMan.GetActivity()->GetTeamOfPlayer(player) == m_Team) { g_GUISound.FundsChangedSound()->Play(player); } } } } diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index 6538a7931..1c5a88405 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -1922,7 +1922,6 @@ void AHuman::UpdateAI() { // 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; } @@ -3310,7 +3309,6 @@ void AHuman::Update() ThrownDevice *thrownDevice = nullptr; if (HeldDevice *device = GetEquippedItem(); device && m_Status != INACTIVE) { if (!dynamic_cast(device)) { - device->SetSharpAim(m_SharpAimProgress); if (HDFirearm *deviceAsFirearm = dynamic_cast(device)) { diff --git a/Entities/Actor.cpp b/Entities/Actor.cpp index 48eef38b2..c41ee927a 100644 --- a/Entities/Actor.cpp +++ b/Entities/Actor.cpp @@ -721,7 +721,7 @@ void Actor::AddGold(float 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()->IsBeingPlayed()) { g_GUISound.FundsChangedSound()->Play(player); } + if (g_ActivityMan.GetActivity()->GetTeamOfPlayer(player) == m_Team) { g_GUISound.FundsChangedSound()->Play(player); } } } } diff --git a/Entities/Attachable.cpp b/Entities/Attachable.cpp index 775ce974c..30b300d52 100644 --- a/Entities/Attachable.cpp +++ b/Entities/Attachable.cpp @@ -295,11 +295,8 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool Attachable::CollideAtPoint(HitData &hd) { - if (m_IgnoresParticlesWhileAttached && m_Parent && !m_Parent->ToDelete()) { - MOSRotating *hitorAsMOSR = dynamic_cast(hd.Body[HITOR]); - if (!hitorAsMOSR) { - return false; - } + if (m_IgnoresParticlesWhileAttached && m_Parent && !m_Parent->ToDelete() && !dynamic_cast(hd.Body[HITOR])) { + return false; } return MOSRotating::CollideAtPoint(hd); } diff --git a/Entities/Attachable.h b/Entities/Attachable.h index 1b80bafbe..7c8403fbb 100644 --- a/Entities/Attachable.h +++ b/Entities/Attachable.h @@ -145,7 +145,7 @@ namespace RTE { /// Sets whether this Attachable will be deleted when removed from its parent. /// /// Whether this Attachable should be deleted when removed from its parent. - virtual void SetDeleteWhenRemovedFromParent(bool deleteWhenRemovedFromParent) { m_DeleteWhenRemovedFromParent = deleteWhenRemovedFromParent; } + 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. @@ -157,7 +157,7 @@ namespace RTE { /// Sets whether this Attachable will gib when removed from its parent. /// /// Whether this Attachable should gib when removed from its parent. - virtual void SetGibWhenRemovedFromParent(bool gibWhenRemovedFromParent) { m_GibWhenRemovedFromParent = gibWhenRemovedFromParent; } + 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. diff --git a/Entities/HDFirearm.cpp b/Entities/HDFirearm.cpp index 2dc158949..90e3146e3 100644 --- a/Entities/HDFirearm.cpp +++ b/Entities/HDFirearm.cpp @@ -815,7 +815,7 @@ void HDFirearm::Update() 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, (double)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)) { diff --git a/Entities/HeldDevice.cpp b/Entities/HeldDevice.cpp index 51bd9ab32..2707b1900 100644 --- a/Entities/HeldDevice.cpp +++ b/Entities/HeldDevice.cpp @@ -566,8 +566,8 @@ 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; } } - if (!m_SeenByPlayer.at(viewingPlayer)) { - m_SeenByPlayer.at(viewingPlayer) = unheldItemDisplayRange < 0 || (unheldItemDisplayRange > 0 && m_Vel.MagnitudeIsLessThan(2.0F) && 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; } diff --git a/Entities/MOSRotating.cpp b/Entities/MOSRotating.cpp index 014649324..a377dc303 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; diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp index e95c93172..9d6c9d3a8 100644 --- a/Managers/SceneMan.cpp +++ b/Managers/SceneMan.cpp @@ -1005,7 +1005,6 @@ bool SceneMan::TryPenetrate(int posX, ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// MovableObject * SceneMan::DislodgePixel(int posX, int posY) { - unsigned char materialID = _getpixel(m_pCurrentScene->GetTerrain()->GetMaterialBitmap(), posX, posY); if (materialID == g_MaterialAir) { return nullptr; @@ -1018,7 +1017,7 @@ MovableObject * SceneMan::DislodgePixel(int posX, int posY) { } else { spawnColor.SetRGBWithIndex(m_pCurrentScene->GetTerrain()->GetFGColorPixel(posX, posY)); } - // No point generating a key-colored MOPixel + // No point generating a key-colored MOPixel. if (spawnColor.GetIndex() == g_MaskColor) { return nullptr; } From ae9a127f12e4b902aa460c76000e2f2e226d241d Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Thu, 13 Apr 2023 18:25:31 -0300 Subject: [PATCH 71/76] Review changes - renamed m_ActivateBGItem to m_CanActivateBGItem --- Entities/AHuman.cpp | 26 +++++++++++++------------- Entities/AHuman.h | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index d28622a54..fbfac8684 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -74,7 +74,7 @@ void AHuman::Clear() m_JetTimeLeft = 0.0; m_JetReplenishRate = 1.0F; m_JetAngleRange = 0.25F; - m_ActivateBGItem = false; + m_CanActivateBGItem = false; m_TriggerPulled = false; m_WaitingToReloadOffhand = false; m_ThrowTmr.Reset(); @@ -3322,14 +3322,14 @@ void AHuman::Update() if (HDFirearm *deviceAsFirearm = dynamic_cast(device)) { if (m_Controller.IsState(WEAPON_FIRE)) { - if (!m_ActivateBGItem) { + if (!m_CanActivateBGItem) { if (deviceAsFirearm->IsFullAuto()) { deviceAsFirearm->Activate(); - m_ActivateBGItem = (deviceAsFirearm->FiredOnce() || deviceAsFirearm->IsReloading()) && deviceAsFirearm->HalfwayToNextRound(); + m_CanActivateBGItem = (deviceAsFirearm->FiredOnce() || deviceAsFirearm->IsReloading()) && deviceAsFirearm->HalfwayToNextRound(); } else if (!m_TriggerPulled) { deviceAsFirearm->Activate(); if (deviceAsFirearm->FiredOnce()) { - m_ActivateBGItem = true; + m_CanActivateBGItem = true; m_TriggerPulled = true; } } @@ -3339,7 +3339,7 @@ void AHuman::Update() m_TriggerPulled = false; } } else { - m_ActivateBGItem = true; + m_CanActivateBGItem = true; if (m_Controller.IsState(WEAPON_FIRE)) { device->Activate(); if (device->IsEmpty()) { @@ -3355,13 +3355,13 @@ void AHuman::Update() } if (device->IsReloading()) { - m_ActivateBGItem = true; + m_CanActivateBGItem = true; m_SharpAimTimer.Reset(); m_SharpAimProgress = 0; device->SetSharpAim(m_SharpAimProgress); } } else { - m_ActivateBGItem = true; + m_CanActivateBGItem = true; if (thrownDevice = dynamic_cast(device)) { thrownDevice->SetSharpAim(isSharpAiming ? 1.0F : 0); if (m_Controller.IsState(WEAPON_FIRE)) { @@ -3410,16 +3410,16 @@ void AHuman::Update() } } } else { - m_ActivateBGItem = true; + 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_ActivateBGItem && (!m_TriggerPulled || (deviceAsFirearm->IsFullAuto() && deviceAsFirearm->HalfwayToNextRound()))) { + if (m_CanActivateBGItem && (!m_TriggerPulled || (deviceAsFirearm->IsFullAuto() && deviceAsFirearm->HalfwayToNextRound()))) { deviceAsFirearm->Activate(); if (deviceAsFirearm->FiredOnce()) { - m_ActivateBGItem = false; + m_CanActivateBGItem = false; m_TriggerPulled = true; } } @@ -3428,7 +3428,7 @@ void AHuman::Update() m_TriggerPulled = false; } } else { - m_ActivateBGItem = false; + m_CanActivateBGItem = false; if (m_Controller.IsState(WEAPON_FIRE)) { device->Activate(); } else { @@ -3442,13 +3442,13 @@ void AHuman::Update() device->SetSharpAim(m_SharpAimProgress); if (device->IsReloading()) { - m_ActivateBGItem = false; + m_CanActivateBGItem = false; m_SharpAimTimer.Reset(); m_SharpAimProgress = 0; device->SetSharpAim(m_SharpAimProgress); } } else { - m_ActivateBGItem = false; + m_CanActivateBGItem = false; } if (m_ArmsState == THROWING_PREP && !thrownDevice) { diff --git a/Entities/AHuman.h b/Entities/AHuman.h index c9dbd96d4..7ced26754 100644 --- a/Entities/AHuman.h +++ b/Entities/AHuman.h @@ -1010,7 +1010,7 @@ 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_ActivateBGItem; //!< 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_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. // Blink timer From 106e97355a271c9e6e3a224ba4d230e7a07fef9a Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Thu, 13 Apr 2023 18:58:43 -0300 Subject: [PATCH 72/76] Fix a logic flaw that didn't actually matter --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index fbfac8684..c24f5622c 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -3325,7 +3325,7 @@ void AHuman::Update() if (!m_CanActivateBGItem) { if (deviceAsFirearm->IsFullAuto()) { deviceAsFirearm->Activate(); - m_CanActivateBGItem = (deviceAsFirearm->FiredOnce() || deviceAsFirearm->IsReloading()) && deviceAsFirearm->HalfwayToNextRound(); + m_CanActivateBGItem = deviceAsFirearm->FiredOnce() && deviceAsFirearm->HalfwayToNextRound(); } else if (!m_TriggerPulled) { deviceAsFirearm->Activate(); if (deviceAsFirearm->FiredOnce()) { From fb31dc8c709ebdc3430a114dfaf347f0ce545d37 Mon Sep 17 00:00:00 2001 From: MaximDude Date: Fri, 28 Apr 2023 14:21:51 +0300 Subject: [PATCH 73/76] Prevent SceneMan::DislodgePixel from imploding if passing in out-of-bounds position Const ptr -> ptr to const Misc cleanup --- Managers/SceneMan.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp index 91969364a..e18d55708 100644 --- a/Managers/SceneMan.cpp +++ b/Managers/SceneMan.cpp @@ -1005,12 +1005,13 @@ bool SceneMan::TryPenetrate(int posX, ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// MovableObject * SceneMan::DislodgePixel(int posX, int posY) { - unsigned char materialID = _getpixel(m_pCurrentScene->GetTerrain()->GetMaterialBitmap(), posX, posY); - if (materialID == g_MaterialAir) { + int materialID = getpixel(m_pCurrentScene->GetTerrain()->GetMaterialBitmap(), posX, posY); + if (materialID <= MaterialColorKeys::g_MaterialAir) { return nullptr; } - Material const * sceneMat = GetMaterialFromID(materialID); - Material const * spawnMat = sceneMat->GetSpawnMaterial() ? GetMaterialFromID(sceneMat->GetSpawnMaterial()) : sceneMat; + const Material *sceneMat = GetMaterialFromID(static_cast(materialID)); + const Material *spawnMat = sceneMat->GetSpawnMaterial() ? GetMaterialFromID(sceneMat->GetSpawnMaterial()) : sceneMat; + Color spawnColor; if (spawnMat->UsesOwnColor()) { spawnColor = spawnMat->GetColor(); @@ -1018,16 +1019,17 @@ MovableObject * SceneMan::DislodgePixel(int posX, int posY) { spawnColor.SetRGBWithIndex(m_pCurrentScene->GetTerrain()->GetFGColorPixel(posX, posY)); } // No point generating a key-colored MOPixel. - if (spawnColor.GetIndex() == g_MaskColor) { + if (spawnColor.GetIndex() == ColorKeys::g_MaskColor) { return nullptr; } - MOPixel *pixelMO = new MOPixel(spawnColor, spawnMat->GetPixelDensity(), Vector(posX, posY), Vector(), new Atom(Vector(), spawnMat->GetIndex(), 0, spawnColor, 2), 0); + 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, g_MaskColor); - RegisterTerrainChange(posX, posY, 1, 1, g_MaskColor, false); - m_pCurrentScene->GetTerrain()->SetMaterialPixel(posX, posY, g_MaterialAir); + 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; } From ce17eff0c84eaf157955501aa818a5b694f52aaf Mon Sep 17 00:00:00 2001 From: MaximDude Date: Fri, 28 Apr 2023 14:22:09 +0300 Subject: [PATCH 74/76] Use FlipFactor --- Entities/AEmitter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AEmitter.cpp b/Entities/AEmitter.cpp index aab69e23d..44815c71b 100644 --- a/Entities/AEmitter.cpp +++ b/Entities/AEmitter.cpp @@ -420,7 +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); - m_pFlash->SetRotAngle(m_Rotation.GetRadAngle() + (m_HFlipped ? -m_EmitAngle.GetRadAngle() : m_EmitAngle.GetRadAngle())); + m_pFlash->SetRotAngle(m_Rotation.GetRadAngle() + (m_EmitAngle.GetRadAngle() * GetFlipFactor())); m_pFlash->SetScale(m_FlashScale); m_pFlash->SetNextFrame(); } From 60f446eef06bfe6075561f1a7a3d18e54abd6c3e Mon Sep 17 00:00:00 2001 From: MaximDude Date: Fri, 28 Apr 2023 14:36:26 +0300 Subject: [PATCH 75/76] Move ScrapCompactingHeight from SettingsMan to SceneMan --- CHANGELOG.md | 2 +- Lua/LuaBindingsManagers.cpp | 2 +- Managers/SceneMan.cpp | 25 ++++++++++++++----------- Managers/SceneMan.h | 14 ++++++++++++++ Managers/SettingsMan.cpp | 5 ++--- Managers/SettingsMan.h | 13 ------------- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29728685d..45e040d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,7 +101,7 @@ 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 `SettingsMan` 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 `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). diff --git a/Lua/LuaBindingsManagers.cpp b/Lua/LuaBindingsManagers.cpp index 60996e343..060ebc005 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) @@ -357,7 +358,6 @@ namespace RTE { .property("RecommendedMOIDCount", &SettingsMan::RecommendedMOIDCount) .property("AIUpdateInterval", &SettingsMan::GetAIUpdateInterval, &SettingsMan::SetAIUpdateInterval) .property("ShowEnemyHUD", &SettingsMan::ShowEnemyHUD) - .property("ScrapCompactingHeight", &SettingsMan::GetScrapCompactingHeight, &SettingsMan::SetScrapCompactingHeight) .property("AutomaticGoldDeposit", &SettingsMan::GetAutomaticGoldDeposit); } diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp index e18d55708..bf2420019 100644 --- a/Managers/SceneMan.cpp +++ b/Managers/SceneMan.cpp @@ -44,6 +44,8 @@ namespace RTE const std::string SceneMan::c_ClassName = "SceneMan"; std::vector> SceneMan::m_IntermediateSettlingBitmaps; +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void SceneMan::Clear() { m_DefaultSceneName = "Tutorial Bunker"; @@ -75,6 +77,8 @@ void SceneMan::Clear() if (m_pOrphanSearchBitmap) destroy_bitmap(m_pOrphanSearchBitmap); m_pOrphanSearchBitmap = create_bitmap_ex(8, MAXORPHANRADIUS , MAXORPHANRADIUS); + + m_ScrapCompactingHeight = 25; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -578,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 @@ -936,10 +940,9 @@ bool SceneMan::TryPenetrate(int posX, // Save the impulse force effects of the penetrating particle. // retardation = -sceneMat.density; retardation = -(sceneMat->GetIntegrity() / std::sqrt(sqrImpMag)); - int compactingHeight = g_SettingsMan.GetScrapCompactingHeight(); // 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 (compactingHeight > 0 && (sceneMat->IsScrap() || _getpixel(m_pCurrentScene->GetTerrain()->GetBGColorBitmap(), posX, posY) == g_MaskColor)) { + 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(); @@ -951,7 +954,7 @@ bool SceneMan::TryPenetrate(int posX, float sprayMag = std::sqrt(velocity.GetMagnitude() * sprayScale); Vector sprayVel; - for (int testY = posY - 1; testY > posY - compactingHeight && testY >= 0; --testY) { + for (int testY = posY - 1; testY > posY - m_ScrapCompactingHeight && testY >= 0; --testY) { if ((testMaterialID = _getpixel(pMaterial, posX, testY)) != g_MaterialAir) { sceneMat = GetMaterialFromID(testMaterialID); @@ -965,7 +968,7 @@ bool SceneMan::TryPenetrate(int posX, } 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(compactingHeight)))); + sprayVel.SetXY(0, -sprayMag * (1.0F - (static_cast(posY - testY) / static_cast(m_ScrapCompactingHeight)))); sprayVel.RadRotate(RandomNum(-c_HalfPI, c_HalfPI)); pixelMO = new MOPixel(spawnColor, spawnMat->GetPixelDensity(), Vector(posX, testY), sprayVel, new Atom(Vector(), spawnMat->GetIndex(), 0, spawnColor, 2), 0); @@ -2715,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); @@ -2723,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); } @@ -2797,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); } } @@ -2806,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 fa084a54a..0b86c33b1 100644 --- a/Managers/SceneMan.h +++ b/Managers/SceneMan.h @@ -1407,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 @@ -1471,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 237ee90a2..20d4aeb34 100644 --- a/Managers/SettingsMan.cpp +++ b/Managers/SettingsMan.cpp @@ -31,7 +31,6 @@ namespace RTE { m_CrabBombThreshold = 42; m_ShowEnemyHUD = true; m_EnableSmartBuyMenuNavigation = true; - m_ScrapCompactingHeight = 25; m_AutomaticGoldDeposit = true; m_NetworkServerAddress = "127.0.0.1:8000"; @@ -163,7 +162,7 @@ namespace RTE { } else if (propName == "SmartBuyMenuNavigation") { reader >> m_EnableSmartBuyMenuNavigation; } else if (propName == "ScrapCompactingHeight") { - reader >> m_ScrapCompactingHeight; + reader >> g_SceneMan.m_ScrapCompactingHeight; } else if (propName == "AutomaticGoldDeposit") { reader >> m_AutomaticGoldDeposit; } else if (propName == "ScreenShakeStrength") { @@ -357,7 +356,7 @@ namespace RTE { writer.NewPropertyWithValue("CrabBombThreshold", m_CrabBombThreshold); writer.NewPropertyWithValue("ShowEnemyHUD", m_ShowEnemyHUD); writer.NewPropertyWithValue("SmartBuyMenuNavigation", m_EnableSmartBuyMenuNavigation); - writer.NewPropertyWithValue("ScrapCompactingHeight", m_ScrapCompactingHeight); + writer.NewPropertyWithValue("ScrapCompactingHeight", g_SceneMan.m_ScrapCompactingHeight); writer.NewPropertyWithValue("AutomaticGoldDeposit", m_AutomaticGoldDeposit); writer.NewLine(false, 2); diff --git a/Managers/SettingsMan.h b/Managers/SettingsMan.h index c36cca1cb..f97b8ce94 100644 --- a/Managers/SettingsMan.h +++ b/Managers/SettingsMan.h @@ -245,18 +245,6 @@ namespace RTE { /// Whether to enable smart BuyMenu navigation or not. void SetSmartBuyMenuNavigation(bool enable) { m_EnableSmartBuyMenuNavigation = enable; } - /// - /// 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; } - /// /// Gets whether gold gathered by Actors is automatically added into team funds. /// @@ -517,7 +505,6 @@ 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. - int m_ScrapCompactingHeight; //!< The maximum height of a column of scrap terrain to collapse, when the bottom pixel is knocked loose. 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. From bfd204d9a459b50ce6070ae77d03f44f69603070 Mon Sep 17 00:00:00 2001 From: Gareth YR Date: Sat, 29 Apr 2023 19:01:30 -0300 Subject: [PATCH 76/76] Added new rounding mode to round things to nearest 0.5 (accounting for precision) Made buy menu descriptions use new rounding mode and round to 1 sigfig if mass is < 50. This is a bit arbitrary, but it makes it so you generally get no decimal places on bodies but do get em on guns --- CHANGELOG.md | 2 +- Menus/BuyMenuGUI.cpp | 10 +++++----- System/RTETools.cpp | 6 ++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45e040d37..f85a5371f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -981,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/Menus/BuyMenuGUI.cpp b/Menus/BuyMenuGUI.cpp index 3f6c1e92c..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, craftMaxMass < 9.95F ? 1 : 0) + " 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: " + (itemAsActor->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsActor->GetMass(), itemAsActor->GetMass() < 10.0F ? 1 : 0, 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: " + (itemAsMO->GetMass() + extraMass < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsMO->GetMass() + extraMass, itemAsMO->GetMass() + extraMass < 10.0F ? 1 : 0, 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: " + (itemAsActor->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsActor->GetMass(), itemAsActor->GetMass() < 10.0F ? 1 : 0, 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: " + (itemAsMO->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsMO->GetMass(), itemAsMO->GetMass() < 10.0F ? 1 : 0, 2) + " kg"); + description += "\nMass: " + (itemAsMO->GetMass() < 0.1F ? "<0.1 kg" : RoundFloatToPrecision(itemAsMO->GetMass(), itemAsMO->GetMass() < 50.0F ? 1 : 0, 3) + " kg"); } } } 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;