@@ -2784,13 +2784,29 @@ namespace Oxide.Plugins
|
||||
if (container == null)
|
||||
continue;
|
||||
foreach (Item item in container.itemList)
|
||||
if (item != null && item.GetHeldEntity() is T)
|
||||
if (item != null && HeldAs<T>(item) != null)
|
||||
return item;
|
||||
}
|
||||
|
||||
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>
|
||||
private static bool Hold(RunicNpcPlayer npc, Item item)
|
||||
{
|
||||
@@ -2914,7 +2930,8 @@ namespace Oxide.Plugins
|
||||
if (amount <= 0f)
|
||||
return false;
|
||||
|
||||
Hold(npc, item);
|
||||
if (!Hold(npc, item))
|
||||
return false; // a full belt: nowhere to hold it.
|
||||
npc.SignalBroadcast(BaseEntity.Signal.Attack);
|
||||
item.UseItem(1);
|
||||
npc.Heal(amount);
|
||||
@@ -3011,8 +3028,10 @@ namespace Oxide.Plugins
|
||||
|
||||
private static bool HasFuel(RunicNpcPlayer npc, Item flameItem)
|
||||
{
|
||||
FlameThrower f = flameItem.GetHeldEntity() as FlameThrower;
|
||||
return f != null && (f.ammo > 0 || (f.fuelType != null && npc.inventory.FindItemByItemName(f.fuelType.shortname) != null));
|
||||
// Off the belt it may have no held entity, so no tank (HeldAs): only the kit's fuel counts then.
|
||||
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>
|
||||
|
||||
@@ -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.
|
||||
/// </para>
|
||||
/// </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.")]
|
||||
internal class RunicNpcTest : RustPlugin
|
||||
{
|
||||
@@ -1753,6 +1753,31 @@ namespace Oxide.Plugins
|
||||
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 IEnumerator Stage5()
|
||||
@@ -1793,16 +1818,18 @@ namespace Oxide.Plugins
|
||||
Vector3 d = OnMesh(f + new Vector3(70, 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 blue = Spawn(OnMesh(a + new Vector3(0, 0, 15)), "t_blue");
|
||||
BasePlayer blue = Spawn(aTo, "t_blue");
|
||||
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");
|
||||
ScientistNPC bystander = SpawnScientist(c + new Vector3(0, 0, 15));
|
||||
ScientistNPC bystander = SpawnScientist(cTo);
|
||||
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 bully = Spawn(OnMesh(e + new Vector3(0, 0, 15)), "t_bully");
|
||||
BasePlayer bully = Spawn(eTo, "t_bully");
|
||||
var fighters = new Dictionary<string, BaseEntity>
|
||||
{
|
||||
{ "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 (scientistAt < 0f && Gone(prey)) scientistAt = t;
|
||||
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;
|
||||
foreach (BasePlayer x in new[] { red, blue, hunter })
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user