worldbox-observer-mod/IdleSpectator/CameraDirector.cs
2026-07-14 14:37:34 -05:00

138 lines
4 KiB
C#

using NeoModLoader.services;
using UnityEngine;
namespace IdleSpectator;
public static class CameraDirector
{
public static string LastWatchLabel { get; private set; } = "";
public static string LastWatchAssetId { get; private set; } = "";
public static void FocusUnit(Actor actor)
{
if (actor != null && actor.isAlive())
{
RetargetFollow(actor);
}
}
public static void Watch(InterestEvent interest)
{
if (interest == null || !interest.HasValidPosition)
{
return;
}
if (!Config.game_loaded || World.world == null)
{
return;
}
string tip = FormatWatchTip(interest);
LastWatchLabel = interest.Label ?? "";
LastWatchAssetId = interest.AssetId ?? "";
if (ModSettings.ShowWatchReasons)
{
WorldTip.showNowTop(tip, pTranslate: false);
}
LogService.LogInfo($"[IdleSpectator] {tip}");
Actor follow = interest.HasFollowUnit
? interest.FollowUnit
: null;
// Never attach a random nearby unit to a "new species" tip - that caused ghost focuses
// (e.g. tip says crab, camera follows a cat).
if (follow == null && !string.IsNullOrEmpty(interest.AssetId) && interest.AssetId != "live_battle"
&& interest.AssetId != "scored_unit" && interest.Label != null && interest.Label.StartsWith("New species:"))
{
follow = WorldActivityScanner.FindNearestAliveUnit(interest.Position, 24f, interest.AssetId);
}
else if (follow == null)
{
follow = WorldActivityScanner.FindNearestAliveUnit(interest.Position, 24f);
}
if (follow != null && follow.isAlive())
{
// If this is a species event, refuse mismatched units.
if (interest.Label != null && interest.Label.StartsWith("New species:")
&& !string.IsNullOrEmpty(interest.AssetId)
&& follow.asset != null && follow.asset.id != interest.AssetId)
{
Actor matched = WorldActivityScanner.FindNearestAliveUnit(interest.Position, 24f, interest.AssetId);
if (matched == null)
{
return;
}
follow = matched;
}
RetargetFollow(follow);
return;
}
// No unit available: pan only if we are not already following someone.
// Never clear an existing focus unit here - that flashes the power bar.
if (!MoveCamera.hasFocusUnit())
{
PanCamera(interest.Position);
}
}
public static string FormatWatchTip(InterestEvent interest)
{
string tier = interest.Tier.ToString();
string reason = string.IsNullOrEmpty(interest.Label)
? "something interesting"
: interest.Label;
return $"Watching [{tier}]: {reason}";
}
public static void ClearFollow()
{
MoveCamera.clearFocusUnitOnly();
}
private static void RetargetFollow(Actor actor)
{
if (MoveCamera.isCameraFollowingUnit(actor))
{
return;
}
bool firstFocus = !MoveCamera.hasFocusUnit();
MoveCamera.setFocusUnit(actor);
MoveCamera cam = MoveCamera.instance;
if (cam == null)
{
return;
}
// Same as vanilla killer handoff: reset ease so the camera lerps to the new unit.
cam._focus_timer = 0f;
if (firstFocus)
{
cam._target_zoom = 15f;
cam._focus_zoom = 15f;
}
}
private static void PanCamera(Vector3 pos)
{
MoveCamera cam = MoveCamera.instance;
if (cam == null)
{
World.world.locatePosition(pos);
return;
}
pos.z = cam.transform.position.z;
cam.transform.position = pos;
cam._target_zoom = 15f;
cam._focus_zoom = 15f;
}
}