C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Oxide.Core;
using Oxide.Core.Plugins;
using UnityEngine;
using Rust; // DamageType, MapMarkerGenericRadius, etc.
using Oxide.Game.Rust.Cui;
namespace Oxide.Plugins
{
[Info("BeastBoss", "Somescrub", "0.4.0")]
[Description("BossMonster-style animal bosses with tiers, lockouts, spawnpoints, events and a boss health HUD")]
public class BeastBoss : RustPlugin
{
[PluginReference("ImageLibrary")]
private Plugin ImageLibrary;
#region Config
internal ConfigData _config;
public class ConfigData
{
public Dictionary<string, BeastDef> Beasts = new Dictionary<string, BeastDef>
{
["bear"] = new BeastDef
{
DisplayName = "Dire Bear",
TierId = "T1",
Theme = "fire",
Prefab = "assets/rust.ai/agents/bear/bear.prefab",
BaseHealth = 2500f,
DamageMultiplier = 1.5f,
InitialScale = 1.2f,
EnragedScale = 1.5f,
PhaseScales = new List<PhaseScale>
{
new PhaseScale { HealthFraction = 0.75f, Scale = 1.3f },
new PhaseScale { HealthFraction = 0.50f, Scale = 1.4f },
new PhaseScale { HealthFraction = 0.25f, Scale = 1.5f }
},
AbilityRoar = new RoarSettings
{
Enabled = true,
Interval = 12f,
Radius = 10f,
Damage = 10f,
Bleed = 10f,
ScreenShake = 2.0f
},
AbilityCharge = new ChargeSettings
{
Enabled = true,
Interval = 20f,
Range = 25f,
ImpactRadius = 3.0f,
ImpactDamage = 40f,
ChargeForce = 13f
},
AbilityFrostAura = new FrostAuraSettings
{
Enabled = false,
Interval = 18f,
Radius = 9f,
Duration = 4f,
TickRate = 1f,
DamagePerTick = 4f,
ColdPerTick = 10f
},
AbilityCubSummon = new CubSummonSettings
{
Enabled = true,
TriggerHealthFraction = 0.5f,
Count = 3,
Prefab = "assets/rust.ai/agents/wolf/wolf.prefab",
Radius = 6f
},
AbilityEnrage = new EnrageSettings
{
Enabled = true,
TriggerHealthFraction = 0.3f,
DamageMultiplier = 1.6f,
SpeedMultiplier = 1.4f,
EffectPrefab = null
},
AbilityFireTrail = new FireTrailSettings
{
Enabled = true,
Interval = 16f,
Duration = 5f,
Step = 0.7f,
Radius = 3f,
DamagePerStep = 6f
},
Loot = new List<LootEntry>
{
new LootEntry { ShortName = "hq.metal.ore", Amount = 200, Skin = 0 },
new LootEntry { ShortName = "bearmeat", Amount = 15, Skin = 0 },
new LootEntry { ShortName = "scrap", Amount = 150, Skin = 0 }
}
},
["wolf"] = new BeastDef
{
DisplayName = "Nightfang",
TierId = "T1",
Theme = "frost",
Prefab = "assets/rust.ai/agents/wolf/wolf.prefab",
BaseHealth = 1500f,
DamageMultiplier = 1.3f,
InitialScale = 1.1f,
EnragedScale = 1.4f,
PhaseScales = new List<PhaseScale>
{
new PhaseScale { HealthFraction = 0.50f, Scale = 1.2f },
new PhaseScale { HealthFraction = 0.25f, Scale = 1.3f }
},
AbilityRoar = new RoarSettings
{
Enabled = true,
Interval = 14f,
Radius = 8f,
Damage = 7f,
Bleed = 7f,
ScreenShake = 1.2f
},
AbilityCharge = new ChargeSettings
{
Enabled = true,
Interval = 16f,
Range = 20f,
ImpactRadius = 2.5f,
ImpactDamage = 30f,
ChargeForce = 15f
},
AbilityFrostAura = new FrostAuraSettings
{
Enabled = true,
Interval = 16f,
Radius = 10f,
Duration = 4f,
TickRate = 1f,
DamagePerTick = 5f,
ColdPerTick = 15f
},
AbilityCubSummon = new CubSummonSettings
{
Enabled = true,
TriggerHealthFraction = 0.4f,
Count = 4,
Prefab = "assets/rust.ai/agents/wolf/wolf.prefab",
Radius = 8f
},
AbilityEnrage = new EnrageSettings
{
Enabled = true,
TriggerHealthFraction = 0.25f,
DamageMultiplier = 1.7f,
SpeedMultiplier = 1.5f,
EffectPrefab = null
},
AbilityFireTrail = new FireTrailSettings
{
Enabled = false,
Interval = 18f,
Duration = 4f,
Step = 0.7f,
Radius = 3f,
DamagePerStep = 5f
},
Loot = new List<LootEntry>
{
new LootEntry { ShortName = "leather", Amount = 100, Skin = 0 },
new LootEntry { ShortName = "wolfmeat", Amount = 10, Skin = 0 },
new LootEntry { ShortName = "scrap", Amount = 80, Skin = 0 }
}
},
["storm_wolf"] = new BeastDef
{
DisplayName = "Stormfang Alpha",
TierId = "T2",
Theme = "storm",
Prefab = "assets/rust.ai/agents/wolf/wolf.prefab",
BaseHealth = 2200f,
DamageMultiplier = 1.45f,
InitialScale = 1.0f,
EnragedScale = 1.0f,
PhaseScales = new List<PhaseScale>
{
new PhaseScale { HealthFraction = 0.50f, Scale = 1.0f },
new PhaseScale { HealthFraction = 0.25f, Scale = 1.0f }
},
AbilityRoar = new RoarSettings
{
Enabled = true,
Interval = 12f,
Radius = 10f,
Damage = 9f,
Bleed = 6f,
ScreenShake = 1.6f
},
AbilityCharge = new ChargeSettings
{
Enabled = true,
Interval = 14f,
Range = 26f,
ImpactRadius = 2.8f,
ImpactDamage = 38f,
ChargeForce = 16f
},
AbilityFrostAura = new FrostAuraSettings
{
Enabled = false,
Interval = 16f,
Radius = 9f,
Duration = 4f,
TickRate = 1f,
DamagePerTick = 0f,
ColdPerTick = 0f
},
AbilityCubSummon = new CubSummonSettings
{
Enabled = true,
TriggerHealthFraction = 0.45f,
Count = 2,
Prefab = "assets/rust.ai/agents/wolf/wolf.prefab",
Radius = 8f
},
AbilityEnrage = new EnrageSettings
{
Enabled = true,
TriggerHealthFraction = 0.30f,
DamageMultiplier = 1.8f,
SpeedMultiplier = 1.55f,
EffectPrefab = null
},
AbilityFireTrail = new FireTrailSettings
{
Enabled = false,
Interval = 18f,
Duration = 4f,
Step = 0.7f,
Radius = 3f,
DamagePerStep = 0f
},
Loot = new List<LootEntry>
{
new LootEntry { ShortName = "scrap", Amount = 180, Skin = 0 },
new LootEntry { ShortName = "metal.fragments", Amount = 350, Skin = 0 },
new LootEntry { ShortName = "wolfmeat", Amount = 15, Skin = 0 }
}
},
["plague_boar"] = new BeastDef
{
DisplayName = "Plague Tusk",
TierId = "T2",
Theme = "toxic",
Prefab = "assets/rust.ai/agents/boar/boar.prefab",
BaseHealth = 2000f,
DamageMultiplier = 1.35f,
InitialScale = 1.0f,
EnragedScale = 1.0f,
PhaseScales = new List<PhaseScale>
{
new PhaseScale { HealthFraction = 0.60f, Scale = 1.0f },
new PhaseScale { HealthFraction = 0.30f, Scale = 1.0f }
},
AbilityRoar = new RoarSettings
{
Enabled = true,
Interval = 16f,
Radius = 9f,
Damage = 8f,
Bleed = 8f,
ScreenShake = 1.2f
},
AbilityCharge = new ChargeSettings
{
Enabled = true,
Interval = 18f,
Range = 22f,
ImpactRadius = 3.2f,
ImpactDamage = 34f,
ChargeForce = 14f
},
AbilityFrostAura = new FrostAuraSettings
{
Enabled = true,
Interval = 16f,
Radius = 10f,
Duration = 5f,
TickRate = 1f,
DamagePerTick = 6f,
ColdPerTick = 0f
},
AbilityCubSummon = new CubSummonSettings
{
Enabled = true,
TriggerHealthFraction = 0.50f,
Count = 2,
Prefab = "assets/rust.ai/agents/boar/boar.prefab",
Radius = 7f
},
AbilityEnrage = new EnrageSettings
{
Enabled = true,
TriggerHealthFraction = 0.28f,
DamageMultiplier = 1.75f,
SpeedMultiplier = 1.45f,
EffectPrefab = null
},
AbilityFireTrail = new FireTrailSettings
{
Enabled = false,
Interval = 18f,
Duration = 4f,
Step = 0.7f,
Radius = 3f,
DamagePerStep = 0f
},
Loot = new List<LootEntry>
{
new LootEntry { ShortName = "scrap", Amount = 150, Skin = 0 },
new LootEntry { ShortName = "leather", Amount = 140, Skin = 0 },
new LootEntry { ShortName = "boarmeat", Amount = 18, Skin = 0 }
}
},
["shade_stag"] = new BeastDef
{
DisplayName = "Shadehorn",
TierId = "T2",
Theme = "shadow",
Prefab = "assets/rust.ai/agents/stag/stag.prefab",
BaseHealth = 1800f,
DamageMultiplier = 1.25f,
InitialScale = 1.0f,
EnragedScale = 1.0f,
PhaseScales = new List<PhaseScale>
{
new PhaseScale { HealthFraction = 0.50f, Scale = 1.0f },
new PhaseScale { HealthFraction = 0.20f, Scale = 1.0f }
},
AbilityRoar = new RoarSettings
{
Enabled = true,
Interval = 18f,
Radius = 11f,
Damage = 6f,
Bleed = 10f,
ScreenShake = 1.8f
},
AbilityCharge = new ChargeSettings
{
Enabled = true,
Interval = 16f,
Range = 24f,
ImpactRadius = 3.0f,
ImpactDamage = 28f,
ChargeForce = 15f
},
AbilityFrostAura = new FrostAuraSettings
{
Enabled = false,
Interval = 16f,
Radius = 9f,
Duration = 4f,
TickRate = 1f,
DamagePerTick = 0f,
ColdPerTick = 0f
},
AbilityCubSummon = new CubSummonSettings
{
Enabled = true,
TriggerHealthFraction = 0.45f,
Count = 3,
Prefab = "assets/rust.ai/agents/wolf/wolf.prefab",
Radius = 9f
},
AbilityEnrage = new EnrageSettings
{
Enabled = true,
TriggerHealthFraction = 0.22f,
DamageMultiplier = 1.6f,
SpeedMultiplier = 1.6f,
EffectPrefab = null
},
AbilityFireTrail = new FireTrailSettings
{
Enabled = false,
Interval = 18f,
Duration = 4f,
Step = 0.7f,
Radius = 3f,
DamagePerStep = 0f
},
Loot = new List<LootEntry>
{
new LootEntry { ShortName = "scrap", Amount = 130, Skin = 0 },
new LootEntry { ShortName = "cloth", Amount = 120, Skin = 0 },
new LootEntry { ShortName = "deermeat.raw", Amount = 14, Skin = 0 }
}
}
};
public Dictionary<string, TierConfig> Tiers = new Dictionary<string, TierConfig>
{
["T1"] = new TierConfig
{
DisplayName = "Tier 1 Beasts",
LockoutMinutes = 60.0,
AutoSpawn = false,
AutoRespawnMinutes = 30.0
}
};
public float GlobalIncomingDamageMultiplier = 0.8f;
public float GlobalOutgoingDamageMultiplier = 1.0f;
public float AnnounceRadius = 80f;
public string ChatPrefix = "<color=#d57b00>[Beast]</color> ";
public string PermissionAdmin = "beastboss.admin";
public float EventCheckIntervalSeconds = 60f;
// Enable entity scaling via EntityScaleManager plugin.
// If false, no scaling occurs. If true, requires EntityScaleManager to be installed.
public bool UseScaling = false;
// Enable phase-based scaling (beasts scale as health drops to phases).
// If false, only InitialScale and EnragedScale are applied.
public bool UsePhaseScaling = false;
// Enable debug logging for boss lifecycle events (spawn, enrage, summon, etc).
public bool Debug = false;
// Boss HUD config (roughly compatible look with TargetHealthHUD)
public UiConfig Ui = new UiConfig();
// Chat announcements on boss spawn/enrage/death
public AnnouncementSettings Announcements = new AnnouncementSettings();
// World map markers that follow active bosses
public MarkerSettings Markers = new MarkerSettings();
// Proximity-based warnings to nearby players
public ProximityWarningSettings ProximityWarnings = new ProximityWarningSettings();
// Boss leash system: soft reset when kiting too far
public LeashSettings Leash = new LeashSettings();
// Weather-enhanced ability procs (lightning strikes, etc.)
public WeatherProcSettings WeatherProcs = new WeatherProcSettings();
// Timed world events that spawn bosses automatically
public WorldEventSettings WorldEvents = new WorldEventSettings();
// Per-player tier escalation: each player progresses through tiers based on their kill counts
public PlayerTierEscalationSettings TierEscalation = new PlayerTierEscalationSettings();
// Mythic variants: rare boss versions with special FX and loot
public MythicSettings Mythic = new MythicSettings();
// Boss title plate (CUI overlay at top-center showing boss name + subtitle)
public TitlePlateSettings TitlePlate = new TitlePlateSettings();
// Enrage countdown indicator shown in title plate
public EnrageIndicatorSettings EnrageIndicator = new EnrageIndicatorSettings();
/*
* FX Library keys and usage:
* - "roar_blast" : Used by DoRoar() for the main roar visual + optional impact FX.
* - "fire_trail" : Used by DoFireTrail() for the trail FX behind fire-themed beasts.
* - "frost_aura" : Used by DoFrostAura() as the primary cold aura visual.
* - "toxic_aura" : Optional alternative to frost_aura for poison-themed beasts.
* - "ground_impact": Used by DoRoar() and other impact-style abilities for ground shock FX.
* - "enrage_burst" : Used by TriggerEnrage() for the enrage burst visual.
* - "enrage_aura" : Used by TriggerEnrage() for the repeating aura during enrage.
* - "summon_burst" : Used by DoCubSummon() when spawning minions.
*
* These keys are defined in ConfigData.FxLibrary and can be customized in BeastBoss.json
* without changing code.
*/
public Dictionary<string, List<string>> FxLibrary = new Dictionary<string, List<string>>
{
// Generic categories used by abilities
["roar_blast"] = new List<string>
{
"assets/bundled/prefabs/fx/player/howl.prefab",
"assets/bundled/prefabs/fx/impacts/additive/explosion.prefab"
},
["fire_trail"] = new List<string>
{
"assets/bundled/prefabs/fx/fire/fire_v2.prefab",
"assets/bundled/prefabs/fx/fire/fire_v3.prefab"
},
["frost_aura"] = new List<string>
{
"assets/bundled/prefabs/fx/smoke/generator_smoke.prefab"
},
["toxic_aura"] = new List<string>
{
"assets/bundled/prefabs/fx/smoke_cover_full.prefab"
},
["ground_impact"] = new List<string>
{
"assets/bundled/prefabs/fx/explosions/explosion_01.prefab",
"assets/bundled/prefabs/fx/impacts/blunt/dirt/dirt1.prefab"
},
["enrage_burst"] = new List<string>
{
"assets/bundled/prefabs/fx/explosions/explosion_core_flash.prefab",
"assets/bundled/prefabs/fx/explosions/explosion_02.prefab"
},
["enrage_aura"] = new List<string>
{
"assets/bundled/prefabs/fx/fire/fire_v2.prefab",
"assets/bundled/prefabs/fx/smoke_signal.prefab"
},
["summon_burst"] = new List<string>
{
"assets/bundled/prefabs/fx/impacts/additive/fire.prefab",
"assets/bundled/prefabs/fx/explosions/explosion_01.prefab"
},
["lightning_strike"] = new List<string>
{
"assets/bundled/prefabs/fx/player/electrocute.prefab",
"assets/bundled/prefabs/fx/impacts/additive/explosion.prefab"
}
};
}
public class UiConfig
{
public bool Enabled = true;
public string Style = "top detailed";
public string TextFormat = "{0} {1}/{2} HP";
public int ActiveDuration = 10; // seconds to keep HUD after last hit
public string TextColor = "1 1 1 1";
public string PrimaryColor = "0.7058824 0.07843138 0.07843138 1";
public string SecondaryColor = "1 0.1960784 0.1960784 1";
public string BorderColor = "0 0 0 0.9"; // dark outline so it reads over red fill
public int BorderPx = 2; // pixel thickness
// Slightly below your TargetHealthHUD default so they don't overlap perfectly
public string AnchorMin = "0.3297916 0.88";
public string AnchorMax = "0.6683334 0.91";
}
public class AnnouncementSettings
{
public bool Enabled = true;
public bool AnnounceSpawn = true;
public bool AnnounceEnrage = true;
public bool AnnounceDeath = true;
public string SpawnMessage = "<color=#ffcc00>[BeastBoss]</color> <color=#ff5555>{name}</color> has appeared at <color=#aaddff>{grid}</color>!";
public string EnrageMessage = "<color=#ffcc00>[BeastBoss]</color> <color=#ff5555>{name}</color> is <color=#ff0000>ENRAGED</color>!";
public string DeathMessage = "<color=#ffcc00>[BeastBoss]</color> <color=#ff5555>{name}</color> was defeated by <color=#aaffaa>{killer}</color>!";
}
public class MarkerSettings
{
public bool Enabled = true;
public float Radius = 0.25f;
public float Alpha = 0.65f;
public string Color = "#ff4444";
public bool ShowLabel = true;
public float UpdateIntervalSeconds = 3f;
}
public class ProximityWarningSettings
{
public bool Enabled = true;
public float RadiusMeters = 120f;
public bool WarnOnSpawn = true;
public bool WarnOnEnrage = true;
public bool WarnOnDeath = false;
public string SpawnMessage = "<color=#ffcc00>[Warning]</color> A BeastBoss (<color=#ff5555>{name}</color>) has spawned nearby!";
public string EnrageMessage = "<color=#ffcc00>[Warning]</color> <color=#ff5555>{name}</color> is <color=#ff0000>ENRAGED</color> nearby!";
public string DeathMessage = "<color=#ffcc00>[Info]</color> Nearby BeastBoss (<color=#ff5555>{name}</color>) has been defeated.";
public float PlayerCooldownSeconds = 20f;
}
public class LeashSettings
{
public bool Enabled = true;
// Primary leash radius from the boss spawn point (meters).
public float RadiusMeters = 80f;
// If boss exceeds this distance, it resets immediately (safety mechanism).
// Set to 0 to disable. Recommended: RadiusMeters * 1.25
public float HardResetRadiusMeters = 120f;
// If boss remains outside RadiusMeters for this long (seconds), it will reset.
public float ResetAfterSecondsOutside = 8f;
// Heal fraction on reset (1.0 = full heal).
public float ResetHealFraction = 1.0f;
// Incoming damage multiplier when boss is outside RadiusMeters.
// Example: 0.25 reduces player damage to 25% while boss is outside leash.
public float OutsideIncomingDamageMultiplier = 0.25f;
// Return-to-spawn walk behavior
public bool WalkBackToSpawn = true;
public float ReturnStopDistanceMeters = 6f; // how close is "back home"
public float ReturnMaxSeconds = 30f; // fail-safe: if stuck too long, fallback to teleport OR force-reset in place
public bool FallbackTeleportIfStuck = true; // if true, teleport to spawn after ReturnMaxSeconds
public float ReturnDestinationRefreshSeconds = 2f; // how often to re-issue destination while returning
// Optional messaging on reset
public bool AnnounceResetToChat = true;
public string ResetMessage = "<color=#ffcc00>[BeastBoss]</color> <color=#ff5555>{name}</color> has retreated and recovered!";
}
public class WeatherProcSettings
{
public bool Enabled = true;
public bool StormThemeOnly = true;
public bool RequireBadWeather = false; // if true, only proc during rain/fog
public float ProcChancePerCheck = 0.15f;
public float ProcCheckIntervalSeconds = 3f;
public float ProcRadiusMeters = 4f;
public float ProcDamage = 12f; // set 0 for FX only
}
public class WorldEventSettings
{
public bool Enabled = false;
// Example: every 60–90 minutes, pick one boss and spawn it at a spawnpoint group.
public float MinMinutesBetweenEvents = 60f;
public float MaxMinutesBetweenEvents = 90f;
public string SpawnpointGroup = "default"; // reuse your BotReSpawn-like manual spawnpoint grouping
public List<string> AllowedBeastKeys = new List<string>(); // empty => all
}
public class PlayerTierEscalationSettings
{
public bool Enabled = true;
public float ResetAfterHours = 0f; // 0 = disabled
public bool CycleEnabled = false;
public string CycleAtTierId = "T3";
public string CycleToTierId = "T1";
public List<PlayerTierRule> Rules = new List<PlayerTierRule>
{
new PlayerTierRule { FromTierId = "T1", KillsToAdvance = 10, ToTierId = "T2" },
new PlayerTierRule { FromTierId = "T2", KillsToAdvance = 10, ToTierId = "T3" }
};
public bool KillerOnly = true;
}
public class PlayerTierRule
{
public string FromTierId;
public int KillsToAdvance;
public string ToTierId;
}
public class PlayerTierProgress
{
public string CurrentTierId = "T1";
public Dictionary<string, int> KillsByTier = new Dictionary<string, int>();
public double LastProgressUtc = 0;
}
public class MythicSettings
{
public bool Enabled = true;
public float Chance = 0.05f;
public string NamePrefix = "Mythic ";
public string NameSuffix = "";
public string ThemeOverride = ""; // optional, empty => keep original
public string SpawnFxKey = "enrage_burst";
public string EnrageFxKey = "enrage_aura";
public string DeathFxKey = "explosion_big"; // if missing, reuse "enrage_burst"
public float LootMultiplier = 1.5f; // multiply amounts; do not change items
}
public class TitlePlateSettings
{
public bool Enabled = true;
public float ShowWithinMeters = 120f;
public float UpdateIntervalSeconds = 1.0f;
public string PanelAnchorMin = "0.325 0.85";
public string PanelAnchorMax = "0.675 0.90";
public string PanelColor = "0.2 0.02 0.02 0.75";
public int FontSize = 18;
public string TextColor = "1 1 1 1";
public string OutlineColor = "0 0 0 0.8";
// Tier-colored border configuration
public float BorderThickness = 0.006f; // Anchor units, clamped 0.001-0.05
public Dictionary<string, string> TierBorderColors = new Dictionary<string, string>
{
["T1"] = "#ffcc00", // Gold
["T2"] = "#66ccff", // Cyan
["T3"] = "#cc66ff", // Magenta
["T4"] = "#ff4444", // Red
["T5"] = "#ffffff" // White
};
// Mythic border overrides
public string MythicBorderColor = "#ffd700"; // gold
public bool MythicBorderOverridesTier = true; // if true, use mythic color instead of tier color
public bool MythicPulseOverrides = true;
public float MythicPulseSpeedMultiplier = 1.35f; // pulse slightly faster than normal
public float MythicPulseAlphaBoost = 0.08f; // increases max alpha slightly (still subtle)
// Title format tokens:
// {name} -> boss display name (supports mythic override)
// {subtitle} -> generated subtitle based on Theme/TierId
public string TitleFormat = "{name}, {subtitle}";
// Optional mini health bar rendered INSIDE the title plate (bottom strip)
public bool ShowHealthBar = true;
public string HealthBarAnchorMin = "0.02 0.05";
public string HealthBarAnchorMax = "0.98 0.22";
public string HealthBarBackColor = "0 0 0 0.35";
public string HealthBarFillColor = "0.8 0.1 0.1 0.9";
public float HealthBarBorderThickness = 0.06f; // relative to bar height (0..0.2)
}
public class EnrageIndicatorSettings
{
public bool Enabled = true;
public string TextColor = "1 0.2 0.2 1";
public int FontSize = 14;
// Position BELOW the title plate panel (as a separate UI element)
public string AnchorMin = "0.0 0.0";
public string AnchorMax = "1.0 0.0";
public string OffsetMin = "0 -18";
public string OffsetMax = "0 -4";
public string Format = "ENRAGED: {seconds}s";
}
public class TierConfig
{
public string DisplayName;
public double LockoutMinutes;
public bool AutoSpawn;
public double AutoRespawnMinutes;
}
public class BeastDef
{
public string DisplayName;
public string TierId;
public string Theme;
public string Prefab;
public float BaseHealth;
public float DamageMultiplier;
public float InitialScale = 1f;
public float EnragedScale = 1f;
public RoarSettings AbilityRoar = new RoarSettings();
public ChargeSettings AbilityCharge = new ChargeSettings();
public FrostAuraSettings AbilityFrostAura = new FrostAuraSettings();
public CubSummonSettings AbilityCubSummon = new CubSummonSettings();
public EnrageSettings AbilityEnrage = new EnrageSettings();
public FireTrailSettings AbilityFireTrail = new FireTrailSettings();
public List<LootEntry> Loot = new List<LootEntry>();
public List<PhaseScale> PhaseScales = new List<PhaseScale>();
}
public class RoarSettings
{
public bool Enabled;
public float Interval;
public float Radius;
public float Damage;
public float Bleed;
public float ScreenShake;
}
public class ChargeSettings
{
public bool Enabled;
public float Interval;
public float Range;
public float ImpactRadius;
public float ImpactDamage;
public float ChargeForce;
}
public class FrostAuraSettings
{
public bool Enabled;
public float Interval;
public float Radius;
public float Duration;
public float TickRate;
public float DamagePerTick;
public float ColdPerTick;
}
public class CubSummonSettings
{
public bool Enabled;
public float TriggerHealthFraction;
public int Count;
public string Prefab;
public float Radius;
}
public class EnrageSettings
{
public bool Enabled;
public float TriggerHealthFraction;
public float DamageMultiplier;
public float SpeedMultiplier;
public string EffectPrefab = null; // Falls back to FxLibrary "enrage_burst" if null
public float Duration = 10f; // Duration of enrage effect in seconds
}
public class FireTrailSettings
{
public bool Enabled;
public float Interval;
public float Duration;
public float Step;
public float Radius;
public float DamagePerStep;
}
public class PhaseScale
{
// Health fraction at or below which this phase is triggered (0-1, e.g. 0.75 = 75% HP).
public float HealthFraction;
// Scale to apply when this phase triggers (e.g. 1.1, 1.3, 1.5).
public float Scale;
}
public class LootEntry
{
public string ShortName;
public int Amount;
public ulong Skin;
}
protected override void LoadDefaultConfig()
{
_config = new ConfigData();
SaveConfig();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
_config = Config.ReadObject<ConfigData>();
if (_config == null) throw new Exception();
}
catch
{
PrintWarning("Invalid config, generating new one.");
LoadDefaultConfig();
}
}
protected override void SaveConfig() => Config.WriteObject(_config, true);
#endregion
#region Data & State
private class Vector3Serializable
{
public float x;
public float y;
public float z;
public Vector3 ToVector3() => new Vector3(x, y, z);
public static Vector3Serializable FromVector3(Vector3 v) =>
new Vector3Serializable { x = v.x, y = v.y, z = v.z };
}
private class SpawnPoint
{
public Vector3Serializable Position;
public float Weight = 1f;
}
private class StoredData
{
public Dictionary<string, List<Vector3Serializable>> TierSpawns =
new Dictionary<string, List<Vector3Serializable>>();
public Dictionary<string, List<SpawnPoint>> Spawnpoints =
new Dictionary<string, List<SpawnPoint>>();
public Dictionary<string, Dictionary<ulong, double>> TierLockouts =
new Dictionary<string, Dictionary<ulong, double>>();
// Per-player tier progression (replaces global TierKills)
public Dictionary<ulong, PlayerTierProgress> PlayerProgress =
new Dictionary<ulong, PlayerTierProgress>();
}
private StoredData _data;
private const string ExternalHudPermission = "targethealthhud.use";
// Players for whom we've temporarily revoked TargetHealthHUD permission
private readonly HashSet<ulong> _suspendedExternalHud = new HashSet<ulong>();
private readonly HashSet<BaseEntity> _bosses = new HashSet<BaseEntity>();
private readonly Dictionary<ulong, float> _damageMeter = new Dictionary<ulong, float>();
private readonly Dictionary<uint, BeastDef> _beastDefs = new Dictionary<uint, BeastDef>();
private readonly Dictionary<uint, float> _bossDamageMultipliers = new Dictionary<uint, float>();
private readonly Dictionary<string, BaseEntity> _activeBossByTier = new Dictionary<string, BaseEntity>();
private readonly Dictionary<uint, string> _bossTierById = new Dictionary<uint, string>();
private readonly Dictionary<string, double> _tierLastDeathTime = new Dictionary<string, double>();
private readonly Dictionary<uint, MapMarkerGenericRadius> _bossMarkers =
new Dictionary<uint, MapMarkerGenericRadius>();
// HUD tracking: player -> boss & timers
private readonly Dictionary<ulong, BaseCombatEntity> _hudBossTargets = new Dictionary<ulong, BaseCombatEntity>();
private readonly Dictionary<ulong, Timer> _hudUpdateTimers = new Dictionary<ulong, Timer>();
private readonly Dictionary<ulong, Timer> _hudExpireTimers = new Dictionary<ulong, Timer>();
// Marker update timer and proximity warning cooldowns
private Timer _markerUpdateTimer;
private Timer _worldEventTimer;
private readonly Dictionary<ulong, float> _proximityCooldown = new Dictionary<ulong, float>();
// Boss component lookup for leash system and spawn point retrieval
private readonly Dictionary<uint, BeastComponent> _bossComponents = new Dictionary<uint, BeastComponent>();
// Mythic boss tracking
private readonly HashSet<uint> _mythicBossIds = new HashSet<uint>();
// Runtime name and theme overrides (for mythic variants)
private readonly Dictionary<uint, string> _runtimeBossName = new Dictionary<uint, string>();
private readonly Dictionary<uint, string> _runtimeBossTheme = new Dictionary<uint, string>();
// Last attacker tracking for target acquisition (when npc.Target unavailable)
private readonly Dictionary<uint, ulong> _lastAttacker = new Dictionary<uint, ulong>();
private readonly Dictionary<uint, float> _lastAttackerAt = new Dictionary<uint, float>();
// Title plate (CUI overlay) timer
private Timer _titlePlateTimer;
private const string TitlePlateUi = "BeastBoss_TitlePlateUI";
private void LoadData()
{
try
{
_data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>("BeastBossData");
}
catch
{
_data = new StoredData();
}
if (_data.TierSpawns == null)
_data.TierSpawns = new Dictionary<string, List<Vector3Serializable>>();
if (_data.Spawnpoints == null)
_data.Spawnpoints = new Dictionary<string, List<SpawnPoint>>();
if (_data.TierLockouts == null)
_data.TierLockouts = new Dictionary<string, Dictionary<ulong, double>>();
if (_data.PlayerProgress == null)
_data.PlayerProgress = new Dictionary<ulong, PlayerTierProgress>();
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject("BeastBossData", _data);
}
#endregion
#region Constants
private const string TIER_FRAME_BASE_URL = "https://raw.githubusercontent.com/jmg5215/BeastBosses/main/assets/ui_images/";
private const float HUD_MAX_DISTANCE = 80f; // Max distance for HUD visibility before removal
#endregion
#region Hooks
private void OnServerInitialized()
{
RegisterTierFrameImages();
}
private void Init()
{
permission.RegisterPermission(_config.PermissionAdmin, this);
LoadData();
StartAutoSpawnTimer();
// Start marker update timer
if (_markerUpdateTimer == null && _config.Markers.Enabled)
_markerUpdateTimer = timer.Every(Mathf.Max(1f, _config.Markers.UpdateIntervalSeconds), UpdateBossMarkers);
// Start title plate update timer
if (_titlePlateTimer == null && _config.TitlePlate.Enabled)
_titlePlateTimer = timer.Every(Mathf.Max(0.5f, _config.TitlePlate.UpdateIntervalSeconds), UpdateTitlePlates);
// Schedule world events
if (_config.WorldEvents.Enabled)
ScheduleNextWorldEvent();
}
private void Unload()
{
foreach (var boss in _bosses.ToList())
{
if (boss == null) continue;
var comp = boss.GetComponent<BeastComponent>();
if (comp != null) UnityEngine.Object.Destroy(comp);
}
foreach (var marker in _bossMarkers.Values)
{
if (marker != null && !marker.IsDestroyed)
marker.Kill();
}
foreach (var player in BasePlayer.activePlayerList)
{
RemoveHud(player);
DestroyTitlePlate(player);
}
_bosses.Clear();
_beastDefs.Clear();
_bossDamageMultipliers.Clear();
_activeBossByTier.Clear();
_bossTierById.Clear();
_bossMarkers.Clear();
_proximityCooldown.Clear();
_bossComponents.Clear();
_mythicBossIds.Clear();
_runtimeBossName.Clear();
_runtimeBossTheme.Clear();
_lastAttacker.Clear();
_lastAttackerAt.Clear();
if (_markerUpdateTimer != null)
{
_markerUpdateTimer.Destroy();
_markerUpdateTimer = null;
}
if (_titlePlateTimer != null)
{
_titlePlateTimer.Destroy();
_titlePlateTimer = null;
}
if (_worldEventTimer != null)
{
_worldEventTimer.Destroy();
_worldEventTimer = null;
}
foreach (var userId in _suspendedExternalHud.ToList())
{
RestoreExternalHud(userId);
}
_suspendedExternalHud.Clear();
SaveData();
}
private void StartAutoSpawnTimer()
{
if (_config.EventCheckIntervalSeconds <= 0f)
_config.EventCheckIntervalSeconds = 60f;
timer.Every(_config.EventCheckIntervalSeconds, CheckAutoSpawns);
}
private void CheckAutoSpawns()
{
var now = Interface.Oxide.Now;
foreach (var kv in _config.Tiers)
{
var tierId = kv.Key;
var tier = kv.Value;
if (!tier.AutoSpawn)
continue;
if (_activeBossByTier.TryGetValue(tierId, out var existing) &&
existing != null && !existing.IsDestroyed)
continue;
_tierLastDeathTime.TryGetValue(tierId, out var lastDeath);
var respawnDelaySeconds = Math.Max(0, tier.AutoRespawnMinutes) * 60;
if (now - lastDeath < respawnDelaySeconds)
continue;
if (!_data.TierSpawns.TryGetValue(tierId, out var spawns) || spawns.Count == 0)
continue;
var beastsInTier = _config.Beasts.Values
.Where(b => string.Equals(b.TierId, tierId, StringComparison.OrdinalIgnoreCase))
.ToList();
if (beastsInTier.Count == 0)
continue;
var spawn = spawns.GetRandom();
var pos = spawn.ToVector3();
pos.y = TerrainMeta.HeightMap.GetHeight(pos);
var def = beastsInTier.GetRandom();
SpawnBeast(def, pos);
}
}
private string GetMythicDisplayName(BeastDef def)
{
return _config.Mythic.NamePrefix + def.DisplayName + _config.Mythic.NameSuffix;
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
if (entity == null || info == null) return;
// Outgoing damage from boss
if (info.Initiator != null && _bosses.Contains(info.Initiator))
{
uint initiatorId = NetId(info.Initiator);
float mult = 1f;
if (_bossDamageMultipliers.TryGetValue(initiatorId, out var m))
mult = m;
info.damageTypes.ScaleAll(_config.GlobalOutgoingDamageMultiplier * mult);
}
// Incoming damage to boss
if (_bosses.Contains(entity))
{
BeastDef def;
uint bossId = NetId(entity);
_beastDefs.TryGetValue(bossId, out def);
var tierId = def != null ? def.TierId : null;
var initiatorPlayer = info.InitiatorPlayer;
// Track last attacker for target acquisition fallback
if (bossId != 0 && initiatorPlayer != null)
{
_lastAttacker[bossId] = initiatorPlayer.userID;
_lastAttackerAt[bossId] = Time.realtimeSinceStartup;
}
if (initiatorPlayer != null && !string.IsNullOrEmpty(tierId))
{
if (IsPlayerLockedOut(initiatorPlayer.userID, tierId, out var remainingSeconds))
{
info.damageTypes.ScaleAll(0f);
var mins = Mathf.CeilToInt((float)(remainingSeconds / 60d));
initiatorPlayer.Ch
// ... [Code preview truncated due to length. Full source in attached file] ...