C#:
using System;
using System.Collections.Generic;
using UnityEngine;
using Oxide.Core.Plugins;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using System.Collections;
using Oxide.Game.Rust.Cui;
using Newtonsoft.Json;
using System.Linq;
using Color = UnityEngine.Color;
namespace Oxide.Plugins
{
[Info("MetalEvent", "Ridamees", "1.7.1")]
[Description("Spawns an unlimited metal node/ore for a limited time")]
class MetalEvent : RustPlugin
{
private const string vendingPrefab = "assets/prefabs/deployable/vendingmachine/vending_mapmarker.prefab";
private const string genericPrefab = "assets/prefabs/tools/map/genericradiusmarker.prefab";
private Dictionary<int, List<BasePlayer>> clusterPlayerLists = new Dictionary<int, List<BasePlayer>>();
private List<VendingMachineMapMarker> vendingMarkers = new List<VendingMachineMapMarker>();
private List<MapMarkerGenericRadius> genericMarkers = new List<MapMarkerGenericRadius>();
private List<List<BaseEntity>> metalNodeClusters = new List<List<BaseEntity>>();
private const string UI_METAL_EVENT = "MetalEventUI";
private const string CUSTOM_GAME_TIP = "CustomGameTip";
private bool destroyMetalClusterAtEnd;
private bool isEventActive = false;
private bool resetNodesSpawnPoints;
private bool onlyCommandStartEvent;
private int minPlayersToStartEvent;
private string eventStartMessage;
private bool playStartEventSound;
private bool enableChatMessages;
private string eventEndMessage;
private int minEventInterval;
private int maxEventInterval;
private float gameTipDuration;
private float eventStartTime;
private bool enableGameTips;
private PluginConfig config;
private SpawnData spawnData;
private int maxMetalNodes;
private Timer eventTimer;
private string spawnMode;
private class PluginConfig
{
[JsonProperty("Clear non-Monument Spawn Locations on map Wipe")] public bool ResetNodesSpawnPoints { get; set; } = true;
[JsonProperty("Only Command Start Event")] public bool OnlyCommandStartEvent { get; set; } = false;
[JsonProperty("Minimum Players to Start Event")] public int MinPlayersToStartEvent { get; set; } = 1;
[JsonProperty("Prevent Overlap with Sulfur/Stone Event")] public bool PreventOverlap { get; set; } = true;
[JsonProperty("Prevent Overlap Retry Timer (seconds)")] public float PreventOverlapRetry { get; set; } = 180f;
[JsonProperty("Event Minimum Interval (seconds)")] public int MinEventInterval { get; set; } = 3600;
[JsonProperty("Event Maximum Interval (seconds)")] public int MaxEventInterval { get; set; } = 7200;
[JsonProperty("Event Duration Seconds")] public int EventDuration { get; set; } = 1230;
[JsonProperty("Event Start Notification Sound")] public bool PlayStartEventSound { get; set; } = true;
[JsonProperty("Event Start Explosion Effects")] public bool SpawnExplosionEffect { get; set; } = true;
[JsonProperty("Event Chat Messages")] public bool EnableChatMessages { get; set; } = true;
[JsonProperty("Event GameTip Messages")] public bool EnableGameTips { get; set; } = true;
[JsonProperty("Event GameTip Duration (seconds)")] public float GameTipDuration { get; set; } = 7f;
[JsonProperty("Event Start Message")] public string EventStartMessage { get; set; } = "<color=red>Metal Event</color> <color=white>Has Started! Check The Map!</color>";
[JsonProperty("Event End Message")] public string EventEndMessage { get; set; } = "<color=red>Metal Event</color> <color=white>Has Ended!</color>";
[JsonProperty("UI Enabled")] public bool ShowMetalEventUI { get; set; } = true;
[JsonProperty("UI Location X")] public float UILocationX { get; set; } = 0.37f;
[JsonProperty("UI Location Y")] public float UILocationY { get; set; } = 0.8f;
[JsonProperty("UI Handwriting Font")] public bool fontsEnabled { get; set; } = true;
[JsonProperty("UI Style (1 = With UI Msg, 2 = No UI Msg)")] public int UIStyle { get; set; } = 1;
[JsonProperty("UI Message")] public string UiText { get; set; } = "Unlimited metal node marked on map!";
[JsonProperty("Proximity UI Visibility Mode")] public bool ProximityUI { get; set; } = false;
[JsonProperty("Proximity UI Visibility Range")] public int ProximityRange { get; set; } = 350;
[JsonProperty("Proximity UI 'Players Here: X'")] public bool ProximityPlayersUI { get; set; } = true;
[JsonProperty("Proximity UI 'Players Here' Visibility Range")] public int ProximityRangePVP { get; set; } = 70;
[JsonProperty("MapMarker Enabled")] public bool MarkerEnabled { get; set; } = true;
[JsonProperty("MapMarker Colour")] public string MarkerColour { get; set; } = "#050505";
[JsonProperty("MapMarker Colour2")] public string MarkerColour2 { get; set; } = "#FF0000";
[JsonProperty("MapMarker Radius")] public float MapMarkerSize { get; set; } = 0.25f;
[JsonProperty("MapMarker Alpha")] public float MarkerAlpha { get; set; } = 0.7f;
[JsonProperty("MapMarker Name/Message")] public string MarkerMessage { get; set; } = "UNLIMITED METAL NODE";
[JsonProperty("Metal Ore Minimum Gather Amount")] public int MinMetalOreAmount { get; set; } = 1;
[JsonProperty("Metal Ore Maximum Gather Amount")] public int MaxMetalOreAmount { get; set; } = 10;
[JsonProperty("HQM Ore Minimum Gather Amount")] public int MinHQMetalOreAmount { get; set; } = 0;
[JsonProperty("HQM Ore Maximum Gather Amount")] public int MaxHQMetalOreAmount { get; set; } = 3;
[JsonProperty("Delete MetalNode at Event End")] public bool DestroyMetalClusterAtEnd { get; set; } = true;
[JsonProperty("MetalNode is Gatherable")] public bool GatherYield { get; set; } = true;
[JsonProperty("MetalNode Size 1-100")] public int MaxMetalNodes { get; set; } = 30;
[JsonProperty("MetalNode Size Radius")] public float NodeRadius { get; set; } = 0.10f;
[JsonProperty("Lightning during Event")] public bool EventLightning { get; set; } = true;
[JsonProperty("Lightning Min Interval (seconds)")] public float EventLightningMin { get; set; } = 1f;
[JsonProperty("Lightning Max Interval (seconds)")] public float EventLightningMax { get; set; } = 60f;
[JsonProperty("MetalNode Spawnmodes ('All' or 'Random')")] public string SpawnMode { get; set; } = "Random";
[JsonProperty("MetalNode Spawnmode 'Random' Amount")] public int RandomCount { get; set; } = 2;
[JsonProperty("Monument Spawn Locations")]
public Dictionary<string, bool> DefaultSpawnLocations { get; set; } = new Dictionary<string, bool>
{
{ "xlarge/launch_site_1.prefab", true },
{ "medium/nuclear_missile_silo.prefab", true },
{ "large/military_tunnel_1.prefab", true },
{ "large/airfield_1.prefab", true },
{ "small/sphere_tank.prefab", true },
{ "large/water_treatment_plant_1.prefab", true },
{ "railside/trainyard_1.prefab", true },
{ "medium/radtown_small_3.prefab", true },
{ "roadside/gas_station_1.prefab", true },
{ "roadside/supermarket_1.prefab", true },
{ "large/powerplant_1.prefab", true },
{ "lighthouse/lighthouse.prefab", true },
{ "roadside/warehouse.prefab", true },
{ "medium/junkyard_1.prefab", true },
{ "small/satellite_dish.prefab", true },
{ "harbor/harbor_1.prefab", true },
{ "harbor/harbor_2.prefab", true },
{ "harbor/ferry_terminal_1.prefab", true },
{ "arctic_bases/arctic_research_base_a.prefab", true },
{ "military_bases/desert_military_base_a.prefab", false },
{ "military_bases/desert_military_base_b.prefab", false },
{ "military_bases/desert_military_base_c.prefab", false },
{ "military_bases/desert_military_base_d.prefab", false },
{ "OilrigAI", false },
{ "OilrigAI2", false }
};
[JsonProperty("Monument Spawn Limits")]
public Dictionary<string, int> MonumentLimits { get; set; } = new Dictionary<string, int>
{
{ "xlarge/launch_site_1.prefab", 1 },
{ "medium/nuclear_missile_silo.prefab", 1 },
{ "large/military_tunnel_1.prefab", 1 },
{ "large/airfield_1.prefab", 1 },
{ "small/sphere_tank.prefab", 1 },
{ "large/water_treatment_plant_1.prefab", 1 },
{ "railside/trainyard_1.prefab", 1 },
{ "medium/radtown_small_3.prefab", 1 },
{ "roadside/gas_station_1.prefab", 3 },
{ "roadside/supermarket_1.prefab", 3 },
{ "large/powerplant_1.prefab", 1 },
{ "lighthouse/lighthouse.prefab", 2 },
{ "roadside/warehouse.prefab", 3 },
{ "medium/junkyard_1.prefab", 1 },
{ "small/satellite_dish.prefab", 1 },
{ "harbor/harbor_1.prefab", 1 },
{ "harbor/harbor_2.prefab", 1 },
{ "harbor/ferry_terminal_1.prefab", 1 },
{ "arctic_bases/arctic_research_base_a.prefab", 1 },
{ "military_bases/desert_military_base_a.prefab", 1 },
{ "military_bases/desert_military_base_b.prefab", 1 },
{ "military_bases/desert_military_base_c.prefab", 1 },
{ "military_bases/desert_military_base_d.prefab", 1 },
{ "OilrigAI", 1 },
{ "OilrigAI2", 1 }
};
[JsonProperty("Monument Spawn Location Offsets")]
public Dictionary<string, Vector3> DefaultSpawnLocationOffsets { get; set; } = new Dictionary<string, Vector3>
{
{ "xlarge/launch_site_1.prefab", new Vector3(150f, 3.5f, -7.5f) },
{ "medium/nuclear_missile_silo.prefab", new Vector3(53.3f, -13.2f, 1.1f) },
{ "large/military_tunnel_1.prefab", new Vector3(-0.5f, 18.35f, 25f) },
{ "large/airfield_1.prefab", new Vector3(20f, 0.5f, -27.5f) },
{ "small/sphere_tank.prefab", new Vector3(0f, 72f, 0f) },
{ "large/water_treatment_plant_1.prefab", new Vector3(-51.9f, 1.5f, -98.8f) },
{ "railside/trainyard_1.prefab", new Vector3(-36.3f, 9.1f, -33.0f) },
{ "medium/radtown_small_3.prefab", new Vector3(-21.5f, 1.1f, -4.5f) },
{ "roadside/gas_station_1.prefab", new Vector3(8.4f, 9.5f, 5.0f) },
{ "roadside/supermarket_1.prefab", new Vector3(10.0f, 6f, -3.5f) },
{ "large/powerplant_1.prefab", new Vector3(-8.0f, 0.5f, 38.0f) },
{ "lighthouse/lighthouse.prefab", new Vector3(0f, 57.7f, 0.36f) },
{ "roadside/warehouse.prefab", new Vector3(20f, 0.3f, -7.7f) },
{ "medium/junkyard_1.prefab", new Vector3(24.0f, 0.3f, 0.0f) },
{ "small/satellite_dish.prefab", new Vector3(8.0f, 6.5f, -14.7f) },
{ "harbor/harbor_1.prefab", new Vector3(8.1f, 8.6f, 6.3f) },
{ "harbor/harbor_2.prefab", new Vector3(41.5f, 5.3f, -20.0f) },
{ "harbor/ferry_terminal_1.prefab", new Vector3(-38.1f, 5.2f, 18.6f) },
{ "arctic_bases/arctic_research_base_a.prefab", new Vector3(-32.8f, 1.85f, 5.5f) },
{ "military_bases/desert_military_base_a.prefab", new Vector3(0f, 2f, 0f) },
{ "military_bases/desert_military_base_b.prefab", new Vector3(0f, 2f, 0f) },
{ "military_bases/desert_military_base_c.prefab", new Vector3(0f, 2f, 0f) },
{ "military_bases/desert_military_base_d.prefab", new Vector3(0f, 2f, 0f) },
{ "OilrigAI", new Vector3(11.0f, 30.3f, -25.0f) },
{ "OilrigAI2", new Vector3(-1.3f, 39.15f, -14.95f) }
};
}
private class SpawnData
{
public Dictionary<string, Vector3> SpawnPositions { get; set; } = new Dictionary<string, Vector3>();
}
private bool IsPlayerInProximity(BasePlayer player)
{
foreach (var cluster in metalNodeClusters)
{
foreach (var node in cluster)
{
if (node != null && node.IsDestroyed == false)
{
if (Vector3.Distance(node.transform.position, player.transform.position) <= config.ProximityRange)
{
return true;
}
}
}
}
return false;
}
private void UpdatePlayerClusters()
{
foreach (var cluster in metalNodeClusters)
{
int clusterIndex = metalNodeClusters.IndexOf(cluster);
List<BasePlayer> playersInProximity = new List<BasePlayer>();
foreach (var node in cluster)
{
foreach (var player in BasePlayer.activePlayerList)
{
if (node != null && player != null)
{
if (Vector3.Distance(node.transform.position, player.transform.position) <= config.ProximityRangePVP)
{
if (!playersInProximity.Contains(player))
{
playersInProximity.Add(player);
}
}
else if (playersInProximity.Contains(player))
{
playersInProximity.Remove(player);
}
}
}
}
clusterPlayerLists[clusterIndex] = playersInProximity;
}
}
private bool IsPlayerInProximityPVP(BasePlayer player)
{
foreach (var cluster in metalNodeClusters)
{
foreach (var node in cluster)
{
if (node != null && node.IsDestroyed == false)
{
if (Vector3.Distance(node.transform.position, player.transform.position) <= config.ProximityRangePVP)
{
return true;
}
}
}
}
return false;
}
protected override void LoadDefaultConfig()
{
config = Config.ReadObject<PluginConfig>();
Config.WriteObject(config, true);
}
private void LoadConfigVariables()
{
LoadDefaultConfig();
minEventInterval = config.MinEventInterval;
maxEventInterval = config.MaxEventInterval;
eventStartMessage = config.EventStartMessage;
eventEndMessage = config.EventEndMessage;
enableGameTips = config.EnableGameTips;
enableChatMessages = config.EnableChatMessages;
gameTipDuration = config.GameTipDuration;
destroyMetalClusterAtEnd = config.DestroyMetalClusterAtEnd;
playStartEventSound = config.PlayStartEventSound;
spawnMode = config.SpawnMode;
onlyCommandStartEvent = config.OnlyCommandStartEvent;
minPlayersToStartEvent = config.MinPlayersToStartEvent;
resetNodesSpawnPoints = config.ResetNodesSpawnPoints;
}
private void Init()
{
LoadConfigVariables();
spawnData = Interface.Oxide.DataFileSystem.ReadObject<SpawnData>("MetalEventSpawnData");
if (spawnData == null)
{
spawnData = new SpawnData();
Interface.Oxide.DataFileSystem.WriteObject("MetalEventSpawnData", spawnData);
}
if (!onlyCommandStartEvent)
{
SetRandomEventTimer();
}
permission.RegisterPermission("metalevent.admin", this);
}
private void OnServerInitialized()
{
PopulateDefaultSpawnPositions();
Interface.Oxide.DataFileSystem.WriteObject("MetalEventSpawnData", spawnData);
}
private void PopulateDefaultSpawnPositions()
{
Dictionary<string, int> prefabCount = new Dictionary<string, int>();
foreach (var kvp in config.DefaultSpawnLocations)
{
string monumentPrefabPath = kvp.Key;
bool isDefaultSpawn = kvp.Value;
if (isDefaultSpawn)
{
if (!config.MonumentLimits.ContainsKey(monumentPrefabPath))
continue;
int limit = config.MonumentLimits[monumentPrefabPath];
if (limit == 0)
continue;
MonumentInfo[] monuments = TerrainMeta.Path.Monuments.Where(info => info.gameObject.name.EndsWith(monumentPrefabPath)).ToArray();
int count = monuments.Length;
if (count > 0)
{
if (!prefabCount.ContainsKey(monumentPrefabPath))
{
prefabCount[monumentPrefabPath] = 0;
}
for (int i = 0; i < Math.Min(count, limit); i++)
{
MonumentInfo monument = monuments[i];
Vector3 spawnPosition = monument.transform.TransformPoint(config.DefaultSpawnLocationOffsets.GetValueOrDefault(monumentPrefabPath, Vector3.zero));
string spawnLocation = $"{monumentPrefabPath}_{prefabCount[monumentPrefabPath]}";
spawnData.SpawnPositions[spawnLocation] = spawnPosition;
prefabCount[monumentPrefabPath]++;
}
}
}
}
// Remove spawn positions that are set to false in DefaultSpawnLocations
RemoveInactiveSpawns();
}
private void RemoveInactiveSpawns()
{
// Collect keys to remove to avoid modifying the collection while iterating
List<string> keysToRemove = new List<string>();
foreach (var kvp in config.DefaultSpawnLocations)
{
string monumentPrefabPath = kvp.Key;
bool isDefaultSpawn = kvp.Value;
if (!isDefaultSpawn)
{
foreach (var spawnLocation in spawnData.SpawnPositions.Keys)
{
// Check if the spawnLocation starts with the monumentPrefabPath
if (spawnLocation.StartsWith(monumentPrefabPath))
{
keysToRemove.Add(spawnLocation);
}
}
}
}
// Remove the collected keys
foreach (var key in keysToRemove)
{
spawnData.SpawnPositions.Remove(key);
Puts($"Removed inactive spawn location: {key}");
}
}
private bool IsDefaultSpawn(string monumentName)
{
if (config.DefaultSpawnLocations.ContainsKey(monumentName))
{
return config.DefaultSpawnLocations[monumentName];
}
return false;
}
private void SetRandomEventTimer()
{
if (!onlyCommandStartEvent)
{
int eventInterval = UnityEngine.Random.Range(minEventInterval, maxEventInterval + 1);
eventTimer = timer.Once(eventInterval, StartEvent);
}
}
Vector3 FindGround(Vector3 position)
{
return position;
}
private bool IsEventActive()
{
return isEventActive;
}
bool IsSulfurEventActive()
{
var sulfurEventPlugin = plugins.Find("SulfurEvent");
if (sulfurEventPlugin != null && sulfurEventPlugin.IsLoaded)
{
var sulfurEvent = sulfurEventPlugin?.Call<bool>("IsEventActive");
return sulfurEvent ?? false;
}
return false;
}
bool IsStoneEventActive()
{
var stoneEventPlugin = plugins.Find("StoneEvent");
if (stoneEventPlugin != null && stoneEventPlugin.IsLoaded)
{
var stoneEvent = stoneEventPlugin?.Call<bool>("IsEventActive");
return stoneEvent ?? false;
}
return false;
}
private List<Vector3> positions;
void StartEvent()
{
if (config.PreventOverlap && (IsSulfurEventActive() || IsStoneEventActive()))
{
Puts("Skipping metal event start because sulfur or stone event is already active.Trying again soon.");
timer.Once(config.PreventOverlapRetry, () =>
{
StartEvent();
});
return;
}
if (BasePlayer.activePlayerList.Count < minPlayersToStartEvent)
{
Puts("Not enough players to start MetalEvent.");
return;
}
if (isEventActive)
{
Puts("The event is already active.");
return;
}
positions = GetSpawnPositions();
if (positions.Count == 0)
{
Puts("Unable to start MetalEvent event.No spawn position set.");
return;
}
PopulateDefaultSpawnPositions();
Subscribe(nameof(OnMeleeAttack));
Subscribe(nameof(OnPlayerConnected));
isEventActive = true;
eventStartTime = Time.realtimeSinceStartup;
Puts("MetalEvent Event Started");
SendGameTipToAllPlayers(eventStartMessage);
PlayStartEventSound();
metalNodeClusters.Clear();
genericMarkers.Clear();
vendingMarkers.Clear();
foreach (var position in positions)
{
Effect.server.Run("assets/bundled/prefabs/fx/ore_break.prefab", position);
Effect.server.Run("assets/prefabs/misc/orebonus/effects/bonus_finish.prefab", position);
Effect.server.Run("assets/bundled/prefabs/fx/ore_break.prefab", position);
Effect.server.Run("assets/prefabs/misc/orebonus/effects/hotspot_death.prefab", position);
Effect.server.Run("assets/bundled/prefabs/fx/ore_break.prefab", position);
Effect.server.Run("assets/prefabs/misc/junkpile/effects/despawn.prefab", position);
Effect.server.Run("assets/bundled/prefabs/fx/ore_break.prefab", position);
CreateMarker(position, config.MarkerMessage);
CreateVendingMarker(position, config.MarkerMessage);
StartLightningEffect(position);
}
timer.Once(2f, () =>
{
foreach (var position in positions)
{
SpawnLargeMetalNodePyramid(position, 9);
UpdateEventUI();
Puts($"A large metal node has spawned at position {position}.");
float explosionRadius = 3.5f; // adjust as needed
float nodeClusterHeight = 2.5f;
Vector3 explosionPosition = new Vector3(position.x, position.y + nodeClusterHeight / 2, position.z);
foreach (var basePlayer in BasePlayer.activePlayerList)
{
float distanceToPlayer = Vector3.Distance(explosionPosition, basePlayer.transform.position);
if (distanceToPlayer <= explosionRadius)
{
float damage = (5 - (distanceToPlayer / explosionRadius)) * 100;
basePlayer.Hurt(damage);
}
}
if (config.SpawnExplosionEffect)
{
Effect.server.Run("assets/content/vehicles/mlrs/effects/pfx_mlrs_rocket_explosion_ground.prefab", explosionPosition);
Effect.server.Run("assets/content/vehicles/mlrs/effects/pfx_mlrs_rocket_explosion_air.prefab", explosionPosition);
Effect.server.Run("assets/content/vehicles/mlrs/effects/pfx_mlrs_rocket_explosion_air.prefab", explosionPosition);
Effect.server.Run("assets/prefabs/npc/m2bradley/effects/bradley_explosion.prefab", explosionPosition);
Effect.server.Run("assets/prefabs/npc/patrol helicopter/effects/rocket_airburst_explosion.prefab", explosionPosition);
Effect.server.Run("assets/prefabs/npc/patrol helicopter/effects/rocket_explosion.prefab", explosionPosition);
Effect.server.Run("assets/prefabs/npc/patrol helicopter/effects/rocket_fire.prefab", explosionPosition);
Effect.server.Run("assets/prefabs/npc/sam_site_turret/effects/rocket_sam_explosion.prefab", explosionPosition);
Effect.server.Run("assets/content/effects/weather/pfx_lightning_strong.prefab", explosionPosition);
Effect.server.Run("assets/content/effects/weather/pfx_lightning_mild.prefab", explosionPosition);
Effect.server.Run("assets/content/effects/weather/pfx_lightning_medium.prefab", explosionPosition);
Effect.server.Run("assets/prefabs/npc/m2bradley/sound/bradley-explosion-debris.asset", explosionPosition);
}
}
});
timer.Once(config.EventDuration, () =>
{
StopEvent();
if (!onlyCommandStartEvent)
{
SetRandomEventTimer();
}
});
}
List<string> lightningPrefabs = new List<string>()
{
"assets/content/effects/weather/pfx_lightning_medium.prefab",
"assets/content/effects/weather/pfx_lightning_mild.prefab",
"assets/content/effects/weather/pfx_lightning_strong.prefab"
};
List<Timer> lightningTimers = new List<Timer>();
void StartLightningEffect(Vector3 position)
{
if (!config.EventLightning) return;
float minDelay = config.EventLightningMin;
float maxDelay = config.EventLightningMax;
Action lightningAction = null;
lightningAction = () =>
{
float delay = UnityEngine.Random.Range(minDelay, maxDelay);
Timer lightningTimer = timer.Once(delay, () =>
{
string randomPrefab = lightningPrefabs[UnityEngine.Random.Range(0, lightningPrefabs.Count)];
Effect.server.Run(randomPrefab, position);
lightningAction();
});
lightningTimers.Add(lightningTimer);
};
lightningAction();
}
void UpdateEventUI()
{
if (!isEventActive || !config.ShowMetalEventUI) return;
foreach (var basePlayer in BasePlayer.activePlayerList)
{
ShowMetalEventUI(basePlayer);
}
timer.Once(2.5f, () =>
{
UpdateEventUI();
});
}
List<Vector3> GetAllSpawnPositions()
{
List<Vector3> positions = new List<Vector3>();
foreach (var spawnPointPosition in spawnData.SpawnPositions.Values)
{
positions.Add(FindGround(spawnPointPosition));
}
return positions;
}
Vector3 GetRandomSpawnPosition()
{
if (spawnData.SpawnPositions.Count == 0)
{
Puts("Warning: No spawn positions have been set.");
return Vector3.zero;
}
string spawnPointName = spawnData.SpawnPositions.Keys.ToList()[UnityEngine.Random.Range(0, spawnData.SpawnPositions.Count)];
return FindGround(spawnData.SpawnPositions[spawnPointName]);
}
private void PlayStartEventSound()
{
if (!playStartEventSound) return;
foreach (var basePlayer in BasePlayer.activePlayerList)
{
Effect.server.Run("assets/bundled/prefabs/fx/invite_notice.prefab", basePlayer, 0, Vector3.zero, Vector3.forward);
Effect.server.Run("assets/bundled/prefabs/fx/item_unlock.prefab", basePlayer, 0, Vector3.zero, Vector3.forward);
}
}
void OnPlayerConnected(BasePlayer player)
{
if (isEventActive && config.ShowMetalEventUI)
{
ShowMetalEventUI(player);
}
}
void StopEvent()
{
if (!isEventActive) return;
isEventActive = false;
Puts($"MetalEvent Event Ended");
SendGameTipToAllPlayers(eventEndMessage);
if (destroyMetalClusterAtEnd)
{
foreach (var metalNodeCluster in metalNodeClusters)
{
foreach (var node in metalNodeCluster)
{
node.Kill();
}
}
}
foreach (var mapmarker in genericMarkers)
mapmarker.Kill();
foreach (var vending in vendingMarkers)
vending.Kill();
foreach (var basePlayer in BasePlayer.activePlayerList)
{
CuiHelper.DestroyUi(basePlayer, UI_METAL_EVENT);
}
foreach (var timer in lightningTimers)
{
timer.Destroy();
}
lightningTimers.Clear();
clusterPlayerLists.Clear();
genericMarkers.Clear();
vendingMarkers.Clear();
metalNodeClusters.Clear();
Unsubscribe(nameof(OnMeleeAttack));
Unsubscribe(nameof(OnPlayerConnected));
}
private void SendGameTipToAllPlayers(string message)
{
if (enableGameTips)
{
ShowGameTipToAllPlayers(message, gameTipDuration);
}
if (enableChatMessages)
{
PrintToChat(message);
}
}
private void ShowGameTipToAllPlayers(string message, float displayDuration)
{
foreach (var basePlayer in BasePlayer.activePlayerList)
{
basePlayer.SendConsoleCommand("gametip.hidegametip");
basePlayer.SendConsoleCommand("gametip.showgametip", message);
timer.Once(gameTipDuration, () => basePlayer.SendConsoleCommand("gametip.hidegametip"));
}
}
private int GetPlayerClusterIndex(BasePlayer player)
{
if (player == null)
return -1;
foreach (var cluster in metalNodeClusters)
{
if (cluster == null)
continue;
int clusterIndex = metalNodeClusters.IndexOf(cluster);
foreach (var node in cluster)
{
if (node != null && !node.IsDestroyed && node.transform != null && player.transform != null)
{
if (Vector3.Distance(node.transform.position, player.transform.position) <= config.ProximityRangePVP)
{
return clusterIndex;
}
}
}
}
return -1;
}
private void ShowMetalEventUI(BasePlayer player)
{
if (!config.ShowMetalEventUI) return;
if (config.ProximityUI)
{
if (!IsPlayerInProximity(player))
{
CuiHelper.DestroyUi(player, UI_METAL_EVENT);
return;
}
}
foreach (var genericMarker in genericMarkers)
{
genericMarker.SendUpdate(player);
}
float timeRemaining = config.EventDuration - (Time.realtimeSinceStartup - eventStartTime);
string timerText = $"{Mathf.FloorToInt(timeRemaining / 60)}m {Mathf.FloorToInt(timeRemaining % 60)}s";
if (timeRemaining >= 3600f) // 3600 seconds = 1 hour
{
int hours = Mathf.FloorToInt(timeRemaining / 3600);
int minutes = Mathf.FloorToInt((timeRemaining % 3600) / 60);
int seconds = Mathf.FloorToInt(timeRemaining % 60);
timerText = $"{hours}h {minutes}m {seconds}s";
}
else if (timeRemaining >= 60f)
{
int minutes = Mathf.FloorToInt(timeRemaining / 60);
int seconds = Mathf.FloorToInt(timeRemaining % 60);
timerText = $"{minutes}m {seconds}s";
}
else
{
int seconds = Mathf.FloorToInt(timeRemaining);
timerText = $"{seconds}s";
}
var elements = new CuiElementContainer();
float uiWidth = 0.26f;
float uiHeight = 0.14f;
string anchorMin = $"{config.UILocationX} {config.UILocationY}";
string anchorMax = $"{config.UILocationX + uiWidth} {config.UILocationY + uiHeight}";
string distance = "0.55 -0.55";
string OutlineColor = "0 0 0 1";
var mainPanel = elements.Add(new CuiPanel
{
Image = { Color = "35 35 35 0" },
RectTransform = { AnchorMin = anchorMin, AnchorMax = anchorMax },
CursorEnabled = false,
}, "Under", UI_METAL_EVENT);
if (config.ProximityPlayersUI && IsPlayerInProximityPVP(player))
{
int clusterIndex = GetPlayerClusterIndex(player);
int playersNear = clusterPlayerLists.ContainsKey(clusterIndex) ? clusterPlayerLists[clusterIndex].Count : 0;
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = $"Players Here: {playersNear}",
FontSize = 17,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.10",
AnchorMax = "0.9 0.4"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
}
switch(config.UIStyle)
{
case 1:
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = "<color=red>Metal </color><color=red>Event</color>",
FontSize = 19,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.70",
AnchorMax = "0.9 0.99"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = $"{config.UiText}",
FontSize = 14,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.45",
AnchorMax = "0.9 0.85"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = timerText,
FontSize = 18,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.2",
AnchorMax = "0.9 0.7"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
break;
case 2:
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = "<color=red>Metal </color><color=red>Event</color>",
FontSize = 19,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.50",
AnchorMax = "0.9 0.79"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = timerText,
FontSize = 18,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.25",
AnchorMax = "0.9 0.6"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
break;
default:
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = "<color=red>Metal </color><color=red>Event</color>",
FontSize = 19,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.50",
AnchorMax = "0.9 0.79"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
elements.Add(new CuiElement
{
Parent = mainPanel,
Components =
{
new CuiTextComponent
{
Text = timerText,
FontSize = 18,
Align = TextAnchor.MiddleCenter,
Font = config.fontsEnabled ? "permanentmarker.ttf" : "robotocondensed-bold.ttf"
},
new CuiRectTransformComponent
{
AnchorMin = "0.1 0.25",
AnchorMax = "0.9 0.6"
},
new CuiOutlineComponent
{
Color = OutlineColor,
Distance = distance,
UseGraphicAlpha = true
}
}
});
break;
}
UpdatePlayerClusters();
CuiHelper.DestroyUi(player, UI_METAL_EVENT);
CuiHelper.AddUi(player, elements);
}
private List<Vector3> GetSpawnPositions()
{
List<Vector3> positions = new List<Vector3>();
Puts($"Spawn mode: {config.SpawnMode}");
Puts($"Number of spawn positions: {spawnData.SpawnPositions.Count}");
if (config.SpawnMode.ToLower() == "all" && spawnData.SpawnPositions.Count > 0)
{
foreach (Vector3 spawnPointPosition in spawnData.SpawnPositions.Values)
{
positions.Add(FindGround(spawnPointPosition));
}
}
else if (config.SpawnMode.ToLower() == "random")
{
int spawnCount = Mathf.Min(config.RandomCount, spawnData.SpawnPositions.Count);
var spawnPointNames = spawnData.SpawnPositions.Keys.ToList();
for (int i = 0; i < spawnCount; i++)
{
string spawnPointName = spawnPointNames[UnityEngine.Random.Range(0, spawnPointNames.Count)];
positions.Add(FindGround(spawnData.SpawnPositions[spawnPointName]));
spawnPointNames.Remove(spawnPointName);
}
}
Puts($"Number of positions selected: {positions.Count}");
return positions;
}
void SpawnLargeMetalNodePyramid(Vector3 center, int baseLayerNodeCount)
{
List<BaseEntity> metalNodeCluster = new List<BaseEntity>();
float nodeRadius = config.NodeRadius;
for (int layer = 0; layer < baseLayerNodeCount; layer++)
{
int layerNodeCount = baseLayerNodeCount - layer;
float layerRadius = layerNodeCount * nodeRadius;
for (int i = 0; i < layerNodeCount; i++)
{
if (metalNodeCluster.Count >= config.MaxMetalNodes)
{
break;
}
float angle = (Mathf.PI * 2 * i) / layerNodeCount;
float x = center.x + layerRadius * Mathf.Cos(angle);
float z = center.z + layerRadius * Mathf.Sin(angle);
float y = center.y + layer * nodeRadius * 2;
Vector3 metalNodePosition = new Vector3(x, y, z);
BaseEntity metalNodeEntity = GameManager.server.CreateEntity("assets/bundled/prefabs/autospawn/resource/ores/metal-ore.prefab", metalNodePosition);
if (metalNodeEntity == null) continue;
metalNodeEntity.Spawn();
metalNodeCluster.Add(metalNodeEntity);
}
}
metalNodeClusters.Add(metalNodeCluster);
}
void OnMeleeAttack(BasePlayer attacker, HitInfo info)
{
if (info.WeaponPrefab is BaseMelee)
{
if (EntityInAnyCluster(info.HitEntity) && isEventActive)
{
if (config.GatherYield && attacker != null)
{
ItemDefinition hqMetalOreDef = ItemManager.FindItemDefinition("hq.metal.ore");
if (hqMetalOreDef != null)
{
int bonusAmount = UnityEngine.Random.Range(config.MinHQMetalOreAmount, config.MaxHQMetalOreAmount + 1);
if (bonusAmount > 0)
{
Item bonusItem = ItemManager.Create(hqMetalOreDef, bonusAmount);
if (bonusItem != null)
{
attacker.GiveItem(bonusItem);
}
}
}
ItemDefinition metalOreDef = ItemManager.FindItemDefinition("metal.ore");
if (metalOreDef != null)
{
int metalOreAmount = UnityEngine.Random.Range(config.MinMetalOreAmount, config.MaxMetalOreAmount + 1);
if (metalOreAmount > 0)
{
Item metalOreItem = ItemManager.Create(metalOreDef, metalOreAmount);
if (metalOreItem != null)
{
Effect.server.Run("assets/prefabs/misc/orebonus/effects/bonus_finish.prefab", info.HitEntity.transform.position);
attacker.GiveItem(metalOreItem);
}
}
}
}
info.damageTypes = new Rust.DamageTypeList();
info.HitMaterial = 0;
info.PointStart = info.PointEnd = Vector3.zero;
}
}
}
bool EntityInAnyCluster(BaseEntity entity)
{
foreach (var cluster in metalNodeClusters)
{
if (cluster.Contains(entity))
return true;
}
return false;
}
void CreateMarker(Vector3 position, string name)
{
if (!config.MarkerEnabled) return;
var mainMarker = GameManager.server.CreateEntity(genericPrefab, position) as MapMarkerGenericRadius;
if (mainMarker == null) return;
mainMarker.radius = config.MapMarkerSize;
mainMarker.alpha = config.MarkerAlpha;
Color markerColor;
if (ColorUtility.TryParseHtmlString(config.MarkerColour, out markerColor))
{
mainMarker.color1 = markerColor;
}
else
{
Puts($"Invalid MarkerColour value: {config.MarkerColour}.Using default color.");
mainMarker.color1 = Color.yellow;
}
Color markerColor2;
if (ColorUtility.TryParseHtmlString(config.MarkerColour2, out markerColor2))
{
mainMarker.color2 = markerColor2;
}
else
{
Puts($"Invalid MarkerColour2 value: {config.MarkerColour2}.Using default color.");
mainMarker.color2 = Color.red;
}
mainMarker.Spawn();
genericMarkers.Add(mainMarker);
mainMarker.SendUpdate();
}
void CreateVendingMarker(Vector3 position, string displayName)
{
if (!config.MarkerEnabled) return;
var vending = GameManager.server.CreateEntity(vendingPrefab, position) as VendingMachineMapMarker;
if (vending == null) return;
vending.enabled = true;
vending.markerShopName = displayName;
vending.Spawn();
vendingMarkers.Add(vending);
}
[ChatCommand("metalevent")]
private void HandleMetalEventCommand(BasePlayer player, string command, string[] args)
{
if (!permission.UserHasPermission(player.UserIDString, "metalevent.admin"))
{
SendReply(player, "You do not have permission to use this command.");
return;
}
if (args.Length == 0)
{
ShowAvailableCommands(player);
return;
}
string subCommand = args[0].ToLower();
switch (subCommand)
{
case "start":
CreateMetalEventChat(player);
break;
case "stop":
StopMetalEventChat(player);
break;
case "add":
if (args.Length > 1)
SetMetalEventSpawn(player, args[1]);
else
SendReply(player, "Please specify a name for the spawn point.");
break;
case "list":
ListMetalEventSpawn(player);
break;
case "clear":
ClearMetalEventSpawnChat(player);
break;
case "delete":
if (args.Length > 1)
DeleteMetalEventSpawn(player, args[1]);
else
SendReply(player, "Please specify the name of the spawn point to delete.");
break;
default:
SendReply(player, "Invalid command.Use '/metalevent' to view available commands.");
break;
}
}
private void ShowAvailableCommands(BasePlayer player)
{
string availableCommands = "Available commands: \n" +
"/metalevent start - Start MetalEvent \n" +
"/metalevent stop - Stop MetalEvent \n" +
"/metalevent add <name> - Set new MetalEvent spawn position \n" +
"/metalevent list - List all spawn positions \n" +
"/metalevent clear - Clear all spawn positions \n" +
"/metalevent delete <name> - Delete a spawn position \n" +
"/metalevent - View all commands \n" +
"Console Commands - MetalStart, MetalStop, MetalList, MetalListClear";
SendReply(player, availableCommands);
}
private void SetMetalEventSpawn(BasePlayer player, string spawnPointName)
{
if (permission.UserHasPermission(player.UserIDString, "metalevent.admin"))
{
if (string.IsNullOrEmpty(spawnPointName))
{
SendReply(player, "Please specify a name for the spawn point.");
return;
}
spawnData.SpawnPositions[spawnPointName] = player.transform.position;
Interface.Oxide.DataFileSystem.WriteObject("MetalEventSpawnData", spawnData);
Puts($"Set spawn position '{spawnPointName}' to {player.transform.position.x}, {player.transform.position.y}, {player.transform.position.z}");
SendReply(player, $"Set Unlimited Metal Node '{spawnPointName}' spawn position.");
}
else
{
SendReply(player, "You do not have permission to use this command.");
}
}
private void ListMetalEventSpawn(BasePlayer player)
{
if (permission.UserHasPermission(player.UserIDString, "metalevent.admin"))
{
if (spawnData.SpawnPositions.Count > 0)
{
int index = 1;
foreach (KeyValuePair<string, Vector3> entry in spawnData.SpawnPositions)
{
SendReply(player, $"{index}.Name: {entry.Key}, Position: {entry.Value}");
index++;
}
}
else
{
SendReply(player, "No spawn points have been set.");
}
}
else
{
SendReply(player, "You do not have permission to use this command.");
}
}
private void ClearMetalEventSpawnChat(BasePlayer player)
{
if (permission.UserHasPermission(player.UserIDString, "metalevent.admin"))
{
spawnData.SpawnPositions.Clear();
Interface.Oxide.DataFileSystem.WriteObject("MetalEventSpawnData", spawnData);
SendReply(player, "All MetalEvent spawn points have been cleared.");
}
else
{
SendReply(player, "You do not have permission to use this command.");
}
}
private void CreateMetalEventChat(BasePlayer player)
{
if (permission.UserHasPermission(player.UserIDString, "metalevent.admin"))
{
StartEvent();
}
else
{
SendReply(player, "You do not have permission to use this command.");
}
}
private void StopMetalEventChat(BasePlayer player)
{
if (permission.UserHasPermission(player.UserIDString, "metalevent.admin"))
{
StopEvent();
}
else
{
SendReply(player, "You do not have permission to use this command.");
}
}
private void OnNewSave(string filename)
{
if (resetNodesSpawnPoints)
{
ClearMetalEventSpawn(null);
}
}
[ConsoleCommand("metallistclear")]
private void ClearMetalEventSpawn(ConsoleSystem.Arg arg)
{
if (arg == null || arg.Connection?.player == null)
{
// Called from the server console or OnNewSave
spawnData.SpawnPositions.Clear();
Interface.Oxide.DataFileSystem.WriteObject("MetalEventSpawnData", spawnData);
Puts("All MetalEvent spawn points have been cleared.");
}
else
{
var player = arg.Connection.player as BasePlayer;
if (player != null && player.IsAdmin)
{
spawnData.SpawnPositions.Clear();
Interface.Oxide.DataFileSystem.WriteObject("MetalEventSpawnData", spawnData);
SendReply(player, "All MetalEvent spawn points have been cleared.");
}
else
{
SendReply(player, "You do not have permission to use this command.");
}
}
}
[ConsoleCommand("metallist")]
private void MetalListConsole(ConsoleSystem.Arg arg)
{
if (spawnData.SpawnPositions.Count > 0)
{
int index = 1;
foreach (KeyValuePair<string, Vector3> entry in spawnData.SpawnPositions)
{
Puts($"{index}.Name: {entry.Key}, Position: {entry.Value}");
index++;
}
}
}
[ConsoleCommand("metalstart")]
private void CreateMetalEvent(ConsoleSystem.Arg arg)
{
var player = arg.Connection?.player as BasePlayer;
if (player != null && player.IsAdmin)
{
StartEvent();
}
else if (player == null)
{
StartEvent();
}
}
[ConsoleCommand("metalstop")]
private void StopMetalEvent(ConsoleSystem.Arg arg)
{
var player = arg.Connection?.player as BasePlayer;
if (player != null && player.IsAdmin)
{
StopEvent();
}
else if (player == null)
{
StopEvent();
}
}
private void DeleteMetalEventSpawn(BasePlayer player, string spawnPointName)
{
if (permission.UserHasPermission(player.UserIDString, "metalevent.admin"))
{
if (string.IsNullOrEmpty(spawnPointName))
{
SendReply(player, "Please specify the name of the spawn point to delete.");
return;
}
if (spawnData.SpawnPositions.ContainsKey(spawnPointName))
{
spawnData.SpawnPositions.Remove(spawnPointName);
Interface.Oxide.DataFileSystem.WriteObject("MetalEventSpawnData", spawnData);
Puts($"Removed spawn position '{spawnPointName}'");
SendReply(player, $"Deleted Unlimited Metal Node '{spawnPointName}' spawn position.");
}
else
{
SendReply(player, $"No spawn point found with the name '{spawnPointName}'.");
}
}
else
{
SendReply(player, "You do not have permission to use this command.");
}
}
private void Unload()
{
eventTimer?.Destroy();
Unsubscribe(nameof(OnMeleeAttack));
Unsubscribe(nameof(OnPlayerConnected));
foreach (var basePlayer in BasePlayer.activePlayerList)
{
basePlayer.SendConsoleCommand("gametip.hidegametip");
CuiHelper.DestroyUi(basePlayer, UI_METAL_EVENT);
}
foreach (var cluster in metalNodeClusters)
{
foreach (BaseEntity node in cluster)
{
node.Kill();
}
}
foreach (var mapmarker in genericMarkers)
{
if(mapmarker != null)
{
mapmarker.Kill();
}
}
foreach (var vending in vendingMarkers)
{
if(vending != null)
{
vending.Kill();
}
}
foreach (var timer in lightningTimers)
{
timer.Destroy();
}
lightningTimers.Clear();
}
}
}