diff --git a/rts/Lua/LuaSyncedCtrl.cpp b/rts/Lua/LuaSyncedCtrl.cpp index 36567b7cda..138164f857 100644 --- a/rts/Lua/LuaSyncedCtrl.cpp +++ b/rts/Lua/LuaSyncedCtrl.cpp @@ -28,6 +28,7 @@ #include "Rendering/Env/GrassDrawer.h" #include "Rendering/Env/IGroundDecalDrawer.h" #include "Rendering/Models/IModelParser.h" +#include "Rendering/Units/UnitDrawer.h" #include "Sim/Features/Feature.h" #include "Sim/Features/FeatureDef.h" #include "Sim/Features/FeatureDefHandler.h" @@ -181,6 +182,7 @@ bool LuaSyncedCtrl::PushEntries(lua_State* L) REGISTER_LUA_CFUNC(SetUnitStealth); REGISTER_LUA_CFUNC(SetUnitSonarStealth); REGISTER_LUA_CFUNC(SetUnitSeismicSignature); + REGISTER_LUA_CFUNC(SetUnitLeavesGhost); REGISTER_LUA_CFUNC(SetUnitAlwaysVisible); REGISTER_LUA_CFUNC(SetUnitUseAirLos); REGISTER_LUA_CFUNC(SetUnitMetalExtraction); @@ -2684,6 +2686,35 @@ int LuaSyncedCtrl::SetUnitSeismicSignature(lua_State* L) return 0; } +/*** + * @function Spring.SetUnitLeavesGhost + * + * Change the unit leavesGhost attribute. + * + * Controls unit having static radar ghosts. + * + * @number unitID + * @bool leavesGhost + * @bool[opt] leaveDeadGhost leave a dead ghost behind if disabling and the unit had a live static ghost. + * @treturn nil + */ +int LuaSyncedCtrl::SetUnitLeavesGhost(lua_State* L) +{ + if (!gameSetup->ghostedBuildings) + return 0; + + CUnit* unit = ParseUnit(L, __func__, 1); + + if (unit == nullptr) + return 0; + + bool prevValue = unit->leavesGhost; + unit->SetLeavesGhost(luaL_checkboolean(L, 2)); + if (prevValue != unit->leavesGhost) + unitDrawer->UnitLeavesGhostChanged(unit, luaL_optboolean(L, 3, false)); + return 0; +} + /*** * @function Spring.SetUnitAlwaysVisible * @number unitID diff --git a/rts/Lua/LuaSyncedCtrl.h b/rts/Lua/LuaSyncedCtrl.h index 09b64d0bb1..0d51634905 100644 --- a/rts/Lua/LuaSyncedCtrl.h +++ b/rts/Lua/LuaSyncedCtrl.h @@ -90,6 +90,7 @@ class LuaSyncedCtrl static int SetUnitStealth(lua_State* L); static int SetUnitSonarStealth(lua_State* L); static int SetUnitSeismicSignature(lua_State* L); + static int SetUnitLeavesGhost(lua_State* L); static int SetUnitAlwaysVisible(lua_State* L); static int SetUnitUseAirLos(lua_State* L); static int SetUnitResourcing(lua_State* L); diff --git a/rts/Lua/LuaSyncedRead.cpp b/rts/Lua/LuaSyncedRead.cpp index bd6ea4548c..bb035357b5 100644 --- a/rts/Lua/LuaSyncedRead.cpp +++ b/rts/Lua/LuaSyncedRead.cpp @@ -215,6 +215,7 @@ bool LuaSyncedRead::PushEntries(lua_State* L) REGISTER_LUA_CFUNC(GetUnitArmored); REGISTER_LUA_CFUNC(GetUnitIsActive); REGISTER_LUA_CFUNC(GetUnitIsCloaked); + REGISTER_LUA_CFUNC(GetUnitLeavesGhost); REGISTER_LUA_CFUNC(GetUnitSelfDTime); REGISTER_LUA_CFUNC(GetUnitStockpile); REGISTER_LUA_CFUNC(GetUnitSensorRadius); @@ -3850,6 +3851,22 @@ int LuaSyncedRead::GetUnitSeismicSignature(lua_State* L) return 1; } +/*** + * + * @function Spring.GetUnitLeavesGhost + * @number unitID + * @treturn nil|number + */ +int LuaSyncedRead::GetUnitLeavesGhost(lua_State* L) +{ + const CUnit* const unit = ParseAllyUnit(L, __func__, 1); + if (unit == nullptr) + return 0; + + lua_pushboolean(L, unit->leavesGhost); + return 1; +} + /*** * * @function Spring.GetUnitSelfDTime diff --git a/rts/Lua/LuaSyncedRead.h b/rts/Lua/LuaSyncedRead.h index 9b7e27c140..c6ab7fc426 100644 --- a/rts/Lua/LuaSyncedRead.h +++ b/rts/Lua/LuaSyncedRead.h @@ -120,6 +120,7 @@ class LuaSyncedRead { static int GetUnitIsActive(lua_State* L); static int GetUnitIsCloaked(lua_State* L); static int GetUnitSeismicSignature(lua_State* L); + static int GetUnitLeavesGhost(lua_State* L); static int GetUnitSelfDTime(lua_State* L); static int GetUnitStockpile(lua_State* L); static int GetUnitSensorRadius(lua_State* L); diff --git a/rts/Lua/LuaUnitDefs.cpp b/rts/Lua/LuaUnitDefs.cpp index 7fc37b2e85..a873f68f7f 100644 --- a/rts/Lua/LuaUnitDefs.cpp +++ b/rts/Lua/LuaUnitDefs.cpp @@ -727,6 +727,7 @@ ADD_BOOL("canAttackWater", canAttackWater); // CUSTOM ADD_FLOAT("seismicSignature", ud.seismicSignature); ADD_BOOL("stealth", ud.stealth); ADD_BOOL("sonarStealth", ud.sonarStealth); + ADD_BOOL("leavesGhost", ud.leavesGhost); ADD_FLOAT("mass", ud.mass); diff --git a/rts/Rendering/Units/UnitDrawer.h b/rts/Rendering/Units/UnitDrawer.h index 10f9c452f1..6e080c8f5c 100644 --- a/rts/Rendering/Units/UnitDrawer.h +++ b/rts/Rendering/Units/UnitDrawer.h @@ -54,6 +54,7 @@ class CUnitDrawer : public CModelDrawerBase static const std::vector& GetUnsortedUnits() { return modelDrawerData->GetUnsortedObjects(); } static void ClearPreviousDrawFlags() { modelDrawerData->ClearPreviousDrawFlags(); } + static void UnitLeavesGhostChanged(const CUnit* unit, const bool leaveDeadGhost) { modelDrawerData->UnitLeavesGhostChanged(unit, leaveDeadGhost); } public: // DrawUnit* virtual void DrawUnitNoTrans(const CUnit* unit, uint32_t preList, uint32_t postList, bool lodCall, bool noLuaCall) const = 0; diff --git a/rts/Rendering/Units/UnitDrawerData.cpp b/rts/Rendering/Units/UnitDrawerData.cpp index 8c56776cba..bba8622a4d 100644 --- a/rts/Rendering/Units/UnitDrawerData.cpp +++ b/rts/Rendering/Units/UnitDrawerData.cpp @@ -255,7 +255,7 @@ const icon::CIconData* CUnitDrawerData::GetUnitIcon(const CUnit* unit) // use the unit's custom icon if we can currently see it, // or have seen it before and did not lose contact since bool unitVisible = ((losStatus & (LOS_INLOS | LOS_INRADAR)) && ((losStatus & prevMask) == prevMask)); - unitVisible |= gameSetup->ghostedBuildings && unit->unitDef->IsBuildingUnit() && (losStatus & LOS_PREVLOS); + unitVisible |= unit->leavesGhost && (losStatus & LOS_PREVLOS); const bool customIcon = (unitVisible || gu->spectatingFullView); if (customIcon) @@ -577,16 +577,17 @@ void CUnitDrawerData::RenderUnitCreated(const CUnit* unit, int cloaked) UpdateUnitIcon(unit, false, false); } -void CUnitDrawerData::RenderUnitDestroyed(const CUnit* unit) +bool CUnitDrawerData::UpdateUnitGhosts(const CUnit* unit, const bool addNewGhost) { - RECOIL_DETAILED_TRACY_ZONE; + if (!gameSetup->ghostedBuildings) + return false; + + bool addedOwnAllyTeam = false; CUnit* u = const_cast(unit); const UnitDef* unitDef = unit->unitDef; const UnitDef* decoyDef = unitDef->decoyDef; - const bool addNewGhost = unitDef->IsBuildingUnit() && gameSetup->ghostedBuildings; - // TODO - make ghosted buildings per allyTeam - so they are correctly dealt with // when spectating GhostSolidObject* gso = nullptr; @@ -615,10 +616,23 @@ void CUnitDrawerData::RenderUnitDestroyed(const CUnit* unit) // (the ref-counter saves us come deletion time) savedData.deadGhostBuildings[allyTeam][gsoModel->type].push_back(gso); gso->IncRef(); + u->losStatus[allyTeam] &= ~LOS_PREVLOS; + if (allyTeam == gu->myAllyTeam) + addedOwnAllyTeam = true; + } spring::VectorErase(savedData.liveGhostBuildings[allyTeam][MDL_TYPE(u)], u); } + return addedOwnAllyTeam; +} + +void CUnitDrawerData::RenderUnitDestroyed(const CUnit* unit) +{ + RECOIL_DETAILED_TRACY_ZONE; + CUnit* u = const_cast(unit); + + UpdateUnitGhosts(unit, unit->leavesGhost); DelObject(unit, true); UpdateUnitIcon(unit, false, true); @@ -640,7 +654,7 @@ void CUnitDrawerData::UnitEnteredLos(const CUnit* unit, int allyTeam) RECOIL_DETAILED_TRACY_ZONE; CUnit* u = const_cast(unit); //cleanup - if (gameSetup->ghostedBuildings && unit->unitDef->IsBuildingUnit()) + if (unit->leavesGhost) spring::VectorErase(savedData.liveGhostBuildings[allyTeam][MDL_TYPE(unit)], u); if (allyTeam != gu->myAllyTeam) @@ -654,7 +668,7 @@ void CUnitDrawerData::UnitLeftLos(const CUnit* unit, int allyTeam) RECOIL_DETAILED_TRACY_ZONE; CUnit* u = const_cast(unit); //cleanup - if (gameSetup->ghostedBuildings && unit->unitDef->IsBuildingUnit()) + if (unit->leavesGhost) spring::VectorInsertUnique(savedData.liveGhostBuildings[allyTeam][MDL_TYPE(unit)], u, true); if (allyTeam != gu->myAllyTeam) @@ -663,6 +677,15 @@ void CUnitDrawerData::UnitLeftLos(const CUnit* unit, int allyTeam) UpdateUnitIcon(unit, false, false); } +void CUnitDrawerData::UnitLeavesGhostChanged(const CUnit* unit, const bool leaveDeadGhost) +{ + if (unit->leavesGhost) + return; + + if (UpdateUnitGhosts(unit, leaveDeadGhost)) + UpdateUnitIcon(unit, false, true); +} + void CUnitDrawerData::PlayerChanged(int playerNum) { RECOIL_DETAILED_TRACY_ZONE; @@ -677,4 +700,4 @@ void CUnitDrawerData::PlayerChanged(int playerNum) // force an erase (no-op) followed by an insert UpdateUnitIcon(unit, true, false); } -} \ No newline at end of file +} diff --git a/rts/Rendering/Units/UnitDrawerData.h b/rts/Rendering/Units/UnitDrawerData.h index acd9ea43a5..62aa5c84db 100644 --- a/rts/Rendering/Units/UnitDrawerData.h +++ b/rts/Rendering/Units/UnitDrawerData.h @@ -60,6 +60,8 @@ class CUnitDrawerData : public CUnitDrawerDataBase { void UnitLeftLos(const CUnit* unit, int allyTeam) override; void PlayerChanged(int playerNum) override; + bool UpdateUnitGhosts(const CUnit* unit, const bool addNewGhost); + void UnitLeavesGhostChanged(const CUnit* unit, const bool leaveDeadGhost); public: class TempDrawUnit { CR_DECLARE_STRUCT(TempDrawUnit) diff --git a/rts/Sim/Units/Unit.cpp b/rts/Sim/Units/Unit.cpp index 9f1ec62fbd..92f50d12db 100644 --- a/rts/Sim/Units/Unit.cpp +++ b/rts/Sim/Units/Unit.cpp @@ -295,6 +295,8 @@ void CUnit::PreInit(const UnitLoadParams& params) wantCloak |= unitDef->startCloaked; decloakDistance = unitDef->decloakDistance; + leavesGhost = gameSetup->ghostedBuildings && unitDef->leavesGhost; + flankingBonusMode = unitDef->flankingBonusMode; flankingBonusDir = unitDef->flankingBonusDir; flankingBonusMobility = unitDef->flankingBonusMobilityAdd * 1000; @@ -543,6 +545,11 @@ void CUnit::ForcedMove(const float3& newPos) } +void CUnit::SetLeavesGhost(bool newLeavesGhost) +{ + leavesGhost = newLeavesGhost; +} + float3 CUnit::GetErrorVector(int argAllyTeam) const { @@ -555,7 +562,7 @@ float3 CUnit::GetErrorVector(int argAllyTeam) const const int atSightMask = losStatus[argAllyTeam]; const int isVisible = 2 * ((atSightMask & LOS_INLOS ) != 0 || teamHandler.Ally(argAllyTeam, allyteam)); // in LOS or allied, no error - const int seenGhost = 4 * ((atSightMask & LOS_PREVLOS) != 0 && gameSetup->ghostedBuildings && unitDef->IsBuildingUnit()); // seen ghosted building, no error + const int seenGhost = 4 * ((atSightMask & LOS_PREVLOS) != 0 && leavesGhost); // seen ghosted immobiles, no error const int isOnRadar = 8 * ((atSightMask & LOS_INRADAR) != 0 ); // current radar contact float errorMult = 0.0f; @@ -3013,6 +3020,8 @@ CR_REG_METADATA(CUnit, ( CR_MEMBER(isCloaked), CR_MEMBER(decloakDistance), + CR_MEMBER(leavesGhost), + CR_MEMBER(lastTerrainType), CR_MEMBER(curTerrainType), diff --git a/rts/Sim/Units/Unit.h b/rts/Sim/Units/Unit.h index fe4af49685..19ba427727 100644 --- a/rts/Sim/Units/Unit.h +++ b/rts/Sim/Units/Unit.h @@ -190,6 +190,8 @@ class CUnit : public CSolidObject unsigned short CalcLosStatus(int allyTeam); void UpdateLosStatus(int allyTeam); + void SetLeavesGhost(bool newLeavesGhost); + void UpdateWeapons(); void UpdateWeaponVectors(); @@ -548,7 +550,8 @@ class CUnit : public CSolidObject bool isCloaked = false; // true if the unit currently wants to be cloaked bool wantCloak = false; - + // true if the unit leaves static ghosts + bool leavesGhost = false; // unsynced vars bool noMinimap = false; diff --git a/rts/Sim/Units/UnitDef.cpp b/rts/Sim/Units/UnitDef.cpp index 82d530ae19..cbca6bc33d 100644 --- a/rts/Sim/Units/UnitDef.cpp +++ b/rts/Sim/Units/UnitDef.cpp @@ -106,6 +106,7 @@ UnitDef::UnitDef() , seismicSignature(0.0f) , stealth(false) , sonarStealth(false) + , leavesGhost(false) , buildRange3D(false) , buildDistance(16.0f) // 16.0f is the minimum distance between two 1x1 units , buildSpeed(0.0f) @@ -658,6 +659,8 @@ UnitDef::UnitDef(const LuaTable& udTable, const std::string& unitName, int id) if (IsImmobileUnit()) CreateYardMap(udTable.GetString("yardMap", "")); + leavesGhost = udTable.GetBool("leavesGhost", IsBuildingUnit()); + decalDef.Parse(udTable); canDropFlare = udTable.GetBool("canDropFlare", false); diff --git a/rts/Sim/Units/UnitDef.h b/rts/Sim/Units/UnitDef.h index 28d11a001d..b400c779cf 100644 --- a/rts/Sim/Units/UnitDef.h +++ b/rts/Sim/Units/UnitDef.h @@ -168,6 +168,7 @@ struct UnitDef: public SolidObjectDef float seismicSignature; bool stealth; bool sonarStealth; + bool leavesGhost; bool buildRange3D; float buildDistance;