Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions S1API/Cartel/Cartel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,47 @@ public static Cartel? Instance
public int HoursSinceStatusChange =>
S1Cartel.HoursSinceStatusChange;

/// <summary>
/// Gets the goon manager for spawning and controlling cartel goons.
/// Returns null if the goon pool is not available.
/// </summary>
public GoonManager? GoonPool
{
get
{
var pool = S1Cartel.GoonPool;
if (pool == null)
return null;
return new GoonManager(pool);
}
}

/// <summary>
/// Gets the cartel influence manager for regional influence tracking.
/// Returns null if the influence system is not available.
/// </summary>
public CartelInfluence? Influence
{
get
{
var influence = S1Cartel.Influence;
if (influence == null)
return null;
return new CartelInfluence(influence);
}
}

/// <summary>
/// Sets the cartel status. This is a server RPC that will sync to all clients.
/// </summary>
/// <param name="status">The new cartel status.</param>
/// <param name="resetTimer">Whether to reset the hours since status change timer.</param>
public void SetStatus(CartelStatus status, bool resetTimer = true)
{
// Cast through int since our CartelStatus values match the game's ECartelStatus values
S1Cartel.SetStatus_Server((ECartelStatus)(int)status, resetTimer);
}

/// <summary>
/// Event fired when the Cartel status changes.
/// Provides the old status and new status as parameters.
Expand Down
132 changes: 132 additions & 0 deletions S1API/Cartel/CartelGoon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#if (IL2CPPMELON)
using S1Cartel = Il2CppScheduleOne.Cartel;
using S1Player = Il2CppScheduleOne.PlayerScripts;
using S1Combat = Il2CppScheduleOne.Combat;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Cartel = ScheduleOne.Cartel;
using S1Player = ScheduleOne.PlayerScripts;
using S1Combat = ScheduleOne.Combat;
#endif

using S1API.Entities;
using S1API.Entities.Interfaces;
using UnityEngine;

namespace S1API.Cartel
{
/// <summary>
/// Represents a cartel goon (enemy NPC) that can be spawned and controlled.
/// </summary>
public class CartelGoon
{
/// <summary>
/// INTERNAL: Reference to the game's CartelGoon instance.
/// </summary>
internal readonly S1Cartel.CartelGoon S1Goon;

/// <summary>
/// INTERNAL: Constructor to create a wrapper from a game CartelGoon instance.
/// </summary>
internal CartelGoon(S1Cartel.CartelGoon goon)
{
S1Goon = goon;
}

/// <summary>
/// Whether this goon is still conscious (alive and not knocked out).
/// </summary>
public bool IsConscious => S1Goon.IsConscious;

/// <summary>
/// Whether this goon is currently spawned in the world.
/// </summary>
public bool IsSpawned => S1Goon.IsSpawned;

/// <summary>
/// The current world position of this goon.
/// </summary>
public Vector3 Position => S1Goon.transform.position;

/// <summary>
/// The GameObject associated with this goon.
/// </summary>
public GameObject GameObject => S1Goon.gameObject;

/// <summary>
/// Teleports this goon to a specific world position.
/// </summary>
/// <param name="position">The target position.</param>
public void WarpTo(Vector3 position)
{
S1Goon.Movement?.Warp(position);
}

/// <summary>
/// Makes this goon attack the local player.
/// </summary>
public void AttackPlayer()
{
var player = S1Player.Player.Local;
if (player == null) return;

var targetable = player.GetComponent<S1Combat.ICombatTargetable>();
if (targetable != null)
{
S1Goon.AttackEntity(targetable);
}
}

/// <summary>
/// Makes this goon attack a specific entity.
/// </summary>
/// <param name="target">The target entity to attack.</param>
public void Attack(IEntity target)
{
if (target?.gameObject == null) return;

// Get the ICombatTargetable component for attacking
var targetable = target.gameObject.GetComponent<S1Combat.ICombatTargetable>();
if (targetable != null)
{
S1Goon.AttackEntity(targetable);
}
}

/// <summary>
/// Despawns this goon, removing them from the world.
/// </summary>
public void Despawn()
{
S1Goon.Despawn();
}

/// <summary>
/// Sets or clears the default weapon for this goon.
/// Pass null to make them use fists.
/// </summary>
/// <param name="weaponAssetPath">The weapon asset path, or null for fists.</param>
public void SetDefaultWeapon(string? weaponAssetPath)
{
var combatBehaviour = S1Goon.Behaviour?.CombatBehaviour;
if (combatBehaviour == null) return;

if (string.IsNullOrEmpty(weaponAssetPath))
{
combatBehaviour.DefaultWeapon = null;
}
else
{
var go = Resources.Load(weaponAssetPath) as GameObject;
if (go != null)
{
#if (IL2CPPMELON)
var equippable = UnityEngine.Object.Instantiate(go).GetComponent<Il2CppScheduleOne.AvatarFramework.Equipping.AvatarWeapon>();
#else
var equippable = UnityEngine.Object.Instantiate(go).GetComponent<ScheduleOne.AvatarFramework.Equipping.AvatarWeapon>();
#endif
combatBehaviour.DefaultWeapon = equippable;
}
}
}
}
}
93 changes: 93 additions & 0 deletions S1API/Cartel/CartelInfluence.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#if (IL2CPPMELON)
using S1Cartel = Il2CppScheduleOne.Cartel;
using EMapRegion = Il2CppScheduleOne.Map.EMapRegion;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Cartel = ScheduleOne.Cartel;
using EMapRegion = ScheduleOne.Map.EMapRegion;
#endif

using System;
using S1API.Map;

