C#:
using System;
using System.Collections.Generic;
using HarmonyLib;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using Oxide.Plugins.PlayerHealthMethods;
using UnityEngine;
/*1.0.7
* Added "Revert to vanilla in SurvivalArena" to config
*/
/*1.0.6
* Added "Revert to vanilla in RaidableBases" and "Revert to vanilla in AbandonedBases" to config
*/
namespace Oxide.Plugins {
[Info("Player Health", "beee", "1.0.7")]
[Description("Permission based max health")]
class PlayerHealth : RustPlugin {
private const string PREFIX_LONG = "playerhealth";
[PluginReference]
private Plugin RaidableBases, AbandonedBases, SurvivalArena;
private PluginConfig _config;
#region Load/Unload Hooks
private void Init() {
UnsubscribeHooks();
}
private void OnServerInitialized() {
RegisterPermissions();
foreach (var player in BasePlayer.allPlayerList) ValidateMaxHealth(player);
}
private void Unload() {
foreach (var player in BasePlayer.allPlayerList) SetMaxHealth(player);
}
private void UnsubscribeHooks() {
if (!_config.GeneralSettings.RevertInRaidableBases) {
Unsubscribe(nameof(OnPlayerEnteredRaidableBase));
Unsubscribe(nameof(OnPlayerExitedRaidableBase));
}
if (!_config.GeneralSettings.RevertInAbandonedBases) {
Unsubscribe(nameof(OnPlayerEnteredAbandonedBase));
Unsubscribe(nameof(OnPlayerExitAbandonedBase));
}
if (!_config.GeneralSettings.RevertInSurvivalArena) {
Unsubscribe(nameof(OnEventJoined));
Unsubscribe(nameof(OnEventLeave));
}
}
#endregion
#region Hooks
private void OnPlayerConnected(BasePlayer player) => OnPlayerRespawned(player);
private void OnPlayerRespawned(BasePlayer player) {
ValidateMaxHealth(player);
}
void OnUserPermissionGranted(string id, string permName) => OnUserPermissionRevoked(id, permName);
void OnUserPermissionRevoked(string id, string permName) {
if(permName.StartsWith(PREFIX_LONG)) {
if (!ulong.TryParse(id, out ulong userId)) return;
var player = BasePlayer.FindByID(userId);
ValidateMaxHealth(player);
}
}
private void OnPlayerAddModifiers(BasePlayer player, Item item, ItemModConsumable consumable) {
//player.SendNetworkUpdate(); //Temp fix for hp bar not updating when consuming max health tea
NextTick(() => ValidateMaxHealthModifiers(player));
var configPermSettings = GetPermissionBasedMaxHealth(player);
if (configPermSettings == null || !configPermSettings.EnhancedHealing) return;
ApplyBonusHealing(player, consumable);
}
private void OnHealingItemUse(MedicalTool medicalTool, BasePlayer player) {
var configPermSettings = GetPermissionBasedMaxHealth(player);
if (configPermSettings == null || !configPermSettings.EnhancedHealing) return;
ItemModConsumable consumable = medicalTool.GetOwnerItemDefinition().GetComponent<ItemModConsumable>();
if(!consumable) return;
ApplyBonusHealing(player, consumable);
}
#endregion
#region Functions
private void RegisterPermissions() {
if (_config == null || _config.Permissions == null) return;
foreach(var perm in _config.Permissions) permission.RegisterPermission($"{PREFIX_LONG}.{perm.Key.ToLower()}", this);
}
private Config_PermissionSettings GetPermissionBasedMaxHealth(BasePlayer player) {
for (int i = _config.Permissions.Count - 1; i >= 0; i--) {
if (permission.UserHasPermission(player.UserIDString, $"{PREFIX_LONG}.{_config.Permissions.ElementAt(i).Key}")) {
return _config.Permissions.ElementAt(i).Value;
}
}
return null;
}
private bool IsCustomMaxHealthProhibited(BasePlayer player) {
return CheckIsRaidTerritory(player.transform.position) || CheckIsInEvent(player);
}
private void ValidateMaxHealth(BasePlayer player) {
if (player == null) return;
// Check if any third-party plugin prohibits custom max health
if (IsCustomMaxHealthProhibited(player)) {
SetMaxHealth(player, null);
return;
}
var configPermSettings = GetPermissionBasedMaxHealth(player);
SetMaxHealth(player, configPermSettings);
}
private void SetMaxHealth(BasePlayer player, Config_PermissionSettings configPermSettings = null, float startHealthRatio = 0.6f) {
float newMaxHealthOverride = 0;
float newMaxHealth = player.StartMaxHealth();
float newStartHealth = newMaxHealth * startHealthRatio;
if (configPermSettings != null) {
newMaxHealthOverride = configPermSettings.MaxHealth;
newMaxHealth = configPermSettings.MaxHealth;
newStartHealth = newMaxHealth * (configPermSettings.StartHealthPercent / 100f);
}
if (player._maxHealth == newMaxHealth) return;
player._maxHealth = newMaxHealth;
player.maxHealthOverride = newMaxHealthOverride > 0 ? GetMaxHealthWithModifier(player) : 0;
player._health = Mathf.Clamp(newStartHealth, 1, newMaxHealth);
player.metabolism.pending_health.max = newMaxHealth;
player.SendNetworkUpdate();
}
public static void ValidateMaxHealthModifiers(BasePlayer player) {
if (player == null || player.maxHealthOverride <= 0) return;
float maxHealthWithModifier = GetMaxHealthWithModifier(player);
if(player.maxHealthOverride == maxHealthWithModifier) return;
player.maxHealthOverride = GetMaxHealthWithModifier(player);
if (player._health > player.maxHealthOverride) player._health = player.maxHealthOverride;
player.SendNetworkUpdate();
}
private void ApplyBonusHealing(BasePlayer player, ItemModConsumable consumable) {
if(player.maxHealthOverride <= 0) return;
float bonusRatio = player._maxHealth / player.StartMaxHealth() - 1f;
if(bonusRatio <= 0) return;
foreach (ItemModConsumable.ConsumableEffect effect in consumable.effects) {
switch (effect.type) {
case(MetabolismAttribute.Type.Health) : player.health += effect.amount * bonusRatio;
break;
case(MetabolismAttribute.Type.HealthOverTime) : player.metabolism.ApplyChange(effect.type, effect.amount * bonusRatio, effect.time);
break;
}
}
}
private static float GetMaxHealthWithModifier(BasePlayer player) => player._maxHealth * (float) (1.0 + (player.modifiers != null ? (double) player.modifiers.GetValue(Modifier.ModifierType.Max_Health) : 0.0));
#endregion
#region Config
private class PluginConfig {
[JsonProperty(Order = 1000)]
public Oxide.Core.VersionNumber Version;
[JsonProperty("Permissions")]
public Dictionary<string, Config_PermissionSettings> Permissions {
get;
set;
}
[JsonProperty("General Settings")]
public Config_GeneralSettings GeneralSettings {
get;
set;
}
= new();
}
private class Config_PermissionSettings {
[JsonProperty("Max Health")]
public float MaxHealth {
get;
set;
}
[JsonProperty("Start health %")]
public float StartHealthPercent {
get;
set;
}
= 60;
[JsonProperty("Healing items to match max health")]
public bool EnhancedHealing {
get;
set;
}
= true;
}
private class Config_GeneralSettings {
[JsonProperty("Revert to vanilla in RaidableBases")]
public bool RevertInRaidableBases {
get;
set;
}
[JsonProperty("Revert to vanilla in AbandonedBases")]
public bool RevertInAbandonedBases {
get;
set;
}
[JsonProperty("Revert to vanilla in SurvivalArena")]
public bool RevertInSurvivalArena {
get;
set;
}
}
private PluginConfig GetDefaultConfig() {
PluginConfig result = new PluginConfig {
Version = Version, Permissions = new Dictionary<string, Config_PermissionSettings> {
["tier1"]
= new Config_PermissionSettings() {
MaxHealth = 150
}
, ["tier2"] = new Config_PermissionSettings() {
MaxHealth = 200
}
, ["tier3"] = new Config_PermissionSettings() {
MaxHealth = 300
}
}
}
;
return result;
}
private void CheckForConfigUpdates() {
bool changes = false;
if (_config == null || _config.Permissions == null) {
PluginConfig tmpDefaultConfig = GetDefaultConfig();
_config = tmpDefaultConfig;
changes = true;
}
if(_config.Version != Version) changes = true;
if (changes) {
_config.Version = Version;
PrintWarning("Config updated");
SaveConfig();
}
}
protected override void LoadDefaultConfig() => _config = GetDefaultConfig();
protected override void LoadConfig() {
base.LoadConfig();
_config = Config.ReadObject<PluginConfig>();
CheckForConfigUpdates();
}
protected override void SaveConfig() => Config.WriteObject(_config);
#endregion
#region Harmony
[AutoPatch]
[HarmonyPatch(typeof(BaseModifiers<BasePlayer>), "Remove")]
internal class BaseModifiers_Remove {
static void Postfix(BasePlayer ___owner, Modifier toRemove) {
if (toRemove.Type == Modifier.ModifierType.Max_Health) PlayerHealth.ValidateMaxHealthModifiers(___owner);
}
}
#endregion
#region Third Party Plugin Support
//Copied from NTeleportation
private bool CheckIsRaidTerritory(Vector3 worldPosition) {
if (_config.GeneralSettings.RevertInAbandonedBases && AbandonedBases?.Call("EventTerritory", worldPosition) is true) return true;
if (_config.GeneralSettings.RevertInRaidableBases && RaidableBases?.Call("EventTerritory", worldPosition) is true) return true;
return false;
}
private bool CheckIsInEvent(BasePlayer player) {
if (_config.GeneralSettings.RevertInSurvivalArena && SurvivalArena?.Call("IsEventPlayer", player) is true) return true;
return false;
}
private void OnPlayerEnteredRaidableBase(BasePlayer player, Vector3 raidPosition, bool allowPVP) => SetMaxHealth(player, null, 1f);
private void OnPlayerExitedRaidableBase(BasePlayer player, Vector3 raidPosition, bool allowPVP) => ValidateMaxHealth(player);
private void OnPlayerEnteredAbandonedBase(BasePlayer player, Vector3 center, float radius, bool AllowPVP, List<BasePlayer> intruders, List<ulong> intruderIds, List<BaseEntity> entities, List<BuildingPrivlidge> privs, bool canDropBackpack, bool automatedEvent, bool attackEvent, Guid guid) => SetMaxHealth(player, null, 1f);
private void OnPlayerExitAbandonedBase(BasePlayer player, Vector3 center, float radius, bool AllowPVP, List<BasePlayer> intruders, List<ulong> intruderIds, List<BaseEntity> entities, List<BuildingPrivlidge> privs, bool canDropBackpack, bool automatedEvent, bool attackEvent, Guid guid) => ValidateMaxHealth(player);
private void OnEventJoined(BasePlayer player, string pluginName) {
if(_config.GeneralSettings.RevertInSurvivalArena && pluginName == nameof(SurvivalArena)) SetMaxHealth(player, null, 1f);
}
private void OnEventLeave(BasePlayer player, string pluginName) {
if(_config.GeneralSettings.RevertInSurvivalArena && pluginName == nameof(SurvivalArena)) NextTick(() => ValidateMaxHealth(player));
}
#endregion
}
}
#region Extension Methods
namespace Oxide.Plugins.PlayerHealthMethods {
public static class ExtensionMethods {
public static T ElementAt<T>(this IEnumerable<T> a, int b) {
using (var c = a.GetEnumerator()) {
while (c.MoveNext()) {
if (b == 0) {
return c.Current;
}
b--;
}
}
return default(T);
}
}
}
#endregion