mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-30 08:07:28 +00:00
* Fix the sensor monitoring console forcing a GC every 3 seconds (#38146) * Optimize sensor monitoring window graph drawing * Add shared static Vector2 pool for all GraphView instances * Address requested changes * remove lock * Update submodule to 264.0.0 (#38629) * Toy/Plushie Inhands and Wearables (#38514) * Fixed dependency injection and some other issues in a few places. * More issue fixes * patchfor broken NetworkConfiguratorLinkMenu (#38632) fix * Fix solutions flickering when transferring contents (#34838) * Use Solution clones when applying SolutionComponent states * Revert "Use Solution clones when applying SolutionComponent states" This reverts commit 013fd111cf92b22562e00f98a7aaa49bc4b4ed62. * Make Solution implement ICloneable and rename Clone method. * Copy CanReact value when cloning a Solution * Convert to IRobustCloneable * Fix typos in guidebook: Buisness → Business (#38636) * Fix typo in `MinorAntagonists.xml` Buisness → Business * Fix typo in `YourFirstCharacter.xml` Buisness → Business * Various Headphones Fixes and Tweaks (#38479) * sprites, wearables * neck * icon-on sprite * Cleanup prototype instantiation in `DamageTest` (#38639) Cleanup prototype instantiation in DamageTest * Cleanup prototype instantiation in `ExplosionSystem` (#38642) Cleanup prototype instantiation in ExplosionSystem * AddBodyPartCommand localization. (#38612) commit * fix ItemSlotsSystem debug assert (#38655) * Allow the Command & Super door remotes to use the access of their user. (Re-creation of PR due to changes to game balance) (#35536) * Added directional beacons (#38284) * Added directional beacons Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Fixed names Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Power stat and nuke codes commands get some LEC love. (#38585) * commit * requested changes. * Dsay Dirty and Follow commands converted to LEC and localized. (#38666) * commit * whoopwhoopwhoop * Retro laser sprite fix (#38676) * Fixed everything except the icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fixed icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fix water coolers (#38681) * Monochromacy typo fix (#38686) * fixes the typo * Fixed cloning looking for the trait, not the component, RE https://github.com/space-wizards/space-station-14/pull/38686#issuecomment-3025093504 * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Validate `CloningSettingsPrototype`s (#38688) * Validate CloningSettingsPrototypes * Update Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Check EventComponents too --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Add test of objective-related console commands (#36400) * Add test of objective add/list/remove commands * Not sure why we're validating test prototypes, but sure * We don't need a map * Improvements and fixups for New Status Effect API (#38660) * Predict healing and bloodstream (#38690) * initial commit * reapply 38126 * fix rootable * someone missed an important minus sign here * try this * fix * fix * reenable crit hits * cleanup * fix status time dirtying * fix * camelCase * Healing and bloodstream prediction fixes. * Resolving Wizard casting recall on nuke disk making it impossible to disarm (#38661) * Resolving Wizard Recall on Nuke disk making it impossible to disarm - Adding a DisarmBomb case to nuke status update loop - Changing a few methods and parameters to properly follow formatting standards - Updating some names to follow camelCase * Updating missed tag * Reverting DataField change Should prevent this preventative bugfix being a breaking change. * Switch HSV to the default colorspace for character customization (#38434) * Made HSV default for character editor * Adds/fixes comments to HSV defaulting * Added dropbox fix, potentially cursed * Revert "Added dropbox fix, potentially cursed" This reverts commit a709883366fbee813e839742125e70844672af29. --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Fix: Don't deploy foldables when clicking on items inside containers (#38709) * Fix * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Pressure Relief Valve (#36708) * initial system (this math is probably WRONG) * General code cleanup and OnExamined support (holy moly this code sucks) * UICode and related events foundation TODO: - Actually write the XAML UI and the underlying system - Un-shitcode the entire thing - Actually test everything... * Working UI code TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't. * Remove one TODO for unshitcoding the examine code * Add reminder yea * Make predicted (defenitely isn't) (also defenitely isn't a copypaste from pressure pump code) * It's predicted! TODO: - Give it snazzy predicted visuals! - Have a different field for pressure entry, lest it gets bulldozed every UI update. * Improve gas pressure relief valve UI TODO: Reminder to reduce amount of dirties using deltafields * Implement DirtyField prediction * Entity<T> cleanup A lot of Entity<T> conversions and lukewarm cleanup. Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell. * More cleanup and comments * Remove TODO comment on bulldozing window title * """refactoring""" - Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance. - More Entity<T> conversions because I like them. - Move UI creation handling over entirely to the ActivatableUI system. - Fix a hardcoded locale string (why????). * Add visuals * Revert debugging variable replacememt yea * Revert skissue * Remove unused using directives and remove TODO * Localize, cleanup, document * Fix adminlogging discrepancy * Add ability to construct, add guidebook entry * Clear up comment * Add guidebook tooltip to valve * Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor * Adds more input handling and adds autofill on open * Un-deepfry input validator shitcode Genuinely what was I smoking * improve visuals logic * Refactor again - Update math to the correct implementation - Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs * I'm sorry but I hate warnings * Remove unused using directive in AtmosphereSystem.Gases.cs * Review and cleanup * Lukewarm UI glossup * Maintainer for the upstream project btw * Remove redundant state sets and messy logic * Unduplicate valve updater code * Redo UI (im sorry Slarti) * run tests * Test refactored UI messaging * Second round of UI improvements - God please find a way to improve this system. Feels bad. * Update loop implementation * Further predict UI * Clear up SetToCurrentThreshold * cleanup * Update to master + pipe layers and bug fixes want to run tests * fixes * Deploy rename pipebomb * Documentation and requested changes * Rename the method that wiggled away * Undo rounding changes * Fix comment * Rename and cleanup * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Fix TextLinkTag (#32203) * Adjust uplink buy button to be under item icon (#38596) * Adjusted uplink buy button to be under item icon * Put the discount subtext under the icon * Indent fixes, added margin --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Scurrets - Audio Improvements (#38482) * Scurret audio tuning * Add new sfx * Update sneezing sfx * YAML support * Rename a folder * make telesci wreck easier (#37569) rel * Validate `ShaderPrototype` IDs (#38728) * Convert all shader prototype string literals to protoids in overlays * Convert more shader prototype literal strings to protoids * Convert ValidatePrototypeId to ProtoId * Later * [BUGFIX] Fixed revenant malfunction ability not working properly only MediBots and Stasis bed (#38664) * fixed * clean up * orks fix smart * review fix 1 * more requested changes * less cursed * more descriptive description * better wording * Tiny Tiny Cleanup of the EyeClosingSystem. (#38734) Update EyeClosingSystem.cs * Fix human skin tone distribution (#38701) * fix: Use PredictedQueueDel for gib spell (#38729) * fix: don't default to uncharged sprite state for cells (#38730) * Fix Hristov description - remove inaccurate technical specs (#38746) - Removes inaccurate 'armor piercing 14.5mm shells' reference - Replaces it with a more funny description, matching the style of the other snipers and guns - Keeps ammunition type '.60 anti-materiel ammo' specification - Fixes issue #38590 Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br> * feat: allow mopping evaporating puddles (#38743) * Validate remaining `ProtoId` strings (#38747) Validate remaining ProtoId strings * Validate `ProtoId`s in tests (#38745) * Convert string literals to protoids in Content.Tests * Convert string literals to protoids or consts in Content.IntegrationTests * Fix linter failures Tricksy static using misled me * Cleanup warning in StomachSystem (#38748) you did not see this * Vox scars (#38592) * Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl * Revert "Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl" This reverts commit c73da55ba3b39ddf93b493aecd85604c54dd8a15. * locale key fix * Changed top surgery scar names to be more generalized * Adjusted face scars * Formatting fixes --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * UnlockNode command to LEC. (#38751) * commit * Update UnlockNodeCommand.cs * commit * move command locale to its own file. * Update Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Typofixes for figurine dialogue (#38737) * Typofixes for figurine dialogue * Forgot two * Janitor Tool: Wire Brush (#38667) * Wow! It's -brush- * spacing. * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * fixed changes --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Minor escape menu UX improvements (#38650) * fix: spellbooks can have infinite charges (#38376) * fix: spellbooks can have infinite charges * refactor: indicate infinite spellbook charges with null Not sure if I like this much better...
612 lines
22 KiB
C#
612 lines
22 KiB
C#
using Content.Server.Atmos.Components;
|
|
using Content.Server.Fluids.EntitySystems;
|
|
using Content.Server.Hands.Systems;
|
|
using Content.Server.NPC.Queries;
|
|
using Content.Server.NPC.Queries.Considerations;
|
|
using Content.Server.NPC.Queries.Curves;
|
|
using Content.Server.NPC.Queries.Queries;
|
|
using Content.Server.Nutrition.Components;
|
|
using Content.Server.Nutrition.EntitySystems;
|
|
using Content.Server.Storage.Components;
|
|
using Content.Server.Temperature.Components;
|
|
using Content.Shared.Chemistry.EntitySystems;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.Fluids.Components;
|
|
using Content.Shared.Hands.Components;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.Mobs;
|
|
using Content.Shared.Mobs.Systems;
|
|
using Content.Shared.NPC.Systems;
|
|
using Content.Shared.Nutrition.Components;
|
|
using Content.Shared.Nutrition.EntitySystems;
|
|
using Content.Shared.Stunnable;
|
|
using Content.Shared.Tools.Systems;
|
|
using Content.Shared.Turrets;
|
|
using Content.Shared.Weapons.Melee;
|
|
using Content.Shared.Weapons.Ranged.Components;
|
|
using Content.Shared.Weapons.Ranged.Events;
|
|
using Content.Shared.Whitelist;
|
|
using Microsoft.Extensions.ObjectPool;
|
|
using Robust.Server.Containers;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Utility;
|
|
using System.Linq;
|
|
|
|
namespace Content.Server.NPC.Systems;
|
|
|
|
/// <summary>
|
|
/// Handles utility queries for NPCs.
|
|
/// </summary>
|
|
public sealed class NPCUtilitySystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
|
[Dependency] private readonly ContainerSystem _container = default!;
|
|
[Dependency] private readonly DrinkSystem _drink = default!;
|
|
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
|
[Dependency] private readonly FoodSystem _food = default!;
|
|
[Dependency] private readonly HandsSystem _hands = default!;
|
|
[Dependency] private readonly InventorySystem _inventory = default!;
|
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
|
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
|
[Dependency] private readonly OpenableSystem _openable = default!;
|
|
[Dependency] private readonly PuddleSystem _puddle = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
[Dependency] private readonly SharedSolutionContainerSystem _solutions = default!;
|
|
[Dependency] private readonly WeldableSystem _weldable = default!;
|
|
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
|
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
|
[Dependency] private readonly MobThresholdSystem _thresholdSystem = default!;
|
|
[Dependency] private readonly TurretTargetSettingsSystem _turretTargetSettings = default!;
|
|
|
|
private EntityQuery<PuddleComponent> _puddleQuery;
|
|
private EntityQuery<TransformComponent> _xformQuery;
|
|
|
|
private ObjectPool<HashSet<EntityUid>> _entPool =
|
|
new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), 256);
|
|
|
|
// Temporary caches.
|
|
private List<EntityUid> _entityList = new();
|
|
private HashSet<Entity<IComponent>> _entitySet = new();
|
|
private List<EntityPrototype.ComponentRegistryEntry> _compTypes = new();
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
_puddleQuery = GetEntityQuery<PuddleComponent>();
|
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs the UtilityQueryPrototype and returns the best-matching entities.
|
|
/// </summary>
|
|
/// <param name="bestOnly">Should we only return the entity with the best score.</param>
|
|
public UtilityResult GetEntities(
|
|
NPCBlackboard blackboard,
|
|
string proto,
|
|
bool bestOnly = true)
|
|
{
|
|
// TODO: PickHostilesop or whatever needs to juse be UtilityQueryOperator
|
|
|
|
var weh = _proto.Index<UtilityQueryPrototype>(proto);
|
|
var ents = _entPool.Get();
|
|
|
|
foreach (var query in weh.Query)
|
|
{
|
|
switch (query)
|
|
{
|
|
case UtilityQueryFilter filter:
|
|
Filter(blackboard, ents, filter);
|
|
break;
|
|
default:
|
|
Add(blackboard, ents, query);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ents.Count == 0)
|
|
{
|
|
_entPool.Return(ents);
|
|
return UtilityResult.Empty;
|
|
}
|
|
|
|
var results = new Dictionary<EntityUid, float>();
|
|
var highestScore = 0f;
|
|
|
|
foreach (var ent in ents)
|
|
{
|
|
if (results.Count > weh.Limit)
|
|
break;
|
|
|
|
var score = 1f;
|
|
|
|
foreach (var con in weh.Considerations)
|
|
{
|
|
var conScore = GetScore(blackboard, ent, con);
|
|
var curve = con.Curve;
|
|
var curveScore = GetScore(curve, conScore);
|
|
|
|
var adjusted = GetAdjustedScore(curveScore, weh.Considerations.Count);
|
|
score *= adjusted;
|
|
|
|
// If the score is too low OR we only care about best entity then early out.
|
|
// Due to the adjusted score only being able to decrease it can never exceed the highest from here.
|
|
if (score <= 0f || bestOnly && score <= highestScore)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (score <= 0f)
|
|
continue;
|
|
|
|
highestScore = MathF.Max(score, highestScore);
|
|
results.Add(ent, score);
|
|
}
|
|
|
|
var result = new UtilityResult(results);
|
|
blackboard.Remove<EntityUid>(NPCBlackboard.UtilityTarget);
|
|
_entPool.Return(ents);
|
|
return result;
|
|
}
|
|
|
|
private float GetScore(IUtilityCurve curve, float conScore)
|
|
{
|
|
switch (curve)
|
|
{
|
|
case BoolCurve:
|
|
return conScore > 0f ? 1f : 0f;
|
|
case InverseBoolCurve:
|
|
return conScore.Equals(0f) ? 1f : 0f;
|
|
case PresetCurve presetCurve:
|
|
return GetScore(_proto.Index<UtilityCurvePresetPrototype>(presetCurve.Preset).Curve, conScore);
|
|
case QuadraticCurve quadraticCurve:
|
|
return Math.Clamp(quadraticCurve.Slope * MathF.Pow(conScore - quadraticCurve.XOffset, quadraticCurve.Exponent) + quadraticCurve.YOffset, 0f, 1f);
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private float GetScore(NPCBlackboard blackboard, EntityUid targetUid, UtilityConsideration consideration)
|
|
{
|
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
|
switch (consideration)
|
|
{
|
|
case FoodValueCon:
|
|
{
|
|
if (!TryComp<FoodComponent>(targetUid, out var food))
|
|
return 0f;
|
|
|
|
// mice can't eat unpeeled bananas, need monkey's help
|
|
if (_openable.IsClosed(targetUid))
|
|
return 0f;
|
|
|
|
if (!_food.IsDigestibleBy(owner, targetUid, food))
|
|
return 0f;
|
|
|
|
var avoidBadFood = !HasComp<IgnoreBadFoodComponent>(owner);
|
|
|
|
// only eat when hungry or if it will eat anything
|
|
if (TryComp<HungerComponent>(owner, out var hunger) && hunger.CurrentThreshold > HungerThreshold.Okay && avoidBadFood)
|
|
return 0f;
|
|
|
|
// no mouse don't eat the uranium-235
|
|
if (avoidBadFood && HasComp<BadFoodComponent>(targetUid))
|
|
return 0f;
|
|
|
|
return 1f;
|
|
}
|
|
case DrinkValueCon:
|
|
{
|
|
if (!TryComp<DrinkComponent>(targetUid, out var drink))
|
|
return 0f;
|
|
|
|
// can't drink closed drinks
|
|
if (_openable.IsClosed(targetUid))
|
|
return 0f;
|
|
|
|
// only drink when thirsty
|
|
if (TryComp<ThirstComponent>(owner, out var thirst) && thirst.CurrentThirstThreshold > ThirstThreshold.Okay)
|
|
return 0f;
|
|
|
|
// no janicow don't drink the blood puddle
|
|
if (HasComp<BadDrinkComponent>(targetUid))
|
|
return 0f;
|
|
|
|
// needs to have something that will satiate thirst, mice wont try to drink 100% pure mutagen.
|
|
var hydration = _drink.TotalHydration(targetUid, drink);
|
|
if (hydration <= 1.0f)
|
|
return 0f;
|
|
|
|
return 1f;
|
|
}
|
|
case OrderedTargetCon:
|
|
{
|
|
if (!blackboard.TryGetValue<EntityUid>(NPCBlackboard.CurrentOrderedTarget, out var orderedTarget, EntityManager))
|
|
return 0f;
|
|
|
|
if (targetUid != orderedTarget)
|
|
return 0f;
|
|
|
|
return 1f;
|
|
}
|
|
case TargetAccessibleCon:
|
|
{
|
|
if (_container.TryGetContainingContainer(targetUid, out var container))
|
|
{
|
|
if (container.Owner == owner)
|
|
return 0f;
|
|
|
|
if (TryComp<EntityStorageComponent>(container.Owner, out var storageComponent))
|
|
{
|
|
if (storageComponent is { Open: false } && _weldable.IsWelded(container.Owner))
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we're in a container (e.g. held or whatever) then we probably can't get it. Only exception
|
|
// Is a locker / crate
|
|
// TODO: Some mobs can break it so consider that.
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
// TODO: Pathfind there, though probably do it in a separate con.
|
|
return 1f;
|
|
}
|
|
case TargetAmmoMatchesCon:
|
|
{
|
|
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, EntityManager) ||
|
|
!_hands.TryGetHeldItem(owner, activeHand, out var heldEntity) ||
|
|
!TryComp<BallisticAmmoProviderComponent>(heldEntity, out var heldGun))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
if (_whitelistSystem.IsWhitelistFailOrNull(heldGun.Whitelist, targetUid))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
return 1f;
|
|
}
|
|
case TargetDistanceCon:
|
|
{
|
|
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
|
|
|
|
if (!TryComp(targetUid, out TransformComponent? targetXform) ||
|
|
!TryComp(owner, out TransformComponent? xform))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
if (!targetXform.Coordinates.TryDistance(EntityManager, _transform, xform.Coordinates,
|
|
out var distance))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
return Math.Clamp(distance / radius, 0f, 1f);
|
|
}
|
|
case TargetAmmoCon:
|
|
{
|
|
if (!HasComp<GunComponent>(targetUid))
|
|
return 0f;
|
|
|
|
var ev = new GetAmmoCountEvent();
|
|
RaiseLocalEvent(targetUid, ref ev);
|
|
|
|
if (ev.Count == 0)
|
|
return 0f;
|
|
|
|
// Wat
|
|
if (ev.Capacity == 0)
|
|
return 1f;
|
|
|
|
return (float) ev.Count / ev.Capacity;
|
|
}
|
|
case TargetHealthCon con:
|
|
{
|
|
if (!TryComp(targetUid, out DamageableComponent? damage))
|
|
return 0f;
|
|
if (con.TargetState != MobState.Invalid && _thresholdSystem.TryGetPercentageForState(targetUid, con.TargetState, damage.TotalDamage, out var percentage))
|
|
return Math.Clamp((float)(1 - percentage), 0f, 1f);
|
|
if (_thresholdSystem.TryGetIncapPercentage(targetUid, damage.TotalDamage, out var incapPercentage))
|
|
return Math.Clamp((float)(1 - incapPercentage), 0f, 1f);
|
|
return 0f;
|
|
}
|
|
case TargetInLOSCon:
|
|
{
|
|
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
|
|
|
|
return _examine.InRangeUnOccluded(owner, targetUid, radius + 0.5f, null) ? 1f : 0f;
|
|
}
|
|
case TargetInLOSOrCurrentCon:
|
|
{
|
|
var radius = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
|
|
const float bufferRange = 0.5f;
|
|
|
|
if (blackboard.TryGetValue<EntityUid>("Target", out var currentTarget, EntityManager) &&
|
|
currentTarget == targetUid &&
|
|
TryComp(owner, out TransformComponent? xform) &&
|
|
TryComp(targetUid, out TransformComponent? targetXform) &&
|
|
xform.Coordinates.TryDistance(EntityManager, _transform, targetXform.Coordinates, out var distance) &&
|
|
distance <= radius + bufferRange)
|
|
{
|
|
return 1f;
|
|
}
|
|
|
|
return _examine.InRangeUnOccluded(owner, targetUid, radius + bufferRange, null) ? 1f : 0f;
|
|
}
|
|
case TargetIsAliveCon:
|
|
{
|
|
return _mobState.IsAlive(targetUid) ? 1f : 0f;
|
|
}
|
|
case TargetIsCritCon:
|
|
{
|
|
return _mobState.IsCritical(targetUid) ? 1f : 0f;
|
|
}
|
|
case TargetIsDeadCon:
|
|
{
|
|
return _mobState.IsDead(targetUid) ? 1f : 0f;
|
|
}
|
|
case TargetMeleeCon:
|
|
{
|
|
if (TryComp<MeleeWeaponComponent>(targetUid, out var melee))
|
|
{
|
|
return melee.Damage.GetTotal().Float() * melee.AttackRate / 100f;
|
|
}
|
|
|
|
return 0f;
|
|
}
|
|
case TargetOnFireCon:
|
|
{
|
|
if (TryComp(targetUid, out FlammableComponent? fire) && fire.OnFire)
|
|
return 1f;
|
|
return 0f;
|
|
}
|
|
case TargetIsStunnedCon:
|
|
{
|
|
return HasComp<StunnedComponent>(targetUid) ? 1f : 0f;
|
|
}
|
|
case TurretTargetingCon:
|
|
{
|
|
if (!TryComp<TurretTargetSettingsComponent>(owner, out var turretTargetSettings) ||
|
|
_turretTargetSettings.EntityIsTargetForTurret((owner, turretTargetSettings), targetUid))
|
|
return 1f;
|
|
|
|
return 0f;
|
|
}
|
|
case TargetLowTempCon con:
|
|
{
|
|
if (!TryComp<TemperatureComponent>(targetUid, out var temperature))
|
|
return 0f;
|
|
|
|
return temperature.CurrentTemperature <= con.MinTemp ? 1f : 0f;
|
|
}
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private float GetAdjustedScore(float score, int considerations)
|
|
{
|
|
/*
|
|
* Now using the geometric mean
|
|
* for n scores you take the n-th root of the scores multiplied
|
|
* e.g. a, b, c scores you take Math.Pow(a * b * c, 1/3)
|
|
* To get the ACTUAL geometric mean at any one stage you'd need to divide by the running consideration count
|
|
* however, the downside to this is it will fluctuate up and down over time.
|
|
* For our purposes if we go below the minimum threshold we want to cut it off, thus we take a
|
|
* "running geometric mean" which can only ever go down (and by the final value will equal the actual geometric mean).
|
|
*/
|
|
|
|
var adjusted = MathF.Pow(score, 1 / (float) considerations);
|
|
return Math.Clamp(adjusted, 0f, 1f);
|
|
}
|
|
|
|
private void Add(NPCBlackboard blackboard, HashSet<EntityUid> entities, UtilityQuery query)
|
|
{
|
|
var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
|
|
var vision = blackboard.GetValueOrDefault<float>(blackboard.GetVisionRadiusKey(EntityManager), EntityManager);
|
|
|
|
switch (query)
|
|
{
|
|
case ComponentQuery compQuery:
|
|
{
|
|
if (compQuery.Components.Count == 0)
|
|
return;
|
|
|
|
var mapPos = _transform.GetMapCoordinates(owner, xform: _xformQuery.GetComponent(owner));
|
|
_compTypes.Clear();
|
|
var i = -1;
|
|
EntityPrototype.ComponentRegistryEntry compZero = default!;
|
|
|
|
foreach (var compType in compQuery.Components.Values)
|
|
{
|
|
i++;
|
|
|
|
if (i == 0)
|
|
{
|
|
compZero = compType;
|
|
continue;
|
|
}
|
|
|
|
_compTypes.Add(compType);
|
|
}
|
|
|
|
_entitySet.Clear();
|
|
_lookup.GetEntitiesInRange(compZero.Component.GetType(), mapPos, vision, _entitySet);
|
|
|
|
foreach (var comp in _entitySet)
|
|
{
|
|
var ent = comp.Owner;
|
|
|
|
if (ent == owner)
|
|
continue;
|
|
|
|
var othersFound = true;
|
|
|
|
foreach (var compOther in _compTypes)
|
|
{
|
|
if (!HasComp(ent, compOther.Component.GetType()))
|
|
{
|
|
othersFound = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!othersFound)
|
|
continue;
|
|
|
|
entities.Add(ent);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case InventoryQuery:
|
|
{
|
|
if (!_inventory.TryGetContainerSlotEnumerator(owner, out var enumerator))
|
|
break;
|
|
|
|
while (enumerator.MoveNext(out var slot))
|
|
{
|
|
foreach (var child in slot.ContainedEntities)
|
|
{
|
|
RecursiveAdd(child, entities);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case NearbyHostilesQuery:
|
|
{
|
|
foreach (var ent in _npcFaction.GetNearbyHostiles(owner, vision))
|
|
{
|
|
entities.Add(ent);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private void RecursiveAdd(EntityUid uid, HashSet<EntityUid> entities)
|
|
{
|
|
// TODO: Probably need a recursive struct enumerator on engine.
|
|
var xform = _xformQuery.GetComponent(uid);
|
|
var enumerator = xform.ChildEnumerator;
|
|
entities.Add(uid);
|
|
|
|
while (enumerator.MoveNext(out var child))
|
|
{
|
|
RecursiveAdd(child, entities);
|
|
}
|
|
}
|
|
|
|
private void Filter(NPCBlackboard blackboard, HashSet<EntityUid> entities, UtilityQueryFilter filter)
|
|
{
|
|
switch (filter)
|
|
{
|
|
case ComponentFilter compFilter:
|
|
{
|
|
_entityList.Clear();
|
|
|
|
foreach (var ent in entities)
|
|
{
|
|
foreach (var comp in compFilter.Components)
|
|
{
|
|
if (HasComp(ent, comp.Value.Component.GetType()))
|
|
continue;
|
|
|
|
_entityList.Add(ent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (var ent in _entityList)
|
|
{
|
|
entities.Remove(ent);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case RemoveAnchoredFilter:
|
|
{
|
|
_entityList.Clear();
|
|
|
|
foreach (var ent in entities)
|
|
{
|
|
if (!TryComp(ent, out TransformComponent? xform))
|
|
continue;
|
|
|
|
if (xform.Anchored)
|
|
_entityList.Add(ent);
|
|
}
|
|
|
|
foreach (var ent in _entityList)
|
|
{
|
|
entities.Remove(ent);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case PuddleFilter:
|
|
{
|
|
_entityList.Clear();
|
|
|
|
foreach (var ent in entities)
|
|
{
|
|
if (!_puddleQuery.TryGetComponent(ent, out var puddleComp) ||
|
|
!_solutions.TryGetSolution(ent, puddleComp.SolutionName, out _, out var sol) ||
|
|
_puddle.CanFullyEvaporate(sol))
|
|
{
|
|
_entityList.Add(ent);
|
|
}
|
|
}
|
|
|
|
foreach (var ent in _entityList)
|
|
{
|
|
entities.Remove(ent);
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|
|
|
|
public readonly record struct UtilityResult(Dictionary<EntityUid, float> Entities)
|
|
{
|
|
public static readonly UtilityResult Empty = new(new Dictionary<EntityUid, float>());
|
|
|
|
public readonly Dictionary<EntityUid, float> Entities = Entities;
|
|
|
|
/// <summary>
|
|
/// Returns the entity with the highest score.
|
|
/// </summary>
|
|
public EntityUid GetHighest()
|
|
{
|
|
if (Entities.Count == 0)
|
|
return EntityUid.Invalid;
|
|
|
|
return Entities.MaxBy(x => x.Value).Key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the entity with the lowest score. This does not consider entities with a 0 (invalid) score.
|
|
/// </summary>
|
|
public EntityUid GetLowest()
|
|
{
|
|
if (Entities.Count == 0)
|
|
return EntityUid.Invalid;
|
|
|
|
return Entities.MinBy(x => x.Value).Key;
|
|
}
|
|
}
|