LuaUserData.RegisterType("Barotrauma.DamageModifier") LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.Items.Components.Sonar"], "pingDirection") -- Returns true if there is a valid path from a hull breach to the character. function RealSonar.pathToBreach(character) -- Creature damage. if not RealSonar.Config.CreatureDamage and not character.isHuman then return false end -- Player damage. if not RealSonar.Config.PlayerDamage and character.isPlayer then return false end -- Bot damage. if not RealSonar.Config.BotDamage and character.isBot then return false end -- Returns false for ignored players. if character.isPlayer then for name in RealSonar.Config.IgnoredPlayers do if name == character.name then return false end end end -- Returns false for ignored characters. for identifier in RealSonar.Config.IgnoredCharacters do if identifier == character.Prefab.Identifier.Value then return false end end -- Returns true for characters outside. if not character.AnimController.currentHull then return true end -- Creature hull detection. if not RealSonar.Config.CreatureHullDetection and not character.isHuman and character.InWater then return true end -- Human hull detection. if not RealSonar.Config.HumanHullDetection and character.isHuman and character.InWater then return true end -- This code looks absolutely atrocious. It's so bad that I'm afraid to go near it to refactor it. local hulls = character.AnimController.currentHull.GetConnectedHulls(true, 100, true) local hullCount = 0 local waterFilledHulls = 0 for hull in hulls do hullCount = hullCount + 1 if hull.WaterPercentage >= 50 then waterFilledHulls = waterFilledHulls + 1 end for gap in hull.ConnectedGaps do -- If breach is external, more than 40% open, and water level is above the lower boundary of the gap . if gap.IsRoomToRoom == false and gap.open >= 0.4 and hull.Surface + hull.WaveY[#hull.WaveY - 1] > gap.rect.Y - gap.Size then -- If character position is below the gap. if character.Position.Y < gap.rect.Y - gap.Size then -- If the majority of the connected hulls are filled with water. if waterFilledHulls >= hullCount - 1 then return true end else return true end end end end return false end -- Returns true if there's a mild external hull breach in the character's structure. function RealSonar.hullBreached(character) if not character.AnimController.currentHull then return true end local hulls = character.AnimController.currentHull.GetConnectedHulls(true, 100, false) for hull in hulls do for gap in hull.ConnectedGaps do if gap.IsRoomToRoom == false and gap.open >= 0.4 then return true end end end return false end -- Returns the distance between two points in 2D space. function RealSonar.distanceBetween(point1, point2) local xd = point1.X - point2.X local yd = point1.Y - point2.Y return math.sqrt(xd*xd + yd*yd) end -- Returns true if the character is dangerously close to any active sonar source. -- Ideally this checks if the gap is in range instead of the character, but I can't find a way to pass it in. function RealSonar.characterInSonarRange(character) local inSonar = false local sonar for item in RealSonar.sonarItems do sonar = item.GetComponentString("Sonar") if tostring(sonar.currentMode) == "Active" and sonar.Voltage > sonar.minVoltage and RealSonar.distanceBetween(item.worldPosition, character.worldPosition) <= sonar.Range * 0.75 then if sonar.UseDirectionalPing then if RealSonar.inDirectionalSector(character, item) then inSonar = true break end else inSonar = true break end end end return inSonar end -- Returns true if the charcter is inside the direction ping sector. function RealSonar.inDirectionalSector(character, item) local sonar = item.GetComponentString("Sonar") local px, py = character.worldPosition.X, character.worldPosition.Y local cx, cy = item.Submarine.WorldPosition.X, item.Submarine.WorldPosition.Y local r = sonar.Range local theta = math.atan2(-sonar.pingDirection.Y, sonar.pingDirection.X) * 180 / math.pi local dx = px - cx local dy = py - cy local angle = math.atan2(dy, dx) * 180 / math.pi if angle < 0 then angle = angle + 360 end local angle_diff = math.abs(angle - theta) if angle_diff <= 15 or angle_diff >= 345 then local dist = math.sqrt((px - cx)^2 + (py - cy)^2) if dist <= r then return true end end return false end function RealSonar.getSonarResistanceData(character) local resistanceData = {} local damageModifier = 1 local muffleSonar = false if not character or not character.Inventory then table.insert(resistanceData, damageModifier) table.insert(resistanceData, muffleSonar) return resistanceData end local outerClothes = character.Inventory.GetItemInLimbSlot(InvSlotType.OuterClothes) local innerClothes = character.Inventory.GetItemInLimbSlot(InvSlotType.InnerClothes) local head = character.Inventory.GetItemInLimbSlot(InvSlotType.Head) local equippedClothing = {} if outerClothes then table.insert(equippedClothing, outerClothes) end if innerClothes then table.insert(equippedClothing, innerClothes) end if head then table.insert(equippedClothing, head) end local identifier local wearable local configOverride = false for clothing in equippedClothing do identifier = clothing.Prefab.Identifier.Value wearable = clothing.GetComponentString("Wearable") -- Checking config. for id, data in pairs(RealSonar.Config.WearableProtections) do if id == identifier then damageModifier = damageModifier - data["damageMultiplier"] configOverride = true if data["anechoic"] == true then muffleSonar = true end break end end -- Checking items. if not configOverride then if clothing.HasTag("anechoic") then muffleSonar = true end if wearable ~= nil then -- Prevents errors if the player equips a flashlight on their head etc. for modifier in wearable.DamageModifiers do if modifier.AfflictionTypes == "activesonar" then damageModifier = damageModifier - (1 - modifier.DamageMultiplier) end end end end configOverride = false end -- Constraints. Having 2 be the max allows for negative protection values on diving suits up to -1.0 if damageModifier < 0 then damageModifier = 0 elseif damageModifier > 2 then damageModifier = 2 end table.insert(resistanceData, damageModifier) table.insert(resistanceData, muffleSonar) return resistanceData end function RealSonar.getTerminalFromSub(sub) for terminal in RealSonar.sonarItems do if terminal.Submarine == sub then return terminal end end end function RealSonar.isEnabledForTerminal(terminal_id) if terminal_id == "navterminal" then if RealSonar.Config.SubmarineSonar then return true else return false end elseif terminal_id == "sonarmonitor" then if RealSonar.Config.BeaconSonar then return true else return false end elseif terminal_id =="shuttlenavterminal" then if RealSonar.Config.ShuttleSonar then return true else return false end elseif RealSonar.Config.SonarTerminals[terminal_id] then if RealSonar.Config.CustomSonar then return true else return false end -- The default return value is true because all terminals should be enabled by default. else return true end end function RealSonar.getSonarItems() RealSonar.sonarItems = {} for item in Item.RepairableItems do local item_id = item.Prefab.Identifier.Value for terminal_id in pairs(RealSonar.Config.SonarTerminals) do if item_id == terminal_id then table.insert(RealSonar.sonarItems, item) end end end end function RealSonar.getCaptain(sub) for _, character in pairs(Character.CharacterList) do if character.isHuman and character.Submarine == sub and character.IsCaptain then return character end end end function RealSonar.getVFXPresetString() if RealSonar.Config.VFXPreset == 2 then return "" elseif RealSonar.Config.VFXPreset == 1 then return "lowfx" else return "nofx" end end -- General reminder: It might be worth globally changing the naming convention of "", "shuttle", and "beacon" to match "high", "medium", "low" function RealSonar.getTerminalType(terminal_id) local damagePreset = "high" if RealSonar.Config.SonarTerminals[terminal_id] then damagePreset = RealSonar.Config.SonarTerminals[terminal_id]["damage"] end local terminal_type = "" if damagePreset == "medium" then terminal_type = "shuttle" elseif damagePreset == "low" then terminal_type = "beacon" end return terminal_type end function RealSonar.getEnemySub() RealSonar.EnemySub = nil if Submarine.MainSubs[2] then local sub = Submarine.MainSubs[2] RealSonar.EnemySub = {} RealSonar.EnemySub.terminal = RealSonar.getTerminalFromSub(sub) RealSonar.EnemySub.sonar = RealSonar.EnemySub.terminal.GetComponentString("Sonar") RealSonar.EnemySub.captain = RealSonar.getCaptain(sub) end end function RealSonar.updateEnemySonarMode() local terminal = RealSonar.EnemySub.terminal local sonar = RealSonar.EnemySub.sonar local captain = RealSonar.EnemySub.captain if tostring(sonar.currentMode) == "Passive" and captain and not captain.IsUnconscious and not captain.isDead and RealSonar.distanceBetween(captain.WorldPosition, terminal.WorldPosition) < 700 and not RealSonar.hullBreached(captain) then sonar.currentMode = 0 if Game.IsMultiplayer then terminal.CreateServerEvent(Components.Sonar, sonar) end return math.random(1,3) elseif tostring(sonar.currentMode) == "Active" and captain and not captain.IsUnconscious and not captain.isDead then sonar.currentMode = 1 if Game.IsMultiplayer then terminal.CreateServerEvent(Components.Sonar, sonar) end end return math.random(6,12) end function RealSonar.setSonarRanges() if Game.IsMultiplayer then return end local defaultRange = 12000 local terminal_id local sonar for terminal in RealSonar.sonarItems do terminal_id = terminal.Prefab.Identifier.Value if RealSonar.Config.SonarTerminals[terminal_id] then defaultRange = RealSonar.Config.SonarTerminals[terminal_id]["range"] end sonar = terminal.GetComponentString("Sonar") sonar.Range = defaultRange * RealSonar.Config.SonarRange --if Game.IsMultiplayer then terminal.CreateServerEvent(Components.Sonar, sonar) end end end -- This function adds the "anechoic" tag to specified items in the config file. -- Allowing bots to wear these items while doing repairs with the sonar on. function RealSonar.setItemTags() local property for item in Item.ItemList do for id, data in pairs(RealSonar.Config.WearableProtections) do if item.Prefab.Identifier.Value == id and data["anechoic"] then item.AddTag("anechoic") if Game.IsMultiplayer then property = item.SerializableProperties[Identifier("Tags")] Networking.CreateEntityEvent(item, Item.ChangePropertyEventData(property, item)) end end end end end -- This function corrects the brain hemorrhage and serversidelua afflictions. function RealSonar.updateAfflictions() local brainHemorrhages local hasServerSideLua local fx_preset = RealSonar.getVFXPresetString() for _, character in pairs(Character.CharacterList) do brainHemorrhages = {} hasServerSideLua = false for affliction in character.CharacterHealth.GetAllAfflictions() do if (character.isHuman and not character.isDead) and (affliction.Prefab.Identifier.Value == "brainhemorrhage" or affliction.Prefab.Identifier.Value == "brainhemorrhagelowfx" or affliction.Prefab.Identifier == "brainhemorrhagenofx") then table.insert(brainHemorrhages, affliction) end if affliction.Prefab.Identifier.Value == "serversidelua" then hasServerSideLua = true end end for _, brainHemorrhage in pairs(brainHemorrhages) do if brainHemorrhage.Prefab.Identifier.Value ~= string.format("brainhemorrhage%s", fx_preset) then RealSonar.GiveAfflictionHead(character, string.format("brainhemorrhage%s", fx_preset), brainHemorrhage.Strength) brainHemorrhage.Strength = 0 end end if not hasServerSideLua then RealSonar.SetAffliction(character, "serversidelua", 1) end end return 2 end function RealSonar.GiveAffliction(character, identifier, strength) local prefab = AfflictionPrefab.Prefabs[identifier] local affliction = prefab.Instantiate(strength) character.CharacterHealth.ApplyAffliction(character.AnimController.GetLimb(LimbType.Torso), affliction, true) end function RealSonar.SetAffliction(character, identifier, strength) local prefab = AfflictionPrefab.Prefabs[identifier] local affliction = prefab.Instantiate(strength) character.CharacterHealth.ApplyAffliction(character.AnimController.GetLimb(LimbType.Torso), affliction, false) end function RealSonar.GiveAfflictionHead(character, identifier, strength) local prefab = AfflictionPrefab.Prefabs[identifier] local affliction = prefab.Instantiate(strength) character.CharacterHealth.ApplyAffliction(character.AnimController.GetLimb(LimbType.Head), affliction, true) end -- Nav terminal sounds. function RealSonar.airSounds(character, strength) if strength > 95 then RealSonar.GiveAffliction(character, "sonarPingAirClose", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingAirMedium", 1) else RealSonar.GiveAffliction(character, "sonarPingAirFar", 1) end end function RealSonar.airSoundsDirectional(character, strength) if strength > 95 then RealSonar.GiveAffliction(character, "sonarPingAirCloseDirectional", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingAirMediumDirectional", 1) else RealSonar.GiveAffliction(character, "sonarPingAirFarDirectional", 1) end end function RealSonar.waterSounds(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingWaterVeryClose", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingWaterClose", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingWaterMedium", 1) else RealSonar.GiveAffliction(character, "SonarPingWaterFar", 1) end end function RealSonar.waterSoundsDirectional(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingWaterVeryCloseDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingWaterCloseDirectional", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingWaterMediumDirectional", 1) else RealSonar.GiveAffliction(character, "SonarPingWaterFarDirectional", 1) end end function RealSonar.suitSounds(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingSuitVeryClose", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingSuitClose", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingSuitMedium", 1) else RealSonar.GiveAffliction(character, "SonarPingSuitFar", 1) end end function RealSonar.suitSoundsDirectional(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingSuitVeryCloseDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingSuitCloseDirectional", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingSuitMediumDirectional", 1) else RealSonar.GiveAffliction(character, "SonarPingSuitFarDirectional", 1) end end -- Shuttle sounds function RealSonar.airSoundsShuttle(character, strength) if strength > 95 then RealSonar.GiveAffliction(character, "sonarPingAirCloseShuttle", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingAirMediumShuttle", 1) else RealSonar.GiveAffliction(character, "sonarPingAirFarShuttle", 1) end end function RealSonar.airSoundsShuttleDirectional(character, strength) if strength > 95 then RealSonar.GiveAffliction(character, "sonarPingAirCloseShuttleDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingAirMediumShuttleDirectional", 1) else RealSonar.GiveAffliction(character, "sonarPingAirFarShuttleDirectional", 1) end end function RealSonar.waterSoundsShuttle(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingWaterVeryCloseShuttle", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingWaterCloseShuttle", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingWaterMediumShuttle", 1) else RealSonar.GiveAffliction(character, "SonarPingWaterFarShuttle", 1) end end function RealSonar.waterSoundsShuttleDirectional(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingWaterVeryCloseShuttleDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingWaterCloseShuttleDirectional", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingWaterMediumShuttleDirectional", 1) else RealSonar.GiveAffliction(character, "SonarPingWaterFarShuttleDirectional", 1) end end function RealSonar.suitSoundsShuttle(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingSuitVeryCloseShuttle", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingSuitCloseShuttle", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingSuitMediumShuttle", 1) else RealSonar.GiveAffliction(character, "SonarPingSuitFarShuttle", 1) end end function RealSonar.suitSoundsShuttleDirectional(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingSuitVeryCloseShuttleDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingSuitCloseShuttleDirectional", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingSuitMediumShuttleDirectional", 1) else RealSonar.GiveAffliction(character, "SonarPingSuitFarShuttleDirectional", 1) end end -- Beacon sounds function RealSonar.airSoundsBeacon(character, strength) if strength > 95 then RealSonar.GiveAffliction(character, "sonarPingAirCloseBeacon", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingAirMediumBeacon", 1) else RealSonar.GiveAffliction(character, "sonarPingAirFarBeacon", 1) end end function RealSonar.airSoundsBeaconDirectional(character, strength) if strength > 95 then RealSonar.GiveAffliction(character, "sonarPingAirCloseBeaconDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingAirMediumBeaconDirectional", 1) else RealSonar.GiveAffliction(character, "sonarPingAirFarBeaconDirectional", 1) end end function RealSonar.waterSoundsBeacon(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingWaterVeryCloseBeacon", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingWaterCloseBeacon", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingWaterMediumBeacon", 1) else RealSonar.GiveAffliction(character, "SonarPingWaterFarBeacon", 1) end end function RealSonar.waterSoundsBeaconDirectional(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingWaterVeryCloseBeaconDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingWaterCloseBeaconDirectional", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingWaterMediumBeaconDirectional", 1) else RealSonar.GiveAffliction(character, "SonarPingWaterFarBeaconDirectional", 1) end end function RealSonar.suitSoundsBeacon(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingSuitVeryCloseBeacon", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingSuitCloseBeacon", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingSuitMediumBeacon", 1) else RealSonar.GiveAffliction(character, "SonarPingSuitFarBeacon", 1) end end function RealSonar.suitSoundsBeaconDirectional(character, strength) if strength > 75 then RealSonar.GiveAffliction(character, "sonarPingSuitVeryCloseBeaconDirectional", 1) elseif strength > 50 then RealSonar.GiveAffliction(character, "sonarPingSuitCloseBeaconDirectional", 1) elseif strength > 25 then RealSonar.GiveAffliction(character, "sonarPingSuitMediumBeaconDirectional", 1) else RealSonar.GiveAffliction(character, "SonarPingSuitFarBeaconDirectional", 1) end end