ConstructionBase.c
class ConstructionBaseclass StaticConstructionMethodsclass CollisionCheckDataclass ConstructionBoxTriggerenum ConstructionMaterialTypeenum EConstructionDirCheckTypeenum EConstructionDebugModes
//! used to guide action sound selection, mostly. Could be also used for particles and stuff, if need be
enum ConstructionMaterialType
{
MATERIAL_NONE = 0,
MATERIAL_LOG = 1,
MATERIAL_WOOD = 2,
MATERIAL_STAIRS = 3,
MATERIAL_METAL = 4,
MATERIAL_WIRE = 5,
MATERIAL_BRICK = 6,
MATERIAL_RUBBLE = 7
}
enum EConstructionDirCheckType
{
INDOORS,
REFERENCE_DIRECTION,
MISC
}
#ifdef DIAG_DEVELOPER
enum EConstructionDebugModes
{
DISABLED,
FAST,
INSTANT
}
#endif
class ConstructionBase : ConstructionBasic
{
protected static const int MAX_MATERIAL_PILES = 64; //safety cap for material pile spawning
static const float REPAIR_MATERIAL_PERCENTAGE = 0.15;
static const float DECONSTURCT_MATERIAL_LOSS = 0.2;
static const float DEFAULT_DECAY_RATE = -1.0; //! HP/s change
static const float DEFAULT_DECAY_THRESHOLD = 0.3; //! Percentage of max health where the decay stops
static const float DEFAULT_DECAY_RATE_COEF = 1.0; //! Default decay speed coef
static const float SANDSTORM_DECAY_RATE_COEF = 1.5; //! Sandstorm decay speed coef
protected ConstructionDataTypeHolder m_TypeLevelHolder;
protected ref map<string, ref ConstructionPart> m_ConstructionParts //! INSTANCED construction part data;
protected ref array<ConstructionPart> m_ConstructionPartArray; //ordered array for easier access
protected ref array<ConstructionPart> m_BuiltConstructionPartArray; //built parts array
protected EntityAI m_EntityParent;
protected bool m_AnyPartBuildableByHands;
#ifdef DIAG_DEVELOPER
//Debug
protected Shape m_CollisionBox;
protected Shape m_PlayerInsideTestCylinder;
protected ref array<Shape> m_InsideTestBoxes;
#endif
//============================================
// Construction
//============================================
void ConstructionBase(EntityAI parent)
{
m_AnyPartBuildableByHands = false;
m_ConstructionParts = new map<string, ref ConstructionPart>();
m_ConstructionPartArray = new array<ConstructionPart>();
m_BuiltConstructionPartArray = new array<ConstructionPart>();
#ifdef DIAG_DEVELOPER
m_InsideTestBoxes = new array<Shape>();
#endif
//set parent object
SetParent(parent);
EntityAIType entityType;
if (!Class.CastTo(entityType, m_EntityParent.GetEntityType()) || !Class.CastTo(m_TypeLevelHolder, entityType.GetConstructionDataHolderBasic()))
{
ErrorEx("invalid EntityType or ConstructionDataTypeHolder for entity instance: " + parent);
}
}
void ~ConstructionBase()
{
ConstructionManagerStatic.RemoveFromProcessing(this);
Cleanup();
}
override void Init()
{
UpdateConstructionParts();
//InitBaseState(); //called from EEInit instead (physics updates require physics to be initialized)
}
override void EEInitConstruction()
{
if (m_EntityParent.IsPendingDeletion())
return;
foreach (ConstructionPart part : m_ConstructionPartArray)
{
part.PartInstanceDeferredInit(m_EntityParent);
}
#ifdef DIAG_DEVELOPER
if ((g_Game.IsServer() || !g_Game.IsMultiplayer()) && IsCLIParam("RBLDRebuildEverythingWithLocks"))
{
RebuildingSpawner.RebuildAllWoodenPartsFiltered(this, m_EntityParent);
}
#endif
}
protected void Cleanup()
{
m_ConstructionParts = null;
m_ConstructionPartArray = null;
m_BuiltConstructionPartArray = null;
#ifdef DIAG_DEVELOPER
m_InsideTestBoxes = null;
#endif
}
protected void SetParent(EntityAI parent)
{
m_EntityParent = parent;
}
//============================================
// Syncing
//============================================
protected void HandleSyncDataBuild(ConstructionPart part);
protected void HandleSyncDataDismantle(ConstructionPart part);
protected void HandleSyncDataDestroy(ConstructionPart part);
//! Returns 'true' if part state was locally changed
protected bool UpdatePartSyncStateLocal(ConstructionPart part)
{
return false;
}
//============================================
// Construction process
//============================================
//constructed parts
void AddToConstructedParts(string part_name)
{
ConstructionPart part = GetConstructionPart(part_name);
if (part)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction " + Object.GetDebugName(m_EntityParent) + " AddToConstructedParts part=" + part.GetPartName());
part.SetBuiltState(true);
part.TryHandleUtilityParts(m_EntityParent);
if (m_BuiltConstructionPartArray.Find(part) == -1)
m_BuiltConstructionPartArray.Insert(part);
#ifndef SERVER
if (m_EntityParent.IsInitialized())
part.PlayConstructionEffects(m_EntityParent);
#endif
}
}
void RemoveFromConstructedParts(string part_name)
{
ConstructionPart part = GetConstructionPart(part_name);
if (part)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction " + Object.GetDebugName(m_EntityParent) + " RemoveFromConstructedParts part=" + part.GetPartName());
part.SetBuiltState(false);
part.TryHandleUtilityParts(m_EntityParent);
m_BuiltConstructionPartArray.RemoveItem(part);
#ifndef SERVER
if (m_EntityParent.IsInitialized())
part.PlayConstructionEffects(m_EntityParent);
#endif
}
}
/**
\brief new build part method
\note: 'player' CAN be null
*/
void BuildPartServerEx(Man player, string part_name, int action_id)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction BuildPartServerEx | " + part_name);
//on action
TakeMaterialsServer(part_name);
}
/**
\brief new dismantle part method
\note: 'player' CAN be null
*/
void DismantlePartServerEx(Man player, string part_name, int action_id)
{
string damageZone;
DamageSystem.GetDamageZoneFromComponentName(m_EntityParent,part_name,damageZone);
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction DismantlePartServerEx | " + part_name);
//receive materials
if (player)
ReceiveMaterialsServer(player, part_name, damageZone);
//drop non-usable materials
DropNonUsableMaterialsServer(player, part_name);
DropEssentialAttachments(player, part_name);
if (!CfgGameplayHandler.GetDisableItemFalling())
HandleEntityFalling(part_name);
}
//! destroy part method
void DestroyPartServer(Man player, string part_name, int action_id, bool destroyed_by_connected_part = false)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction DestroyPartServer | " + part_name);
//destroy attached materials (if locked)
DestroyMaterialsServer(player, part_name);
//drop non-usable materials
DropNonUsableMaterialsServer(player, part_name);
DropEssentialAttachments(player, part_name);
if (!CfgGameplayHandler.GetDisableItemFalling())
HandleEntityFalling(part_name);
}
void DestroyConnectedParts(string part_name)
{
array<string> partNames;
partNames = GetValidDepenentPartsArray(part_name);
if (partNames && partNames.Count() > 0)
{
foreach (string partName : partNames)
{
if (!ExceptionCheck(partName))
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction DestroyConnectedParts | " + partName);
DestroyPartServer(null,partName,AT_DESTROY_PART,true);
}
}
}
}
//! when supporting part is removed, entities on top of it should transform to surface Y height
void HandleEntityFalling(string partName)
{
ConstructionPart part = GetConstructionPart(partName);
if (part.CanSimulateItemFalling())
{
g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).Call(ItemFall, partName); //deferred call to allow for material spawning/part's phx adjustment
}
}
protected void ItemFall(string partName)
{
vector minMax[2];
if (!GetItemFallDetectionBoxData(partName, minMax))
{
ErrorEx(string.Format("Wrong collision_data configuration of %1 for object %2; valid collision_data must contain exactly 2 strings (min/max) or must be a base building object for auto evaluation for boundings!", partName, m_EntityParent), ErrorExSeverity.INFO);
return;
}
vector center = GetBoxCenter(minMax);
center = m_EntityParent.ModelToWorld(center); //convert to world coordinates
vector edgeLength = GetCollisionBoxSize(minMax);
vector orientation = m_EntityParent.GetOrientation();
BoxCollidingParams params = new BoxCollidingParams();
params.SetParams(center, orientation, edgeLength, ObjIntersect.View, ObjIntersect.Geom, false);
array<Object> excludes; //null by default
array<ref BoxCollidingResult> results = new array<ref BoxCollidingResult>();
if (g_Game.IsBoxCollidingGeometryProxy(params, excludes, results))
{
foreach (BoxCollidingResult res : results)
{
if (res.obj == m_EntityParent) //skip parent
continue;
Physics objPhysics = res.obj.GetPhysics();
SimulationState simState = objPhysics.GetSimulationState();
if (simState != SimulationState.NONE && simState != SimulationState.COLLISION) //! Temp fix for DZ-32838
continue;
Transport vehicle;
if (Class.CastTo(vehicle, res.obj)) //special treatment for transports
{
dBodyActive(vehicle, ActiveState.ACTIVE);
continue;
}
PlayerBase player;
if (Class.CastTo(player, res.obj)) //special treatment for characters
{
objPhysics.ApplyImpulse(-vector.Up * 0.01);
continue;
}
InventoryItem item;
if (Class.CastTo(item, res.obj))
{
item.OnItemFallRebuilding();
if (item) //! We checking if the item instance still exists as it could have been deleted with the OnItemFallRebuilding call
item.ThrowPhysically(null, vector.Zero);
}
}
}
}
//! Handles decay for constructed parts, if enabled
override void ProcessConstructionDecay(float dTime)
{
float healthTarget;
float currentHP;
float threshold;
foreach (ConstructionPart part : m_ConstructionParts)
{
if (part.IsBuilt() && part.CanPartDecay())
{
string damageZone;
DamageSystem.GetDamageZoneFromComponentName(m_EntityParent,part.GetPartName(),damageZone);
if (damageZone != "")
{
currentHP = m_EntityParent.GetHealth(damageZone,"Health");
threshold = part.GetPartDecayThreshold() * m_EntityParent.GetMaxHealth(damageZone,"Health");
if (currentHP > threshold)
{
healthTarget = m_EntityParent.GetHealth(damageZone,"Health") + (dTime * part.GetPartDecayRate() * GetCurrentDecayCoef());
healthTarget = Math.Clamp(healthTarget,threshold,m_EntityParent.GetMaxHealth(damageZone,"Health"));
m_EntityParent.SetHealth(damageZone,"Health",healthTarget);
}
}
}
}
}
protected float GetCurrentDecayCoef()
{
if (g_Game.GetWeather().GetSandstorm().IsActive())
return SANDSTORM_DECAY_RATE_COEF;
return DEFAULT_DECAY_RATE_COEF;
}
array<ConstructionPart> GetConstructionPartArray()
{
return m_ConstructionPartArray;
}
override bool CanUseHandConstruction()
{
return m_AnyPartBuildableByHands != 0;
}
override void OnHealthLevelChanged(int oldLevel, int newLevel, string zone)
{
super.OnHealthLevelChanged(oldLevel, newLevel, zone);
string partName = zone;
partName.ToLower();
if (newLevel == GameConstants.STATE_RUINED)
{
ConstructionPart constructionPart = GetConstructionPart(partName);
if (constructionPart && IsPartConstructed(partName))
{
DestroyPartServer(null, partName, AT_DESTROY_PART);
}
}
}
//!Exceptions from 'dependent parts' hierarchy are handled here
bool ExceptionCheck(string part_name)
{
return false;
}
//============================================
// Construction-based event processing, not for BBB
//============================================
void ProcessOnPartBuiltServer(Man player, string part_name, int action_id)
{
ConstructionPart part = GetConstructionPart(part_name);
if (part.IsUtilityPart())
return;
string damageZone;
if (DamageSystem.GetDamageZoneFromComponentName(m_EntityParent,part_name,damageZone))
{
m_EntityParent.SetAllowDamage(true);
m_EntityParent.SetHealthMax(damageZone);
m_EntityParent.ProcessInvulnerabilityCheck(m_EntityParent.GetInvulnerabilityTypeString());
}
part.OnBeforeBuildServer(m_EntityParent, player); //performs any checks specific part type may need
HandleSyncDataBuild(part);
UpdatePartSyncStateLocal(part);
ConstructionManagerStatic.AddPartToProcess(this, part);
part.OnBuiltServer(m_EntityParent, player);
m_EntityParent.SetSynchDirty();
}
void ProcessOnPartDismantledServer(Man player, string part_name, int action_id)
{
ConstructionPart part = GetConstructionPart(part_name);
part.OnBeforeDismantleServer(m_EntityParent, player); //performs any checks specific part type may need
HandleSyncDataDismantle(part);
UpdatePartSyncStateLocal(part);
ConstructionManagerStatic.AddPartToProcess(this, part);
part.OnDismantledServer(m_EntityParent, player);
m_EntityParent.SetSynchDirty();
}
void ProcessOnPartDestroyedServer(Man player, string part_name, int action_id, bool destroyed_by_connected_part = false)
{
ConstructionPart part = GetConstructionPart(part_name);
part.OnBeforeDestroyServer(m_EntityParent, player); //performs any checks specific part type may need
HandleSyncDataDestroy(part);
UpdatePartSyncStateLocal(part);
ConstructionManagerStatic.AddPartToProcess(this, part);
part.OnDestroyedServer(m_EntityParent, player);
m_EntityParent.SetSynchDirty();
DestroyConnectedParts(part_name);
}
//============================================
// Update construction
//============================================
//!init ConstructionPart visuals
void InitVisuals()
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction " + Object.GetDebugName(m_EntityParent) + " InitVisuals");
UpdateVisuals();
}
//!update SPECIFIC ConstructionPart visuals
void UpdateSpecificPartVisuals(ConstructionPart part)
{
if (part.IsBuilt())
{
ShowConstructionPart(part.GetPartName());
}
else
{
HideConstructionPart(part.GetPartName());
}
}
//!update ALL ConstructionPart visuals. Use only if the update queue system won't cut it.
void UpdateVisuals()
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction " + Object.GetDebugName(m_EntityParent) + " UpdateVisuals");
foreach (ConstructionPart part : m_ConstructionPartArray)
{
if (part.IsBuilt())
{
ShowConstructionPart(part.GetPartName());
}
else
{
HideConstructionPart(part.GetPartName());
}
}
}
//!update SPECIFIC part's physics
void UpdateSpecificPartPhysics(ConstructionPart part)
{
if (part.IsBuilt())
{
ShowConstructionPartPhysics(part.GetPartName());
}
else
{
HideConstructionPartPhysics(part.GetPartName());
}
}
//!update ALL parts physics. Sets hide first, then shows the rest. Use only if the update queue system won't cut it.
void UpdatePhysics()
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction " + Object.GetDebugName(m_EntityParent));
array<ConstructionPart> builtArray = new array<ConstructionPart>();
foreach (ConstructionPart anyPart : m_ConstructionPartArray)
{
if (anyPart.IsBuilt())
builtArray.Insert(anyPart);
else
UpdateSpecificPartPhysics(anyPart);
}
foreach (ConstructionPart builtPart : builtArray)
{
UpdateSpecificPartPhysics(builtPart);
}
builtArray = null;
}
void UpdateNavmesh()
{
g_Game.GetWorld().UpdatePathgraphEntityState(m_EntityParent);
}
//!Parse type data and create part instance data objects
protected void UpdateConstructionParts()
{
m_ConstructionParts.Clear();
m_ConstructionPartArray.Clear();
m_BuiltConstructionPartArray.Clear();
EntityAIType entityType;
ConstructionDataTypeHolder holder;
if (Class.CastTo(entityType, m_EntityParent.GetEntityType()) && Class.CastTo(holder, entityType.GetConstructionDataHolderBasic()))
{
array<ConstructionPartTypeData> partTypesArray = holder.GetConstructionPartTypeDataArray();
foreach (ConstructionPartTypeData typeData : partTypesArray)
{
ConstructionPart part;
typename customInstanceTypename = typeData.GetCustomInstanceType();
if (!customInstanceTypename || !Class.CastTo(part, CreatePartInstanceObject(customInstanceTypename)))
{
//parameters are purely optional at this point, all config data is cached on entity type level
part = CreatePartInstanceObject(GetDefaultPartInstanceTypename());
}
part.SetPartTypeData(typeData);
PartInstanceInitData initData = new PartInstanceInitData();
initData.m_ParentEnt = m_EntityParent;
part.InitAdditionalPartInstanceInfo(initData);
m_ConstructionParts.Insert(typeData.GetPartName(), part);
m_ConstructionPartArray.Insert(part);
m_AnyPartBuildableByHands |= part.GetBuildIndicationTypeMask() & EConstructionTools.TOOL_HANDS;
}
}
}
protected ConstructionPart CreatePartInstanceObject(typename partTypename)
{
return ConstructionPart.Cast(partTypename.Spawn());
}
//! Override on lower implements to create desired part types
protected typename GetDefaultPartInstanceTypename()
{
ErrorEx("missing valid override!");
return typename;
}
protected void QueueFullUpdateParts(array<ConstructionPart> changedParts)
{
foreach (ConstructionPart part : changedParts)
{
ConstructionManagerStatic.AddPartToProcess(this, part);
}
}
//============================================
// Parts
//============================================
map<string, ref ConstructionPart> GetConstructionParts()
{
return m_ConstructionParts;
}
ConstructionPart GetConstructionPart(string part_name)
{
return m_ConstructionParts.Get(part_name);
}
bool CanBuildPart(string part_name, ItemBase tool, bool use_tool)
{
int dump;
return CanBuildPartEx(part_name, tool, dump);
}
//! not meant to replace, but rather extend the functionality for specific use
bool CanBuildPartEx(string partName, ItemBase tool, out EConstructionInfoCategories failureReasonsMask)
{
failureReasonsMask = EConstructionInfoCategories.NONE;
if (IsPartConstructed(partName))
failureReasonsMask |= EConstructionInfoCategories.PART_CONSTRUCTED;
if (!HasRequiredPart(partName))
failureReasonsMask |= EConstructionInfoCategories.PART_MISSING_REQUIREMENT;
if (!PartHasAnySupportFulfilled(partName))
failureReasonsMask |= EConstructionInfoCategories.PART_MISSING_SUPPORT;
if (HasConflictPart(partName))
failureReasonsMask |= EConstructionInfoCategories.PART_CONFLICTED;
if (!HasMaterials(partName))
failureReasonsMask |= EConstructionInfoCategories.MATERIALS_PRESENCE;
if (MaterialIsRuined(partName))
failureReasonsMask |= EConstructionInfoCategories.MATERIALS_DAMAGE;
if (!CanUseToolToBuildPart(partName, tool))
failureReasonsMask |= EConstructionInfoCategories.TOOL_PRESENCE;
return failureReasonsMask == EConstructionInfoCategories.NONE;
}
//! Simplified condition for utility (non-)parts
bool CanBuildUtilityPart(string partName)
{
if (IsPartConstructed(partName))
return false;
if (!HasRequiredPart(partName))
return false;
if (!PartHasAnySupportFulfilled(partName))
return false;
if (HasConflictPart(partName))
return false;
return true;
}
bool MaterialIsRuined(string part_name)
{
ConstructionPart part = GetConstructionPart(part_name);
array<ref ConstructionMaterialData> materials = part.GetMaterialsData();
foreach (ConstructionMaterialData dta : materials)
{
ItemBase attachment = ItemBase.Cast(m_EntityParent.FindAttachmentBySlotName(dta.m_MaterialSlotName));
if (attachment && attachment.IsRuined())
return true;
}
return false;
}
//Get all construction parts that can currently be built
void GetConstructionPartsToBuild(string main_part_name, out array<ConstructionPart> construction_parts, ItemBase tool, out string real_constructionTarget, bool use_tool)
{
construction_parts.Clear();
foreach (ConstructionPart part : m_ConstructionPartArray)
{
if ((main_part_name == part.GetMainPartName() || main_part_name == part.GetPartName()) && CanBuildPart(part.GetPartName(), tool, use_tool))
{
construction_parts.Insert(part);
}
}
}
//Get all construction parts from the targeted actionComponentName, that currently CANNOT be built
void GetConstructionPartsUnableToBuild(string actionComponentName, out array<ConstructionPart> constructionParts, ItemBase tool, out string realConstructionTarget, bool useTool)
{
constructionParts.Clear();
foreach (ConstructionPart part : m_ConstructionPartArray)
{
if (actionComponentName == part.GetMainPartName() && !CanBuildPart(part.GetPartName(), tool, useTool))
{
constructionParts.Insert(part);
}
}
}
//Returns (first found) base construction part
ConstructionPart GetBaseConstructionPart()
{
foreach (ConstructionPart part : m_ConstructionPartArray)
{
if (part.IsBase())
return part;
}
return NULL;
}
//!checks if construction part has all the required parts built
bool HasRequiredPart(string part_name)
{
ConstructionPart part = m_ConstructionParts.Get(part_name);
//check if simple required parts are already built
array<string> requiredParts = part.GetRequiredParts();
foreach (string partName : requiredParts)
{
if (!IsPartConstructed(partName))
return false;
}
return true;
}
//!checks if construction part has all the required support
bool PartHasAnySupportFulfilled(string part_name)
{
return true;
}
//checks if there are conflict parts already built. Simple variant, still in use
bool HasConflictPart(string part_name)
{
string conflictingPartName = "";
return HasConflictPart(part_name, conflictingPartName);
}
//checks if there are conflict parts already built. Outputs the conflicting part name.
bool HasConflictPart(string partName, out string conflictingPartName)
{
conflictingPartName = "";
ConstructionPart part = GetConstructionPart(partName);
array<string> conflictedParts = part.GetConflictedParts();
foreach (string partNameTest : conflictedParts)
{
if (IsPartConstructed(partNameTest))
{
conflictingPartName = partNameTest;
return true;
}
}
return false;
}
//DECONSTRUCTION
ConstructionPart GetConstructionPartToDismantle(string part_name, ItemBase tool)
{
if (CanDismantlePart(part_name, tool))
{
return GetConstructionPart(part_name);
}
return NULL;
}
bool CanDismantlePart(string part_name, ItemBase tool)
{
if (IsPartConstructed(part_name) && !HasDependentPart(part_name) && SupportRemovalCheck(part_name) && CanUseToolToDismantlePart(part_name, tool))
{
#ifdef DIAG_DEVELOPER
if (DiagMenu.GetBool(DiagMenuIDs.BASEBUILDING_FULLDEBUG))
{
ConstructionDebug.ResetDismantlePartData();
}
#endif
return true;
}
#ifdef DIAG_DEVELOPER
if (GetConstructionPart(part_name))
{
if (DiagMenu.GetBool(DiagMenuIDs.BASEBUILDING_FULLDEBUG))
{
ConstructionDismantleDebugData data = new ConstructionDismantleDebugData();
data.partName = part_name;
data.constructed = IsPartConstructed(part_name);
data.hasDependents = HasDependentPart(part_name);
data.supportState = SupportRemovalCheck(part_name);
ConstructionDebug.SetCanDismantlePartData(data);
}
}
#endif
return false;
}
//!checks if construction part has dependent part (that is already built) because of which it cannot be dismantled
bool HasDependentPart(string part_name)
{
foreach (ConstructionPart part : m_BuiltConstructionPartArray)
{
if (!part.IsUtilityPart() && part.GetRequiredParts().Find(part_name) > -1)
{
return true;
}
}
return false;
}
//! Checks if all supports would hold on part removal. Returns 'false' if some would fail
bool SupportRemovalCheck(string part_name)
{
return true;
}
bool CanRepairPart(string partName, ItemBase tool = null, PlayerBase player = null, bool facingCheck = false)
{
ConstructionPart constructionPart = GetConstructionPart(partName);
if (constructionPart)
{
//damage check
string zoneName;
if (!DamageSystem.GetDamageZoneFromComponentName(m_EntityParent, partName, zoneName)) //no zone, no repair
return false;
float currentZoneHealthLevel = m_EntityParent.GetHealthLevel(zoneName);
if (currentZoneHealthLevel == GameConstants.STATE_PRISTINE || currentZoneHealthLevel == GameConstants.STATE_RUINED)
return false;
ConstructionPart part = GetConstructionPart(partName);
if (!part.CanBeRepaired(currentZoneHealthLevel, tool, player))
return false;
if (!player) //for debug purposes, I guess?
return true;
//materials check
if (!HasMaterials(partName,true))
return false;
//facing and position checks
if (!ConstructionDirectionCheck(player, constructionPart, EConstructionDirCheckType.INDOORS) || !ConstructionDirectionCheck(player, constructionPart, EConstructionDirCheckType.REFERENCE_DIRECTION))
return false;
if (!m_EntityParent.IsFacingPlayerEx(player, partName) && !player.GetInputController().CameraIsFreeLook())
{
//Camera check (client-only)
if (!g_Game.IsDedicatedServer())
{
if (facingCheck)
{
if (m_EntityParent.IsFacingCamera(partName))
return false;
}
}
ConstructionActionData cad = player.GetConstructionActionData();
cad.SetTargetPart(constructionPart);
return true;
}
}
return false;
}
//! Check character positioning with a box check (character must be inside at least one of the defined boxes)
bool ConstructionInsiderBoxCheck(PlayerBase player, ConstructionPart part)
{
const float CYLINDER_HEIGHT = 0.01;
const float CYLINDER_RADIUS = 0.01;
#ifdef DIAG_DEVELOPER
bool fullDebugEnabled = DiagMenu.GetBool(DiagMenuIDs.BASEBUILDING_COLLISIONDEBUG);
if (fullDebugEnabled || (!fullDebugEnabled && m_InsideTestBoxes.Count() > 0))
DestroyDebugInsideTestBoxes();
#endif
TConstructionPartInsiderBoxData modelMinMaxs = part.GetInsiderBoxData();
if (!modelMinMaxs)
return true;
vector matPlayer[4];
Math3D.MatrixIdentity4(matPlayer);
vector adjustedPos = player.GetPosition();
adjustedPos[1] = adjustedPos[1] + CYLINDER_HEIGHT / 2;
matPlayer[3] = adjustedPos;
#ifdef DIAG_DEVELOPER
if (fullDebugEnabled)
{
if (m_PlayerInsideTestCylinder)
m_PlayerInsideTestCylinder.Destroy();
m_PlayerInsideTestCylinder = Debug.DrawCylinder("0 0 0", CYLINDER_RADIUS * 5, CYLINDER_HEIGHT, COLOR_YELLOW_A);
m_PlayerInsideTestCylinder.SetMatrix(matPlayer);
}
#endif
vector matParent[4];
m_EntityParent.GetTransform(matParent);
bool success = false;
foreach (array<vector> minMaxPair : modelMinMaxs)
{
vector min = minMaxPair[0];
vector max = minMaxPair[1];
bool playerInside = false;
//temporary test solution
playerInside = Math3D.IntersectCylinderOBB(min, max, matParent, matPlayer, CYLINDER_RADIUS, CYLINDER_HEIGHT);
if (playerInside)
{
success = true;
#ifdef DIAG_DEVELOPER
if (fullDebugEnabled)
{
DrawDebugInsideTestBox(min, max, ARGB(90, 128, 128, 128));
}
else
#else
return true;
#endif
}
#ifdef DIAG_DEVELOPER
else
{
if (fullDebugEnabled)
{
DrawDebugInsideTestBox(min, max, ARGB(90, 128, 0, 128));
}
}
#endif
}
#ifdef DIAG_DEVELOPER
if (fullDebugEnabled)
{
if (!success)
ConstructionDebug.SetInsideboxCheckDebugData(part.GetPartName());
else
ConstructionDebug.SetInsideboxCheckDebugData("");
}
#endif
return success;
}
bool ConstructionDirectionCheck(PlayerBase player, ConstructionPart part, EConstructionDirCheckType type)
{
string name = part.GetPartName();
//first we check actual part names in case of individual handling
string memStart = "" + name + "_pos";
string memEnd;
if (m_EntityParent.MemoryPointExists(memStart))
{
switch (type)
{
case EConstructionDirCheckType.INDOORS:
memEnd = "" + name + "_inside";
break;
case EConstructionDirCheckType.REFERENCE_DIRECTION:
memEnd = "" + name + "_ref";
break;
default:
memEnd = "";
break;
}
}
//next we check main part name for mass handling
name = part.GetMainPartName();
memStart = "" + name + "_pos";
if (m_EntityParent.MemoryPointExists(memStart))
{
switch (type)
{
case EConstructionDirCheckType.INDOORS:
memEnd = "" + name + "_inside";
break;
case EConstructionDirCheckType.REFERENCE_DIRECTION:
memEnd = "" + name + "_ref";
break;
default:
memEnd = "";
break;
}
}
if (memEnd != "" && m_EntityParent.MemoryPointExists(memEnd))
{
return MiscGameplayFunctions.MemPointDirectionalCheck(player, m_EntityParent, memStart, memEnd);
}
return true;
}
//returns array of BUILT parts that directly depend on 'part_name' in some fashion
protected array<string> GetValidDepenentPartsArray(string part_name, array<string> recurs = null)
{
string name = "";
array<string> dependentParts;
foreach (ConstructionPart part : m_BuiltConstructionPartArray)
{
name = part.GetPartName();
array<string> reqPartsArray = part.GetRequiredParts();
if (part.IsBuilt() && reqPartsArray && reqPartsArray.Find(part_name) > -1)
{
if (!dependentParts)
dependentParts = new array<string>();
if (!recurs || (recurs.Find(name) == -1))
dependentParts.Insert(name);
}
}
return dependentParts;
}
//gets simple required parts of a construction part; caches into ConstructionPartTypeData array on init
array<string> GetRequiredParts(string part_name, string main_part_name)
{
string cfgPath = "cfgVehicles" + " " + m_EntityParent.GetType() + " " + "Construction" + " " + main_part_name + " " + part_name + " " + "required_parts";
array<string> requiredParts = new array<string>();
g_Game.ConfigGetTextArray(cfgPath, requiredParts);
return requiredParts;
}
//DESTROY
ConstructionPart GetConstructionPartToDestroy(string part_name)
{
if (CanDestroyPart(part_name))
return GetConstructionPart(part_name);
return NULL;
}
bool CanDestroyPart(string part_name)
{
if (IsPartConstructed(part_name) && !HasDependentPart(part_name) && SupportRemovalCheck(part_name))
return true;
return false;
}
//CONSTRUCTION PART STATE
//! Animates AnimationSource. Use on server only, AUTO-SYNCED
protected void ShowConstructionPart(string part_name)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction ShowConstructionPart " + m_EntityParent.GetType() + " | part = " + part_name);
ConstructionPart part = m_ConstructionParts.Get(part_name);
part.HandleShowConstructionPart(m_EntityParent);
}
//! Animates AnimationSource. Use on server only, AUTO-SYNCED
protected void HideConstructionPart(string part_name)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction HideConstructionPart " + m_EntityParent.GetType() + " | part = " + part_name);
ConstructionPart part = m_ConstructionParts.Get(part_name);
part.HandleHideConstructionPart(m_EntityParent);
}
//! sets physics LOCALLY
void ShowConstructionPartPhysics(string part_name)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] ShowConstructionPartPhysics " + m_EntityParent.GetType() + " | part = " + part_name);
ConstructionPart part = m_ConstructionParts.Get(part_name);
part.HandleAddPhysics(m_EntityParent);
}
//! sets physics LOCALLY
void HideConstructionPartPhysics(string part_name)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] HideConstructionPartPhysics " + m_EntityParent.GetType() + " | part = " + part_name);
ConstructionPart part = m_ConstructionParts.Get(part_name);
part.HandleRemovePhysics(m_EntityParent);
}
//! returns true if part constructed
bool IsPartConstructed(string part_name)
{
ConstructionPart part = GetConstructionPart(part_name);
if (part)
return part.IsBuilt();
return false;
}
//============================================
// Materials for construction
//============================================
//! returns true if materials to build the part are available
bool HasMaterialsEx(string part_name, bool repairing = false, out string slotName = "", out float requiredQuantity = 0.0)
{
#ifdef DIAG_DEVELOPER
if (g_Game.IsClient() || !g_Game.IsMultiplayer()) //weird, I know, but this is just used for initial filtering purposes. Action conditions take care of the rest for both sides
{
PlayerBase player = PlayerBase.Cast(g_Game.GetPlayer());
if (player)
{
if (player.GetConstructionActionData().GetFastConstructionMode())
return true;
}
}
#endif
ConstructionPart part = GetConstructionPart(part_name);
array<ref ConstructionMaterialData> materials = part.GetMaterialsData();
foreach (ConstructionMaterialData dta : materials)
{
float quantity = dta.m_MaterialQuantity;
if (quantity == 0.0) //0 - ignores quantity
continue;
if (repairing)
{
quantity *= REPAIR_MATERIAL_PERCENTAGE;
quantity = Math.Max(Math.Floor(quantity),1);
}
//if the selected material (or its quantity) is not available
if (!HasMaterialWithQuantityAttached(dta.m_MaterialSlotName, quantity))
{
slotName = dta.m_MaterialSlotName;
requiredQuantity = quantity;
return false;
}
}
return true; //return true even if no material required
}
//simplified variant of the above (legacy)
bool HasMaterials(string part_name, bool repairing = false)
{
string dumpSlotName;
float dumpQty;
return HasMaterialsEx(part_name, repairing, dumpSlotName, dumpQty);
}
//check if parent object has attachment of required quantity attached to it
protected bool HasMaterialWithQuantityAttached(string slot_name, float quantity)
{
ItemBase attachment = ItemBase.Cast(m_EntityParent.FindAttachmentBySlotName(slot_name));
if (attachment && ((int)quantity == -1 || attachment.GetQuantity() >= quantity)) //-1 - deletes the object
return true;
return false;
}
//take materials when building
void TakeMaterialsServer(string part_name, bool repairing = false)
{
ConstructionPart part = GetConstructionPart(part_name);
array<ref ConstructionMaterialData> materials = part.GetMaterialsData();
foreach (ConstructionMaterialData dta : materials)
{
ItemBase attachment = ItemBase.Cast(m_EntityParent.FindAttachmentBySlotName(dta.m_MaterialSlotName));
if (!attachment)
continue;
if (repairing && dta.m_SkipOnRepair)
continue;
if (dta.m_MaterialLockable)
{
//lock attachment
InventoryLocation invloc = new InventoryLocation();
attachment.GetInventory().GetCurrentInventoryLocation(invloc);
m_EntityParent.GetInventory().SetSlotLock(invloc.GetSlot(), true);
}
else
{
if (dta.m_MaterialQuantity > 0.0) //0 - ignores quantity
{
float quantity = dta.m_MaterialQuantity;
if (repairing)
{
quantity *= REPAIR_MATERIAL_PERCENTAGE;
quantity = Math.Max(Math.Floor(quantity),1);
}
//subtract quantity
attachment.AddQuantity(-quantity);
}
else if (dta.m_MaterialQuantity == -1.0) //-1 - deletes the object
{
g_Game.ObjectDelete(attachment);
}
}
}
}
// Receive materials when dismantling
// ToDo: Use cached materials data from part
protected void ReceiveMaterialsServer(notnull Man player, string part_name, string damagezone_name)
{
ConstructionPart part = GetConstructionPart(part_name);
bool isBase = part.IsBase();
string mainPartName = part.GetMainPartName();
string cfgPath = "cfgVehicles" + " " + m_EntityParent.GetType() + " "+ "Construction" + " " + mainPartName + " " + part_name + " " + "Materials";
if (g_Game.ConfigIsExisting(cfgPath))
{
StaticConstructionMethods.SpawnConstructionMaterialPilesEx(m_EntityParent,player,cfgPath,part_name,damagezone_name,isBase);
}
}
//destroy lockable materials when destroying
protected void DestroyMaterialsServer(Man player, string part_name)
{
ConstructionPart part = GetConstructionPart(part_name);
array<ref ConstructionMaterialData> materials = part.GetMaterialsData();
foreach (ConstructionMaterialData dta : materials)
{
//get material
ItemBase attachment = ItemBase.Cast(m_EntityParent.FindAttachmentBySlotName(dta.m_MaterialSlotName));
if (!attachment)
continue;
//material still attached
if (dta.m_MaterialLockable)
{
InventoryLocation invloc = new InventoryLocation();
attachment.GetInventory().GetCurrentInventoryLocation(invloc);
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] " + Object.GetDebugName(m_EntityParent) + " DestroyMaterialsServer unlock slot=" + invloc.GetSlot());
m_EntityParent.GetInventory().SetSlotLock(invloc.GetSlot() , false);
g_Game.ObjectDelete(attachment);
}
}
}
void DropNonUsableMaterialsServer(Man player, string part_name)
{
ConstructionPart part = GetConstructionPart(part_name);
//! We only care about base parts with locked materials/attachments here.
//! Refund of materials that where used to construct the part are refunded by the ReceiveMaterialsServer method
if (!part.IsBase())
return;
array<ref ConstructionMaterialData> materials = part.GetMaterialsData();
if (!materials)
return;
GameInventory parentInv = m_EntityParent.GetInventory();
foreach (ConstructionMaterialData dta : materials)
{
if (dta.m_SkipOnDismantle)
continue;
int slotId = dta.m_MaterialSlotID;
if (slotId == InventorySlots.INVALID)
{
ErrorEx(string.Format("DropNonUsableMaterialsServer: invalid cached slot id for '%1' on part %2", dta.m_MaterialSlotName, part_name));
continue;
}
ItemBase attachment = ItemBase.Cast(parentInv.FindAttachment(slotId));
if (!attachment)
{
ErrorEx(string.Format("Attachment is not present for material type '%1' on part %2", dta.m_MaterialType, part_name));
continue;
}
if (LogManager.IsBaseBuildingLogEnable())
bsbDebugPrint("[bsb] " + Object.GetDebugName(m_EntityParent) + " DropNonUsableMaterials UNlocking slot=" + slotId + " (" + dta.m_MaterialSlotName + ")");
parentInv.SetSlotLock(slotId, false);
//-1 => consumed by construction. Lockable materials are NEVER consumed
//(TakeMaterialsServer locks them instead) so they must be returned.
//Example: Fence wall_gate: MetalWire quantity=-1 lockable=1
if (dta.m_MaterialQuantity == -1.0 && !dta.m_MaterialLockable)
{
if (LogManager.IsBaseBuildingLogEnable())
bsbDebugPrint("[bsb] " + Object.GetDebugName(m_EntityParent) + " DropNonUsableMaterials DELETING (qty==-1, non-lockable) slot=" + slotId);
g_Game.ObjectDelete(attachment);
continue;
}
InventoryLocation invloc = new InventoryLocation();
attachment.GetInventory().GetCurrentInventoryLocation(invloc);
MoveMaterialAttachmentToGround(m_EntityParent, player, attachment, invloc, part_name);
}
}
//! The method does not care about the amount of materials that should be returned based on the cached quantity in the ConstructionMaterialData struct.
//! If we start using this method for e.g. drop of materials that are not attached and locked to the construction parent then we need to change the method to handle the quantity accordingly
protected void MoveMaterialAttachmentToGround(notnull EntityAI parent, Man player, notnull ItemBase attachment, notnull InventoryLocation invloc, string partName)
{
// 1) base drop position
vector basePos;
bool hasMemPoint = parent.MemoryPointExists(partName + "_materials");
if (hasMemPoint)
basePos = g_Game.ObjectModelToWorld(parent, parent.GetMemoryPointPos(partName + "_materials"));
else if (player)
basePos = player.GetPosition();
else
basePos = attachment.GetPosition();
// 2) capture everything we need BEFORE touching the entity
string itemType = attachment.GetType();
float health = attachment.GetHealth("", "Health");
int total = attachment.GetQuantity();
int stackMax = attachment.GetTargetQuantityMax(-1);
if (stackMax <= 0)
stackMax = 1;
// number of fresh entities needed to carry total at stackMax each
int pileCount = Math.Ceil((float)total / stackMax);
if (pileCount > MAX_MATERIAL_PILES)
{
ErrorEx(string.Format("MoveMaterialAttachmentToGround: pile count %1 exceeds cap %2 for %3 on part %4, truncating!", pileCount, MAX_MATERIAL_PILES, itemType, partName), ErrorExSeverity.INFO);
pileCount = MAX_MATERIAL_PILES;
}
// 3) spawn ALL units as fresh, cleanly-networked ground entities
int remaining = total;
for (int i = 0; i < pileCount; ++i)
{
int thisQty = stackMax;
if (remaining < stackMax)
thisQty = remaining;
float ang = i * 90 * Math.DEG2RAD;
vector dropPos = basePos + Vector(Math.Cos(ang) * 0.45, 0, Math.Sin(ang) * 0.45);
ItemBase newItem = ItemBase.Cast(g_Game.CreateObjectEx(itemType, dropPos, ECE_PLACE_ON_SURFACE));
if (!newItem)
{
ErrorEx("MoveMaterialAttachmentToGround: failed to create '" + itemType + "' on part " + partName + ", aborting drop!");
return; //bail BEFORE deleting the original, so materials aren't lost
}
newItem.SetQuantity(thisQty);
newItem.SetHealth("", "Health", health);
newItem.PlaceOnSurface();
remaining -= thisQty;
}
// 4) remove the original - it never becomes a ground item, so it can never be a stale one
attachment.Delete();
}
//! Drop other, non-material attachments tied to the construction part
protected void DropEssentialAttachments(Man player, string partName)
{
ConstructionPart part = GetConstructionPart(partName);
if (part.HasEssentialAttachmentSlots())
{
array<string> slots = part.GetEssentialAttachmentSlots();
ItemBase attachment;
GameInventory parentInventory = m_EntityParent.GetInventory();
vector groundPos;
foreach (string slotName: slots)
{
if (Class.CastTo(attachment, m_EntityParent.FindAttachmentBySlotName(slotName)))
{
//first get rid of any possible locks preventing the drop
parentInventory.SetSlotLock(InventorySlots.GetSlotIdFromString(slotName), false);
//now drop
int mode = InventoryMode.LOCAL;
if (g_Game.IsMultiplayer())
mode = InventoryMode.SERVER;
if (m_EntityParent.MemoryPointExists(slotName))
{
groundPos = m_EntityParent.ModelToWorld(m_EntityParent.GetMemoryPointPos(slotName));
}
else if (player)
{
groundPos = player.GetPosition();
}
else
{
groundPos = attachment.GetPosition();
}
vector m4[4];
Math3D.MatrixIdentity4(m4);
m4[3] = groundPos;
parentInventory.DropEntityWithTransform(mode, m_EntityParent, attachment, m4);
}
}
}
}
//set lock on materials that are attached and cannot be locked/unlocked
void SetLockOnAttachedMaterials(string part_name, bool lock_slot)
{
ConstructionPart part = GetConstructionPart(part_name);
array<ref ConstructionMaterialData> materials = part.GetMaterialsData();
foreach (ConstructionMaterialData dta : materials)
{
ItemBase attachment = ItemBase.Cast(m_EntityParent.FindAttachmentBySlotName(dta.m_MaterialSlotName));
if (attachment && dta.m_MaterialLockable)
{
InventoryLocation inventoryLocation = new InventoryLocation();
attachment.GetInventory().GetCurrentInventoryLocation(inventoryLocation);
m_EntityParent.GetInventory().SetSlotLock(inventoryLocation.GetSlot(), lock_slot);
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] " + Object.GetDebugName(m_EntityParent) + " SetLockOnAttachedMaterials lock=" + lock_slot +" slot=" + inventoryLocation.GetSlot());
}
}
}
//============================================
// Construction tools
//============================================
bool CanUseToolToBuildPart(string part_name, ItemBase tool)
{
#ifdef DIAG_DEVELOPER
if (g_Game.IsClient() || !g_Game.IsMultiplayer())
{
PlayerBase player = PlayerBase.Cast(g_Game.GetPlayer());
if (player)
{
if (player.GetConstructionActionData().GetFastConstructionMode())
return true;
}
}
#endif
ConstructionPart part = GetConstructionPart(part_name);
int actionMask = part.GetBuildActionTypeMask();
if (actionMask != EConstructionTools.TOOL_NO_ACTION)
{
if (tool)
{
string toolCfgPath = "cfgVehicles" + " " + tool.GetType() + " " + "build_action_type";
if (g_Game.ConfigIsExisting(toolCfgPath))
{
int toolActionType = g_Game.ConfigGetInt(toolCfgPath);
if ((actionMask & toolActionType) > 0)
{
return true;
}
}
}
else if (actionMask & EConstructionTools.TOOL_HANDS)
{
return true;
}
}
return false;
}
bool CanUseToolToDismantlePart(string part_name, ItemBase tool)
{
ConstructionPart part = GetConstructionPart(part_name);
int actionMask = part.GetDismantleActionTypeMask();
if (actionMask != EConstructionTools.TOOL_NO_ACTION)
{
if (tool)
{
string toolCfgPath = "cfgVehicles" + " " + tool.GetType() + " " + "dismantle_action_type";
if (g_Game.ConfigIsExisting(toolCfgPath))
{
int toolActionType = g_Game.ConfigGetInt(toolCfgPath);
if ((actionMask & toolActionType) > 0)
{
return true;
}
}
}
else if (actionMask & EConstructionTools.TOOL_HANDS)
{
return true;
}
}
return false;
}
ConstructionMaterialType GetMaterialType(string part_name)
{
ConstructionPart part = GetConstructionPart(part_name);
return part.GetMaterialType();
}
//============================================
// Collision check
//============================================
//! Collision check for building part
bool IsCollidingEx(CollisionCheckData check_data)
{
if (CfgGameplayHandler.GetDisableIsCollidingCheck())
return false;
ConstructionPart part = GetConstructionPart(check_data.m_PartName);
if (part && part.GetCollisionData().Count() == 2)
{
vector center;
float absoluteOfset = 0.05; //we need to lift BBox even more, because it colliddes with house floors due to various reasons (probably geometry or float imperfections)
vector edgeLength;
vector minMax[2]; //data used for creating trigger
array<Object> excludedObjects = new array<Object>();
array<Object> collidedObjects = new array<Object>();
excludedObjects.Insert(m_EntityParent);
if (check_data.m_AdditionalExcludes.Count() > 0)
{
excludedObjects.InsertAll(check_data.m_AdditionalExcludes);
}
GetCollisionBoxData(check_data.m_PartName, minMax);
center = GetBoxCenter(minMax);
center = m_EntityParent.ModelToWorld(center); //convert to world coordinates
edgeLength = GetCollisionBoxSize(minMax);
#ifdef DIAG_DEVELOPER
bool collisionDebugEnabled = DiagMenu.GetBool(DiagMenuIDs.BASEBUILDING_COLLISIONDEBUG);
#endif
if (g_Game.IsBoxCollidingGeometry(Vector(center[0], center[1] + absoluteOfset, center[2]), m_EntityParent.GetOrientation(), edgeLength, check_data.m_PrimaryGeometry, check_data.m_SecondaryGeometry, excludedObjects, collidedObjects))
{
bool validCollision = false;
#ifdef DIAG_DEVELOPER
array<string> validEntityCollisions = new array<string>();
#endif
for (int i = 0; i < collidedObjects.Count(); ++i)
{
EntityAI entity = EntityAI.Cast(collidedObjects.Get(i));
if (entity && !entity.IsIgnoredByConstruction())
{
validCollision = true;
#ifdef DIAG_DEVELOPER
if (collisionDebugEnabled)
{
validEntityCollisions.Insert(entity.GetType());
}
#else
break;
#endif
}
}
#ifdef DIAG_DEVELOPER
if (collisionDebugEnabled)
{
if (validCollision)
{
ConstructionDebug.SetCollisionPrintoutData(check_data.m_PartName, validEntityCollisions);
DrawDebugCollisionBox(minMax, ARGB(150, 0, 0, 255));
}
else
{
ConstructionDebug.ResetCollisionPrintoutData();
DrawDebugCollisionBox(minMax, ARGB(150, 255, 255, 255));
}
}
else if (m_CollisionBox)
{
DestroyDebugCollisionBox();
}
#endif
return validCollision;
}
#ifdef DIAG_DEVELOPER
else //Debug
{
if (collisionDebugEnabled)
{
ConstructionDebug.ResetCollisionPrintoutData();
DrawDebugCollisionBox(minMax, ARGB(150, 255, 255, 255));
}
else if (m_CollisionBox)
{
DestroyDebugCollisionBox();
}
}
#endif
}
return false;
}
vector GetCollisionBoxSize(vector min_max[2])
{
vector boxSize = Vector(0, 0, 0);
boxSize[0] = Math.AbsFloat(min_max[1][0] - min_max[0][0]);
boxSize[1] = Math.AbsFloat(min_max[1][1] - min_max[0][1]);
boxSize[2] = Math.AbsFloat(min_max[1][2] - min_max[0][2]);
return boxSize;
}
//outputs collision box minMax from construction config and model p3d, in MODELSPACE coordinates
protected void GetCollisionBoxData(string part_name, out vector min_max[2])
{
ConstructionPart path = GetConstructionPart(part_name);
array<string> dta = path.GetCollisionData();
if (dta.Count() == 2)
{
if (m_EntityParent.MemoryPointExists(dta[0]))
{
min_max[0] = m_EntityParent.GetMemoryPointPos(dta[0]);
}
if (m_EntityParent.MemoryPointExists(dta[1]))
{
min_max[1] = m_EntityParent.GetMemoryPointPos(dta[1]);
}
}
else if (dta.Count() > 0)
{
ErrorEx(string.Format("Wrong collision_data configuration of %1 for object %2; valid collision_data must contain exactly 2 strings (min/max)!", part_name, m_EntityParent), ErrorExSeverity.INFO);
}
}
//outputs item fall detection box minMax from construction config and model p3d, in MODELSPACE coordinates
protected bool GetItemFallDetectionBoxData(string partName, out vector minMax[2])
{
ConstructionPart path = GetConstructionPart(partName);
array<string> dta = path.GetItemFallingData();
if (dta.Count() == 2)
{
if (m_EntityParent.MemoryPointExists(dta[0]))
{
minMax[0] = m_EntityParent.GetMemoryPointPos(dta[0]);
}
if (m_EntityParent.MemoryPointExists(dta[1]))
{
minMax[1] = m_EntityParent.GetMemoryPointPos(dta[1]);
}
return true;
}
else if (dta.Count() > 0)
{
ErrorEx(string.Format("Wrong collision_data configuration of %1 for object %2; valid item_falling_data must contain exactly 2 strings (min/max)!", partName, m_EntityParent), ErrorExSeverity.INFO);
}
return false;
}
//returns center point of box defined by min/max values
vector GetBoxCenter(vector min_max[2])
{
vector center;
center[0] = (min_max[1][0] - min_max[0][0]) / 2;
center[1] = (min_max[1][1] - min_max[0][1]) / 2;
center[2] = (min_max[1][2] - min_max[0][2]) / 2;
center = Vector(min_max[1][0] - center[0], min_max[1][1] - center[1], min_max[1][2] - center[2]); //offset to box center
return center;
}
void GetTriggerExtents(vector min_max[2], out vector extents[2])
{
vector egdeLength = GetCollisionBoxSize(min_max);
extents[0][0] = -egdeLength[0] / 2; //min
extents[0][1] = -egdeLength[1] / 2;
extents[0][2] = -egdeLength[2] / 2;
extents[1][0] = egdeLength[0] / 2; //max
extents[1][1] = egdeLength[1] / 2;
extents[1][2] = egdeLength[2] / 2;
}
#ifdef DIAG_DEVELOPER
//Debug
protected void DrawDebugCollisionBox(vector min_max[2], int color)
{
DestroyDebugCollisionBox();
vector mat[4];
m_EntityParent.GetTransform(mat);
m_CollisionBox = Debug.DrawBox(min_max[0], min_max[1], color);
m_CollisionBox.SetMatrix(mat);
}
protected void DestroyDebugCollisionBox()
{
if (m_CollisionBox)
{
m_CollisionBox.Destroy();
m_CollisionBox = NULL;
}
}
protected void DrawDebugInsideTestBox(vector min, vector max, int color)
{
vector mat[4];
m_EntityParent.GetTransform(mat);
Shape box = Debug.DrawBox(min, max, color);
box.SetMatrix(mat);
m_InsideTestBoxes.Insert(box);
}
protected void DestroyDebugInsideTestBoxes()
{
foreach (Shape box : m_InsideTestBoxes)
{
box.Destroy();
box = NULL;
}
m_InsideTestBoxes.Clear();
}
#endif
//////////////////
//Deprecated Code
//////////////////
[Obsolete("call BuildPartServerEx instead")]
void BuildPartServer(notnull Man player, string part_name, int action_id)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction BuildPartServer | " + part_name);
BuildPartServerEx(player,part_name,action_id);
}
[Obsolete("call DismantlePartServerEx instead")]
void DismantlePartServer(notnull Man player, string part_name, int action_id)
{
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] Construction DismantlePartServer | " + part_name);
DismantlePartServerEx(player,part_name,action_id);
}
[Obsolete("call IsCollidingEx instead")]
bool IsColliding(string part_name)
{
if (CfgGameplayHandler.GetDisableIsCollidingCheck())
return false;
ConstructionPart part = GetConstructionPart(part_name);
if (part && part.GetCollisionData().Count() == 2)
{
vector center;
float absolute_ofset = 0.05; //we need to lift BBox even more, because it colliddes with house floors due to various reasons (probably geometry or float imperfections)
vector edge_length;
vector min_max[2]; //data used for creating trigger
array<Object> excludedObjects = new array<Object>();
array<Object> collidedObjects = new array<Object>();
excludedObjects.Insert(m_EntityParent);
//get min_max and center from config and memory points
GetCollisionBoxData(part_name, min_max);
center = GetBoxCenter(min_max);
center = m_EntityParent.ModelToWorld(center); //convert to world coordinates
edge_length = GetCollisionBoxSize(min_max);
if (g_Game.IsBoxCollidingGeometry(Vector(center[0], center[1] + absolute_ofset, center[2]), m_EntityParent.GetOrientation(), edge_length, ObjIntersectView, ObjIntersectGeom, excludedObjects, collidedObjects))
{
for (int i = 0; i < collidedObjects.Count(); ++i)
{
EntityAI entity = EntityAI.Cast(collidedObjects.Get(i));
if (entity && !entity.IsIgnoredByConstruction())
return true;
}
}
}
return false;
}
[Obsolete("no replacement")]
void CreateCollisionTrigger(string part_name, vector min_max[2], vector center)
{
if (m_ConstructionBoxTrigger)
{
if (m_ConstructionBoxTrigger.GetPartName() == part_name) //already created
{
return;
}
else
{
DestroyCollisionTrigger();
}
}
//get proper trigger extents (min<max)
vector extents[2];
GetTriggerExtents(min_max, extents);
//create trigger
m_ConstructionBoxTrigger = ConstructionBoxTrigger.Cast(g_Game.CreateObject("ConstructionBoxTrigger", center, false, false, false));
m_ConstructionBoxTrigger.SetPosition(center);
m_ConstructionBoxTrigger.SetOrientation(m_EntityParent.GetOrientation());
m_ConstructionBoxTrigger.SetExtents(extents[0], extents[1]);
m_ConstructionBoxTrigger.SetPartName(part_name);
}
[Obsolete("no replacement")]
void DestroyCollisionTrigger()
{
if (!m_ConstructionBoxTrigger)
return;
g_Game.ObjectDelete(m_ConstructionBoxTrigger);
m_ConstructionBoxTrigger = NULL;
}
[Obsolete("no replacement")]
bool IsTriggerColliding()
{
if (!m_ConstructionBoxTrigger)
return false;
return m_ConstructionBoxTrigger.IsColliding();
}
protected ConstructionBoxTrigger m_ConstructionBoxTrigger; //! Deprecated
}
class StaticConstructionMethods
{
//! spawns material from any construction; 'player' parameter optional; 'main_part_name' is actually a PART_NAME!
static void SpawnConstructionMaterialPilesEx(notnull EntityAI entity, Man player, string cfgPath, string mainPartName, string damagezoneName = "", bool isBase = false, bool throwPiles = false)
{
string partName = mainPartName; //'mainPartName' is actually a PART_NAME, using this to avoid further confustion
int childCount = g_Game.ConfigGetChildrenCount(cfgPath);
for (int i = 0; i < childCount; ++i)
{
string child_name;
g_Game.ConfigGetChildName(cfgPath, i, child_name);
//get type, quantity from material
string configPath;
string type;
string slot_name;
configPath = cfgPath + " " + child_name + " " + "type";
g_Game.ConfigGetText(configPath, type);
configPath = cfgPath + " " + child_name + " " + "slot_name";
g_Game.ConfigGetText(configPath, slot_name);
configPath = cfgPath + " " + child_name + " " + "quantity";
float quantity = g_Game.ConfigGetFloat(configPath);
configPath = cfgPath + " " + child_name + " " + "lockable";
bool lockable = g_Game.ConfigGetInt(configPath);
configPath = cfgPath + " " + child_name + " " + "skipOnDismantle";
bool skipOnDismantle = g_Game.ConfigGetInt(configPath);
if (skipOnDismantle)
continue;
//receive material quantity
ItemBase attachment = ItemBase.Cast(entity.FindAttachmentBySlotName(slot_name));
int slot_id;
//material still attached
if (lockable) //if lockable
{
if (attachment)
{
InventoryLocation src = new InventoryLocation();
attachment.GetInventory().GetCurrentInventoryLocation(src);
if (LogManager.IsBaseBuildingLogEnable()) bsbDebugPrint("[bsb] " + Object.GetDebugName(entity) + " DropNonUsableMaterials UNlocking slot=" + src.GetSlot());
entity.GetInventory().SetSlotLock(src.GetSlot() , false);
//detach if base
if (isBase)
{
if (g_Game.IsMultiplayer() && player)
{
InventoryLocation dst = new InventoryLocation();
GameInventory.SetGroundPosByOwner(player, src.GetItem(), dst);
player.ServerTakeToDst(src, dst);
}
else
{
entity.GetInventory().DropEntity(InventoryMode.PREDICTIVE, entity, attachment);
}
}
}
}
else
{
float pileHealth;
float qtyCoef;
vector destination = entity.GetPosition();
vector playerPosition;
//placed on helper memory point, if available, or on player pos, if available, or on entity pos otherwise
if (player)
{
playerPosition = player.GetPosition();
destination = playerPosition;
}
if (entity.MemoryPointExists("" + partName + "_materials"))
{
destination = entity.GetMemoryPointPos("" + partName + "_materials");
destination = g_Game.ObjectModelToWorld(entity, destination);
}
else if (entity.MemoryPointExists(partName))
{
destination = entity.GetMemoryPointPos(partName);
destination = g_Game.ObjectModelToWorld(entity, destination);
}
pileHealth = entity.GetHealth01(damagezoneName, "Health") * MiscGameplayFunctions.GetTypeMaxGlobalHealth(type);
qtyCoef = 1 - (entity.GetHealthLevel(damagezoneName) * Construction.DECONSTURCT_MATERIAL_LOSS) - Construction.DECONSTURCT_MATERIAL_LOSS;
quantity *= qtyCoef;
quantity = Math.Max(Math.Floor(quantity),1);
if (player)
{
vector posHead = MiscGameplayFunctions.GetPlayerHeadPosition(PlayerBase.Cast(player));
MiscGameplayFunctions.CreateItemBasePilesDispersed(type, posHead, destination, UAItemsSpreadRadius.NARROW, quantity, pileHealth, player);
}
else
{
MiscGameplayFunctions.CreateItemBasePilesEx(type, destination, quantity, pileHealth, true, throwPiles);
}
}
}
}
[Obsolete("1.30: Use SpawnConstructionMaterialPilesEx instead")]
static void SpawnConstructionMaterialPiles(notnull EntityAI entity, Man player, string cfg_path, string main_part_name, string damagezone_name = "", bool is_base = false)
{
SpawnConstructionMaterialPilesEx(entity, player, cfg_path, main_part_name, damagezone_name, is_base);
}
}
//! Data structure for passing parameters (extendable, modable)
class CollisionCheckData
{
ref array<Object> m_AdditionalExcludes;
string m_PartName;
int m_PrimaryGeometry;
int m_SecondaryGeometry;
void CollisionCheckData()
{
m_AdditionalExcludes = new array<Object>();
m_PartName = "";
m_PrimaryGeometry = ObjIntersectGeom;
m_SecondaryGeometry = ObjIntersectView;
}
}
class ConstructionBoxTrigger : ManTrigger
{
string m_PartName;
void SetPartName(string part_name)
{
m_PartName = part_name;
}
string GetPartName()
{
return m_PartName;
}
override protected void UpdateInsiders(int timeout)
{
super.UpdateInsiders(20);
}
bool IsColliding()
{
if (GetInsiders().Count() > 0)
{
return true;
}
return false;
}
}