C#:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using UnityEngine;
using VLB;
using Network;
using Oxide.Core;
using Oxide.Core.Libraries;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System.Linq;
using Facepunch;
#if CARBON
using Carbon;
using Carbon.Plugins;
using Carbon.Components;
#else
using Oxide.Ext.CarbonAliases;
using System.Reflection;
#endif
namespace Oxide.Plugins {
[Info("MeteorEvent", "ThePitereq", "2.0.9")]
#if CARBON
public class MeteorEvent : CarbonPlugin
#else
public class MeteorEvent : RustPlugin
#endif
{
[PluginReference]
private readonly Plugin Economics, ServerRewards, BankSystem, ShoppyStock;
private static string webhookEmbed, webhookText;
private static readonly HashSet<SphereEntity> meteors = new HashSet<SphereEntity>();
private static readonly HashSet<SphereEntity> fallingMeteors = new HashSet<SphereEntity>();
private static readonly Dictionary<ulong, string> cachedSelectedEvent = new Dictionary<ulong, string>();
private static readonly Dictionary<ulong, Dictionary<string, int>> cooldowns = new Dictionary<ulong, Dictionary<string, int>>();
private static string cachedJson = "";
private static Timer cooldownTimer;
private static bool checkExplosives = true;
#if !CARBON
public static CUI.Handler CuiHandler {
get;
private set;
}
#endif
private void OnServerInitialized() {
LoadConfig();
LoadMessages();
ValidateHookRequirement();
RegisterCommandsAndPermissions();
StartEventTimers();
InitializeWebhooks();
GenerateStaticJson();
}
private void Unload() {
ClearMeteors();
using CUI cui = new CUI(CuiHandler);
foreach (var player in BasePlayer.activePlayerList) {
cui.Destroy("MeteorEventUI_corePanel", player);
cui.Destroy("MeteorEventUI_updateModule", player);
}
}
private void RegisterCommandsAndPermissions() {
cmd.AddConsoleCommand("msadmin", this, nameof(MeteorEventUserConsoleCommand));
#if CARBON
foreach (var command in config.commands) cmd.AddCovalenceCommand(command, this, nameof(MeteorEventCommand));
cmd.AddConsoleCommand(Community.Protect("UI_MeteorEvent"), this, nameof(MeteorEventConsoleCommand), @protected : true);
#else
foreach (var command in config.commands) cmd.AddChatCommand(command, this, nameof(MeteorEventCommand));
cmd.AddConsoleCommand("UI_MeteorEvent", this, nameof(MeteorEventConsoleCommand));
#endif
if (config.commandPermission) permission.RegisterPermission($"meteorevent.use", this);
permission.RegisterPermission($"meteorevent.admin", this);
foreach (var profile in config.profiles) if (profile.Value.purchaseConfig.requirePerm) permission.RegisterPermission($"meteorevent.purchase.{profile.Key}", this);
}
private void ValidateHookRequirement() {
bool lockLargeMeteorHooks = true;
bool lockExplosiveHooks = true;
foreach (var profile in config.meteorPrefabs.Values) {
if (profile.bigMeteors.bigMeteorChance > 0) {
lockLargeMeteorHooks = false;
foreach (var explosive in profile.bigMeteors.explosiveConfig.Keys) {
if (explosive == "ammo.rifle.explosive") {
lockExplosiveHooks = false;
break;
}
}
if (!lockExplosiveHooks) break;
}
}
if (lockLargeMeteorHooks) {
Unsubscribe(nameof(OnExplosiveThrown));
Unsubscribe(nameof(OnRocketLaunched));
}
if (lockExplosiveHooks) Unsubscribe(nameof(OnWeaponFired));
checkExplosives = !lockLargeMeteorHooks;
}
private void StartEventTimers() {
foreach (var configTimer in config.timedEvents.Keys) RunTimedEvent(configTimer);
}
private static void InitializeWebhooks() {
webhookEmbed = JsonConvert.SerializeObject(new {
embeds = new[] {
new {
title = "{title}", color = -5, fields = new[] {
new {
name = "{message}", value = "", inline = true
}
}
}
}
,
}
, Formatting.None);
webhookText = JsonConvert.SerializeObject(new {
content = "{message}"
}
, Formatting.None);
}
private void GenerateStaticJson() {
using CUI cui = new CUI(CuiHandler);
CuiElementContainer corePanel = cui.CreateContainer("MeteorEventUI_corePanel", BetterColors.Transparent, 0, 1, 0, 1, 0, 0, 0, 0, 0f, 0f, true, true, CUI.ClientPanels.HudMenu, null);
//Background Blur
cui.CreatePanel(corePanel, "MeteorEventUI_corePanel", BetterColors.BlackTransparent10, "assets/content/ui/uibackgroundblur.mat", 0, 1, 0, 1, 0, 0, 0, 0, true, 0f, 0f, false, false, null, null, false, "MeteorEventUI_UiBlur0", null, false);
//Background Darker
cui.CreatePanel(corePanel, "MeteorEventUI_corePanel", BetterColors.CraftingRadialBackground, "assets/content/ui/namefontmaterial.mat", 0, 1, 0, 1, 0, 0, 0, 0, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_UiPanel1", null, false);
//Middle Anchor
cui.CreatePanel(corePanel, "MeteorEventUI_corePanel", BetterColors.Transparent, "assets/content/ui/namefontmaterial.mat", 0.5f, 0.5f, 0.5f, 0.5f, 0, 0, 0, 0, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_middleAnchor", null, false);
//Main Background
cui.CreatePanel(corePanel, "MeteorEventUI_middleAnchor", BetterColors.RadioUiBackground, "assets/content/ui/namefontmaterial.mat", 0f, 0f, 0f, 0f, -227, 209, -108, 120, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_mainBackground", null, false);
//Top Panel
cui.CreatePanel(corePanel, "MeteorEventUI_mainBackground", BetterColors.LightGrayTransparent3_9, "assets/content/ui/namefontmaterial.mat", 0f, 0f, 0f, 0f, 0, 436, 202, 228, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_topPanel", null, false);
//Close Button
cui.CreateProtectedButton(corePanel, "MeteorEventUI_topPanel", BetterColors.RedBackground, BetterColors.RedText, " ✖", 12, "assets/content/ui/namefontmaterial.mat", 0f, 0f, 0f, 0f, 414, 432, 4, 22, "UI_MeteorEvent close", TextAnchor.MiddleLeft, CUI.Handler.FontTypes.RobotoCondensedBold, 0f, 0f, false, false, null, null, false, "MeteorEventUI_closeButton", null, false);
//Right Panel
cui.CreatePanel(corePanel, "MeteorEventUI_mainBackground", BetterColors.BlackTransparent20, "assets/content/ui/namefontmaterial.mat", 0f, 0f, 0f, 0f, 132, 436, 0, 202, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_rightPanel", null, false);
//Cooldown Panel
cui.CreatePanel(corePanel, "MeteorEventUI_rightPanel", BetterColors.LightGrayTransparent3_9, "assets/content/ui/namefontmaterial.mat", 0f, 0f, 0f, 0f, 265, 300, 182, 198, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_cooldownPanel", null, false);
//Cooldown Icon
cui.CreateSimpleImage(corePanel, "MeteorEventUI_cooldownPanel", "", "assets/icons/stopwatch.png", BetterColors.LightGrayTransparent73, null, 0f, 0f, 0f, 0f, 2, 14, 2, 14, 0f, 0f, false, false, null, null, false, "MeteorEventUI_UiImage4", null, false);
//Meteor Amount Panel
cui.CreatePanel(corePanel, "MeteorEventUI_rightPanel", BetterColors.LightGrayTransparent3_9, "assets/content/ui/namefontmaterial.mat", 0f, 0f, 0f, 0f, 265, 300, 164, 180, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_meteorAmountPanel", null, false);
//Meteor Amount Icon
cui.CreateSimpleImage(corePanel, "MeteorEventUI_meteorAmountPanel", "", "assets/content/ui/map/icon-map_rock.png", BetterColors.LightGrayTransparent73, null, 0f, 0f, 0f, 0f, 0, 16, 0, 16, 0f, 0f, false, false, null, null, false, "MeteorEventUI_UiImage5", null, false);
//Bottom Panel
cui.CreatePanel(corePanel, "MeteorEventUI_rightPanel", BetterColors.LightGrayTransparent8, "assets/content/ui/namefontmaterial.mat", 0f, 0f, 0f, 0f, 0, 304, 0, 28, false, 0f, 0f, false, false, null, null, false, "MeteorEventUI_bottomPanel", null, false);
//Info Icon
cui.CreateSimpleImage(corePanel, "MeteorEventUI_bottomPanel", "", "assets/icons/info.png", BetterColors.LightGrayTransparent73, null, 0f, 0f, 0f, 0f, 6, 22, 6, 22, 0f, 0f, false, false, null, null, false, "MeteorEventUI_UiImage7", null, false);
cachedJson = CuiHelper.ToJson(corePanel);
}
private void CheckCooldownTimer() {
int cooldownCount = cooldowns.Count;
if (cooldownCount > 0 && cooldownTimer == null) {
cooldownTimer = timer.Every(1f, () => RunCooldownTimer());
}
else if (cooldownCount == 0 && cooldownTimer != null) {
cooldownTimer.Destroy();
cooldownTimer = null;
}
}
private static void RunCooldownTimer() {
foreach (var user in cooldowns.ToList()) {
foreach (var profile in user.Value.ToList()) {
cooldowns[user.Key][profile.Key]--;
if (cooldowns[user.Key][profile.Key] <= 0) {
cooldowns[user.Key].Remove(profile.Key);
if (cooldowns[user.Key].Count == 0) cooldowns.Remove(user.Key);
}
}
}
}
private static void ClearMeteors() {
var meteorsCopy = new List<BaseEntity>(meteors);
foreach (var meteor in meteorsCopy) {
if (meteor == null || meteor.IsDestroyed) continue;
MeteorController controller = meteor.GetComponent<MeteorController>();
if (controller == null) continue;
controller.KillMeteor();
}
var fallingMeteorsCopy = new List<BaseEntity>(fallingMeteors);
foreach (var meteor in fallingMeteorsCopy) {
if (meteor == null || meteor.IsDestroyed) continue;
FallingMeteorController controller = meteor.GetComponent<FallingMeteorController>();
controller.DestroyFallingMeteor(false);
}
meteors.Clear();
fallingMeteors.Clear();
if (config.enableHooks) Interface.CallHook("OnMeteorsKilled");
}
private void MeteorEventCommand(BasePlayer player, string command, string[] args) {
if (config.commandPermission && !permission.UserHasPermission(player.UserIDString, "meteorevent.use")) {
SendReply(player, Lang("NoPermission", player.UserIDString));
return;
}
if (args.Length == 0) OpenMeteorEventUI(player);
else if (args.Length < 3) {
if (!permission.UserHasPermission(player.UserIDString, "meteorevent.admin")) {
OpenMeteorEventUI(player);
return;
}
SendReply(player, Lang("CommandHelp", player.UserIDString, command));
}
else if (args[0].ToLower() == "direct") {
if (!permission.UserHasPermission(player.UserIDString, "meteorevent.admin")) {
OpenMeteorEventUI(player);
return;
}
string profile = args[1];
if (!config.profiles.ContainsKey(profile)) {
SendReply(player, Lang("NoProfileFound", player.UserIDString, profile));
return;
}
BasePlayer directPlayer = BasePlayer.FindAwakeOrSleeping(args[2]);
if (directPlayer == null) {
SendReply(player, Lang("NoPlayerFound", player.UserIDString, args[2]));
return;
}
int meteorAmount = 1;
if (args.Length > 3) int.TryParse(args[2], out meteorAmount);
SendReply(player, Lang("CallingDirectOnPlayer", player.UserIDString, profile, directPlayer.displayName, meteorAmount));
RunMeteorShower(profile, meteorAmount, true, directPlayer);
}
}
private void MeteorEventConsoleCommand(ConsoleSystem.Arg arg) {
BasePlayer player = arg.Player();
if (config.enableLogs) Puts($"Player {player.displayName} ({player.UserIDString}) ran MeteorEventUI sub-command: {arg.FullString}");
using CUI cui = new CUI(CuiHandler);
switch (arg.Args[0]) {
case "close" : cui.Destroy("MeteorEventUI_corePanel", player);
cui.Destroy("MeteorEventUI_updateModule", player);
break;
case "switch" : string oldCategory = cachedSelectedEvent[player.userID];
cachedSelectedEvent[player.userID] = arg.Args[1];
UpdateDisplay(player, oldCategory);
break;
case "forceStart" : int meteorCount;
if (int.TryParse(arg.Args[2], out meteorCount)) {
if (meteorCount <= 0) return;
cui.Destroy("MeteorEventUI_corePanel", player);
cui.Destroy("MeteorEventUI_updateModule", player);
RunMeteorShower(arg.Args[1], meteorCount);
}
break;
case "start" : cui.Destroy("MeteorEventUI_corePanel", player);
cui.Destroy("MeteorEventUI_updateModule", player);
TryPurchaseEvent(player, arg.Args[1]);
break;
}
}
private void MeteorEventUserConsoleCommand(ConsoleSystem.Arg arg) {
if (!arg.IsAdmin) return;
if (arg.Args == null || arg.Args.Length < 1) {
SendReply(arg, "Meteor Event Admin Commands:\nmsadmin run <profile> <amount> - Starts meteor event of desired profile with certain amount of meteors.\nmsadmin direct <profile> [amount] [userId] - Starts direct meteor event of desired profile with certain amount of meteors at certain player.\n\nmsadmin kill - Kills all meteors around the map");
return;
}
switch (arg.Args[0]) {
case "run" : string profile = arg.Args[1];
if (!config.profiles.ContainsKey(profile)) {
SendReply(arg, "[ERROR] Couldn't find meteor event profile!");
return;
}
int amount;
if (int.TryParse(arg.Args[2], out amount)) RunMeteorShower(profile, amount);
else SendReply(arg, "[ERROR] Specify correct amount of meteors!");
break;
case "direct" : profile = arg.Args[1];
if (!config.profiles.ContainsKey(profile)) {
SendReply(arg, "[ERROR] Couldn't find meteor event profile!");
return;
}
amount = 1;
if (arg.Args.Length > 1) int.TryParse(arg.Args[2], out amount);
BasePlayer directPlayer = null;
ulong directId = 0;
if (arg.Args.Length > 2 && ulong.TryParse(arg.Args[3], out directId)) directPlayer = BasePlayer.FindByID(directId);
if (directId != 0 && directPlayer == null) {
SendReply(arg, "[ERROR] Player not found!");
return;
}
RunMeteorShower(profile, amount, true, directPlayer);
break;
case "kill" : ClearMeteors();
break;
default : SendReply(arg, "[ERROR] Invalid format!\nMeteor Event Admin Commands:\nmsadmin run <profile> <amount> - Starts meteor event of desired profile with certain amount of meteors.\nmsadmin direct <profile> [amount] [userId] - Starts direct meteor event of desired profile with certain amount of meteors at certain player.\n\nmsadmin kill - Kills all meteors around the map");
break;
}
}
private void TryPurchaseEvent(BasePlayer player, string profile) {
MeteorConfig configValue = config.profiles[profile];
if (configValue.purchaseConfig.showerCooldown > 0 && cooldowns.ContainsKey(player.userID) && cooldowns[player.userID].ContainsKey(profile)) return;
bool hasRequiredItem;
if (configValue.purchaseConfig.requiredShortname == "") hasRequiredItem = HaveCurrency(player, configValue.purchaseConfig.economyPlugin, configValue.purchaseConfig.economyCurrency) >= configValue.purchaseConfig.showerItemAmount;
else {
int itemAmount = 0;
List<Item> playerItems = new List<Item>();
playerItems.AddRange(player.inventory.containerMain.itemList);
playerItems.AddRange(player.inventory.containerBelt.itemList);
playerItems.AddRange(player.inventory.containerWear.itemList);
foreach (var item in playerItems) {
if (item.info.shortname == configValue.purchaseConfig.requiredShortname && item.skin == configValue.purchaseConfig.requiredSkin) {
itemAmount += item.amount;
}
}
hasRequiredItem = itemAmount >= configValue.purchaseConfig.showerItemAmount;
}
if (!hasRequiredItem) return;
if (configValue.purchaseConfig.requiredShortname == "") TakeCurrency(player, configValue.purchaseConfig.showerItemAmount, configValue.purchaseConfig.economyPlugin, configValue.purchaseConfig.economyCurrency);
else {
int remainingAmount = configValue.purchaseConfig.showerItemAmount;
// Collect items from all inventory containers (main, belt, and wear)
List<Item> playerItems = new List<Item>();
playerItems.AddRange(player.inventory.containerMain.itemList);
playerItems.AddRange(player.inventory.containerBelt.itemList);
playerItems.AddRange(player.inventory.containerWear.itemList);
// Loop through all items
foreach (var item in playerItems) {
if (item.info.shortname == configValue.purchaseConfig.requiredShortname && item.skin == configValue.purchaseConfig.requiredSkin) {
if (item.amount < remainingAmount) {
remainingAmount -= item.amount;
item.GetHeldEntity()?.Kill();
// Kill the held entity (if applicable)
item.RemoveFromContainer();
// Remove the item from its container
item.Remove();
// Remove the item entirely
}
else {
item.amount -= remainingAmount;
// Reduce item amount by the remaining required amount
item.MarkDirty();
// Mark the item as dirty to update the inventory
break;
// Break after deducting the required amount
}
}
}
}
if (configValue.purchaseConfig.showerCooldown > 0) {
cooldowns.TryAdd(player.userID, new Dictionary<string, int>());
cooldowns[player.userID].TryAdd(profile, 0);
cooldowns[player.userID][profile] = configValue.purchaseConfig.showerCooldown;
CheckCooldownTimer();
}
if (config.enableLogs) Puts($"Player {player.displayName} ({player.UserIDString}) purchased meteor event of profile '{profile}'.");
SendReply(player, Lang("SuccessfullyPurchasedEvent", player.UserIDString));
RunMeteorShower(profile, configValue.purchaseConfig.showerMeteorAmount, configValue.purchaseConfig.isDirect, player, configValue.purchaseConfig.allowLarge);
}
private object OnMeleeAttack(BasePlayer player, HitInfo info) {
if (info.HitEntity == null) return null;
SphereEntity sphere = info.HitEntity.GetParentEntity() as SphereEntity;
if (sphere == null) return null;
MeteorController controller = sphere.GetComponent<MeteorController>();
if (controller == null) return null;
MeteorPrefabConfig configValue = config.meteorPrefabs[controller.profile];
string shortname = player.GetActiveItem()?.info?.shortname ?? "";
if (controller.largeMeteor) {
if (shortname == "" || !configValue.bigMeteors.explosiveConfig.ContainsKey(shortname)) {
SendReply(player, Lang($"WrongToolToGather_{controller.profile}", player.UserIDString));
return false;
}
}
if (configValue.toolBlacklist.Contains(shortname)) {
SendReply(player, Lang("ToolInBlacklist", player.UserIDString));
return false;
}
if (configValue.toolWhitelist.Count > 0 && !configValue.toolWhitelist.Contains(shortname)) {
SendReply(player, Lang("ToolNotInWhitelist", player.UserIDString));
return false;
}
OreResourceEntity ore = info.HitEntity as OreResourceEntity;
if (ore != null) InitializeCustomGatherMethod(info, ore);
else InitializeFullyCustomGatherMethod(player, info, controller, configValue);
return false;
}
private void OnPlayerConnected() => FixMarkerVisibility();
private void OnExplosiveThrown(BasePlayer player, BaseEntity entity) => entity.OwnerID = player.userID;
private void OnRocketLaunched(BasePlayer player, BaseEntity entity) => entity.OwnerID = player.userID;
private void OnEntityKill(TimedExplosive explosive) {
if (!checkExplosives || explosive.OwnerID == 0) return;
OreResourceEntity node = explosive.GetParentEntity() as OreResourceEntity;
if (node == null) {
if (explosive.GetComponent<ServerProjectile>() == null) return;
List<OreResourceEntity> nearbyOres = Pool.GetList<OreResourceEntity>();
Vis.Entities(explosive.transform.position, 1, nearbyOres);
foreach (var nearbyNode in nearbyOres) {
SphereEntity nearbySphere = nearbyNode.GetParentEntity() as SphereEntity;
if (nearbySphere == null) continue;
MeteorController nearbyController = nearbySphere.GetComponent<MeteorController>();
if (nearbyController == null || !nearbyController.largeMeteor) continue;
BasePlayer player1 = BasePlayer.FindByID(explosive.OwnerID);
if (player1 == null) continue;
DamageNode(nearbyNode, nearbyController, player1, explosive.ShortPrefabName);
}
Pool.FreeList(ref nearbyOres);
return;
}
SphereEntity sphere = node.GetParentEntity() as SphereEntity;
if (sphere == null) return;
MeteorController controller = sphere.GetComponent<MeteorController>();
if (controller == null || !controller.largeMeteor) return;
BasePlayer player2 = BasePlayer.FindByID(explosive.OwnerID);
if (player2 == null) return;
DamageNode(node, controller, player2, explosive.ShortPrefabName);
}
private void OnWeaponFired(BaseProjectile projectile, BasePlayer player) {
if (projectile.primaryMagazine.ammoType.shortname != "ammo.rifle.explosive") return;
RaycastHit hit;
if (Physics.Raycast(player.eyes.HeadRay(), out hit, config.allMeteors.maxDistanceAmmo)) {
OreResourceEntity hitRock = hit.GetCollider().ToBaseEntity() as OreResourceEntity;
if (hitRock == null) return;
SphereEntity sphere = hitRock.GetParentEntity() as SphereEntity;
if (sphere == null) return;
MeteorController controller = sphere.GetComponent<MeteorController>();
if (controller == null || !controller.largeMeteor) return;
DamageNode(hitRock, controller, player, "ammo.rifle.explosive");
}
}
private void OnEntityKill(OreResourceEntity rock) => CheckForMeteorKill(rock);
private void CheckForMeteorKill(OreResourceEntity rock) {
if (rock.transform.position.y < 0) return;
SphereEntity sphere = rock.GetParentEntity() as SphereEntity;
if (sphere == null) {
if (!config.randomDirectEventsEnabled) return;
RunRandomMeteorEvent(rock);
return;
}
MeteorController controller = sphere.GetComponent<MeteorController>();
if (controller == null) return;
controller.KillMeteor();
}
private void RunRandomMeteorEvent(OreResourceEntity rock) {
foreach (var randomEvent in config.randomDirectEvents) {
if (Core.Random.Range(0f, 100f) > randomEvent.chance) continue;
int meteorCount = randomEvent.minMeteors == randomEvent.maxMeteors ? randomEvent.minMeteors : Core.Random.Range(randomEvent.minMeteors, randomEvent.maxMeteors + 1);
List<BasePlayer> nearbyPlayers = Pool.GetList<BasePlayer>();
Vis.Entities(rock.transform.position, 2.5f, nearbyPlayers);
if (nearbyPlayers.Count == 0) {
Pool.FreeList(ref nearbyPlayers);
return;
}
RunMeteorShower(randomEvent.profileName, meteorCount, true, nearbyPlayers[0], randomEvent.allowLarge);
Pool.FreeList(ref nearbyPlayers);
}
}
private static void FixMarkerVisibility() {
foreach (var meteor in meteors) foreach (var child in meteor.children) if (child is MapMarkerGenericRadius marker) marker.SendUpdate();
}
private void OnDispenserBonus(ResourceDispenser dispenser, BasePlayer player, Item item) {
OreResourceEntity node = dispenser._baseEntity?.GetComponent<OreResourceEntity>();
if (node == null) return;
TryGiveBonusItems(node, player, item, true);
}
private void OnDispenserGather(ResourceDispenser dispenser, BasePlayer player, Item item) {
OreResourceEntity node = dispenser._baseEntity?.GetComponent<OreResourceEntity>();
if (node == null) return;
TryGiveBonusItems(node, player, item);
}
private static void TryGiveBonusItems(OreResourceEntity node, BasePlayer player, Item item, bool finalHit = false) {
SphereEntity sphere = node.GetParentEntity() as SphereEntity;
if (sphere == null) return;
MeteorController controller = sphere.GetComponent<MeteorController>();
if (controller == null) return;
MeteorPrefabConfig configValue = config.meteorPrefabs[controller.profile];
item.amount = (int)Mathf.Round(item.amount * configValue.meteorYieldMultiplier);
if (configValue.scaleMeteorYield) item.amount = (int)Mathf.Round(item.amount * sphere.lerpRadius);
foreach (var bonusItem in configValue.additionalOutput) {
if (bonusItem.lastHitOnly && !finalHit) continue;
if (bonusItem.requiredAmount > 0 && item.amount < bonusItem.requiredAmount) continue;
if (Core.Random.Range(0f, 100f) <= bonusItem.chance) {
Item bonus = ItemManager.CreateByName(bonusItem.shortname, bonusItem.amount, bonusItem.skinId);
if (bonusItem.name != "") bonus.name = bonusItem.name;
player.GiveItem(bonus);
}
}
}
private static void TryGiveCustomBonusItems(BaseEntity entity, BasePlayer player, int damageDealt, bool finalHit = false) {
SphereEntity sphere = entity.GetParentEntity() as SphereEntity;
if (sphere == null) return;
MeteorController controller = sphere.GetComponent<MeteorController>();
if (controller == null) return;
MeteorPrefabConfig configValue = config.meteorPrefabs[controller.profile];
foreach (var bonusItem in configValue.additionalOutput) {
if (bonusItem.lastHitOnly && !finalHit) continue;
if (bonusItem.requiredAmount > 0 && damageDealt < bonusItem.requiredAmount) continue;
if (Core.Random.Range(0f, 100f) <= bonusItem.chance) {
Item bonus = ItemManager.CreateByName(bonusItem.shortname, bonusItem.amount, bonusItem.skinId);
if (bonusItem.name != "") bonus.name = bonusItem.name;
player.GiveItem(bonus);
}
}
}
private void InitializeFullyCustomGatherMethod(BasePlayer player, HitInfo info, MeteorController controller, MeteorPrefabConfig configValue) {
BaseCombatEntity entity = info.HitEntity as BaseCombatEntity;
if (entity == null) {
Puts($"You've set {info.HitEntity.ShortPrefabName} as meteor, but it isn't an BaseCombatEntity and can't be used as meteor!");
return;
}
#if CARBON
Item tool = info.Weapon?.cachedItem;
#else
Item tool = info.Weapon.GetCachedItem();
#endif
if (tool == null) return;
string itemShortname = configValue.customGather.customGatherTools.ContainsKey(tool.info.shortname) ? tool.info.shortname : "*";
if (!configValue.customGather.customGatherTools.ContainsKey(itemShortname)) {
SendReply(player, Lang($"WrongToolToGather_{controller.profile}", player.UserIDString));
return;
}
CustomToolConfig customToolConfig = configValue.customGather.customGatherTools[itemShortname];
if (customToolConfig.requireSkin && tool.skin != customToolConfig.skin) {
SendReply(player, Lang($"WrongToolToGather_{controller.profile}", player.UserIDString));
return;
}
float dealtDmg = entity.MaxHealth() / 100f * customToolConfig.damageDealtPercentage;
entity.health -= dealtDmg;
entity.SendNetworkUpdate();
bool lastHit = entity.health <= 0;
foreach (var resource in configValue.customGather.meteorItems) {
if (resource.lastHitOnly && !lastHit) continue;
float percentage = lastHit && resource.lastHitOnly ? 100f : customToolConfig.damageDealtPercentage;
int resourceAmount = (int)Mathf.Round(resource.amount / 100f * percentage);
if (resourceAmount == 0) continue;
Item outputItem = ItemManager.CreateByName(resource.shortname, resourceAmount, resource.skinId);
if (resource.name != "") outputItem.name = resource.name;
player.GiveItem(outputItem);
}
TryGiveCustomBonusItems(entity, player, Mathf.CeilToInt(dealtDmg), lastHit);
if (lastHit) {
entity.Kill(BaseNetworkable.DestroyMode.Gib);
controller.KillMeteor();
}
}
#region Edited RUST Gather Code
#if !CARBON
private static readonly Dictionary<OreResourceEntity, int> bonusesKilled = new Dictionary<OreResourceEntity, int>();
#endif
private static void InitializeCustomGatherMethod(HitInfo info, OreResourceEntity ore) {
if (!info.DidGather && info.gatherScale > 0f) {
Jackhammer jackhammer = info.Weapon as Jackhammer;
if (ore._hotSpot || jackhammer) {
if (ore._hotSpot == null) ore._hotSpot = ore.SpawnBonusSpot(ore.lastNodeDir);
#if CARBON
if ((ore._hotSpot != null && Vector3.Distance(info.HitPositionWorld, ore._hotSpot.transform.position) <= ore._hotSpot.GetComponent<SphereCollider>().radius * 1.5f) || jackhammer != null) {
float num = (jackhammer == null) ? 1f : jackhammer.HotspotBonusScale;
ore.bonusesKilled++;
info.gatherScale = 1f + Mathf.Clamp((float)ore.bonusesKilled * 0.5f, 0f, 2f * num);
if (ore._hotSpot != null) {
ore._hotSpot.FireFinishEffect();
ore.ClientRPC<int, Vector3>(null, "PlayBonusLevelSound", ore.bonusesKilled, ore._hotSpot.transform.position);
}
}
else if (ore.bonusesKilled > 0) {
ore.bonusesKilled = 0;
Effect.server.Run(ore.bonusFailEffect.resourcePath, ore.transform.position, ore.transform.up, null, false);
}
if (ore.bonusesKilled > 0) ore.CleanupBonus();
#else
bonusesKilled.TryAdd(ore, 0);
if ((ore._hotSpot != null && Vector3.Distance(info.HitPositionWorld, ore._hotSpot.transform.position) <= ore._hotSpot.GetComponent<SphereCollider>().radius * 1.5f) || jackhammer != null) {
float num = (jackhammer == null) ? 1f : jackhammer.HotspotBonusScale;
bonusesKilled[ore]++;
info.gatherScale = 1f + Mathf.Clamp((float)bonusesKilled[ore] * 0.5f, 0f, 2f * num);
if (ore._hotSpot != null) {
ore._hotSpot.FireFinishEffect();
ore.ClientRPC<int, Vector3>(null, "PlayBonusLevelSound", bonusesKilled[ore], ore._hotSpot.transform.position);
}
}
else if (bonusesKilled[ore] > 0) {
bonusesKilled[ore] = 0;
Effect.server.Run(ore.bonusFailEffect.resourcePath, ore.transform.position, ore.transform.up, null, false);
}
if (bonusesKilled[ore] > 0) ore.CleanupBonus();
#endif
}
}
if (ore._hotSpot == null) ore.DelayedBonusSpawn();
if (ore.isServer && !ore.IsDestroyed) {
if (ore.resourceDispenser != null) {
if (!ore.resourceDispenser.baseEntity.isServer) return;
if (info.DidGather) return;
if (ore.resourceDispenser.gatherType == ResourceDispenser.GatherType.UNSET) {
Debug.LogWarning("Object :" + ore.resourceDispenser.gameObject.name + ": has unset gathertype!");
return;
}
BaseMelee baseMelee = (info.Weapon == null) ? null : (info.Weapon as BaseMelee);
float num;
float num2;
if (baseMelee != null) {
ResourceDispenser.GatherPropertyEntry gatherInfoFromIndex = baseMelee.GetGatherInfoFromIndex(ore.resourceDispenser.gatherType);
num = gatherInfoFromIndex.gatherDamage * info.gatherScale;
num2 = gatherInfoFromIndex.destroyFraction;
if (num == 0f) return;
baseMelee.SendPunch(new Vector3(UnityEngine.Random.Range(0.5f, 1f), UnityEngine.Random.Range(-0.25f, -0.5f), 0f) * -30f * (gatherInfoFromIndex.conditionLost / 6f), 0.05f);
baseMelee.LoseCondition(gatherInfoFromIndex.conditionLost);
if (!baseMelee.IsValid() || baseMelee.IsBroken()) return;
info.DidGather = true;
}
else {
num = info.damageTypes.Total();
num2 = 0.5f;
}
float num3 = ore.resourceDispenser.fractionRemaining;
#if CARBON
ore.resourceDispenser.GiveResources(info.InitiatorPlayer, num, num2, info.Weapon);
ore.resourceDispenser.UpdateFraction();
#else
MethodInfo giveResourcesMethod = ore.resourceDispenser.GetType().GetMethod("GiveResources", BindingFlags.NonPublic | BindingFlags.Instance);
giveResourcesMethod.Invoke(ore.resourceDispenser, new object[] {
info.InitiatorPlayer, num, num2, info.Weapon
}
);
MethodInfo updateFractionMethod = ore.resourceDispenser.GetType().GetMethod("UpdateFraction", BindingFlags.NonPublic | BindingFlags.Instance);
updateFractionMethod.Invoke(ore.resourceDispenser, new object[] {
}
);
#endif
float damageAmount;
if (ore.resourceDispenser.fractionRemaining <= 0f) {
damageAmount = ore.resourceDispenser.baseEntity.MaxHealth();
if (info.DidGather && num2 < ore.resourceDispenser.maxDestroyFractionForFinishBonus) ore.resourceDispenser.AssignFinishBonus(info.InitiatorPlayer, 1f - num2, info.Weapon);
}
else damageAmount = (num3 - ore.resourceDispenser.fractionRemaining) * ore.resourceDispenser.baseEntity.MaxHealth();
HitInfo hitInfo = new HitInfo(info.Initiator, ore.resourceDispenser.baseEntity, Rust.DamageType.Generic, damageAmount, ore.resourceDispenser.transform.position) {
gatherScale = 0f, PointStart = info.PointStart, PointEnd = info.PointEnd, WeaponPrefab = info.WeaponPrefab, Weapon = info.Weapon
}
;
ore.resourceDispenser.baseEntity.OnAttacked(hitInfo);
}
if (!info.DidGather) {
if (ore.baseProtection) ore.baseProtection.Scale(info.damageTypes, 1f);
float num = info.damageTypes.Total();
ore.health -= num;
if (ore.health <= 0f) ore.OnKilled(info);
#if CARBON
ore.OnHealthChanged();
#else
MethodInfo healthChangeMethod = ore.GetType().GetMethod("OnHealthChanged", BindingFlags.NonPublic | BindingFlags.Instance);
healthChangeMethod.Invoke(ore, new object[] {
}
);
#endif
}
}
}
#endregion
private static void DamageNode(OreResourceEntity node, MeteorController controller, BasePlayer player, string prefabName) {
if (node.IsDestroyed) return;
MeteorPrefabConfig configValue = config.meteorPrefabs[controller.profile];
if (!configValue.bigMeteors.explosiveConfig.ContainsKey(prefabName)) return;
float multiplier = configValue.bigMeteors.explosiveConfig[prefabName].resourceMultiplier;
node.health -= configValue.bigMeteors.explosiveConfig[prefabName].damageDealt;
node.UpdateNetworkStage();
foreach (var item in configValue.bigMeteors.output) {
if (item.lastHitOnly && node.health > 0) continue;
if (item.chance < 100 && Core.Random.Range(0f, 100f) > item.chance) continue;
Item createdItem = ItemManager.CreateByName(item.shortname, (int)Mathf.Round(item.amount * multiplier), item.skinId);
if (item.name != "") createdItem.name = item.name;
if (configValue.bigMeteors.callHook) Interface.CallHook("OnDispenserGather", node.resourceDispenser, player, createdItem);
player.GiveItem(createdItem);
}
if (node.health <= 0) {
Effect.server.Run("assets/prefabs/misc/orebonus/effects/ore_finish.prefab", node.transform.position);
controller.KillMeteor();
}
}
private void RunTimedEvent(string keyName) {
TimedEventConfig configValue = config.timedEvents[keyName];
float time = configValue.timerMin == configValue.timerMax ? configValue.timerMin : Core.Random.Range(configValue.timerMin, configValue.timerMax);
timer.Once(time, () => ExecuteTimedEvent(keyName, configValue));
}
private void ExecuteTimedEvent(string keyName, TimedEventConfig configValue) {
if (configValue.eventChance < 100 && Core.Random.Range(0f, 100f) > configValue.eventChance) {
if (config.enableLogs) Puts($"Timed event {keyName} has been skipped due to chance not rolled.");
RunTimedEvent(keyName);
return;
}
if (meteors.Count > config.allMeteors.maxMeteors) {
if (config.enableLogs) Puts($"Timed event {keyName} has been skipped due to too many meteors on map.");
RunTimedEvent(keyName);
return;
}
float meteorCount = configValue.meteorCount;
if (configValue.basedOnOnline) meteorCount *= BasePlayer.activePlayerList.Count;
if (meteorCount > configValue.maxMeteorCount) meteorCount = configValue.maxMeteorCount;
if (meteorCount == 0) {
if (config.enableLogs) Puts("There is not enough players to start meteor event.Skipping event...");
RunTimedEvent(keyName);
return;
}
int sumWeight = 0;
foreach (var profile in configValue.spawnProfiles) sumWeight += profile.Value;
int rolledProfile = Core.Random.Range(0, sumWeight + 1);
sumWeight = 0;
foreach (var profile in configValue.spawnProfiles) {
sumWeight += profile.Value;
if (sumWeight >= rolledProfile) {
int meteorCountInt = (int)Mathf.Round(meteorCount);
foreach (var command in configValue.commandsToRun) Server.Command(command.Replace("{meteorCount}", meteorCountInt.ToString()));
if (configValue.webhook.enabled) SendWebhook(configValue, meteorCountInt);
RunMeteorShower(profile.Key, meteorCountInt, configValue.directPlayer);
break;
}
}
RunTimedEvent(keyName);
}
private void SendWebhook(TimedEventConfig configValue, int meteorCount) {
string message = configValue.webhook.embed ? webhookEmbed.Replace("-5", configValue.webhook.color.ToString()).Replace(true.ToString(), configValue.webhook.inline.ToString().ToLower()).Replace("{title}", configValue.webhook.title).Replace("{message}", string.Format(configValue.webhook.message, meteorCount)) : webhookText.Replace("{message}", string.Format(configValue.webhook.message, meteorCount));
webrequest.Enqueue(configValue.webhook.webhook, message, (i, s) => {
if (i != 204) PrintWarning($"Cannot send Discord Webhook.Error {i}:\n{s}");
}
, this, RequestMethod.POST, new Dictionary<string, string> {
{
"Content-Type", "application/json"
}
}
);
}
private void RunMeteorShower(string profile, int meteorCount, bool direct = false, BasePlayer directPlayer = null, bool allowLarge = true) {
MeteorConfig configValue = config.profiles[profile];
bool soundPrefab = configValue.soundPrefab != "";
if (!direct && (soundPrefab || configValue.message)) foreach (var player in BasePlayer.activePlayerList) {
if (soundPrefab) Effect.server.Run(configValue.soundPrefab, player.eyes.position);
if (configValue.message) SendReply(player, Lang($"MeteorShowerIncoming_{profile}", player.UserIDString, meteorCount));
}
float timeBetween = configValue.maxMeteorShowerLength / meteorCount;
Vector3 angle = configValue.directFalling;
Vector3 offset = Vector3.zero;
if (!direct) {
int sumWeight = 0;
foreach (var angleValue in configValue.angleConfig) sumWeight += angleValue.weight;
int rolledAngle = Core.Random.Range(0, sumWeight + 1);
sumWeight = 0;
foreach (var angleValue in configValue.angleConfig) {
sumWeight += angleValue.weight;
if (sumWeight >= rolledAngle) {
angle = angleValue.angle;
offset = angleValue.offset;
break;
}
}
}
int summedMeteorProfileWeight = 0;
foreach (var meteorProfile in configValue.meteorProfiles) summedMeteorProfileWeight += meteorProfile.Value;
int counter = 0;
float worldSize = World.Size / config.allMeteors.meteorSpread;
if (config.enableLogs) Puts($"Started meteor shower of profile '{profile}' with {meteorCount} meteors.(direct: {direct}, directPlayer: {directPlayer != null})");
if (config.enableHooks) Interface.CallHook("OnMeteorShowerStart", meteorCount, profile, direct, directPlayer);
timer.Repeat(configValue.maxMeteorShowerLength / meteorCount, meteorCount, () => {
Vector3 targetPos;
if (direct) {
if (directPlayer != null) {
Vector3 playerPos = directPlayer.transform.position;
targetPos = new Vector3(playerPos.x + Core.Random.Range(-configValue.impactRadius, configValue.impactRadius), configValue.spawnHeight, playerPos.z + Core.Random.Range(-configValue.impactRadius, configValue.impactRadius));
}
else {
int playerIndex = counter % BasePlayer.activePlayerList.Count;
Vector3 targetPlayerPos = BasePlayer.activePlayerList[playerIndex].transform.position;
targetPos = new Vector3(targetPlayerPos.x + Core.Random.Range(-configValue.impactRadius, configValue.impactRadius), configValue.spawnHeight, targetPlayerPos.z + Core.Random.Range(-configValue.impactRadius, configValue.impactRadius));
}
}
else {
float randX = Core.Random.Range(-worldSize, worldSize);
// ... [Code preview truncated due to length. Full source in attached file] ...