407 lines
10 KiB
C#
407 lines
10 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using EntityStates;
|
||
|
using HG;
|
||
|
using RoR2.UI;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Networking;
|
||
|
|
||
|
namespace RoR2;
|
||
|
|
||
|
[RequireComponent(typeof(CombatSquad))]
|
||
|
public class BossGroup : MonoBehaviour
|
||
|
{
|
||
|
private struct BossMemory
|
||
|
{
|
||
|
public NetworkInstanceId masterInstanceId;
|
||
|
|
||
|
public float maxObservedMaxHealth;
|
||
|
|
||
|
public float lastObservedHealth;
|
||
|
|
||
|
public CharacterMaster cachedMaster;
|
||
|
|
||
|
public CharacterBody cachedBody;
|
||
|
}
|
||
|
|
||
|
private class DefeatBossObjectiveTracker : ObjectivePanelController.ObjectiveTracker
|
||
|
{
|
||
|
public DefeatBossObjectiveTracker()
|
||
|
{
|
||
|
baseToken = "OBJECTIVE_DEFEAT_BOSS";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class EnableHudHealthBarState : EntityState
|
||
|
{
|
||
|
private BossGroup bossGroup;
|
||
|
|
||
|
public override void OnEnter()
|
||
|
{
|
||
|
base.OnEnter();
|
||
|
bossGroup = GetComponent<BossGroup>();
|
||
|
if ((object)bossGroup != null)
|
||
|
{
|
||
|
bossGroup.shouldDisplayHealthBarOnHud = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void OnExit()
|
||
|
{
|
||
|
if ((object)bossGroup != null)
|
||
|
{
|
||
|
bossGroup.shouldDisplayHealthBarOnHud = false;
|
||
|
}
|
||
|
base.OnExit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public float bossDropChance = 0.15f;
|
||
|
|
||
|
public Transform dropPosition;
|
||
|
|
||
|
public PickupDropTable dropTable;
|
||
|
|
||
|
public bool scaleRewardsByPlayerCount = true;
|
||
|
|
||
|
[Tooltip("Whether or not this boss group should display a health bar on the HUD while any of its members are alive. Other scripts can change this at runtime to suppress a health bar until the boss is angered, for example. This field is not networked, so whatever is driving the value should be synchronized over the network.")]
|
||
|
public bool shouldDisplayHealthBarOnHud = true;
|
||
|
|
||
|
private Xoroshiro128Plus rng;
|
||
|
|
||
|
private List<PickupDropTable> bossDropTables;
|
||
|
|
||
|
private Run.FixedTimeStamp enabledTime;
|
||
|
|
||
|
[Header("Deprecated")]
|
||
|
public bool forceTier3Reward;
|
||
|
|
||
|
private List<PickupIndex> bossDrops;
|
||
|
|
||
|
private static readonly int initialBossMemoryCapacity = 8;
|
||
|
|
||
|
private BossMemory[] bossMemories = new BossMemory[initialBossMemoryCapacity];
|
||
|
|
||
|
private int bossMemoryCount;
|
||
|
|
||
|
private static int lastTotalBossCount = 0;
|
||
|
|
||
|
private static bool totalBossCountDirty = false;
|
||
|
|
||
|
public float fixedAge => combatSquad.awakeTime.timeSince;
|
||
|
|
||
|
public float fixedTimeSinceEnabled => enabledTime.timeSince;
|
||
|
|
||
|
public int bonusRewardCount { get; set; }
|
||
|
|
||
|
public CombatSquad combatSquad { get; private set; }
|
||
|
|
||
|
public string bestObservedName { get; private set; } = "";
|
||
|
|
||
|
|
||
|
public string bestObservedSubtitle { get; private set; } = "";
|
||
|
|
||
|
|
||
|
public float totalMaxObservedMaxHealth { get; private set; }
|
||
|
|
||
|
public float totalObservedHealth { get; private set; }
|
||
|
|
||
|
public static event Action<BossGroup> onBossGroupStartServer;
|
||
|
|
||
|
public static event Action<BossGroup> onBossGroupDefeatedServer;
|
||
|
|
||
|
private void Awake()
|
||
|
{
|
||
|
base.enabled = false;
|
||
|
combatSquad = GetComponent<CombatSquad>();
|
||
|
combatSquad.onMemberDiscovered += OnMemberDiscovered;
|
||
|
combatSquad.onMemberLost += OnMemberLost;
|
||
|
if (NetworkServer.active)
|
||
|
{
|
||
|
combatSquad.onDefeatedServer += OnDefeatedServer;
|
||
|
combatSquad.onMemberAddedServer += OnMemberAddedServer;
|
||
|
combatSquad.onMemberDefeatedServer += OnMemberDefeatedServer;
|
||
|
rng = new Xoroshiro128Plus(Run.instance.bossRewardRng.nextUlong);
|
||
|
bossDrops = new List<PickupIndex>();
|
||
|
bossDropTables = new List<PickupDropTable>();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void Start()
|
||
|
{
|
||
|
if (NetworkServer.active)
|
||
|
{
|
||
|
BossGroup.onBossGroupStartServer?.Invoke(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnEnable()
|
||
|
{
|
||
|
InstanceTracker.Add(this);
|
||
|
ObjectivePanelController.collectObjectiveSources += ReportObjective;
|
||
|
enabledTime = Run.FixedTimeStamp.now;
|
||
|
}
|
||
|
|
||
|
private void OnDisable()
|
||
|
{
|
||
|
ObjectivePanelController.collectObjectiveSources -= ReportObjective;
|
||
|
InstanceTracker.Remove(this);
|
||
|
}
|
||
|
|
||
|
private void FixedUpdate()
|
||
|
{
|
||
|
UpdateBossMemories();
|
||
|
}
|
||
|
|
||
|
private void OnDefeatedServer()
|
||
|
{
|
||
|
DropRewards();
|
||
|
Run.instance.OnServerBossDefeated(this);
|
||
|
BossGroup.onBossGroupDefeatedServer?.Invoke(this);
|
||
|
}
|
||
|
|
||
|
private void OnMemberAddedServer(CharacterMaster memberMaster)
|
||
|
{
|
||
|
Run.instance.OnServerBossAdded(this, memberMaster);
|
||
|
}
|
||
|
|
||
|
private void OnMemberDefeatedServer(CharacterMaster memberMaster, DamageReport damageReport)
|
||
|
{
|
||
|
DeathRewards deathRewards = memberMaster.GetBodyObject()?.GetComponent<DeathRewards>();
|
||
|
if (!deathRewards)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if ((bool)deathRewards.bossDropTable)
|
||
|
{
|
||
|
bossDropTables.Add(deathRewards.bossDropTable);
|
||
|
return;
|
||
|
}
|
||
|
PickupIndex pickupIndex = (PickupIndex)deathRewards.bossPickup;
|
||
|
if (pickupIndex != PickupIndex.none)
|
||
|
{
|
||
|
bossDrops.Add(pickupIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnMemberDiscovered(CharacterMaster memberMaster)
|
||
|
{
|
||
|
base.enabled = true;
|
||
|
memberMaster.isBoss = true;
|
||
|
totalBossCountDirty = true;
|
||
|
RememberBoss(memberMaster);
|
||
|
}
|
||
|
|
||
|
private void OnMemberLost(CharacterMaster memberMaster)
|
||
|
{
|
||
|
memberMaster.isBoss = false;
|
||
|
totalBossCountDirty = true;
|
||
|
if (combatSquad.memberCount == 0)
|
||
|
{
|
||
|
base.enabled = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void DropRewards()
|
||
|
{
|
||
|
if (!Run.instance)
|
||
|
{
|
||
|
Debug.LogError("No valid run instance!");
|
||
|
return;
|
||
|
}
|
||
|
if (rng == null)
|
||
|
{
|
||
|
Debug.LogError("RNG is null!");
|
||
|
return;
|
||
|
}
|
||
|
int participatingPlayerCount = Run.instance.participatingPlayerCount;
|
||
|
if (participatingPlayerCount == 0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if ((bool)dropPosition)
|
||
|
{
|
||
|
PickupIndex none = PickupIndex.none;
|
||
|
if ((bool)dropTable)
|
||
|
{
|
||
|
none = dropTable.GenerateDrop(rng);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
List<PickupIndex> list = Run.instance.availableTier2DropList;
|
||
|
if (forceTier3Reward)
|
||
|
{
|
||
|
list = Run.instance.availableTier3DropList;
|
||
|
}
|
||
|
none = rng.NextElementUniform(list);
|
||
|
}
|
||
|
int num = 1 + bonusRewardCount;
|
||
|
if (scaleRewardsByPlayerCount)
|
||
|
{
|
||
|
num *= participatingPlayerCount;
|
||
|
}
|
||
|
float angle = 360f / (float)num;
|
||
|
Vector3 vector = Quaternion.AngleAxis(UnityEngine.Random.Range(0, 360), Vector3.up) * (Vector3.up * 40f + Vector3.forward * 5f);
|
||
|
Quaternion quaternion = Quaternion.AngleAxis(angle, Vector3.up);
|
||
|
bool flag = bossDrops != null && bossDrops.Count > 0;
|
||
|
bool flag2 = bossDropTables != null && bossDropTables.Count > 0;
|
||
|
int num2 = 0;
|
||
|
while (num2 < num)
|
||
|
{
|
||
|
PickupIndex pickupIndex = none;
|
||
|
if (bossDrops != null && (flag || flag2) && rng.nextNormalizedFloat <= bossDropChance)
|
||
|
{
|
||
|
if (flag2)
|
||
|
{
|
||
|
PickupDropTable pickupDropTable = rng.NextElementUniform(bossDropTables);
|
||
|
if (pickupDropTable != null)
|
||
|
{
|
||
|
pickupIndex = pickupDropTable.GenerateDrop(rng);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pickupIndex = rng.NextElementUniform(bossDrops);
|
||
|
}
|
||
|
}
|
||
|
PickupDropletController.CreatePickupDroplet(pickupIndex, dropPosition.position, vector);
|
||
|
num2++;
|
||
|
vector = quaternion * vector;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Debug.LogWarning("dropPosition not set for BossGroup! No item will be spawned.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int FindBossMemoryIndex(NetworkInstanceId id)
|
||
|
{
|
||
|
for (int i = 0; i < bossMemoryCount; i++)
|
||
|
{
|
||
|
if (bossMemories[i].masterInstanceId == id)
|
||
|
{
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
private void RememberBoss(CharacterMaster master)
|
||
|
{
|
||
|
if ((bool)master)
|
||
|
{
|
||
|
int num = FindBossMemoryIndex(master.netId);
|
||
|
if (num == -1)
|
||
|
{
|
||
|
num = AddBossMemory(master);
|
||
|
}
|
||
|
ref BossMemory reference = ref bossMemories[num];
|
||
|
reference.cachedMaster = master;
|
||
|
reference.cachedBody = master.GetBody();
|
||
|
UpdateObservations(ref reference);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void UpdateObservations(ref BossMemory memory)
|
||
|
{
|
||
|
memory.lastObservedHealth = 0f;
|
||
|
if ((bool)memory.cachedMaster && !memory.cachedBody)
|
||
|
{
|
||
|
memory.cachedBody = memory.cachedMaster.GetBody();
|
||
|
}
|
||
|
if (!memory.cachedBody)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (bestObservedName.Length == 0 && bestObservedSubtitle.Length == 0 && Time.fixedDeltaTime * 3f < memory.cachedBody.localStartTime.timeSince)
|
||
|
{
|
||
|
bestObservedName = Util.GetBestBodyName(memory.cachedBody.gameObject);
|
||
|
bestObservedSubtitle = memory.cachedBody.GetSubtitle();
|
||
|
if (bestObservedSubtitle.Length == 0)
|
||
|
{
|
||
|
bestObservedSubtitle = Language.GetString("NULL_SUBTITLE");
|
||
|
}
|
||
|
bestObservedSubtitle = "<sprite name=\"CloudLeft\" tint=1> " + bestObservedSubtitle + "<sprite name=\"CloudRight\" tint=1>";
|
||
|
}
|
||
|
HealthComponent healthComponent = memory.cachedBody.healthComponent;
|
||
|
memory.maxObservedMaxHealth = Mathf.Max(memory.maxObservedMaxHealth, healthComponent.fullCombinedHealth);
|
||
|
memory.lastObservedHealth = healthComponent.combinedHealth;
|
||
|
}
|
||
|
|
||
|
private int AddBossMemory(CharacterMaster master)
|
||
|
{
|
||
|
BossMemory bossMemory = default(BossMemory);
|
||
|
bossMemory.masterInstanceId = master.netId;
|
||
|
bossMemory.maxObservedMaxHealth = 0f;
|
||
|
bossMemory.cachedMaster = master;
|
||
|
BossMemory value = bossMemory;
|
||
|
ArrayUtils.ArrayAppend(ref bossMemories, ref bossMemoryCount, in value);
|
||
|
return bossMemoryCount - 1;
|
||
|
}
|
||
|
|
||
|
private void UpdateBossMemories()
|
||
|
{
|
||
|
totalMaxObservedMaxHealth = 0f;
|
||
|
totalObservedHealth = 0f;
|
||
|
for (int i = 0; i < bossMemoryCount; i++)
|
||
|
{
|
||
|
ref BossMemory reference = ref bossMemories[i];
|
||
|
UpdateObservations(ref reference);
|
||
|
totalMaxObservedMaxHealth += reference.maxObservedMaxHealth;
|
||
|
totalObservedHealth += Mathf.Max(reference.lastObservedHealth, 0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static int GetTotalBossCount()
|
||
|
{
|
||
|
if (totalBossCountDirty)
|
||
|
{
|
||
|
totalBossCountDirty = false;
|
||
|
lastTotalBossCount = 0;
|
||
|
List<BossGroup> instancesList = InstanceTracker.GetInstancesList<BossGroup>();
|
||
|
for (int i = 0; i < instancesList.Count; i++)
|
||
|
{
|
||
|
lastTotalBossCount += instancesList[i].combatSquad.readOnlyMembersList.Count;
|
||
|
}
|
||
|
}
|
||
|
return lastTotalBossCount;
|
||
|
}
|
||
|
|
||
|
public static BossGroup FindBossGroup(CharacterBody body)
|
||
|
{
|
||
|
if (!body || !body.isBoss)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
CharacterMaster master = body.master;
|
||
|
if (!master)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
List<BossGroup> instancesList = InstanceTracker.GetInstancesList<BossGroup>();
|
||
|
for (int i = 0; i < instancesList.Count; i++)
|
||
|
{
|
||
|
BossGroup bossGroup = instancesList[i];
|
||
|
if (bossGroup.combatSquad.ContainsMember(master))
|
||
|
{
|
||
|
return bossGroup;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public void ReportObjective(CharacterMaster master, List<ObjectivePanelController.ObjectiveSourceDescriptor> output)
|
||
|
{
|
||
|
if (combatSquad.readOnlyMembersList.Count != 0)
|
||
|
{
|
||
|
output.Add(new ObjectivePanelController.ObjectiveSourceDescriptor
|
||
|
{
|
||
|
source = this,
|
||
|
master = master,
|
||
|
objectiveType = typeof(DefeatBossObjectiveTracker)
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|