3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Globalization;
9 using FarseerPhysics.Dynamics;
10 using System.Collections.Immutable;
16 private Sprite barrelSprite, railSprite;
17 private readonly List<(
Sprite sprite, Vector2 position)> chargeSprites =
new List<(
Sprite sprite, Vector2 position)>();
18 private readonly List<Sprite> spinningBarrelSprites =
new List<Sprite>();
23 const ushort LaunchWithoutProjectileId = ushort.MaxValue;
25 private Vector2 barrelPos;
26 private Vector2 transformedBarrelPos;
28 private float targetRotation;
31 private int shotCounter;
33 private float minRotation, maxRotation;
37 private float angularVelocity;
39 private int failedLaunchAttempts;
41 private float currentChargeTime;
42 private bool tryingToCharge;
44 private enum ChargingState
51 private ChargingState currentChargingState;
53 private readonly List<Item> activeProjectiles =
new List<Item>();
58 private float resetUserTimer;
60 private float aiFindTargetTimer;
62 private const float CrewAiFindTargetMaxInterval = 1.0f;
63 private const float CrewAIFindTargetMinInverval = 0.2f;
70 private const float MinimumProjectileVelocityForAimAhead = 20.0f;
76 private const float MaximumAimAhead = 10.0f;
78 private float projectileSpeed;
79 private Item previousAmmo;
81 private int currentLoaderIndex;
83 private const float TinkeringPowerCostReduction = 0.2f;
84 private const float TinkeringDamageIncrease = 0.2f;
85 private const float TinkeringReloadDecrease = 0.2f;
88 private float resetActiveUserTimer;
90 private List<LightComponent> lightComponents;
92 private readonly
bool isSlowTurret;
96 [
Serialize(
"0,0",
IsPropertySaveable.No, description:
"The position of the barrel relative to the upper left corner of the base sprite (in pixels).")]
106 UpdateTransformedBarrelPos();
110 [
Serialize(
"0,0",
IsPropertySaveable.No, description:
"The projectile launching location relative to transformed barrel position (in pixels).")]
113 private bool flipFiringOffset;
115 [
Serialize(
false,
IsPropertySaveable.No, description:
"If enabled, the firing offset will alternate from left to right (i.e. flipping the x-component of the offset each shot.)")]
120 [
Serialize(0.0f,
IsPropertySaveable.No, description:
"The impulse applied to the physics body of the projectile (the higher the impulse, the faster the projectiles are launched).")]
129 [
Serialize(
false,
IsPropertySaveable.No, description:
"Can the turret be fired without projectiles (causing it just to execute the OnUse effects and the firing animation without actually firing anything).")]
132 [
Serialize(0.0f,
IsPropertySaveable.No, description:
"Random spread applied to the firing angle of the projectiles (in degrees).")]
141 private float prevScale;
142 float prevBaseRotation;
143 [
Serialize(0.0f,
IsPropertySaveable.Yes, description:
"The angle of the turret's base in degrees.", alwaysUseInstanceValues:
true)]
150 UpdateTransformedBarrelPos();
154 [
Serialize(3500.0f,
IsPropertySaveable.Yes, description:
"How close to a target the turret has to be for an AI character to fire it.")]
157 private float _maxAngleOffset;
158 [
Serialize(10.0f,
IsPropertySaveable.No, description:
"How much off the turret can be from the target for the AI to shoot. In degrees.")]
161 get => _maxAngleOffset;
162 private set => _maxAngleOffset = MathHelper.Clamp(value, 0f, 180f);
165 [
Serialize(1.1f,
IsPropertySaveable.No, description:
"How much does the AI prefer currently selected targets over new targets closer to the turret.")]
168 [
Serialize(-1,
IsPropertySaveable.Yes, description:
"The turret won't fire additional projectiles if the number of previously fired, still active projectiles reaches this limit. If set to -1, there is no limit to the number of projectiles.")]
171 [
Serialize(0f,
IsPropertySaveable.Yes, description:
"The time required for a charge-type turret to charge up before able to fire.")]
174 #region Editable properties
177 Editable(0.0f, 1000.0f, decimals: 3)]
180 [
Serialize(1,
IsPropertySaveable.No, description:
"How many projectiles needs to be shot before we add an extra break? Think of the double coilgun."),
185 Editable(0.0f, 1000.0f, decimals: 3)]
188 [
Serialize(1.0f,
IsPropertySaveable.No, description:
"Modifies the duration of retraction of the barrell after recoil to get back to the original position after shooting. Reload time affects this too."),
196 [
Serialize(0f,
IsPropertySaveable.No, description:
"How long the barrell stays in place after the recoil and before retracting back to the original position."),
200 [
Editable(VectorComponentLabels =
new string[] {
"editable.minvalue",
"editable.maxvalue" }),
201 Serialize(
"0.0,0.0",
IsPropertySaveable.Yes, description:
"The range at which the barrel can rotate.", alwaysUseInstanceValues:
true)]
206 return new Vector2(MathHelper.ToDegrees(minRotation), MathHelper.ToDegrees(maxRotation));
210 float newMinRotation = MathHelper.ToRadians(value.X);
211 float newMaxRotation = MathHelper.ToRadians(value.Y);
213 bool minRotationModified = MathHelper.Distance(newMinRotation, minRotation) > 0.02f;
214 bool maxRotationModified = MathHelper.Distance(newMaxRotation, maxRotation) > 0.02f;
217 if (minRotationModified && !maxRotationModified)
219 newMinRotation = MathHelper.Clamp(newMinRotation, maxRotation - MathHelper.TwoPi, maxRotation);
221 else if (!minRotationModified && maxRotationModified)
223 newMaxRotation = MathHelper.Clamp(newMaxRotation, minRotation, minRotation + MathHelper.TwoPi);
226 maxRotation = newMaxRotation;
227 minRotation = newMinRotation;
229 Rotation = (minRotation + maxRotation) / 2;
231 if (lightComponents !=
null)
233 foreach (var light
in lightComponents)
243 [
Serialize(5.0f,
IsPropertySaveable.No, description:
"How much torque is applied to rotate the barrel when the item is used by a character with insufficient skills to operate it. Higher values make the barrel rotate faster."),
244 Editable(0.0f, 1000.0f, DecimalCount = 2)]
247 [
Serialize(2.0f,
IsPropertySaveable.No, description:
"How much torque is applied to rotate the barrel when the item is used by a character with sufficient skills to operate it. Higher values make the barrel rotate faster."),
248 Editable(0.0f, 1000.0f, DecimalCount = 2)]
251 [
Serialize(50.0f,
IsPropertySaveable.No, description:
"How much torque is applied to resist the movement of the barrel when the item is used by a character with insufficient skills to operate it. Higher values make the aiming more \"snappy\", stopping the barrel from swinging around the direction it's being aimed at."),
252 Editable(0.0f, 1000.0f, DecimalCount = 2)]
255 [
Serialize(10.0f,
IsPropertySaveable.No, description:
"How much torque is applied to resist the movement of the barrel when the item is used by a character with sufficient skills to operate it. Higher values make the aiming more \"snappy\", stopping the barrel from swinging around the direction it's being aimed at."),
256 Editable(0.0f, 1000.0f, DecimalCount = 2)]
259 [
Serialize(1.0f,
IsPropertySaveable.No, description:
"Maximum angular velocity of the barrel when used by a character with insufficient skills to operate it."),
260 Editable(0.0f, 100.0f, DecimalCount = 2)]
263 [
Serialize(5.0f,
IsPropertySaveable.No, description:
"Maximum angular velocity of the barrel when used by a character with sufficient skills to operate it."),
264 Editable(0.0f, 100.0f, DecimalCount = 2)]
267 [
Serialize(
"0,0,0,0",
IsPropertySaveable.Yes, description:
"Optional screen tint color when the item is being operated (R,G,B,A)."),
271 [
Header(localizedTextTag:
"sp.turret.AutoOperate.propertyheader")]
272 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"Should the turret operate automatically using AI targeting? Comes with some optional random movement that can be adjusted below."),
273 Editable(TransferToSwappedItem =
true)]
276 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"Can the Auto Operate functionality be enabled using signals to the turret?"),
277 Editable(TransferToSwappedItem =
true)]
280 [
Serialize(0f,
IsPropertySaveable.Yes, description:
"[Auto Operate] How much the turret should adjust the aim off the target randomly instead of tracking the target perfectly? In Degrees."),
281 Editable(TransferToSwappedItem =
true)]
284 [
Serialize(0f,
IsPropertySaveable.Yes, description:
"[Auto Operate] How often the turret should adjust the aim randomly instead of tracking the target perfectly? Minimum wait time, in seconds."),
285 Editable(TransferToSwappedItem =
true)]
288 [
Serialize(0f,
IsPropertySaveable.Yes, description:
"[Auto Operate] How often the turret should adjust the aim randomly instead of tracking the target perfectly? Maximum wait time, in seconds."),
289 Editable(TransferToSwappedItem =
true)]
293 Editable(TransferToSwappedItem =
true)]
296 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"[Auto Operate] Should the turret have a delay while targeting targets or always aim prefectly?"),
297 Editable(TransferToSwappedItem =
true)]
301 Editable(TransferToSwappedItem =
true)]
305 Editable(TransferToSwappedItem =
true)]
308 [
Serialize(
true,
IsPropertySaveable.Yes, description:
"[Auto Operate] Should the turret target all humans (or creatures in the same group, like pets)?"),
309 Editable(TransferToSwappedItem =
true)]
313 Editable(TransferToSwappedItem =
true)]
317 Editable(TransferToSwappedItem =
true)]
320 [
Serialize(
"",
IsPropertySaveable.Yes, description:
"[Auto Operate] Group or SpeciesName that the AI ignores when the turret is operated automatically."),
321 Editable(TransferToSwappedItem =
true)]
326 private const string SetAutoOperateConnection =
"set_auto_operate";
327 private const string ToggleAutoOperateConnection =
"toggle_auto_operate";
330 : base(
item, element)
334 foreach (var subElement
in element.Elements())
336 switch (subElement.Name.ToString().ToLowerInvariant())
339 barrelSprite =
new Sprite(subElement);
342 railSprite =
new Sprite(subElement);
345 chargeSprites.Add((
new Sprite(subElement), subElement.GetAttributeVector2(
"chargetarget", Vector2.Zero)));
347 case "spinningbarrelsprite":
348 int spriteCount = subElement.GetAttributeInt(
"spriteamount", 1);
349 for (
int i = 0; i < spriteCount; i++)
351 spinningBarrelSprites.Add(
new Sprite(subElement));
358 isSlowTurret =
item.
HasTag(
"slowturret".ToIdentifier());
359 InitProjSpecific(element);
364 private void UpdateTransformedBarrelPos()
377 if (loadedRotationLimits.HasValue) {
RotationLimits = loadedRotationLimits.Value; }
378 if (loadedBaseRotation.HasValue) {
BaseRotation = loadedBaseRotation.Value; }
380 UpdateTransformedBarrelPos();
388 connectionPanel.
Connections.RemoveAll(c => c.Name is ToggleAutoOperateConnection or SetAutoOperateConnection && c.Wires.None());
393 private void FindLightComponents()
395 if (lightComponents !=
null)
400 foreach (LightComponent lc
in item.GetComponents<LightComponent>())
405 lightComponents ??=
new List<LightComponent>();
406 lightComponents.Add(lc);
411 if (lightComponents !=
null)
413 foreach (var light
in lightComponents)
420 light.Light.PriorityMultiplier *= 10.0f;
430 if (reload > 0.0f) { reload -= deltaTime; }
431 if (!MathUtils.NearlyEqual(
item.
Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(
item.
Scale, prevScale))
433 UpdateTransformedBarrelPos();
436 if (user is { Removed:
true })
442 resetUserTimer -= deltaTime;
443 if (resetUserTimer <= 0.0f) { user =
null; }
452 resetActiveUserTimer -= deltaTime;
453 if (resetActiveUserTimer <= 0.0f)
461 float previousChargeTime = currentChargeTime;
467 currentChargeTime =
Reload > 0.0f ?
473 float chargeDeltaTime = tryingToCharge ? deltaTime : -deltaTime;
474 if (chargeDeltaTime > 0f && user !=
null)
478 currentChargeTime = Math.Clamp(currentChargeTime + chargeDeltaTime, 0f,
MaxChargeTime);
480 tryingToCharge =
false;
482 if (currentChargeTime == 0f)
484 currentChargingState = ChargingState.Inactive;
486 else if (currentChargeTime < previousChargeTime)
488 currentChargingState = ChargingState.WindingDown;
493 currentChargingState = ChargingState.WindingUp;
496 UpdateProjSpecific(deltaTime);
498 if (MathUtils.NearlyEqual(minRotation, maxRotation))
504 float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
506 float maxDist = (maxRotation - minRotation) / 2.0f;
508 if (Math.Abs(targetMidDiff) > maxDist)
510 targetRotation = (targetMidDiff < 0.0f) ? minRotation : maxRotation;
514 if (degreeOfSuccess < 0.5f) { degreeOfSuccess *= degreeOfSuccess; }
531 float rotMidDiff = MathHelper.WrapAngle(
Rotation - (minRotation + maxRotation) / 2.0f);
533 float targetRotationDiff = MathHelper.WrapAngle(targetRotation -
Rotation);
535 if ((maxRotation - minRotation) < MathHelper.TwoPi)
537 float targetRotationMaxDiff = MathHelper.WrapAngle(targetRotation - maxRotation);
538 float targetRotationMinDiff = MathHelper.WrapAngle(targetRotation - minRotation);
540 if (Math.Abs(targetRotationMaxDiff) < Math.Abs(targetRotationMinDiff) &&
542 targetRotationDiff < 0.0f)
544 targetRotationDiff += MathHelper.TwoPi;
546 else if (Math.Abs(targetRotationMaxDiff) > Math.Abs(targetRotationMinDiff) &&
548 targetRotationDiff > 0.0f)
550 targetRotationDiff -= MathHelper.TwoPi;
555 (targetRotationDiff * springStiffness - angularVelocity * springDamping) * deltaTime;
556 angularVelocity = MathHelper.Clamp(angularVelocity, -rotationSpeed, rotationSpeed);
558 Rotation += angularVelocity * deltaTime;
560 rotMidDiff = MathHelper.WrapAngle(
Rotation - (minRotation + maxRotation) / 2.0f);
562 if (rotMidDiff < -maxDist)
565 angularVelocity *= -0.5f;
567 else if (rotMidDiff > maxDist)
570 angularVelocity *= -0.5f;
573 if (aiFindTargetTimer > 0.0f)
575 aiFindTargetTimer -= deltaTime;
588 if (lightComponents !=
null)
590 foreach (var light
in lightComponents)
597 partial
void UpdateProjSpecific(
float deltaTime);
599 private bool isUseBeingCalled;
605 if (isUseBeingCalled) {
return false; }
607 isUseBeingCalled =
true;
608 bool wasSuccessful = TryLaunch(deltaTime, character);
609 isUseBeingCalled =
false;
610 return wasSuccessful;
628 private Vector2 GetBarrelDir()
633 private bool TryLaunch(
float deltaTime,
Character character =
null,
bool ignorePower =
false)
635 tryingToCharge =
true;
640 if (reload > 0.0f) {
return false; }
644 activeProjectiles.RemoveAll(it => it.Removed);
656 if (!flashLowPower && character !=
null && character ==
Character.Controlled)
658 flashLowPower =
true;
666 Projectile launchedProjectile =
null;
667 bool loaderBroken =
false;
668 float tinkeringStrength = 0f;
672 var projectiles = GetLoadedProjectiles();
673 if (projectiles.Any())
675 ItemContainer projectileContainer = projectiles.First().Item.Container?.GetComponent<ItemContainer>();
676 if (projectileContainer !=
null && projectileContainer.Item !=
item)
679 projectileContainer?.Item.Use(deltaTime, user:
null, userForOnUsedEvent: user);
689 if (e is not
Item linkedItem) {
continue; }
691 if (linkedItem.Condition <= 0.0f)
696 if (tryUseProjectileContainer(linkedItem)) {
break; }
698 tryUseProjectileContainer(
item);
700 bool tryUseProjectileContainer(
Item containerItem)
702 ItemContainer projectileContainer = containerItem.GetComponent<ItemContainer>();
703 if (projectileContainer !=
null)
705 containerItem.Use(deltaTime, user:
null, userForOnUsedEvent: user);
706 projectiles = GetLoadedProjectiles();
707 if (projectiles.Any()) {
return true; }
717 failedLaunchAttempts++;
719 if (!flashNoAmmo && !flashLoaderBroken && character !=
null && character ==
Character.Controlled && failedLaunchAttempts > 20)
723 flashLoaderBroken =
true;
729 failedLaunchAttempts = 0;
735 failedLaunchAttempts = 0;
739 if (e is not
Item linkedItem) {
continue; }
740 if (!((MapEntity)
item).Prefab.IsLinkAllowed(e.Prefab)) {
continue; }
741 if (linkedItem.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering && linkedItem.HasTag(Tags.TurretAmmoSource))
743 tinkeringStrength = repairable.TinkeringStrength;
753 neededPower /= 1f + (tinkeringStrength * TinkeringPowerCostReduction);
754 while (neededPower > 0.0001f && batteries.Any())
756 float takePower = neededPower / batteries.Count();
757 takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
758 foreach (PowerContainer battery
in batteries)
760 neededPower -= takePower;
761 battery.Charge -= takePower / 3600.0f;
763 battery.Item.CreateServerEvent(battery);
769 launchedProjectile = projectiles.FirstOrDefault();
770 Item container = launchedProjectile?.
Item.Container;
771 if (container !=
null)
773 var repairable = launchedProjectile?.Item.Container.GetComponent<Repairable>();
774 if (repairable !=
null)
776 repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
782 if (projectiles.Any())
784 foreach (Projectile projectile
in projectiles)
786 Launch(projectile.Item, character, tinkeringStrength: tinkeringStrength);
791 Launch(
null, character, tinkeringStrength: tinkeringStrength);
798 if (container !=
null)
800 ShiftItemsInProjectileContainer(container.GetComponent<ItemContainer>());
804 currentLoaderIndex = (currentLoaderIndex + 1) %
item.
linkedTo.Count;
810 if (character !=
null && launchedProjectile !=
null)
812 string msg = GameServer.CharacterLogName(character) +
" launched " +
item.
Name +
" (projectile: " + launchedProjectile.Item.Name;
813 var containedItems = launchedProjectile.Item.ContainedItems;
814 if (containedItems ==
null || !containedItems.Any())
820 msg +=
", contained items: " +
string.Join(
", ", containedItems.Select(i => i.Name)) +
")";
829 private readonly
struct EventData : IEventData
831 public readonly
Item Projectile;
833 public EventData(
Item projectile,
Turret turret)
835 Projectile = projectile;
839 private void Launch(
Item projectile, Character user =
null,
float? launchRotation =
null,
float tinkeringStrength = 0f)
851 reload /= 1f + (tinkeringStrength * TinkeringReloadDecrease);
858 if (projectile !=
null)
862 flipFiringOffset = !flipFiringOffset;
864 activeProjectiles.Add(projectile);
865 projectile.Drop(
null, setTransform:
false);
866 if (projectile.body !=
null)
868 projectile.body.Dir = 1.0f;
869 projectile.body.ResetDynamics();
870 projectile.body.Enabled =
true;
873 float spread = MathHelper.ToRadians(
Spread) * Rand.Range(-0.5f, 0.5f);
875 Vector2 launchPos = ConvertUnits.ToSimUnits(GetRelativeFiringPosition());
879 Body pickedBody =
Submarine.PickBody(ConvertUnits.ToSimUnits(
item.
WorldPosition), launchPos,
null, Physics.CollisionWall, allowInsideFixture:
true,
880 customPredicate: (Fixture f) =>
884 if (pickedBody !=
null)
886 launchPos =
Submarine.LastPickedPosition;
888 projectile.SetTransform(launchPos, -(launchRotation ??
Rotation) + spread);
889 projectile.UpdateTransform();
890 projectile.Submarine = projectile.body?.Submarine;
892 Projectile projectileComponent = projectile.GetComponent<Projectile>();
893 if (projectileComponent !=
null)
895 TryDetermineProjectileSpeed(projectileComponent);
896 projectileComponent.Launcher =
item;
897 projectileComponent.Attacker = projectileComponent.User = user;
898 if (projectileComponent.Attack !=
null)
900 projectileComponent.Attack.DamageMultiplier = (1f *
DamageMultiplier) + (TinkeringDamageIncrease * tinkeringStrength);
903 projectile.GetComponent<Rope>()?.Attach(
item, projectile);
904 projectileComponent.User = user;
909 if (velocitySum.LengthSquared() < NetConfig.MaxPhysicsBodyVelocity * NetConfig.MaxPhysicsBodyVelocity * 0.9f)
911 projectile.body.LinearVelocity = velocitySum;
916 projectile.Container?.RemoveContained(projectile);
919 item.CreateServerEvent(
this,
new EventData(projectile,
this));
923 LaunchProjSpecific();
926 private void TryDetermineProjectileSpeed(Projectile projectile)
928 if (projectile !=
null && !projectile.Hitscan)
931 ConvertUnits.ToDisplayUnits(
932 MathHelper.Clamp((projectile.LaunchImpulse +
LaunchImpulse) / projectile.Item.body.Mass, MinimumProjectileVelocityForAimAhead, NetConfig.MaxPhysicsBodyVelocity));
936 partial
void LaunchProjSpecific();
938 private static void ShiftItemsInProjectileContainer(ItemContainer container)
940 if (container ==
null) {
return; }
945 for (
int i = 1; i < container.Capacity; i++)
947 if (container.Inventory.GetItemAt(i) is
Item item1 && container.Inventory.CanBePutInSlot(item1, i - 1))
949 if (container.Inventory.TryPutItem(item1, i - 1, allowSwapping:
false, allowCombine:
false, user:
null, createNetworkEvent:
true))
958 private float waitTimer;
959 private float randomAimTimer;
961 private float prevTargetRotation;
962 private float updateTimer;
963 private bool updatePending;
967 public void UpdateAutoOperate(
float deltaTime,
bool ignorePower, Identifier friendlyTag =
default)
976 if (friendlyTag.IsEmpty)
988 if (updateTimer < 0.0f)
991 item.CreateServerEvent(
this);
993 prevTargetRotation = targetRotation;
996 updateTimer -= deltaTime;
1001 waitTimer -= deltaTime;
1005 float maxDistance = 10000.0f;
1006 float shootDistance =
AIRange;
1008 float closestDist = shootDistance * shootDistance;
1013 if (!IsValidTarget(character)) {
continue; }
1014 float priority = isSlowTurret ? character.Params.AISlowTurretPriority : character.Params.AITurretPriority;
1015 if (priority <= 0) {
continue; }
1016 if (!IsValidTargetForAutoOperate(character, friendlyTag)) {
continue; }
1017 float dist = Vector2.DistanceSquared(character.WorldPosition,
item.
WorldPosition);
1018 if (dist > closestDist) {
continue; }
1019 if (!IsWithinAimingRadius(character.WorldPosition)) {
continue; }
1021 if (currentTarget !=
null && target == currentTarget)
1023 priority *= GetTargetPriorityModifier();
1025 closestDist = dist / priority;
1032 if (!IsValidTarget(targetItem)) {
continue; }
1034 if (priority <= 0) {
continue; }
1036 if (dist > closestDist) {
continue; }
1037 if (dist > shootDistance * shootDistance) {
continue; }
1038 if (!IsTargetItemCloseEnough(targetItem, dist)) {
continue; }
1039 if (!IsWithinAimingRadius(targetItem.
WorldPosition)) {
continue; }
1040 target = targetItem;
1041 if (currentTarget !=
null && target == currentTarget)
1043 priority *= GetTargetPriorityModifier();
1045 closestDist = dist / priority;
1050 if (target ==
null || target.
Submarine !=
null)
1052 closestDist = maxDistance * maxDistance;
1061 if (dist > closestDist) {
continue; }
1065 closestDist = shootDistance * shootDistance;
1066 if (closestSub !=
null)
1072 if (dist > closestDist) {
continue; }
1084 waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
1085 targetRotation = Rand.Range(minRotation, maxRotation);
1086 updatePending =
true;
1094 if (randomAimTimer < 0)
1098 waitTimer = Rand.Range(0.25f, 1f);
1100 targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-randomAim, randomAim));
1101 updatePending =
true;
1106 randomAimTimer -= deltaTime;
1110 if (target ==
null) {
return; }
1111 currentTarget = target;
1114 targetRotation = MathUtils.WrapAngleTwoPi(angle);
1115 if (Math.Abs(targetRotation - prevTargetRotation) > 0.1f) { updatePending =
true; }
1117 if (target is
Hull targetHull)
1119 Vector2 barrelDir = GetBarrelDir();
1127 if (!IsWithinAimingRadius(angle)) {
return; }
1131 Vector2 end = ConvertUnits.ToSimUnits(target.
WorldPosition);
1133 Body worldTarget = CheckLineOfSight(start, end);
1139 Body transformedTarget = CheckLineOfSight(start, end);
1140 shoot = CanShoot(transformedTarget, user:
null, friendlyTag,
TargetSubmarines) && (worldTarget ==
null || CanShoot(worldTarget, user:
null, friendlyTag,
TargetSubmarines));
1148 TryLaunch(deltaTime, ignorePower: ignorePower);
1156 if (previousTarget.LastAttacker ==
null || previousTarget.LastAttacker == character)
1158 character.
Speak(TextManager.Get(
"DialogTurretTargetDead").Value,
1159 identifier: $
"killedtarget{previousTarget.ID}".ToIdentifier(),
1160 minDurationBetweenSimilar: 5.0f);
1168 float lowestCharge = 0.0f;
1174 if (batteryToLoad ==
null || battery.
Charge < lowestCharge)
1176 batteryToLoad = battery;
1177 lowestCharge = battery.
Charge;
1181 if (battery.
Item.
Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
1188 character.
Speak(TextManager.Get(
"DialogSupercapacitorIsBroken").Value,
1189 identifier:
"supercapacitorisbroken".ToIdentifier(),
1190 minDurationBetweenSimilar: 30.0f);
1194 if (batteryToLoad ==
null) {
return true; }
1195 if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f)
1200 if (lowestCharge <= 0 && batteryToLoad.Item.ConditionPercentage > 0)
1202 character.
Speak(TextManager.Get(
"DialogTurretHasNoPower").Value,
1203 identifier:
"turrethasnopower".ToIdentifier(),
1204 minDurationBetweenSimilar: 30.0f);
1208 int usableProjectileCount = 0;
1209 int maxProjectileCount = 0;
1214 if (e is
Item projectileContainer)
1216 var container = projectileContainer.GetComponent<
ItemContainer>();
1217 if (container !=
null)
1219 maxProjectileCount += container.
Capacity;
1220 var projectiles = projectileContainer.ContainedItems.Where(it => it.Condition > 0.0f);
1221 var firstProjectile = projectiles.FirstOrDefault();
1227 projectileSpeed =
float.PositiveInfinity;
1229 previousAmmo = firstProjectile;
1230 if (projectiles.Any())
1233 firstProjectile.GetComponent<
Projectile>() ??
1234 firstProjectile.ContainedItems.FirstOrDefault()?.GetComponent<
Projectile>();
1235 TryDetermineProjectileSpeed(projectile);
1236 usableProjectileCount += projectiles.Count();
1242 if (usableProjectileCount == 0)
1245 Item containerItem =
null;
1248 containerItem = e as
Item;
1249 if (containerItem ==
null) {
continue; }
1250 if (!containerItem.IsInteractable(character)) {
continue; }
1253 if (container !=
null) {
break; }
1255 if (container ==
null || !container.ContainableItemIdentifiers.Any())
1257 if (character.IsOnPlayerTeam)
1259 character.Speak(TextManager.GetWithVariable(
"DialogCannotLoadTurret",
"[itemname]",
item.
Name, formatCapitals:
FormatCapitals.Yes).Value,
1260 identifier:
"cannotloadturret".ToIdentifier(),
1261 minDurationBetweenSimilar: 30.0f);
1265 if (objective.SubObjectives.None())
1267 var loadItemsObjective = AIContainItems<Turret>(container, character, objective, usableProjectileCount + 1, equip:
true, removeEmpty:
true, dropItemOnDeselected:
true);
1268 loadItemsObjective.ignoredContainerIdentifiers = ((
MapEntity)containerItem).Prefab.Identifier.ToEnumerable().ToImmutableHashSet();
1269 if (character.IsOnPlayerTeam)
1271 character.Speak(TextManager.GetWithVariable(
"DialogLoadTurret",
"[itemname]",
item.
Name, formatCapitals:
FormatCapitals.Yes).Value,
1272 identifier:
"loadturret".ToIdentifier(),
1273 minDurationBetweenSimilar: 30.0f);
1275 loadItemsObjective.Abandoned += CheckRemainingAmmo;
1276 loadItemsObjective.Completed += CheckRemainingAmmo;
1279 void CheckRemainingAmmo()
1281 if (!character.IsOnPlayerTeam) {
return; }
1283 Identifier ammoType = container.ContainableItemIdentifiers.FirstOrNull() ??
"ammobox".ToIdentifier();
1285 if (remainingAmmo == 0)
1287 character.Speak(TextManager.Get($
"DialogOutOf{ammoType}",
"DialogOutOfTurretAmmo").Value,
1288 identifier:
"outofammo".ToIdentifier(),
1289 minDurationBetweenSimilar: 30.0f);
1291 else if (remainingAmmo < 3)
1293 character.Speak(TextManager.Get($
"DialogLowOn{ammoType}").Value,
1294 identifier:
"outofammo".ToIdentifier(),
1295 minDurationBetweenSimilar: 30.0f);
1299 if (objective.SubObjectives.Any())
1307 Vector2? targetPos =
null;
1308 float maxDistance = 10000;
1310 float closestDistance = maxDistance * maxDistance;
1311 bool hadCurrentTarget = currentTarget !=
null;
1312 if (hadCurrentTarget)
1314 bool isValidTarget = IsValidTarget(currentTarget);
1318 if (dist > closestDistance)
1320 isValidTarget =
false;
1322 else if (currentTarget is
Item targetItem)
1324 if (!IsTargetItemCloseEnough(targetItem, dist))
1326 isValidTarget =
false;
1332 currentTarget =
null;
1333 aiFindTargetTimer = CrewAIFindTargetMinInverval;
1336 if (aiFindTargetTimer <= 0.0f)
1340 if (!IsValidTarget(enemy)) {
continue; }
1342 if (priority <= 0) {
continue; }
1343 if (character.Submarine !=
null)
1348 if (enemy.
Submarine.
TeamID == character.Submarine.TeamID) {
continue; }
1358 if (dist > closestDistance) {
continue; }
1359 if (dist < shootDistance * shootDistance)
1363 if (!IsWithinAimingRadius(enemy.
WorldPosition)) {
continue; }
1365 if (currentTarget !=
null && enemy == currentTarget)
1367 priority *= GetTargetPriorityModifier();
1370 closestEnemy = enemy;
1371 closestDistance = dist / priority;
1372 currentTarget = closestEnemy;
1376 if (!IsValidTarget(targetItem)) {
continue; }
1378 if (priority <= 0) {
continue; }
1380 if (dist > closestDistance) {
continue; }
1381 if (dist > shootDistance * shootDistance) {
continue; }
1382 if (!IsTargetItemCloseEnough(targetItem, dist)) {
continue; }
1383 if (!IsWithinAimingRadius(targetItem.
WorldPosition)) {
continue; }
1384 if (currentTarget !=
null && targetItem == currentTarget)
1386 priority *= GetTargetPriorityModifier();
1389 closestDistance = dist / priority;
1391 closestEnemy =
null;
1392 currentTarget = targetItem;
1394 aiFindTargetTimer = currentTarget ==
null ? CrewAiFindTargetMaxInterval : CrewAIFindTargetMinInverval;
1396 else if (currentTarget !=
null)
1400 bool iceSpireSpotted =
false;
1401 Vector2 targetVelocity = Vector2.Zero;
1403 if (currentTarget is
Character targetCharacter)
1406 if (targetCharacter.Submarine !=
null && targetCharacter.CurrentHull !=
null && targetCharacter.Submarine !=
item.
Submarine && !targetCharacter.CanSeeTarget(
Item))
1408 targetPos = targetCharacter.CurrentHull.WorldPosition;
1409 if (closestDistance > maxDistance * maxDistance)
1417 float closestDistSqr = closestDistance;
1418 foreach (
Limb limb
in targetCharacter.AnimController.Limbs)
1421 if (limb.
Hidden) {
continue; }
1422 if (!IsWithinAimingRadius(limb.
WorldPosition)) {
continue; }
1424 if (distSqr < closestDistSqr)
1426 closestDistSqr = distSqr;
1427 if (limb == targetCharacter.AnimController.MainLimb)
1430 closestDistSqr *= 0.5f;
1435 if (projectileSpeed <
float.PositiveInfinity && targetPos.HasValue)
1438 float dist = MathF.Sqrt(closestDistSqr);
1439 float projectileMovementTime = dist / projectileSpeed;
1441 targetVelocity = targetCharacter.AnimController.Collider.LinearVelocity;
1442 Vector2 movementAmount = targetVelocity * projectileMovementTime;
1445 movementAmount = ConvertUnits.ToDisplayUnits(movementAmount.ClampLength(MaximumAimAhead));
1446 Vector2 futurePosition = targetPos.Value + movementAmount;
1447 targetPos = Vector2.Lerp(targetPos.Value, futurePosition,
DegreeOfSuccess(character));
1449 if (closestDistSqr > shootDistance * shootDistance)
1451 aiFindTargetTimer = CrewAIFindTargetMinInverval;
1458 currentTarget =
null;
1459 closestEnemy =
null;
1467 closestDistance = shootDistance;
1471 foreach (var cell
in wall.Cells)
1473 if (!cell.DoesDamage) {
continue; }
1474 foreach (var edge
in cell.Edges)
1476 Vector2 p1 = edge.Point1 + cell.Translation;
1477 Vector2 p2 = edge.Point2 + cell.Translation;
1478 Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(p1, p2,
item.
WorldPosition);
1479 if (!IsWithinAimingRadius(closestPoint))
1482 Vector2 barrelDir =
new Vector2((
float)Math.Cos(
Rotation), -(
float)Math.Sin(
Rotation));
1485 closestPoint = intersection;
1486 if (!IsWithinAimingRadius(closestPoint)) {
continue; }
1498 if (dist >
AIRange + 1000) {
continue; }
1504 float minAngle = 0.5f;
1505 if (dot < minAngle && dist > 1000)
1511 dist -= MathHelper.Lerp(0, 1000, MathUtils.InverseLerp(minAngle, 1, dot));
1512 if (dist > closestDistance) {
continue; }
1513 targetPos = closestPoint;
1514 closestDistance = dist;
1515 iceSpireSpotted =
true;
1521 if (targetPos ==
null) {
return false; }
1523 objective.ForceHighestPriority =
true;
1525 debugDrawTargetPos = targetPos.Value;
1529 if (character.IsOnPlayerTeam)
1531 if (character.AIController.SelectedAiTarget ==
null && !hadCurrentTarget)
1533 if (CreatureMetrics.RecentlyEncountered.Contains(closestEnemy.
SpeciesName) || closestEnemy.
IsHuman)
1535 character.Speak(TextManager.Get(
"DialogNewTargetSpotted").Value,
1536 identifier:
"newtargetspotted".ToIdentifier(),
1537 minDurationBetweenSimilar: 30.0f);
1539 else if (CreatureMetrics.Encountered.Contains(closestEnemy.
SpeciesName))
1541 character.Speak(TextManager.GetWithVariable(
"DialogIdentifiedTargetSpotted",
"[speciesname]", closestEnemy.
DisplayName).Value,
1542 identifier:
"identifiedtargetspotted".ToIdentifier(),
1543 minDurationBetweenSimilar: 30.0f);
1547 character.Speak(TextManager.Get(
"DialogUnidentifiedTargetSpotted").Value,
1548 identifier:
"unidentifiedtargetspotted".ToIdentifier(),
1549 minDurationBetweenSimilar: 5.0f);
1552 else if (!CreatureMetrics.Encountered.Contains(closestEnemy.
SpeciesName))
1554 character.Speak(TextManager.Get(
"DialogUnidentifiedTargetSpotted").Value,
1555 identifier:
"unidentifiedtargetspotted".ToIdentifier(),
1556 minDurationBetweenSimilar: 5.0f);
1558 CreatureMetrics.AddEncounter(closestEnemy.
SpeciesName);
1560 character.AIController.SelectTarget(closestEnemy.
AiTarget);
1562 else if (iceSpireSpotted && character.IsOnPlayerTeam)
1564 character.Speak(TextManager.Get(
"DialogIceSpireSpotted").Value,
1565 identifier:
"icespirespotted".ToIdentifier(),
1566 minDurationBetweenSimilar: 60.0f);
1569 character.CursorPosition = targetPos.Value;
1570 if (character.Submarine !=
null)
1572 character.CursorPosition -= character.Submarine.Position;
1575 if (IsPointingTowards(targetPos.Value))
1577 Vector2 barrelDir = GetBarrelDir();
1580 bool allowShootingIfNothingInWay =
false;
1581 if (currentTarget !=
null)
1584 Vector2 targetEndPos = currentTarget.
WorldPosition + targetVelocity * ConvertUnits.ToDisplayUnits(MaximumAimAhead);
1588 allowShootingIfNothingInWay =
1589 targetVelocity.LengthSquared() > 0.001f &&
1590 MathUtils.LineSegmentsIntersect(
1591 aimStartPos, aimEndPos,
1592 targetStartPos, targetEndPos) &&
1594 Math.Abs(Vector2.Dot(Vector2.Normalize(aimEndPos - aimStartPos), Vector2.Normalize(targetEndPos - targetStartPos))) < 0.5f;
1597 Vector2 start = ConvertUnits.ToSimUnits(aimStartPos);
1598 Vector2 end = ConvertUnits.ToSimUnits(aimEndPos);
1600 Body worldTarget = CheckLineOfSight(start, end);
1601 if (closestEnemy !=
null && closestEnemy.
Submarine !=
null)
1605 Body transformedTarget = CheckLineOfSight(start, end);
1607 CanShoot(transformedTarget, character, allowShootingIfNothingInWay: allowShootingIfNothingInWay) &&
1608 (worldTarget ==
null || CanShoot(worldTarget, character, allowShootingIfNothingInWay: allowShootingIfNothingInWay));
1612 canShoot = CanShoot(worldTarget, character, allowShootingIfNothingInWay: allowShootingIfNothingInWay);
1614 if (!canShoot) {
return false; }
1615 if (character.IsOnPlayerTeam)
1617 character.Speak(TextManager.Get(
"DialogFireTurret").Value,
1618 identifier:
"fireturret".ToIdentifier(),
1619 minDurationBetweenSimilar: 30.0f);
1621 character.SetInput(
InputType.Shoot,
true,
true);
1626 private bool IsPointingTowards(Vector2 targetPos)
1634 maxAngleError *= 2.0f;
1636 return Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) <= maxAngleError;
1639 private bool IsTargetItemCloseEnough(
Item target,
float sqrDist) =>
float.IsPositiveInfinity(target.Prefab.AITurretTargetingMaxDistance) || sqrDist < MathUtils.Pow2(target.Prefab.AITurretTargetingMaxDistance);
1652 if (target ==
null) {
return false; }
1653 if (target is
Character targetCharacter)
1655 if (!targetCharacter.Enabled || targetCharacter.Removed || targetCharacter.IsDead || targetCharacter.AITurretPriority <= 0)
1660 else if (target is
Item targetItem)
1662 if (targetItem.Removed || targetItem.Condition <= 0 || !targetItem.Prefab.IsAITurretTarget || targetItem.Prefab.AITurretPriority <= 0 || targetItem.IsHidden)
1666 if (targetItem.Submarine !=
null)
1670 if (targetItem.ParentInventory !=
null)
1678 private bool IsValidTargetForAutoOperate(Character target, Identifier friendlyTag)
1680 if (!friendlyTag.IsEmpty)
1682 if (target.SpeciesName.Equals(friendlyTag) || target.Group.Equals(friendlyTag)) {
return false; }
1684 bool isHuman = target.IsHuman || target.Group == CharacterPrefab.HumanSpeciesName;
1702 private bool CanShoot(Body targetBody, Character user =
null, Identifier friendlyTag =
default,
bool targetSubmarines =
true,
bool allowShootingIfNothingInWay =
false)
1704 if (targetBody ==
null)
1707 return allowShootingIfNothingInWay;
1710 if (targetBody.UserData is Character c)
1712 targetCharacter = c;
1714 else if (targetBody.UserData is Limb limb)
1716 targetCharacter = limb.character;
1718 if (targetCharacter !=
null && !targetCharacter.Removed)
1722 if (HumanAIController.IsFriendly(user, targetCharacter))
1727 else if (!IsValidTargetForAutoOperate(targetCharacter, friendlyTag))
1735 if (targetBody.UserData is ISpatialEntity e)
1737 if (e is Structure { Indestructible:
true }) {
return false; }
1738 if (!targetSubmarines && e is Submarine) {
return false; }
1740 if (sub ==
null) {
return true; }
1742 if (sub.Info.IsOutpost || sub.Info.IsWreck || sub.Info.IsBeacon) {
return false; }
1754 private Body CheckLineOfSight(Vector2 start, Vector2 end)
1756 var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionProjectile;
1757 Body pickedBody =
Submarine.PickBody(start, end,
null, collisionCategories, allowInsideFixture:
true,
1758 customPredicate: (Fixture f) =>
1760 if (f.UserData is
Item i && i.GetComponent<
Turret>() !=
null) { return false; }
1761 if (f.UserData is Hull) { return false; }
1767 private Vector2 GetRelativeFiringPosition(
bool useOffset =
true)
1769 Vector2 transformedFiringOffset = Vector2.Zero;
1773 if (flipFiringOffset) { currOffSet.X = -currOffSet.X; }
1774 transformedFiringOffset = MathUtils.RotatePoint(
new Vector2(-currOffSet.Y, -currOffSet.X) *
item.
Scale, -
Rotation);
1776 return new Vector2(
item.
WorldRect.X + transformedBarrelPos.X + transformedFiringOffset.X,
item.
WorldRect.Y - transformedBarrelPos.Y + transformedFiringOffset.Y);
1779 private bool IsWithinAimingRadius(
float angle)
1781 float midRotation = (minRotation + maxRotation) / 2.0f;
1782 while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; }
1783 while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; }
1784 return angle >= minRotation && angle <= maxRotation;
1791 base.RemoveComponentSpecific();
1793 barrelSprite?.
Remove(); barrelSprite =
null;
1794 railSprite?.
Remove(); railSprite =
null;
1797 crosshairSprite?.
Remove(); crosshairSprite =
null;
1798 crosshairPointerSprite?.
Remove(); crosshairPointerSprite =
null;
1799 moveSoundChannel?.Dispose(); moveSoundChannel =
null;
1801 if (powerIndicator !=
null)
1804 powerIndicator =
null;
1809 private List<Projectile> GetLoadedProjectiles()
1811 List<Projectile> projectiles =
new List<Projectile>();
1813 CheckProjectileContainer(
item, projectiles, out
bool _);
1818 if (e is
Item projectileContainer)
1820 CheckProjectileContainer(projectileContainer, projectiles, out
bool stopSearching);
1821 if (projectiles.Any() || stopSearching) {
return projectiles; }
1827 private static void CheckProjectileContainer(
Item projectileContainer, List<Projectile> projectiles, out
bool stopSearching)
1829 stopSearching =
false;
1830 if (projectileContainer.Condition <= 0.0f) {
return; }
1832 var containedItems = projectileContainer.ContainedItems;
1833 if (containedItems ==
null) {
return; }
1835 foreach (
Item containedItem
in containedItems)
1837 var projectileComponent = containedItem.GetComponent<Projectile>();
1838 if (projectileComponent !=
null && projectileComponent.Item.body !=
null)
1840 projectiles.Add(projectileComponent);
1848 projectileComponent = subContainedItem.GetComponent<Projectile>();
1849 if (projectileComponent !=
null && projectileComponent.Item.body !=
null)
1851 projectiles.Add(projectileComponent);
1856 if (containedItem.Condition > 0.0f || projectiles.Any())
1858 stopSearching =
true;
1865 public override void FlipX(
bool relativeToSub)
1867 minRotation = MathHelper.Pi - minRotation;
1868 maxRotation = MathHelper.Pi - maxRotation;
1870 var temp = minRotation;
1871 minRotation = maxRotation;
1876 while (minRotation < 0)
1878 minRotation += MathHelper.TwoPi;
1879 maxRotation += MathHelper.TwoPi;
1881 targetRotation =
Rotation = (minRotation + maxRotation) / 2;
1883 UpdateTransformedBarrelPos();
1887 public override void FlipY(
bool relativeToSub)
1891 minRotation = -minRotation;
1892 maxRotation = -maxRotation;
1894 var temp = minRotation;
1895 minRotation = maxRotation;
1898 while (minRotation < 0)
1900 minRotation += MathHelper.TwoPi;
1901 maxRotation += MathHelper.TwoPi;
1903 targetRotation =
Rotation = (minRotation + maxRotation) / 2;
1905 UpdateTransformedBarrelPos();
1912 switch (connection.
Name)
1915 if (
float.TryParse(signal.
value, NumberStyles.Float, CultureInfo.InvariantCulture, out
float newRotation))
1917 if (!MathUtils.IsValid(newRotation)) {
return; }
1918 targetRotation = MathHelper.ToRadians(newRotation);
1923 resetActiveUserTimer = 1f;
1924 resetUserTimer = 10.0f;
1927 if (signal.
value ==
"0") {
return; }
1928 item.
Use((
float)Timing.Step, user: sender);
1931 resetActiveUserTimer = 1f;
1932 resetUserTimer = 10.0f;
1937 TryLaunch((
float)Timing.Step, sender);
1940 case "toggle_light":
1941 if (lightComponents !=
null && signal.
value !=
"0")
1943 foreach (var light
in lightComponents)
1945 light.IsOn = !light.IsOn;
1951 if (lightComponents !=
null)
1953 bool shouldBeOn = signal.
value !=
"0";
1954 foreach (var light
in lightComponents)
1956 light.IsOn = shouldBeOn;
1961 case SetAutoOperateConnection:
1965 case ToggleAutoOperateConnection:
1967 if (signal.
value !=
"0")
1975 private Vector2? loadedRotationLimits;
1976 private float? loadedBaseRotation;
1979 base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
1986 base.OnItemLoaded();
1987 FindLightComponents();
1989 if (!loadedBaseRotation.HasValue)
1998 if (TryExtractEventData(extraData, out EventData eventData))
2000 msg.
WriteUInt16(eventData.Projectile?.ID ?? LaunchWithoutProjectileId);
2006 msg.
WriteRangedSingle(MathHelper.Clamp(wrapAngle(targetRotation), minRotation, maxRotation), minRotation, maxRotation, 16);
2009 float wrapAngle(
float angle)
2011 float wrappedAngle = angle;
2012 while (wrappedAngle < minRotation && MathUtils.IsValid(wrappedAngle))
2014 wrappedAngle += MathHelper.TwoPi;
2016 while (wrappedAngle > maxRotation && MathUtils.IsValid(wrappedAngle))
2018 wrappedAngle -= MathHelper.TwoPi;
2020 return wrappedAngle;
virtual void SelectTarget(AITarget target)
AITarget SelectedAiTarget
void AddSubObjective(AIObjective objective, bool addFirst=false)
readonly AIObjectiveManager objectiveManager
override bool IsValidTarget(Item item)
readonly CharacterParams Params
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
virtual AIController AIController
float GetStatValue(StatTypes statType, bool includeSaved=true)
static readonly List< Character > CharacterList
static bool IsOnFriendlyTeam(CharacterTeamType myTeam, CharacterTeamType otherTeam)
void ApplySkillGain(Identifier skillIdentifier, float baseGain, bool gainedFromAbility=false, float maxGain=2f)
Increases the characters skill at a rate proportional to their current skill. If you want to increase...
float AISlowTurretPriority
float GetAttributeFloat(string key, float def)
Vector2 GetAttributeVector2(string key, in Vector2 def)
virtual Vector2 WorldPosition
RectTransform RectTransform
static GameSession?? GameSession
static NetworkMember NetworkMember
static readonly List< Hull > HullList
readonly List< Item > IgnoredItems
static bool IsFriendly(Character me, Character other, bool onlySameTeam=false)
void ResetCachedVisibleSize()
bool IsShootable
Should the item's Use method be called with the "Use" or with the "Shoot" key?
List< Repairable > Repairables
float OffsetOnSelectedMultiplier
void Use(float deltaTime, Character user=null, Limb targetLimb=null, Entity useTarget=null, Character userForOnUsedEvent=null)
bool IsInteractable(Character character)
Returns interactibility based on whether the character is on a player team
bool HasTag(Identifier tag)
bool RequireAimToUse
If true, the user has to hold the "aim" key before use is registered. False by default.
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
IEnumerable< Item > ContainedItems
static readonly List< Item > ItemList
float ConditionPercentage
Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id=Entity.NullEntityID, bool callOnItemLoaded=true)
List< Fixture > StaticFixtures
float AISlowTurretPriority
readonly List< Connection > Connections
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
float DegreeOfSuccess(Character character)
Returns 0.0f-1.0f based on how well the Character can use the itemcomponent
IEnumerable< PowerContainer > GetDirectlyConnectedBatteries()
float powerConsumption
The maximum amount of power the item can draw from connected items
float GetAvailableInstantaneousBatteryPower()
Returns the amount of power that can be supplied by batteries directly connected to the item
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
override float GetCurrentPowerConsumption(Connection conn=null)
Turret doesn't consume grid power, directly takes from the batteries on its grid instead.
float SpringDampingLowSkill
float AICurrentTargetPriorityMultiplier
float RotationSpeedHighSkill
float SpringDampingHighSkill
override void Update(float deltaTime, Camera cam)
void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData=null)
bool AlternatingFiringOffset
override void FlipX(bool relativeToSub)
float GetPowerRequiredToShoot()
void UpdateAutoOperate(float deltaTime, bool ignorePower, Identifier friendlyTag=default)
float SpringStiffnessHighSkill
Sprite WeaponIndicatorSprite
float FiringRotationSpeedModifier
bool IsWithinAimingRadius(Vector2 target)
float RetractionDurationMultiplier
override void RemoveComponentSpecific()
Turret(Item item, ContentXElement element)
void UpdateLightComponents()
override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
true if the operation was completed
Vector2 TransformedBarrelPos
override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
override void FlipY(bool relativeToSub)
override void ReceiveSignal(Signal signal, Connection connection)
override void OnMapLoaded()
Called when all items have been loaded. Use to initialize connections between items.
float RotationSpeedLowSkill
IEnumerable< Item > ActiveProjectiles
bool LaunchWithoutProjectile
bool AllowAutoOperateWithWiring
float SpringStiffnessLowSkill
override bool Use(float deltaTime, Character character=null)
static bool IsLoadedFriendlyOutpost
Is there a loaded level set, and is it a friendly outpost (FriendlyNPC or Team1). Does not take reput...
List< LevelWall > ExtraWalls
readonly MapEntityPrefab Prefab
readonly List< MapEntity > linkedTo
bool IsLinkAllowed(MapEntityPrefab target)
float SkillIncreasePerSecondWhenOperatingTurret
static SkillSettings Current
List< Item > GetItems(bool alsoFromConnectedSubs)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
override Vector2? WorldPosition
static List< Submarine > Loaded
override Vector2 SimPosition
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)
Interface for entities that the server can send events to the clients
void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
void WriteUInt16(UInt16 val)
ActionType
ActionTypes define when a StatusEffect is executed.
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.