C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
/*
RespawnBradley Copyright (c) 2021-2022 by PinguinNordpol
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
GNU General Public License for more details.You should have received a copy of the GNU General Public License
along with this program.If not, see https://www.gnu.org/licenses/.*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using Rust;
using ConVar;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using Oxide.Core.Libraries;
using Oxide.Core.Libraries.Covalence;
namespace Oxide.Plugins
{
[Info("Respawn Bradley", "PinguinNordpol", "0.3.2")]
[Description("Adds the possibility to respawn Bradley via command")]
class RespawnBradley : CovalencePlugin
{
[PluginReference]
private Plugin ServerRewards, Economics, LootDefender;
#region Fields
private ConfigData config_data;
private CooldownController cooldown_controller;
#endregion
#region Oxide Hooks
void Init()
{
// Register our permissions
permission.RegisterPermission("respawnbradley.use", this);
permission.RegisterPermission("respawnbradley.nolock", this);
permission.RegisterPermission("respawnbradley.nocosts", this);
permission.RegisterPermission("respawnbradley.nocooldown", this);
}
void Loaded() => lang.RegisterMessages(Messages, this);
void OnServerInitialized()
{
LoadConfig();
if (this.config_data.Cooldowns.EnableCooldown)
{
// Initialize cooldown controller if requested to
this.cooldown_controller = new CooldownController(this, this.config_data.Cooldowns.CooldownSecs, this.config_data.Cooldowns.CooldownPerPlayer);
}
}
#endregion
#region Cooldown control
private class CooldownController
{
private DynamicConfigFile cooldown_datafile;
private Dictionary<string, DateTime> cooldown_data;
private RespawnBradley parent;
private double cooldown_secs;
private bool cooldown_per_player;
/*
* CooldownController
*
* Constructor
*/
public CooldownController(RespawnBradley _parent, uint _cooldown_secs, bool _cooldown_per_player)
{
// Save settings
this.parent = _parent;
this.cooldown_secs = Convert.ToDouble(_cooldown_secs);
this.cooldown_per_player = _cooldown_per_player;
// Read and parse cooldown data file
if (Interface.Oxide.DataFileSystem.ExistsDatafile("RespawnBradleyCooldowns"))
{
this.cooldown_datafile = Interface.Oxide.DataFileSystem.GetFile("RespawnBradleyCooldowns");
this.cooldown_data = this.cooldown_datafile.ReadObject<Dictionary<string, DateTime>>();
// Do housekeeping on file content
if (this.cooldown_data.Count != 0)
{
this.parent.Puts("Removing expired cooldown data, this might take a moment.");
List<string> players_on_cooldown = new List<string>(this.cooldown_data.Keys);
foreach(string player_on_cooldown in players_on_cooldown)
{
if (this.cooldown_data[player_on_cooldown].AddSeconds(this.cooldown_secs) <= DateTime.UtcNow)
{
this.cooldown_data.Remove(player_on_cooldown);
}
}
this.cooldown_datafile.WriteObject(this.cooldown_data);
this.parent.Puts($"Removed {players_on_cooldown.Count-this.cooldown_data.Count} expired of {players_on_cooldown.Count} total cooldown(s).");
}
}
else
{
// Create new data file
this.parent.Puts("Creating new cooldown data file!");
this.cooldown_datafile = Interface.Oxide.DataFileSystem.GetFile("RespawnBradleyCooldowns");
this.cooldown_data = new Dictionary<string, DateTime>();
this.cooldown_datafile.WriteObject(this.cooldown_data);
}
}
/*
* AddCooldown
*
* Add a cooldown for a player
*/
public void AddCooldown(string player_id)
{
if (!this.cooldown_per_player)
{
// In case we have a global cooldown, user player id 0, and hope this will never be a real player id
player_id = "0";
}
this.cooldown_data[player_id] = DateTime.UtcNow;
this.cooldown_datafile.WriteObject(this.cooldown_data);
}
/*
* HasCooldown
*
* Check if a player currently has a cooldown
*/
public bool HasCooldown(string player_id) => HasCooldownHelper(player_id, DateTime.UtcNow);
public bool HasCooldown(string player_id, out uint remaining_secs)
{
DateTime now = DateTime.UtcNow;
if (HasCooldownHelper(player_id, now))
{
// Player has a cooldown, return the actual cooldown time in remaining_secs
remaining_secs = Convert.ToUInt32(this.cooldown_secs - (now - this.cooldown_data[player_id]).TotalSeconds);
return true;
}
remaining_secs = 0;
return false;
}
/*
* HasCooldownHelper
*
* Helper function for HasCooldown functions
*/
private bool HasCooldownHelper(string player_id, DateTime now)
{
if (!this.cooldown_per_player)
{
// In case we have a global cooldown, user player id 0, and hope this will never be a real player id
player_id = "0";
}
if (!this.cooldown_data.ContainsKey(player_id))
{
// Player currently has no cooldown
return false;
}
if (this.cooldown_data[player_id].AddSeconds(this.cooldown_secs) <= now)
{
// Cooldown has expired, remove player from our cooldown list
this.cooldown_data.Remove(player_id);
this.cooldown_datafile.WriteObject(this.cooldown_data);
return false;
}
return true;
}
}
#endregion
#region Functions
/*
* IsBradleyAlive
*
* Check if Bradley is currently alive
*/
bool IsBradleyAlive()
{
BradleySpawner singleton = BradleySpawner.singleton;
if (singleton != null && (bool)singleton.spawned) return true;
foreach (HelicopterDebris debris in BaseNetworkable.serverEntities.OfType<HelicopterDebris>())
{
string prefab_name = debris?.ShortPrefabName ?? string.Empty;
if (prefab_name.Contains("bradley"))
{
return true;
}
}
foreach (LockedByEntCrate crate in BaseNetworkable.serverEntities.OfType<LockedByEntCrate>())
{
string prefab_name = crate?.ShortPrefabName ?? string.Empty;
if (prefab_name.Contains("bradley"))
{
return true;
}
}
return false;
}
/*
* DoRespawn
*
* Respawn Bradley
*/
bool DoRespawn(IPlayer player)
{
BradleySpawner singleton = BradleySpawner.singleton;
if (singleton == null)
{
Puts("No Bradley spawner found!");
return false;
}
if (!Bradley.enabled)
{
Puts("Bradley has been disabled! Enable it if you want this plugin to work.");
return false;
}
if ((bool)singleton.spawned)
{
singleton.spawned.Kill(BaseNetworkable.DestroyMode.None);
}
singleton.spawned = null;
singleton.DoRespawn();
if (this.config_data.Options.LockBradleyOnRespawn && !player.HasPermission("respawnbradley.nolock"))
{
if (LootDefender != null)
{
// Telling LootDefender Bradley took max amount of damage, this should hopefully always lock it whatever Damage Lock Threshold has been configured to
HitInfo hit_info = new HitInfo(player.Object as BaseEntity, singleton.spawned as BaseEntity, DamageType.Generic, singleton.spawned.MaxHealth(), new Vector3());
LootDefender.Call("OnEntityTakeDamage", singleton.spawned, hit_info);
}
else
{
Puts("Unable to lock Bradley without LootDefender plugin!");
}
}
return true;
}
/*
* ChargePlayer
*
* Charge RP from a player
*/
bool ChargePlayer(IPlayer player, bool called_by_player)
{
object result = null;
if (called_by_player && player.HasPermission("respawnbradley.nocosts")) return true;
if (called_by_player && !this.config_data.Options.ChargeOnPlayerCommand) return true;
if (!called_by_player && !this.config_data.Options.ChargeOnServerCommand) return true;
if (this.config_data.Options.UseServerRewards && ServerRewards != null)
{
result = ServerRewards.Call("TakePoints", Convert.ToUInt64(player.Id), Convert.ToInt32(this.config_data.Options.RespawnCosts));
}
else if (this.config_data.Options.UseEconomics && Economics != null)
{
result = Economics.Call("Withdraw", player.Id, Convert.ToDouble(this.config_data.Options.RespawnCosts));
}
else
{
// No supported rewards plugin loaded or configured
this.MessagePlayer(player, GetMSG("UnableToCharge", player.Id).Replace("{amount}", this.config_data.Options.RespawnCosts.ToString()).Replace("{currency}", this.config_data.Options.CurrencySymbol));
return false;
}
if (result == null || (result is bool && (bool)result == false))
{
this.MessagePlayer(player, GetMSG("UnableToCharge", player.Id).Replace("{amount}", this.config_data.Options.RespawnCosts.ToString()).Replace("{currency}", this.config_data.Options.CurrencySymbol));
return false;
}
this.MessagePlayer(player, GetMSG("PlayerCharged", player.Id).Replace("{amount}", this.config_data.Options.RespawnCosts.ToString()).Replace("{currency}", this.config_data.Options.CurrencySymbol));
return true;
}
/*
* RefundPlayer
*
* Refund RP to a player
*/
void RefundPlayer(IPlayer player, bool called_by_player)
{
object result = null;
if (called_by_player && player.HasPermission("respawnbradley.nocosts")) return;
if (called_by_player && !this.config_data.Options.RefundOnPlayerCommand) return;
if (!called_by_player && !this.config_data.Options.RefundOnServerCommand) return;
if (this.config_data.Options.UseServerRewards && ServerRewards != null)
{
result = ServerRewards.Call("AddPoints", Convert.ToUInt64(player.Id), Convert.ToInt32(this.config_data.Options.RespawnCosts));
}
else if (this.config_data.Options.UseEconomics && Economics != null)
{
result = Economics.Call("Deposit", player.Id, Convert.ToDouble(this.config_data.Options.RespawnCosts));
}
else
{
// No supported rewards plugin loaded or configured
this.MessagePlayer(player, GetMSG("UnableToRefund", player.Id).Replace("{amount}", this.config_data.Options.RespawnCosts.ToString()).Replace("{currency}", this.config_data.Options.CurrencySymbol));
return;
}
if (result == null || (result is bool && (bool)result == false))
{
this.MessagePlayer(player, GetMSG("UnableToRefund", player.Id).Replace("{amount}", this.config_data.Options.RespawnCosts.ToString()).Replace("{currency}", this.config_data.Options.CurrencySymbol));
return;
}
this.MessagePlayer(player, GetMSG("PlayerRefunded", player.Id).Replace("{amount}", this.config_data.Options.RespawnCosts.ToString()).Replace("{currency}", this.config_data.Options.CurrencySymbol));
}
#endregion
#region Helpers
/*
* MessagePlayer
*
* Sends a chat message to the given player
*/
private void MessagePlayer(IPlayer player, string msg) => this.MessagePlayer(player?.Object as BasePlayer, msg);
private void MessagePlayer(BasePlayer player, string msg)
{
if (player == null) return;
player.ChatMessage(msg);
}
/*
* FindPlayer
*
* Find a player based on steam id
*/
private IPlayer FindPlayer(string player_id)
{
return players.FindPlayerById(player_id);
}
/*
* ColorizeText
*
* Replace color placeholders in messages
*/
private string ColorizeText(string msg)
{
return msg.Replace("{MsgCol}", this.config_data.Messaging.MsgColor).Replace("{HilCol}", this.config_data.Messaging.HilColor).Replace("{ErrCol}", this.config_data.Messaging.ErrColor).Replace("{ColEnd}","</color>");
}
/*
* FormatSecs
*
* Format seconds to human readable hours/minutes/seconds
*/
private string FormatSecs(IPlayer player, uint secs)
{
string result = string.Empty;
if (secs >= 3600)
{
result = $" {secs/3600} {GetMSG("Hours", player.Id)}";
secs = secs % 3600;
}
if (secs >= 60)
{
result += $" {secs/60} {GetMSG("Minutes", player.Id)}";
secs = secs % 60;
}
if (secs != 0)
{
result += $" {secs} {GetMSG("Seconds", player.Id)}";
}
return result;
}
#endregion
#region Commands
/*
* cmdRespawnBradley
*
* Command to respawn Bradley
*/
[Command("respawnbradley")]
private void cmdRespawnBradley(IPlayer player, string command, string[] args)
{
IPlayer target_player = null;
bool called_by_player = false;
if (!player.IsServer && !player.HasPermission("respawnbradley.use"))
{
this.MessagePlayer(player, GetMSG("NoPermission", player.Id));
return;
}
else if (!player.IsServer)
{
// Player has called command directly
target_player = player;
called_by_player = true;
}
else
{
// Command is called via a store, find target player
if (args.Length != 1) {
// Called via shop, but not given target player id
Puts("Erronous invocation of respawnbradley command! Usage: respawnbradley <playerId>");
return;
}
target_player = this.FindPlayer(args[0]);
if (target_player == null)
{
// Called via shop, but no valid player id given
Puts($"Erronous invocation of respawnbradley command! Unknown player id '{args[0]}'");
return;
}
}
// Check for cooldown
if (this.config_data.Cooldowns.EnableCooldown && !target_player.HasPermission("respawnbradley.nocooldown"))
{
uint remaining_secs;
if (this.cooldown_controller.HasCooldown(target_player.Id, out remaining_secs))
{
// Command is still on cooldown
this.MessagePlayer(target_player, GetMSG("PlayerOnCooldown", player.Id).Replace("{time}", this.FormatSecs(target_player, remaining_secs)));
return;
}
}
// Make sure Bradley is not already alive
if(this.IsBradleyAlive())
{
if (!called_by_player)
{
// If called via shop, player has already been charged, so need to refund here
this.RefundPlayer(target_player, called_by_player);
}
this.MessagePlayer(target_player, GetMSG("UnableToRespawnBradley", player.Id));
return;
}
// Charge player for respawn
if (!this.ChargePlayer(target_player, called_by_player))
{
return;
}
// Respawn Bradley
if(!this.DoRespawn(target_player))
{
this.RefundPlayer(target_player, called_by_player);
this.MessagePlayer(target_player, GetMSG("UnableToRespawnBradley", player.Id));
return;
}
// Set cooldown
if (this.config_data.Cooldowns.EnableCooldown && !target_player.HasPermission("respawnbradley.nocooldown"))
{
this.cooldown_controller.AddCooldown(target_player.Id);
}
this.MessagePlayer(target_player, GetMSG("BradleyHasBeenRespawned", player.Id));
}
#endregion
#region Config
class Messaging
{
public string MsgColor { get; set; }
public string HilColor { get; set; }
public string ErrColor { get; set; }
}
class Options
{
public bool LockBradleyOnRespawn { get; set; }
public bool UseServerRewards { get; set; }
public bool UseEconomics { get; set; }
public bool ChargeOnServerCommand { get; set; }
public bool ChargeOnPlayerCommand { get; set; }
public bool RefundOnServerCommand { get; set; }
public bool RefundOnPlayerCommand { get; set; }
public int RespawnCosts { get; set; }
public string CurrencySymbol { get; set; }
}
class Cooldowns
{
public bool EnableCooldown { get; set; }
public bool CooldownPerPlayer { get; set; }
public uint CooldownSecs { get; set; }
}
class PluginVersion
{
public string CurrentVersion { get; set; }
}
class ConfigData
{
public Messaging Messaging { get; set; }
public Options Options { get; set; }
public Cooldowns Cooldowns { get; set; }
public Oxide.Core.VersionNumber Version { get; set; }
}
private void LoadConfig()
{
ConfigData configdata = Config.ReadObject<ConfigData>();
if (configdata.Version < Version)
{
this.config_data = this.UpdateConfig(configdata);
}
else
{
this.config_data = configdata;
}
}
private ConfigData CreateNewConfig()
{
return new ConfigData
{
Messaging = new Messaging
{
MsgColor = "<color=#939393>",
HilColor = "<color=orange>",
ErrColor = "<color=red>"
},
Options = new Options
{
LockBradleyOnRespawn = false,
UseServerRewards = true,
UseEconomics = false,
ChargeOnServerCommand = false,
ChargeOnPlayerCommand = false,
RefundOnServerCommand = true,
RefundOnPlayerCommand = false,
RespawnCosts = 10000,
CurrencySymbol = "RP"
},
Cooldowns = new Cooldowns
{
EnableCooldown = false,
CooldownPerPlayer = true,
CooldownSecs = 1200
},
Version = Version
};
}
protected override void LoadDefaultConfig() => SaveConfig(this.CreateNewConfig());
private ConfigData UpdateConfig(ConfigData old_config)
{
ConfigData new_config;
bool config_changed = false;
if (old_config.Version < new VersionNumber(0, 3, 0))
{
new_config = this.CreateNewConfig();
new_config.Messaging.MsgColor = old_config.Messaging.MsgColor;
new_config.Messaging.HilColor = old_config.Messaging.HilColor;
new_config.Messaging.ErrColor = old_config.Messaging.ErrColor;
new_config.Options.UseServerRewards = old_config.Options.UseServerRewards;
new_config.Options.ChargeOnServerCommand = old_config.Options.ChargeOnServerCommand;
new_config.Options.ChargeOnPlayerCommand = old_config.Options.ChargeOnPlayerCommand;
new_config.Options.RefundOnServerCommand = old_config.Options.RefundOnServerCommand;
new_config.Options.RefundOnPlayerCommand = old_config.Options.RefundOnPlayerCommand;
new_config.Options.RespawnCosts = old_config.Options.RespawnCosts;
new_config.Options.CurrencySymbol = old_config.Options.CurrencySymbol;
if (old_config.Version >= new VersionNumber(0, 2, 0))
{
new_config.Options.LockBradleyOnRespawn = old_config.Options.LockBradleyOnRespawn;
}
if (old_config.Version >= new VersionNumber(0, 3, 0))
{
new_config.Cooldowns.EnableCooldown = old_config.Cooldowns.EnableCooldown;
new_config.Cooldowns.CooldownPerPlayer = old_config.Cooldowns.CooldownPerPlayer;
new_config.Cooldowns.CooldownSecs = old_config.Cooldowns.CooldownSecs;
}
config_changed = true;
}
else
{
new_config = old_config;
new_config.Version = Version;
}
this.SaveConfig(new_config);
if (config_changed) Puts("Configuration of RespawnBradley was updated.Please check configuration file for changes!");
return new_config;
}
void SaveConfig(ConfigData config) => Config.WriteObject(config, true);
#endregion
#region Messaging
private string GetMSG(string key, string userid = null) => ColorizeText(lang.GetMessage(key, this, userid));
Dictionary<string, string> Messages = new Dictionary<string, string>
{
{"NoPermission", "{ErrCol}You do not have permission to use this command!{ColEnd}"},
{"UnableToCharge", "{ErrCol}We were unable to charge you {amount} {currency}! Please contact an admin{ColEnd}" },
{"PlayerCharged", "{MsgCol}You have been charged {ColEnd}{HilCol}{amount} {currency}{ColEnd} {MsgCol}for respawning Bradley{ColEnd}" },
{"UnableToRefund", "{ErrCol}Unable to refund you {amount} {currency}! Please contact an admin{ColEnd}" },
{"PlayerRefunded", "{HilCol}You have been refunded {amount} {currency}{ColEnd}" },
{"UnableToRespawnBradley", "{MsgCol}Unable to respawn Bradley as it's still alive or not all of its debris has been cleared{ColEnd}" },
{"BradleyHasBeenRespawned", "{HilCol}Bradley has been respawned{ColEnd}" },
{"Hours", "hour(s)" },
{"Minutes", "minute(s)" },
{"Seconds", "second(s)" },
{"PlayerOnCooldown", "{MsgCol}This command is on cooldown for{time}{ColEnd}" }
};
#endregion
}
}