C#:
using System;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using System.Linq;
// ____ _ _ _
// / ___|(_) __ _(_) | ___
// \___ \| |/ _` | | |/ _ \
// ___) | | (_| | | | (_) |
// |____/|_|\__, |_|_|\___/
// |___/
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾
// ✦ RUST PLUGINS ✦
// ✦ Sigilo.dev ✦
// ☽✧✵✧✵✧✵✧✵✧✵✧✵✧☾
namespace Oxide.Plugins
{
[Info("Comet", "Sigilo", "1.1.3")]
class Comet : RustPlugin
{
const bool en = true;
static Comet ins;
private List<GameObject> activeComets = new List<GameObject>();
private List<CometFall> activeCometComponents = new List<CometFall>();
private static readonly WaitForSeconds Wait0_05 = new WaitForSeconds(0.05f);
private static readonly WaitForSeconds Wait0_1 = new WaitForSeconds(0.1f);
private static readonly Dictionary<float, WaitForSeconds> WaitDictionary = new Dictionary<float, WaitForSeconds>();
private static WaitForSeconds GetWait(float time)
{
if (time <= 0) return null;
WaitForSeconds wait;
if (WaitDictionary.TryGetValue(time, out wait))
return wait;
wait = new WaitForSeconds(time);
WaitDictionary[time] = wait;
return wait;
}
#region Permissions
const string PERMISSION_ADMIN = "comet.admin";
#endregion
#region Signal System
private const string SUPPLY_SIGNAL_PREFAB = "assets/prefabs/tools/supply signal/supplysignal.prefab";
private const ulong COMET_SIGNAL_SKIN_ID = 2791026167;
private StoredData storedData;
private class StoredData
{
public Dictionary<ulong, double> Cooldowns { get; set; } = new Dictionary<ulong, double>();
}
void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, storedData);
void LoadData()
{
try
{
storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
CleanupExpiredCooldowns();
}
catch
{
storedData = new StoredData();
}
}
void OnServerSave() => SaveData();
private void CleanupExpiredCooldowns()
{
if (storedData?.Cooldowns == null)
return;
double currentTime = GetCurrentTime();
foreach (var userId in storedData.Cooldowns
.Where(kvp => (currentTime - kvp.Value) >= _config.signalConfig.CooldownSeconds)
.Select(kvp => kvp.Key))
{
storedData.Cooldowns.Remove(userId);
}
SaveData();
}
private bool IsOnCooldown(BasePlayer player)
{
if (storedData?.Cooldowns == null || !storedData.Cooldowns.TryGetValue(player.userID, out double lastUsage))
return false;
double timeSinceLastUse = GetCurrentTime() - lastUsage;
float cooldownTime = GetPlayerCooldownTime(player);
return timeSinceLastUse < cooldownTime;
}
private double GetRemainingCooldown(BasePlayer player)
{
if (storedData?.Cooldowns == null || !storedData.Cooldowns.TryGetValue(player.userID, out double lastUsage))
return 0;
double timeSinceLastUse = GetCurrentTime() - lastUsage;
float cooldownTime = GetPlayerCooldownTime(player);
double remainingTime = cooldownTime - timeSinceLastUse;
return remainingTime > 0 ? remainingTime : 0;
}
private float GetPlayerCooldownTime(BasePlayer player)
{
return _config.signalConfig.CooldownSeconds;
}
private void SetCooldown(BasePlayer player)
{
if (storedData?.Cooldowns == null)
storedData = new StoredData();
storedData.Cooldowns[player.userID] = GetCurrentTime();
SaveData();
}
private double GetCurrentTime() => DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds;
private void ResetCooldown(BasePlayer player)
{
if (storedData?.Cooldowns == null)
return;
storedData.Cooldowns.Remove(player.userID);
SaveData();
SendReply(player, "Your Comet Signal cooldown has been reset.");
}
private void OnExplosiveThrown(BasePlayer player, BaseEntity entity)
{
SupplySignal signal = entity as SupplySignal;
if (signal == null || signal.skinID != COMET_SIGNAL_SKIN_ID)
return;
Puts($"COMET: Player {player.displayName} threw a comet signal");
signal.CancelInvoke(signal.Explode);
if (IsOnCooldown(player))
{
double remainingMinutes = Math.Ceiling(GetRemainingCooldown(player) / 60.0);
string cooldownMessage = $"You must wait {remainingMinutes} minutes before using another Comet Signal.";
SendReply(player, cooldownMessage);
ReturnCometSignal(player, signal);
return;
}
SetCooldown(player);
if (signal != null && !signal.IsDestroyed)
{
signal.CancelInvoke(signal.KillMessage);
signal.Invoke(signal.KillMessage, 60f);
timer.Once(3f, () => {
if (signal == null || signal.IsDestroyed)
{
Puts("COMET: Signal was destroyed before it could land");
return;
}
Vector3 signalPosition = signal.transform.position;
Puts($"COMET: Signal has landed at position: {signalPosition}");
SpawnFlareMarker(signalPosition);
timer.Repeat(1.5f, (int)Math.Ceiling(_config.signalConfig.WarmupTime / 1.5f), () => {
if (signal != null && !signal.IsDestroyed)
{
Effect.server.Run("assets/bundled/prefabs/fx/fire/fire_v2.prefab", signalPosition);
}
});
foreach (var nearbyPlayer in BasePlayer.activePlayerList.Where(p =>
Vector3.Distance(p.transform.position, signalPosition) <= 100f))
{
SendReply(nearbyPlayer, $"A Comet has been called! Impact in {_config.signalConfig.WarmupTime} seconds. Take cover!");
}
timer.Once(_config.signalConfig.WarmupTime, () => {
Puts($"COMET: Spawning comet at exact position: {signalPosition}");
foreach (var nearbyPlayer in BasePlayer.activePlayerList.Where(p =>
Vector3.Distance(p.transform.position, signalPosition) <= 150f))
{
SendReply(nearbyPlayer, "Comet incoming! TAKE COVER NOW!");
}
SpawnComet(signalPosition);
if (signal != null && !signal.IsDestroyed)
signal.Kill();
});
});
}
}
private void SpawnFlareMarker(Vector3 position)
{
BaseEntity flareEntity = GameManager.server.CreateEntity("assets/prefabs/tools/flareold/flare.deployed.prefab", position, Quaternion.identity);
if (flareEntity != null)
{
flareEntity.Spawn();
timer.Once(_config.signalConfig.WarmupTime + 10f, () => {
if (flareEntity != null && !flareEntity.IsDestroyed)
flareEntity.Kill();
});
}
Effect.server.Run("assets/bundled/prefabs/fx/fire/fire_v3.prefab", position, Vector3.up, null, true);
}
private void ReturnCometSignal(BasePlayer player, SupplySignal signal)
{
Item item = ItemManager.CreateByName("supply.signal", 1, COMET_SIGNAL_SKIN_ID);
if (item != null)
{
item.name = _config.signalConfig.DisplayName;
player.GiveItem(item);
}
if (signal != null && !signal.IsDestroyed)
signal.Kill();
}
[ChatCommand("givecometcaller")]
void GiveCometSignalCommand(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN))
{
SendReply(player, "You do not have permission to use this command.");
return;
}
BasePlayer targetPlayer = player;
if (args.Length > 0 && args[0].ToLower() != "me")
{
if (args[0].ToLower() == "reset")
{
if (args.Length > 1)
{
var target = BasePlayer.Find(args[1]);
if (target == null)
{
SendReply(player, "Player not found.");
return;
}
ResetCooldown(target);
SendReply(player, $"You've reset the cooldown for {target.displayName}.");
}
else
{
ResetCooldown(player);
}
return;
}
targetPlayer = BasePlayer.Find(args[0]);
if (targetPlayer == null)
{
SendReply(player, "Player not found.");
return;
}
args = args.Skip(1).ToArray();
}
int amount = 1;
if (args.Length > 0)
{
if (!int.TryParse(args[0], out amount) || amount <= 0)
{
SendReply(player, "Invalid amount. Please specify a positive number.");
return;
}
}
GivePlayerCometSignal(targetPlayer, amount);
if (targetPlayer == player)
SendReply(player, $"You've received {amount} Comet Signal{(amount > 1 ? "s" : "")}.");
else
{
SendReply(player, $"You've given {amount} Comet Signal{(amount > 1 ? "s" : "")} to {targetPlayer.displayName}.");
SendReply(targetPlayer, $"You've received {amount} Comet Signal{(amount > 1 ? "s" : "")} from an admin.");
}
}
void GivePlayerCometSignal(BasePlayer player, int amount = 1)
{
Item item = ItemManager.CreateByName("supply.signal", amount, COMET_SIGNAL_SKIN_ID);
if (item != null)
{
item.name = _config.signalConfig.DisplayName;
item.MarkDirty();
player.GiveItem(item);
}
}
#endregion
#region Hooks
void Init()
{
ins = this;
try
{
Puts("Initializing Comet plugin...");
permission.RegisterPermission(PERMISSION_ADMIN, this);
LoadConfig();
LoadData();
_config = Config.ReadObject<PluginConfig>();
if (_config == null)
{
Puts("Config is null, loading default config...");
LoadDefaultConfig();
_config = Config.ReadObject<PluginConfig>();
}
if (_config.cometConfig == null)
{
Puts("Comet config is null, loading default config...");
_config = PluginConfig.DefaultConfig();
SaveConfig();
}
if (_config.signalConfig == null)
{
Puts("Signal config is null, updating config...");
_config.signalConfig = PluginConfig.DefaultConfig().signalConfig;
SaveConfig();
}
Puts("Comet plugin initialized successfully!");
}
catch (Exception ex)
{
PrintError("Error initializing Comet plugin: " + ex.Message);
LoadDefaultConfig();
_config = Config.ReadObject<PluginConfig>();
permission.RegisterPermission(PERMISSION_ADMIN, this);
}
}
void OnServerInitialized()
{
if (ins == null)
ins = this;
if (storedData == null)
LoadData();
CleanupExpiredCooldowns();
}
void Unload()
{
try {
if (activeComets != null && activeComets.Count > 0)
{
Puts($"Cleaning up {activeComets.Count} tracked comet objects on plugin unload");
for (int i = activeComets.Count - 1; i >= 0; i--)
{
var cometObj = activeComets[i];
if (cometObj != null)
{
UnityEngine.Object.Destroy(cometObj);
}
activeComets.RemoveAt(i);
}
}
if (activeCometComponents != null && activeCometComponents.Count > 0)
{
Puts($"Cleaning up {activeCometComponents.Count} comet components on plugin unload");
for (int i = activeCometComponents.Count - 1; i >= 0; i--)
{
var component = activeCometComponents[i];
if (component != null)
{
component.CleanupOnPluginUnload();
UnityEngine.Object.Destroy(component);
}
activeCometComponents.RemoveAt(i);
}
}
}
catch (Exception ex)
{
Debug.LogError($"Error during Comet plugin unload: {ex}");
}
finally
{
ins = null;
}
}
#endregion
#region Commands
[ChatCommand("comet")]
void SummonCometCommand(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN))
{
SendReply(player, "You do not have permission to use this command.");
return;
}
Vector3 position;
if (args.Length > 0 && args[0].ToLower() == "here")
{
RaycastHit hit;
if (Physics.Raycast(player.eyes.HeadRay(), out hit, 1000f, LayerMask.GetMask("Terrain", "World", "Construction")))
{
position = hit.point;
}
else
{
return;
}
}
else
{
position = player.transform.position;
}
SpawnComet(position);
SendReply(player, "A comet is falling from the sky");
}
[ConsoleCommand("comet.summon")]
void ConsoleSummonComet(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
if (player != null && !permission.UserHasPermission(player.UserIDString, PERMISSION_ADMIN))
{
SendReply(player, "You do not have permission to use this command.");
return;
}
Vector3 position;
if (arg.Args != null && arg.Args.Length >= 3)
{
float x, y, z;
if (float.TryParse(arg.Args[0].ToString(), out x) &&
float.TryParse(arg.Args[1].ToString(), out y) &&
float.TryParse(arg.Args[2].ToString(), out z))
{
position = new Vector3(x, y, z);
}
else
{
if (player != null)
SendReply(player, "Invalid position coordinates. Format: comet.summon <x> <y> <z>");
else
Puts("Invalid position coordinates. Format: comet.summon <x> <y> <z>");
return;
}
}
else if (player != null)
{
position = player.transform.position;
}
else
{
Puts("When using from server console, you must specify coordinates: comet.summon <x> <y> <z>");
return;
}
SpawnComet(position);
if (player != null)
SendReply(player, "A comet is falling from the sky");
else
Puts("A comet is falling from the sky");
}
[ConsoleCommand("comet.give")]
void ConsoleGiveCometSignal(ConsoleSystem.Arg arg)
{
BasePlayer caller = arg.Player();
if (caller != null && !permission.UserHasPermission(caller.UserIDString, PERMISSION_ADMIN))
{
SendReply(caller, "You do not have permission to use this command.");
return;
}
if (arg.Args == null || arg.Args.Length < 1)
{
string message = "Usage: comet.give <player name/steamID> [amount=1]";
if (caller != null)
SendReply(caller, message);
else
Puts(message);
return;
}
BasePlayer targetPlayer = null;
string playerNameOrId = arg.Args[0].ToString();
if (playerNameOrId.All(char.IsDigit))
{
targetPlayer = BasePlayer.FindByID(ulong.Parse(playerNameOrId));
}
if (targetPlayer == null)
{
targetPlayer = BasePlayer.Find(playerNameOrId);
}
if (targetPlayer == null)
{
string message = $"Player '{playerNameOrId}' not found.";
if (caller != null)
SendReply(caller, message);
else
Puts(message);
return;
}
int amount = 1;
if (arg.Args.Length > 1 && (!int.TryParse(arg.Args[1].ToString(), out amount) || amount <= 0))
{
string message = "Invalid amount. Please specify a positive number.";
if (caller != null)
SendReply(caller, message);
else
Puts(message);
return;
}
GivePlayerCometSignal(targetPlayer, amount);
string successMessage = $"Given {amount} Comet Signal{(amount > 1 ? "s" : "")} to {targetPlayer.displayName}.";
if (caller != null)
SendReply(caller, successMessage);
else
Puts(successMessage);
SendReply(targetPlayer, $"You've received {amount} Comet Signal{(amount > 1 ? "s" : "")}.");
}
[ConsoleCommand("comet.giveall")]
void ConsoleGiveAllCometSignal(ConsoleSystem.Arg arg)
{
BasePlayer caller = arg.Player();
if (caller != null && !permission.UserHasPermission(caller.UserIDString, PERMISSION_ADMIN))
{
SendReply(caller, "You do not have permission to use this command.");
return;
}
int amount = 1;
if (arg.Args != null && arg.Args.Length > 0)
{
if (!int.TryParse(arg.Args[0].ToString(), out amount) || amount <= 0)
{
string message = "Invalid amount. Please specify a positive number.";
if (caller != null)
SendReply(caller, message);
else
Puts(message);
return;
}
}
int playerCount = 0;
foreach (var player in BasePlayer.activePlayerList)
{
GivePlayerCometSignal(player, amount);
playerCount++;
SendReply(player, $"You've received {amount} Comet Signal{(amount > 1 ? "s" : "")}.");
}
string successMessage = $"Given {amount} Comet Signal{(amount > 1 ? "s" : "")} to {playerCount} online players.";
if (caller != null)
SendReply(caller, successMessage);
else
Puts(successMessage);
}
#endregion
#region Comet Logic
void SpawnComet(Vector3 impactPosition)
{
try
{
if (ins == null)
ins = this;
GameObject cometObj = new GameObject("Comet");
activeComets.Add(cometObj);
CometFall cometFall = cometObj.AddComponent<CometFall>();
activeCometComponents.Add(cometFall);
Vector3 startPosition = FindStartFallPosition(impactPosition);
float horizontalDistance = Mathf.Sqrt(
(startPosition.x - impactPosition.x) * (startPosition.x - impactPosition.x) +
(startPosition.z - impactPosition.z) * (startPosition.z - impactPosition.z)
);
float verticalDistance = startPosition.y - impactPosition.y;
float trajectoryAngle = Mathf.Atan2(verticalDistance, horizontalDistance) * Mathf.Rad2Deg;
Puts($"Comet spawn info:");
Puts($" - Target position: {impactPosition.x}, {impactPosition.y}, {impactPosition.z}");
Puts($" - Start position: {startPosition.x}, {startPosition.y}, {startPosition.z}");
Puts($" - Horizontal offset: {_config.cometConfig.horizontalOffset}");
Puts($" - Horizontal distance: {horizontalDistance}");
Puts($" - Vertical distance: {verticalDistance}");
Puts($" - Trajectory angle: {trajectoryAngle}° from horizontal");
Vector3 direction = (impactPosition - startPosition).normalized;
Puts($" - Direction: {direction.x}, {direction.y}, {direction.z}");
cometFall.Init(impactPosition, startPosition);
}
catch (Exception ex)
{
PrintError("Error spawning comet: " + ex.Message);
}
}
Vector3 FindStartFallPosition(Vector3 impactPosition)
{
float horizontalOffset = _config.cometConfig.horizontalOffset;
float fallHeight = UnityEngine.Random.Range(_config.cometConfig.minFallHeight, _config.cometConfig.maxFallHeight);
Vector3 startPos = new Vector3(
impactPosition.x + horizontalOffset,
impactPosition.y + fallHeight,
impactPosition.z);
return startPos;
}
#endregion
#region Comet Class
class CometFall : FacepunchBehaviour
{
Vector3 startPosition;
Vector3 impactPosition;
Vector3 fallDirection;
Coroutine cometFallCoroutine;
List<BaseEntity> cometEntities = new List<BaseEntity>();
List<BaseEntity> impactEntities = new List<BaseEntity>();
float impactRadius;
float explosionDamage;
float currentFallTime = 0f;
float totalFallTime = 0f;
bool impactOccurred = false;
internal void CleanupOnPluginUnload()
{
try
{
if (cometFallCoroutine != null && ServerMgr.Instance != null)
{
ServerMgr.Instance.StopCoroutine(cometFallCoroutine);
cometFallCoroutine = null;
}
CancelInvoke();
foreach (BaseEntity entity in cometEntities)
{
if (entity != null && !entity.IsDestroyed)
{
entity.Kill();
}
}
cometEntities.Clear();
foreach (BaseEntity entity in impactEntities)
{
if (entity != null && !entity.IsDestroyed)
{
entity.Kill();
}
}
impactEntities.Clear();
}
catch (Exception ex)
{
Debug.LogError($"Error cleaning up CometFall component: {ex}");
}
}
internal void Init(Vector3 impactPosition, Vector3 startPosition)
{
try
{
this.impactPosition = impactPosition;
this.startPosition = startPosition;
if (ins == null)
{
throw new Exception("Plugin instance is null");
}
if (ins._config == null)
{
ins._config = PluginConfig.DefaultConfig();
throw new Exception("Plugin config is null, created default config");
}
if (ins._config.cometConfig == null)
{
ins._config.cometConfig = PluginConfig.DefaultConfig().cometConfig;
throw new Exception("Comet config is null, created default values");
}
this.impactRadius = ins._config.cometConfig.impactRadius;
this.explosionDamage = ins._config.cometConfig.explosionDamage;
DefineFallDirection();
CreateCometEntities();
float distance = Vector3.Distance(startPosition, impactPosition);
totalFallTime = distance / (0.5f * ins._config.cometConfig.fallingSpeedScale);
if (ServerMgr.Instance != null)
cometFallCoroutine = ServerMgr.Instance.StartCoroutine(CometFallCoroutine());
else
throw new Exception("ServerMgr.Instance is null");
InvokeRepeating(CreatePeriodicEffects, 0.1f, 0.1f);
}
catch (Exception ex)
{
Debug.LogError("Error in CometFall.Init: " + ex.Message);
throw;
}
}
void DefineFallDirection()
{
fallDirection = (impactPosition - startPosition).normalized;
}
void CreateCometEntities()
{
ins.Puts($"Creating comet entities at position: {startPosition}");
cometEntities.Clear();
BaseEntity mainEntity = GameManager.server.CreateEntity("assets/bundled/prefabs/oilfireballsmall.prefab", startPosition, Quaternion.identity);
if (mainEntity != null)
{
mainEntity.Spawn();
mainEntity.transform.position = startPosition;
cometEntities.Add(mainEntity);
ins.Puts($"Main comet entity created at {mainEntity.transform.position}");
}
else
{
ins.Puts("Failed to create main comet entity!");
}
int flareCount = Mathf.RoundToInt(ins._config.cometConfig.initialExplosionCount * 0.2f);
for (int i = 0; i < flareCount; i++)
{
Vector3 offset = new Vector3(
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f)
);
Vector3 flarePosition = startPosition + offset;
BaseEntity fireEntity = GameManager.server.CreateEntity("assets/prefabs/tools/flareold/flare.deployed.prefab",
flarePosition, Quaternion.identity);
if (fireEntity != null)
{
fireEntity.Spawn();
fireEntity.transform.position = flarePosition;
cometEntities.Add(fireEntity);
}
}
ins.Puts($"Total comet entities created: {cometEntities.Count}");
}
void CreatePeriodicEffects()
{
if (cometEntities.Count == 0 || cometEntities[0] == null || cometEntities[0].IsDestroyed || impactOccurred)
{
CancelInvoke(CreatePeriodicEffects);
return;
}
Effect.server.Run("assets/bundled/prefabs/fx/fire/fire_v2.prefab", cometEntities[0].transform.position, Vector3.up);
if (UnityEngine.Random.Range(0, 100) < (ins._config.cometConfig.postImpactEffectCount * 10))
{
Vector3 offsetPos = cometEntities[0].transform.position + new Vector3(
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f)
);
Effect.server.Run("assets/prefabs/npc/patrol helicopter/effects/heli_explosion.prefab", offsetPos, Vector3.up);
}
Effect.server.Run("assets/bundled/prefabs/fx/fire/fire_v3.prefab",
cometEntities[0].transform.position - (fallDirection * 3f), Vector3.up);
}
IEnumerator CometFallCoroutine()
{
if (ins == null || ins._config == null || ins._config.cometConfig == null)
{
Debug.LogError("CometFall: Plugin instance or config is null");
yield break;
}
ins.Puts($"Starting comet fall from {startPosition} to {impactPosition}");
ins.Puts($"Direction vector: {fallDirection}");
Vector3 totalDistanceVector = impactPosition - startPosition;
float totalDistance = totalDistanceVector.magnitude;
float horizontalDistance = Mathf.Sqrt(
(startPosition.x - impactPosition.x) * (startPosition.x - impactPosition.x) +
(startPosition.z - impactPosition.z) * (startPosition.z - impactPosition.z)
);
float verticalDistance = startPosition.y - impactPosition.y;
ins.Puts($"Total movement: distance={totalDistance}, horizontal={horizontalDistance}, vertical={verticalDistance}");
ins.Puts($"Movement will be diagonal with {horizontalDistance}m horizontal movement and {verticalDistance}m vertical drop");
float coveredDistance = 0f;
float baseFallSpeed = 10f;
foreach (BaseEntity entity in cometEntities)
{
if (entity == null || entity.IsDestroyed) continue;
if (entity == cometEntities[0])
{
entity.transform.position = startPosition;
}
else
{
Vector3 offset = new Vector3(
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f)
);
entity.transform.position = startPosition + offset;
}
}
float stepDelay = 0.05f;
while (coveredDistance < totalDistance && !impactOccurred)
{
if (ins == null || ins._config == null || ins._config.cometConfig == null)
{
Debug.LogError("CometFall: Plugin instance or config became null during fall");
break;
}
float progress = coveredDistance / totalDistance;
float speedMultiplier = 0.5f + (1.5f * progress);
float stepDistance = baseFallSpeed * speedMultiplier * ins._config.cometConfig.fallingSpeedScale;
if (coveredDistance + stepDistance > totalDistance)
{
stepDistance = totalDistance - coveredDistance;
}
coveredDistance += stepDistance;
Vector3 newMainPosition = Vector3.Lerp(startPosition, impactPosition, coveredDistance / totalDistance);
if (cometEntities.Count > 0 && cometEntities[0] != null && !cometEntities[0].IsDestroyed)
{
cometEntities[0].transform.position = newMainPosition;
for (int i = 1; i < cometEntities.Count; i++)
{
BaseEntity entity = cometEntities[i];
if (entity == null || entity.IsDestroyed) continue;
Vector3 offset = new Vector3(
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f)
);
entity.transform.position = newMainPosition + offset;
entity.transform.Rotate(new Vector3(
UnityEngine.Random.Range(1f, 3f),
UnityEngine.Random.Range(1f, 3f),
UnityEngine.Random.Range(1f, 3f)
), Space.Self);
}
}
if (coveredDistance >= totalDistance - stepDistance)
{
ins.Puts("Comet reached target position");
if (cometEntities.Count > 0 && cometEntities[0] != null && !cometEntities[0].IsDestroyed)
{
cometEntities[0].transform.position = impactPosition;
for (int i = 1; i < cometEntities.Count; i++)
{
BaseEntity entity = cometEntities[i];
if (entity == null || entity.IsDestroyed) continue;
Vector3 offset = new Vector3(
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f),
UnityEngine.Random.Range(-3f, 3f)
);
entity.transform.position = impactPosition + offset;
}
}
OnCometImpact(impactPosition);
break;
}
if (cometEntities.Count > 0 && cometEntities[0] != null && !cometEntities[0].IsDestroyed)
{
RaycastHit hit;
if (Physics.Raycast(cometEntities[0].transform.position, fallDirection, out hit, stepDistance,
LayerMask.GetMask("Terrain", "World", "Construction", "Tree", "Deployed")))
{
ins.Puts($"Comet hit object at {hit.point}");
OnCometImpact(hit.point);
break;
}
}
yield return Wait0_05;
}
ins.Puts("Comet fall ended");
if (!impactOccurred)
{
OnCometImpact(impactPosition);
}
}
void OnCometImpact(Vector3 exactImpactPos)
{
if (impactOccurred) return;
impactOccurred = true;
try
{
Vector3 finalImpactPos = impactPosition;
if (Vector3.Distance(exactImpactPos, impactPosition) > 1f)
{
ins.Puts($"Impact adjustment: Original={exactImpactPos}, Using={finalImpactPos}");
}
CancelInvoke(CreatePeriodicEffects);
foreach (BaseEntity entity in cometEntities)
{
if (entity == null || entity.IsDestroyed) continue;
entity.Kill();
}
CreateExplosion(finalImpactPos);
DealImpactDamage(finalImpactPos);
CreateTemporaryCrater(finalImpactPos);
int cleanupTime = ins?._config?.cometConfig?.impactCleanupTime ?? 30;
Invoke(() => CleanupImpactSite(), cleanupTime);
}
catch (Exception ex)
{
Debug.LogError("Error in OnCometImpact: " + ex.Message);
foreach (BaseEntity entity in cometEntities)
{
if (entity == null || entity.IsDestroyed) continue;
entity.Kill();
}
cometEntities.Clear();
impactEntities.Clear();
}
}
void CreateExplosion(Vector3 position)
{
Effect.server.Run("assets/prefabs/npc/patrol helicopter/effects/heli_explosion.prefab", position, Vector3.up);
ServerMgr.Instance.StartCoroutine(SequentialExplosions(position));
foreach (var player in BasePlayer.activePlayerList)
{
if (Vector3.Distance(player.transform.position, position) < ins._config.cometConfig.playerDamageRadius)
{
Effect.server.Run("assets/prefabs/npc/patrol helicopter/effects/heli_explosion.prefab",
player.transform.position + Vector3.up * 10, Vector3.up);
if (Vector3.Distance(player.transform.position, position) < impactRadius * 2)
{
Vector3 pushDirection = (player.transform.position - position).normalized;
player.Hurt(ins._config.cometConfig.playerDamageAmount, Rust.DamageType.Explosion);
}
}
}
}
void StartFallingExplosionChain(Vector3 startPos)
{
ServerMgr.Instance.StartCoroutine(FallingExplosionChain(startPos));
}
IEnumerator SequentialExplosions(Vector3 position)
{
yield return Wait0_1;
for (int i = 0; i < ins._config.cometConfig.numberOfSecondaryExplosions; i++)
{
float angle = i * (360f / ins._config.cometConfig.numberOfSecondaryExplosions);
float distance = UnityEngine.Random.Range(2f, impactRadius * 0.8f);
Vector3 explosionPos = position + new Vector3(
Mathf.Cos(angle * Mathf.Deg2Rad) * distance,
2f,
Mathf.Sin(angle * Mathf.Deg2Rad) * distance
);
StartFallingExplosionChain(explosionPos);
yield return GetWait(ins._config.cometConfig.chainExplosionDelay);
}
float endTime = Time.time + ins._config.cometConfig.impactCleanupTime * 0.1f;
int maxAdditionalExplosions = 3;
int additionalExplosions = 0;
while (Time.time < endTime && additionalExplosions < maxAdditionalExplosions)
{
Vector3 randomOffset = new Vector3(
UnityEngine.Random.Range(-impactRadius * 0.7f, impactRadius * 0.7f),
1.5f,
UnityEngine.Random.Range(-impactRadius * 0.7f, impactRadius * 0.7f)
);
Vector3 explosionPos = position + randomOffset;
StartFallingExplosionChain(explosionPos);
additionalExplosions++;
yield return GetWait(ins._config.cometConfig.chainExplosionDelay * 3f);
}
}
IEnumerator FallingExplosionChain(Vector3 startPos)
{
Vector3 currentPos = startPos;
bool shouldContinue = true;
int currentChain = 0;
float maxRaycastDistance = 10f;
while (shouldContinue && currentChain < ins._config.cometConfig.maxChainLength)
{
Effect.server.Run("assets/bundled/prefabs/fx/fire/fire_v2.prefab", currentPos, Vector3.up);
RaycastHit hit;
if (Physics.Raycast(currentPos, Vector3.down, out hit, maxRaycastDistance, LayerMask.GetMask("Terrain", "World", "Construction", "Deployed")))
{
float distanceToHit = Vector3.Distance(currentPos, hit.point);
if (distanceToHit <= ins._config.cometConfig.fallSpeed)
{
Effect.server.Run("assets/prefabs/npc/patrol helicopter/effects/heli_explosion.prefab", hit.point, Vector3.up);
Explode(hit.point, impactRadius * 0.5f, explosionDamage * ins._config.cometConfig.secondaryExplosionDamageScale);
BaseCombatEntity hitEntity = hit.GetEntity() as BaseCombatEntity;
if (hitEntity != null)
{
if (hitEntity is BuildingBlock)
{
hitEntity.Hurt(explosionDamage * ins._config.cometConfig.buildingDamageMultiplier, Rust.DamageType.Explosion, null, true);
}
yield return GetWait(ins._config.cometConfig.chainExplosionDelay);
currentPos = hit.point + new Vector3(
UnityEngine.Random.Range(-2f, 2f),
0.5f,
UnityEngine.Random.Range(-2f, 2f)
);
}
else
{
shouldContinue = false;
}
currentChain++;
}
else
{
currentPos = Vector3.MoveTowards(currentPos, hit.point, ins._config.cometConfig.fallSpeed);
}
}
else
{
currentPos += Vector3.down * ins._config.cometConfig.fallSpeed;
if (currentPos.y < impactPosition.y - 5f)
{
shouldContinue = false;
}
}
yield return GetWait(ins._config.cometConfig.chainExplosionDelay);
}
}
void Explode(Vector3 position, float radius, float damage)
{
List<BaseEntity> entities = new List<BaseEntity>();
Vis.Entities(position, radius, entities);
foreach (var entity in entities)
{
if (entity == null || entity.IsDestroyed) continue;
float distance = Vector3.Distance(position, entity.transform.position);
float damageAmount = damage * (1 - Mathf.Clamp01(distance / radius));
BaseCombatEntity combatEntity = entity as BaseCombatEntity;
if (combatEntit
// ... [Code preview truncated due to length. Full source in attached file] ...