modded class SearchZone_Base { ref array m_LootArrayFinalized; protected int m_LootCount; protected float m_ItemDamage; protected float m_BleedingChance; protected float m_TrapDelay; protected bool m_TrapActive; protected bool m_DestroyZoneUponDetonation; protected bool m_GetAllLootAtOnce; protected bool m_DeleteWhenEmpty; protected bool m_ZoneLocked; // action block (serverside) protected bool m_DeleteParentUponZoneDeletion; Object m_ParentObject; // Called when entity is created and constructed override void EEInit() { super.EEInit(); SetAllowDamage(false); m_DeleteWhenEmpty = true; m_LootArrayFinalized = new array; LoadConfig(); if (DeletionConditionsMet()) { DeleteSafe(); return; } ProcessAvailableActions(); } // Called just before entity is deleted override void EEDelete(EntityAI parent) { if (Get_DeleteParentUponZoneDeletion() == true && m_ParentObject) { EntityAI parentEntity = EntityAI.Cast(m_ParentObject); if (parentEntity) { parentEntity.DeleteSafe(); } } super.EEDelete(parent); } // Called when item is taken out from this entity's cargo override void EECargoOut(EntityAI item) { super.EECargoOut(item); if (DeletionConditionsMet()) { DeleteSafe(); } } // Called when item is detached from this zone entity override void EEItemDetached(EntityAI item, string slot_name) { super.EEItemDetached(item, slot_name); if (DeletionConditionsMet()) { DeleteSafe(); } } /** * @brief Checks if there are any items in cargo */ bool HasItemsInCargo() { GameInventory inv = this.GetInventory(); if (!inv) return false; CargoBase cargo = inv.GetCargo(); return cargo && cargo.GetItemCount() > 0; } /** * @brief Checks if zone should be deleted */ bool DeletionConditionsMet() { if (m_DeleteWhenEmpty && !HasLootLeft() && !HasItemsInCargo()) { return true; } return false; } /** * @brief Checks if there is any loot left in the zone */ bool HasLootLeft() { return m_LootCount > 0 && m_LootArrayFinalized.Count() > 0; } /** @brief Searches for config of this zone in SearchZonesManager and calls SetParamsFromConfig() if found */ void LoadConfig() { SearchZonesManager manager = GetSearchZonesManager(); if (!manager) { DeleteSafe(); return; } ref array configs = manager.GetConfig(); if (!configs) { DeleteSafe(); return; } string zoneType = this.GetType(); for (int i = 0, cnt = configs.Count(); i < cnt; ++i) { ref SearchZoneConfig config = configs.Get(i); if (!config) { continue; } ref TStringArray configClassnames = config.ConfigClassnames; if (!configClassnames) { continue; } int configClassnamesCount = configClassnames.Count(); int index = configClassnames.Find(zoneType); if (index == -1) { continue; } SetParamsFromConfig(config); return; } Print("[LootSearchers] Config for " + zoneType + " not found!"); DeleteSafe(); } /** @brief Sets zone parameters according to config @param zoneConfig Config of this zone */ void SetParamsFromConfig(SearchZoneConfig zoneConfig) { if (!zoneConfig) { DeleteSafe(); return; } Set_BleedingChance(zoneConfig.BleedingChance); Set_ItemDamage(zoneConfig.ItemDamage); Set_GetAllLootAtOnce(zoneConfig.GetAllLootAtOnce); Set_TrapDelay(zoneConfig.TrapDelay); Set_DeleteWhenEmpty(zoneConfig.DeleteWhenEmpty); // Parent object if (zoneConfig.BindToParentObject == true && zoneConfig.ParentObjects.Count() > 0) { ParentObjectConfig parent = zoneConfig.ParentObjects.GetRandomElement(); m_ParentObject = GetGame().CreateObjectEx(parent.ParentObjectClassname, this.GetPosition(), ECE_PLACE_ON_SURFACE|ECE_NOLIFETIME|ECE_ROTATIONFLAGS|ECE_SETUP); if (m_ParentObject) { if (parent.BindToMemoryPoint == true && m_ParentObject.MemoryPointExists(parent.MemoryPointName)) this.SetPosition(m_ParentObject.ModelToWorld(m_ParentObject.GetMemoryPointPos(parent.MemoryPointName))); else this.SetPosition(m_ParentObject.ModelToWorld(parent.ZoneLocalCoordinates)); vector newZoneOrientation = parent.ZoneOrientation + m_ParentObject.GetOrientation(); this.SetOrientation(newZoneOrientation); Set_DeleteParentUponZoneDeletion(parent.DeleteParentUponZoneDeletion); } } // Trap if (Math.RandomFloat(0, 100) < zoneConfig.TrapSpawnChance) { Set_TrapActive(true); Math.Randomize(-1); if (Math.RandomFloat(0, 100) < zoneConfig.DestroyZoneUponTrapDetonationChance) Set_DestroyZoneUponDetonation(true); } // Adding possible loot array lootArray = new array; foreach (ref SearchZoneItemConfig itemCfg : zoneConfig.LootArray) { lootArray.Insert(itemCfg); } // Randomizing loot count m_LootCount = Math.RandomIntInclusive(zoneConfig.LootCountMin, zoneConfig.LootCountMax); if (m_LootCount <= 0) { DeleteSafe(); return; } // Randomizing loot PreprocessLootList(lootArray); // Setting health SetHealthAccordingToLootCount(); } /** * @brief Randomizes loot list * @param lootArray notnull array of loot items */ void PreprocessLootList(notnull array lootArray) { // Remove items with zero chance for (int i = lootArray.Count() - 1; i >= 0; i--) { if (lootArray.Get(i).ObtainingChance <= 0) { lootArray.Remove(i); } } // Randomize loot if (lootArray.Count() > m_LootCount) { for (int j = 0; j < m_LootCount; j++) { int itemIndex = SearchZoneWeightedRandom.SelectMostProbableItemIndex(lootArray); m_LootArrayFinalized.Insert(lootArray.Get(itemIndex)); lootArray.Remove(itemIndex); } } else { foreach (ref SearchZoneItemConfig itemCfg : lootArray) { m_LootArrayFinalized.Insert(itemCfg); } } } /** * @brief Sets available actions for player based on loot list * @note This function is calling SetSynchDirty() */ void ProcessAvailableActions() { SetHealthAccordingToLootCount(); TStringArray availableActions = new TStringArray; int itemsCount = m_LootArrayFinalized.Count(); for (int i = 0; i < itemsCount; ++i) { SearchZoneItemConfig itemCfg = m_LootArrayFinalized.Get(i); int actionsCount = itemCfg.CanBeObtainedBy.Count(); for (int b = 0; b < actionsCount; ++b) { if (availableActions.Find(itemCfg.CanBeObtainedBy.Get(b)) == -1) availableActions.Insert(itemCfg.CanBeObtainedBy.Get(b)); } } if (availableActions.Count() <= 0) { if (DeletionConditionsMet()) { DeleteSafe(); return; } } CanBeSearchedBy_Hands = availableActions.Find("Hands") != -1; CanBeSearchedBy_Shovel = availableActions.Find("Shovel") != -1; CanBeSearchedBy_FieldShovel = availableActions.Find("FieldShovel") != -1; CanBeSearchedBy_Wrench = availableActions.Find("Wrench") != -1; CanBeSearchedBy_PipeWrench = availableActions.Find("PipeWrench") != -1; CanBeSearchedBy_Crowbar = availableActions.Find("Crowbar") != -1; CanBeSearchedBy_Screwdriver = availableActions.Find("Screwdriver") != -1; CanBeSearchedBy_Hatchet = availableActions.Find("Hatchet") != -1; CanBeSearchedBy_LootingHammer = availableActions.Find("LootingHammer") != -1; CanBeSearchedBy_CustomKnife = availableActions.Find("CustomKnife") != -1; CanBeSearchedBy_SledgeHammer = availableActions.Find("SledgeHammer") != -1; CanBeSearchedBy_LootingKeys = availableActions.Find("LootingKeys") != -1; CanBeSearchedBy_LootingPickaxe = availableActions.Find("LootingPickaxe") != -1; SetSynchDirty(); } /** * @brief Preprocesses search for player (called from action) * @param player Player who is searching * @param searchType Type of search ("Hands" etc.) */ void PreprocessSearch(PlayerBase player, string searchType) { if (!player || searchType == string.Empty) { return; } if (Get_TrapActive() == true) { ActivateTrap(player.GetPosition()); return; } ProcessSearch(player, searchType); if (Get_GetAllLootAtOnce()) { ProcessSearch(player, searchType); while (m_LootCount > 0 && m_LootArrayFinalized.Count() > 0) { ProcessSearch(player, searchType, false); } } if (DeletionConditionsMet()) // no need to process actions if zone is going to be deleted { DeleteSafe(); return; } ProcessAvailableActions(); } /** * @brief Searches for item in loot list and spawns it, applies damage to item and player * @param player Player who is searching * @param searchType Type of search ("Hands" etc.) * @param applyDamage Should damage be applied to action item or player or gloves */ void ProcessSearch(notnull PlayerBase player, string searchType, bool applyDamage = true) { SearchZoneItemConfig itemCfg; if (!GetRandomItemBySearchType(searchType, itemCfg)) { return; } ObtainItem(itemCfg, player.GetPosition()); m_LootArrayFinalized.RemoveItem(itemCfg); m_LootCount--; if (applyDamage) { ApplySearchDamage(player, searchType); } } /** * @brief Spawns item from loot list */ void ObtainItem(SearchZoneItemConfig itemCfg, vector spawnpos) { if (!TrySpawningItemWithAttachments(itemCfg, spawnpos)) { TrySpawningRegularItem(itemCfg, spawnpos); } } /** * @brief Tries to spawn item with attachments * @param itemCfg Item from loot list * @param spawnpos Position where item should be spawned * @return True if item was spawned, false otherwise * @note If zone inventory is full or missing, item will be spawned on given position */ bool TrySpawningItemWithAttachments(SearchZoneItemConfig itemCfg, vector spawnpos) { SearchZoneItemWithAttachmentsConfig itemWithAttCfg; if (!GetItemWithAttachmentsConfig(itemCfg.Typename, itemWithAttCfg)) { return false; } int iFlags = ECE_SETUP|ECE_LOCAL|ECE_NOSURFACEALIGN|ECE_KEEPHEIGHT; SearchZoneItemWithAttachments objWithAtt = SearchZoneItemWithAttachments.Cast(GetGame().CreateObjectEx("SearchZoneItemWithAttachments", spawnpos, iFlags)); if (!objWithAtt) { Print("[LootSearchers]Failed casting " + itemCfg.Typename + " to SearchZoneItemWithAttachments. Check script!"); return false; } objWithAtt.SetParentZone(this); objWithAtt.SetMainItemConfig(itemCfg); objWithAtt.SetConfig(itemWithAttCfg); objWithAtt.SpawnLoot(); return true; } /** * @brief Searches for item with attachments config * @param presetName Name of the item * @param itemWithAtt Item with attachments config * @return True if item was found, false otherwise */ bool GetItemWithAttachmentsConfig(string presetName, out SearchZoneItemWithAttachmentsConfig itemWithAtt) { SearchZonesItemsWithAttachmentsManager attachmentsManager = GetSearchZonesItemsWithAttachmentsManager(); if (!attachmentsManager) { return false; } array attachmentsConfigs = attachmentsManager.GetConfig(); if (!attachmentsConfigs) { return false; } for (int i = 0, cnt = attachmentsConfigs.Count(); i < cnt; ++i) { SearchZoneItemWithAttachmentsConfig itemWithAttCfg = attachmentsConfigs.Get(i); if (!itemWithAttCfg || itemWithAttCfg.PresetName != presetName) { continue; } itemWithAtt = itemWithAttCfg; return true; } return false; } /** * @brief Tries to spawn regular item * @param itemCfg Item from loot list * @param spawnpos Position where item should be spawned * @return True if item was spawned, false otherwise * @note If zone inventory is full or missing, item will be spawned on given position */ bool TrySpawningRegularItem(notnull SearchZoneItemConfig itemCfg, vector spawnpos) { GameInventory inv = this.GetInventory(); Object newObject; // Try to spawn in cargo if (inv) { InventoryLocation invLoc = new InventoryLocation(); if (inv.FindFirstFreeLocationForNewEntity(itemCfg.Typename, FindInventoryLocationType.CARGO, invLoc)) { newObject = GetGame().SpawnEntity(itemCfg.Typename, invLoc, ECE_IN_INVENTORY|ECE_DYNAMIC_PERSISTENCY, RF_DEFAULT); } } // Try to spawn on ground if inventory is full or missing if (!newObject) { newObject = GetGame().CreateObjectEx(itemCfg.Typename, spawnpos, ECE_NOSURFACEALIGN|ECE_KEEPHEIGHT|ECE_DYNAMIC_PERSISTENCY); } ItemBase newItem = ItemBase.Cast(newObject); if (!newItem) { Print("[LootSearchers]Failed to spawn or item is not an ItemBase. Check config!: " + itemCfg.Typename); return false; } if (itemCfg.IsQuantityPercentageBased == true) { SearchZoneMiscFunctions.TryToSetRandomQuantityPercentage(newItem, itemCfg.QuantityMin, itemCfg.QuantityMax); } else { SearchZoneMiscFunctions.TryToSetRandomQuantity(newItem, itemCfg.QuantityMin, itemCfg.QuantityMax); } SearchZoneMiscFunctions.TryToSetRandomHealthPercentage(newItem, itemCfg.HealthPercentageMin, itemCfg.HealthPercentageMax); newItem.Update(); return true; } /** * @brief Searches for a random item in loot list by search type * @param searchType Type of search ("Hands" etc.) * @param itemCfg Item from loot list * @return True if item was found, false otherwise */ bool GetRandomItemBySearchType(string searchType, out SearchZoneItemConfig itemCfg) { array properItems = new array; int totalLootCount = m_LootArrayFinalized.Count(); int itemActionsCount = 0; for (int lootIndex = 0; lootIndex < totalLootCount; ++lootIndex) { itemCfg = m_LootArrayFinalized.Get(lootIndex); if (!itemCfg) { continue; } itemActionsCount = itemCfg.CanBeObtainedBy.Count(); for (int i = 0; i < itemActionsCount; ++i) { if (searchType == itemCfg.CanBeObtainedBy.Get(i)) { properItems.Insert(itemCfg); } } } if (properItems.Count() > 0) { itemCfg = properItems.GetRandomElement(); return true; } return false; } /** * @brief Applies damage to item in hands (or player, his gloves etc.) * @param player Player who is searching * @param searchType Type of search ("Hands" etc.) */ void ApplySearchDamage(notnull PlayerBase player, string searchType) { if (!player || searchType == string.Empty) { return; } if (searchType != "Hands" && m_ItemDamage > 0) { EntityAI inHands = player.GetHumanInventory().GetEntityInHands(); if (!inHands) { return; } float currentItemHealth = inHands.GetHealth(); inHands.SetHealth(currentItemHealth - m_ItemDamage); } else if (searchType == "Hands") { ItemBase gloves = ItemBase.Cast(player.FindAttachmentBySlotName("Gloves")); if (gloves && m_ItemDamage > 0) { float currentGlovesHealth = gloves.GetHealth(); if (currentGlovesHealth > 0) { gloves.SetHealth(currentGlovesHealth - m_ItemDamage); } } if (m_BleedingChance > 0 && (!gloves || gloves.GetHealth() <= 0)) { if (Math.RandomFloat(0, 100) < m_BleedingChance) { int randNum = Math.RandomIntInclusive(0, PlayerBase.m_BleedingSourcesUp.Count() - 1); player.GetBleedingManagerServer().AttemptAddBleedingSourceBySelection(PlayerBase.m_BleedingSourcesUp.Get(randNum)); if (Math.RandomFloat01() < 0.5) player.ProcessDirectDamage(DT_CUSTOM, this, "RightHand", "FireDamage", "0 0 0", 1); else player.ProcessDirectDamage(DT_CUSTOM, this, "LeftHand", "FireDamage", "0 0 0", 1); } } } } /** * @brief Manages trap activation process * @param position Position of the trap */ void ActivateTrap(vector position) { Set_ZoneLocked(true); GetGame().RPCSingleParam(this, SearchZoneRPC.TRAP_ACTIVATED, new Param1(true), true); GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(this.OnTrapExplosionStart, Get_TrapDelay()*1000, false, position); } /** * @brief Creates trap and starts explosion * @param position Position of the trap */ void OnTrapExplosionStart(vector position) { vector trapPosition = position + "0 0.1 0"; RGD5Grenade trap = RGD5Grenade.Cast(GetGame().CreateObjectEx("RGD5Grenade", trapPosition, ECE_KEEPHEIGHT|ECE_SETUP)); trap.SetPosition(trapPosition); trap.Update(); GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(this.OnTrapExplosionFinish, 50, false, trap); } /** * @brief Activates trap and deletes zone if needed * @param trap Trap object */ void OnTrapExplosionFinish(RGD5Grenade trap) { if (trap) trap.ActivateImmediate(); if (Get_DestroyZoneUponDetonation() == true) { DeleteSafe(); return; } else { Set_TrapActive(false); Set_ZoneLocked(false); } } // Getters & Setters /*! \brief Zone server lock */ void Set_ZoneLocked(bool state) { m_ZoneLocked = state; } bool Get_ZoneLocked() { return m_ZoneLocked; } /*! \brief Trap state */ void Set_TrapActive(bool state) { m_TrapActive = state; } bool Get_TrapActive() { return m_TrapActive; } /*! \brief Trap delay */ void Set_TrapDelay(float seconds) { m_TrapDelay = seconds; } float Get_TrapDelay() { return m_TrapDelay; } /*! \brief Destroy upon trap detonation */ void Set_DestroyZoneUponDetonation(bool state) { m_DestroyZoneUponDetonation = state; } bool Get_DestroyZoneUponDetonation() { return m_DestroyZoneUponDetonation; } /*! \brief Delete parent */ void Set_DeleteParentUponZoneDeletion(bool state) { m_DeleteParentUponZoneDeletion = state; } bool Get_DeleteParentUponZoneDeletion() { return m_DeleteParentUponZoneDeletion; } /*! \brief Set delete when empty */ void Set_DeleteWhenEmpty(bool state) { m_DeleteWhenEmpty = state; } bool Get_DeleteWhenEmpty() { return m_DeleteWhenEmpty; } /*! \brief BleedingChance */ void Set_BleedingChance(float value) { m_BleedingChance = value; } float Get_BleedingChance() { return m_BleedingChance; } /*! \brief Item damage */ void Set_ItemDamage(float value) { m_ItemDamage = value; } float Get_ItemDamage() { return m_ItemDamage; } /*! \brief Getting all loot by single action */ void Set_GetAllLootAtOnce(bool state) { m_GetAllLootAtOnce = state; } bool Get_GetAllLootAtOnce() { return m_GetAllLootAtOnce; } protected void SetHealthAccordingToLootCount() { SetAllowDamage(true); float newHealth01 = GameConstants.DAMAGE_PRISTINE_VALUE; if (m_LootCount <= 0) { newHealth01 = GameConstants.DAMAGE_RUINED_VALUE; } else if (m_LootCount == 1) { newHealth01 = GameConstants.DAMAGE_BADLY_DAMAGED_VALUE; } else if (m_LootCount == 2) { newHealth01 = GameConstants.DAMAGE_DAMAGED_VALUE; } else if (m_LootCount == 3) { newHealth01 = GameConstants.DAMAGE_WORN_VALUE; } SetHealth01("", "Health", newHealth01); SetAllowDamage(false); } }