namespace S1API.Cartel
{
/// <summary>
/// Provides access to cartel influence per map region.
/// Influence ranges from 0 (no cartel presence) to 1 (full cartel control).
/// </summary>
public class CartelInfluence
{
/// <summary>
/// INTERNAL: Reference to the game's CartelInfluence instance.
/// </summary>
internal readonly S1Cartel.CartelInfluence S1Influence;

/// <summary>
/// INTERNAL: Constructor to create a wrapper from a game CartelInfluence instance.
/// </summary>
internal CartelInfluence(S1Cartel.CartelInfluence influence)
{
S1Influence = influence;
}

/// <summary>
/// Gets the cartel influence level for a specific region.
/// </summary>
/// <param name="region">The map region to check.</param>
/// <returns>Influence level from 0.0 to 1.0</returns>
public float GetInfluence(Region region)
{
return S1Influence.GetInfluence(ConvertToGameRegion(region));
}

/// <summary>
/// Changes the cartel influence in a region by a specified amount.
/// Positive values increase influence, negative values decrease it.
/// </summary>
/// <param name="region">The map region to modify.</param>
/// <param name="amount">The amount to change (-1.0 to 1.0).</param>
public void ChangeInfluence(Region region, float amount)
{
S1Influence.ChangeInfluence(ConvertToGameRegion(region), amount);
}

#if !IL2CPPMELON
/// <summary>
/// Event fired when cartel influence changes in any region.
/// Parameters: region, old influence, new influence.
/// Note: This event is only available in Mono builds.
/// </summary>
public event Action<Region, float, float> OnInfluenceChanged
{
add
{
if (value == null) return;
S1Influence.OnInfluenceChanged += (gameRegion, oldVal, newVal) =>
{
value?.Invoke(ConvertFromGameRegion(gameRegion), oldVal, newVal);
};
}
remove
{
// Note: Event removal is not fully supported due to delegate wrapping
}
}

/// <summary>
/// Converts game's EMapRegion to S1API Region.
/// </summary>
private static Region ConvertFromGameRegion(EMapRegion region)
{
return (Region)(int)region;
}
#endif

/// <summary>
/// Converts S1API Region to game's EMapRegion.
/// </summary>
private static EMapRegion ConvertToGameRegion(Region region)
{
return (EMapRegion)(int)region;
}
}
}
113 changes: 113 additions & 0 deletions S1API/Cartel/GoonManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#if (IL2CPPMELON)
using S1Cartel = Il2CppScheduleOne.Cartel;
#elif (MONOMELON || MONOBEPINEX || IL2CPPBEPINEX)
using S1Cartel = ScheduleOne.Cartel;
#endif

using System.Collections.Generic;
using UnityEngine;

namespace S1API.Cartel
{
/// <summary>
/// Manages the spawning and tracking of cartel goons.
/// Provides access to the game's goon pool for spawning hostile NPCs.
/// </summary>
public class GoonManager
{
/// <summary>
/// INTERNAL: Reference to the game's GoonPool instance.
/// </summary>
internal readonly S1Cartel.GoonPool S1GoonPool;

/// <summary>
/// INTERNAL: Constructor to create a wrapper from a game GoonPool instance.
/// </summary>
internal GoonManager(S1Cartel.GoonPool goonPool)
{
S1GoonPool = goonPool;
}

/// <summary>
/// The number of goons available to spawn from the pool.
/// </summary>
public int AvailableGoonCount => S1GoonPool.UnspawnedGoonCount;

/// <summary>
/// Spawns a single goon at the specified position.
/// </summary>
/// <param name="position">The world position to spawn the goon at.</param>
/// <returns>The spawned goon, or null if no goons are available.</returns>
public CartelGoon? SpawnGoon(Vector3 position)
{
if (AvailableGoonCount == 0)
return null;

var goons = S1GoonPool.SpawnMultipleGoons(position, 1);
if (goons == null || goons.Count == 0)
return null;

return new CartelGoon(goons[0]);
}

/// <summary>
/// Spawns multiple goons at the specified position.
/// </summary>
/// <param name="position">The world position to spawn the goons at.</param>
/// <param name="count">The number of goons to spawn.</param>
/// <returns>A list of spawned goons. May contain fewer than requested if pool is depleted.</returns>
public List<CartelGoon> SpawnGoons(Vector3 position, int count)
{
var result = new List<CartelGoon>();

if (count <= 0 || AvailableGoonCount == 0)
return result;

int toSpawn = System.Math.Min(count, AvailableGoonCount);
var goons = S1GoonPool.SpawnMultipleGoons(position, toSpawn);

if (goons == null)
return result;

#if (IL2CPPMELON)
foreach (var goon in goons)
{
if (goon != null)
result.Add(new CartelGoon(goon));
}
#else
foreach (var goon in goons)
{
if (goon != null)
result.Add(new CartelGoon(goon));
}
#endif

return result;
}

/// <summary>
/// Spawns goons at multiple positions, one goon per position.
/// </summary>
/// <param name="positions">The positions to spawn goons at.</param>
/// <returns>A list of spawned goons with their positions set.</returns>
public List<CartelGoon> SpawnGoonsAtPositions(Vector3[] positions)
{
var result = new List<CartelGoon>();

if (positions == null || positions.Length == 0)
return result;

// Spawn all at first position, then warp to individual positions
var goons = SpawnGoons(positions[0], positions.Length);

for (int i = 0; i < goons.Count && i < positions.Length; i++)
{
goons[i].WarpTo(positions[i]);
result.Add(goons[i]);
}

return result;
}
}
}
Loading
Loading