@@ -2784,13 +2784,29 @@ namespace Oxide.Plugins
|
|||||||
if (container == null)
|
if (container == null)
|
||||||
continue;
|
continue;
|
||||||
foreach (Item item in container.itemList)
|
foreach (Item item in container.itemList)
|
||||||
if (item != null && item.GetHeldEntity() is T)
|
if (item != null && HeldAs<T>(item) != null)
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The item's held entity as a <typeparamref name="T"/>, or, when it has none, its definition's
|
||||||
|
/// entity prefab's. Since Rust's November 2026 build a "player-only" item (a syringe) has a held
|
||||||
|
/// entity only in the belt (<c>ItemModEntity.playerOnlyEntityBeltOnly</c>), and an extra waits in
|
||||||
|
/// the main inventory until <see cref="Hold"/> moves it (issue #20).
|
||||||
|
/// </summary>
|
||||||
|
private static T HeldAs<T>(Item item) where T : HeldEntity
|
||||||
|
{
|
||||||
|
BaseEntity held = item.GetHeldEntity();
|
||||||
|
if (held != null)
|
||||||
|
return held as T;
|
||||||
|
ItemModEntity mod = item.info.GetComponent<ItemModEntity>();
|
||||||
|
GameObject prefab = mod != null && mod.entityPrefab != null && mod.entityPrefab.isValid ? mod.entityPrefab.Get() : null;
|
||||||
|
return prefab != null ? prefab.GetComponent<T>() : null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Puts an item in its hands; only the belt can be held, so it is moved there first.</summary>
|
/// <summary>Puts an item in its hands; only the belt can be held, so it is moved there first.</summary>
|
||||||
private static bool Hold(RunicNpcPlayer npc, Item item)
|
private static bool Hold(RunicNpcPlayer npc, Item item)
|
||||||
{
|
{
|
||||||
@@ -2914,7 +2930,8 @@ namespace Oxide.Plugins
|
|||||||
if (amount <= 0f)
|
if (amount <= 0f)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Hold(npc, item);
|
if (!Hold(npc, item))
|
||||||
|
return false; // a full belt: nowhere to hold it.
|
||||||
npc.SignalBroadcast(BaseEntity.Signal.Attack);
|
npc.SignalBroadcast(BaseEntity.Signal.Attack);
|
||||||
item.UseItem(1);
|
item.UseItem(1);
|
||||||
npc.Heal(amount);
|
npc.Heal(amount);
|
||||||
@@ -3011,8 +3028,10 @@ namespace Oxide.Plugins
|
|||||||
|
|
||||||
private static bool HasFuel(RunicNpcPlayer npc, Item flameItem)
|
private static bool HasFuel(RunicNpcPlayer npc, Item flameItem)
|
||||||
{
|
{
|
||||||
FlameThrower f = flameItem.GetHeldEntity() as FlameThrower;
|
// Off the belt it may have no held entity, so no tank (HeldAs): only the kit's fuel counts then.
|
||||||
return f != null && (f.ammo > 0 || (f.fuelType != null && npc.inventory.FindItemByItemName(f.fuelType.shortname) != null));
|
FlameThrower held = flameItem.GetHeldEntity() as FlameThrower;
|
||||||
|
FlameThrower f = HeldAs<FlameThrower>(flameItem);
|
||||||
|
return f != null && ((held != null && held.ammo > 0) || (f.fuelType != null && npc.inventory.FindItemByItemName(f.fuelType.shortname) != null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace Oxide.Plugins
|
|||||||
/// a made-up user id (11400000001 and up), so <c>IsNpc</c> is false. They take no damage.
|
/// a made-up user id (11400000001 and up), so <c>IsNpc</c> is false. They take no damage.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Info("RunicNpcTest", "Runic Gateway", "0.7.4")]
|
[Info("RunicNpcTest", "Runic Gateway", "0.7.5")]
|
||||||
[Description("RunicNPC stage 2 to 7 tests, the stage 6 spike and stage 9's cost check. A developer tool: never ship it.")]
|
[Description("RunicNPC stage 2 to 7 tests, the stage 6 spike and stage 9's cost check. A developer tool: never ship it.")]
|
||||||
internal class RunicNpcTest : RustPlugin
|
internal class RunicNpcTest : RustPlugin
|
||||||
{
|
{
|
||||||
@@ -1753,6 +1753,31 @@ namespace Oxide.Plugins
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Where a stage 5 fight's second NPC stands: <paramref name="dist"/> m from the first, on the navmesh,
|
||||||
|
/// with a clear line between their eyes. North first, as the fights were laid out; then the other seven
|
||||||
|
/// ways round. On the drill's 3500 map a cliff rock stood between the victim and the bully north of it,
|
||||||
|
/// so neither ever saw the other and s5.fight.shootBack failed on every run (issue #20).
|
||||||
|
/// </summary>
|
||||||
|
private Vector3 Partner(Vector3 at, float dist)
|
||||||
|
{
|
||||||
|
for (int k = 0; k < 8; k++)
|
||||||
|
{
|
||||||
|
float angle = k * Mathf.PI / 4f;
|
||||||
|
Vector3 p = OnMesh(at + new Vector3(Mathf.Sin(angle), 0f, Mathf.Cos(angle)) * dist);
|
||||||
|
NavMeshHit hit;
|
||||||
|
if (!SampleNavmesh(p, 1f, out hit)) continue;
|
||||||
|
// Each one's eyes to the other's centre, both ways, as RunicNPC's own line of sight runs.
|
||||||
|
if (Physics.Linecast(at + Vector3.up * 1.6f, p + Vector3.up * 0.9f, 1218519041, QueryTriggerInteraction.Ignore) ||
|
||||||
|
Physics.Linecast(p + Vector3.up * 1.6f, at + Vector3.up * 0.9f, 1218519041, QueryTriggerInteraction.Ignore)) continue;
|
||||||
|
if (k > 0) Note($"s5: the partner {dist:0} m from {at} stands {k * 45}° round from north (no clear line north)");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
Note($"s5: no clear line {dist:0} m round {at}; the partner stands north as before");
|
||||||
|
return OnMesh(at + new Vector3(0, 0, dist));
|
||||||
|
}
|
||||||
|
|
||||||
private int HorrorLeft() => BaseNetworkable.serverEntities.OfType<BaseCombatEntity>().Count(c => !c.IsDestroyed && c.faction == BaseCombatEntity.Faction.Horror);
|
private int HorrorLeft() => BaseNetworkable.serverEntities.OfType<BaseCombatEntity>().Count(c => !c.IsDestroyed && c.faction == BaseCombatEntity.Faction.Horror);
|
||||||
|
|
||||||
private IEnumerator Stage5()
|
private IEnumerator Stage5()
|
||||||
@@ -1793,16 +1818,18 @@ namespace Oxide.Plugins
|
|||||||
Vector3 d = OnMesh(f + new Vector3(70, 0, 0));
|
Vector3 d = OnMesh(f + new Vector3(70, 0, 0));
|
||||||
Vector3 e = OnMesh(f + new Vector3(140, 0, 0));
|
Vector3 e = OnMesh(f + new Vector3(140, 0, 0));
|
||||||
|
|
||||||
|
// Each fight's second NPC where the two can see each other (issue #20), found before anyone stands there.
|
||||||
|
Vector3 aTo = Partner(a, 15f), bTo = Partner(b, 15f), cTo = Partner(c, 15f), dTo = Partner(d, 25f), eTo = Partner(e, 15f);
|
||||||
BasePlayer red = Spawn(a, "t_red");
|
BasePlayer red = Spawn(a, "t_red");
|
||||||
BasePlayer blue = Spawn(OnMesh(a + new Vector3(0, 0, 15)), "t_blue");
|
BasePlayer blue = Spawn(aTo, "t_blue");
|
||||||
BasePlayer hunter = Spawn(b, "t_hunter");
|
BasePlayer hunter = Spawn(b, "t_hunter");
|
||||||
ScientistNPC prey = SpawnScientist(b + new Vector3(0, 0, 15));
|
ScientistNPC prey = SpawnScientist(bTo);
|
||||||
BasePlayer neutral = Spawn(c, "t_roam");
|
BasePlayer neutral = Spawn(c, "t_roam");
|
||||||
ScientistNPC bystander = SpawnScientist(c + new Vector3(0, 0, 15));
|
ScientistNPC bystander = SpawnScientist(cTo);
|
||||||
BasePlayer leashed = Spawn(d, "t_leashed");
|
BasePlayer leashed = Spawn(d, "t_leashed");
|
||||||
ScientistNPC far = SpawnScientist(d + new Vector3(0, 0, 25));
|
ScientistNPC far = SpawnScientist(dTo);
|
||||||
BasePlayer victim = Spawn(e, "t_roam");
|
BasePlayer victim = Spawn(e, "t_roam");
|
||||||
BasePlayer bully = Spawn(OnMesh(e + new Vector3(0, 0, 15)), "t_bully");
|
BasePlayer bully = Spawn(eTo, "t_bully");
|
||||||
var fighters = new Dictionary<string, BaseEntity>
|
var fighters = new Dictionary<string, BaseEntity>
|
||||||
{
|
{
|
||||||
{ "red", red }, { "blue", blue }, { "hunter", hunter }, { "prey", prey }, { "neutral", neutral },
|
{ "red", red }, { "blue", blue }, { "hunter", hunter }, { "prey", prey }, { "neutral", neutral },
|
||||||
@@ -1823,7 +1850,16 @@ namespace Oxide.Plugins
|
|||||||
if (factionsAt < 0f && (Gone(red) || Gone(blue))) factionsAt = t;
|
if (factionsAt < 0f && (Gone(red) || Gone(blue))) factionsAt = t;
|
||||||
if (scientistAt < 0f && Gone(prey)) scientistAt = t;
|
if (scientistAt < 0f && Gone(prey)) scientistAt = t;
|
||||||
if (!Gone(neutral) && TargetOf(Describe(neutral)) != "none") neutralFought = true;
|
if (!Gone(neutral) && TargetOf(Describe(neutral)) != "none") neutralFought = true;
|
||||||
if (!Gone(leashed) && TargetOf(Describe(leashed)) != "none") leashedFought = true;
|
if (!leashedFought && !Gone(leashed) && TargetOf(Describe(leashed)) != "none")
|
||||||
|
{
|
||||||
|
leashedFought = true;
|
||||||
|
// Run 49 failed here once and it has not been seen since: say how it came to fight. A target its
|
||||||
|
// own sense found is within its chase (8 m) of home; a hurt one was shot; otherwise an ally's alarm.
|
||||||
|
Dictionary<string, object> dl = Describe(leashed);
|
||||||
|
Vector3 leashHome = V(dl["home"]);
|
||||||
|
Note($"s5.fight.leash: fighting at {t} s, target {dl["target"]}, health {Convert.ToSingle(dl["health"]):0}/{Convert.ToSingle(dl["maxHealth"]):0}, " +
|
||||||
|
$"{Flat(V(dl["position"]), leashHome):0.0} m from home; the scientist {(Gone(far) ? "gone" : $"{Flat(far.transform.position, leashHome):0.0} m from its home, {far.health:0} hp")}");
|
||||||
|
}
|
||||||
if (!Gone(victim) && TargetOf(Describe(victim)) != "none") victimAnswered = true;
|
if (!Gone(victim) && TargetOf(Describe(victim)) != "none") victimAnswered = true;
|
||||||
foreach (BasePlayer x in new[] { red, blue, hunter })
|
foreach (BasePlayer x in new[] { red, blue, hunter })
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user