362 lines
15 KiB
C#
362 lines
15 KiB
C#
using FarseerPhysics;
|
|
using FarseerPhysics.Dynamics;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using Barotrauma.Extensions;
|
|
#if CLIENT
|
|
using Barotrauma.Particles;
|
|
#endif
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
partial class RepairTool : ItemComponent
|
|
{
|
|
private readonly List<string> fixableEntities;
|
|
private Vector2 pickedPosition;
|
|
private float activeTimer;
|
|
|
|
private Vector2 debugRayStartPos, debugRayEndPos;
|
|
|
|
[Serialize(0.0f, false)]
|
|
public float Range { get; set; }
|
|
|
|
[Serialize(0.0f, false)]
|
|
public float StructureFixAmount
|
|
{
|
|
get; set;
|
|
}
|
|
[Serialize(0.0f, false)]
|
|
public float ExtinguishAmount
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
[Serialize("0.0,0.0", false)]
|
|
public Vector2 BarrelPos { get; set; }
|
|
|
|
[Serialize(false, false)]
|
|
public bool RepairThroughWalls { get; set; }
|
|
|
|
public Vector2 TransformedBarrelPos
|
|
{
|
|
get
|
|
{
|
|
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
|
|
Vector2 flippedPos = BarrelPos;
|
|
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X;
|
|
return (Vector2.Transform(flippedPos, bodyTransform));
|
|
}
|
|
}
|
|
|
|
public RepairTool(Item item, XElement element)
|
|
: base(item, element)
|
|
{
|
|
this.item = item;
|
|
|
|
if (element.Attribute("limbfixamount") != null)
|
|
{
|
|
DebugConsole.ThrowError("Error in item \"" + item.Name + "\" - RepairTool damage should be configured using a StatusEffect with Afflictions, not the limbfixamount attribute.");
|
|
}
|
|
|
|
fixableEntities = new List<string>();
|
|
foreach (XElement subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "fixable":
|
|
if (subElement.Attribute("name") != null)
|
|
{
|
|
DebugConsole.ThrowError("Error in RepairTool " + item.Name + " - use identifiers instead of names to configure fixable entities.");
|
|
fixableEntities.Add(subElement.Attribute("name").Value);
|
|
}
|
|
else
|
|
{
|
|
fixableEntities.Add(subElement.GetAttributeString("identifier", ""));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
InitProjSpecific(element);
|
|
}
|
|
|
|
partial void InitProjSpecific(XElement element);
|
|
|
|
public override void Update(float deltaTime, Camera cam)
|
|
{
|
|
activeTimer -= deltaTime;
|
|
if (activeTimer <= 0.0f) IsActive = false;
|
|
}
|
|
|
|
public override bool Use(float deltaTime, Character character = null)
|
|
{
|
|
if (character == null || character.Removed) return false;
|
|
if (!character.IsKeyDown(InputType.Aim)) return false;
|
|
|
|
float degreeOfSuccess = DegreeOfSuccess(character);
|
|
|
|
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
|
|
{
|
|
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
|
|
return false;
|
|
}
|
|
|
|
Vector2 targetPosition = item.WorldPosition;
|
|
targetPosition += new Vector2(
|
|
(float)Math.Cos(item.body.Rotation),
|
|
(float)Math.Sin(item.body.Rotation)) * Range * item.body.Dir;
|
|
|
|
List<Body> ignoredBodies = new List<Body>();
|
|
foreach (Limb limb in character.AnimController.Limbs)
|
|
{
|
|
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) continue;
|
|
ignoredBodies.Add(limb.body.FarseerBody);
|
|
}
|
|
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
|
|
|
|
IsActive = true;
|
|
activeTimer = 0.1f;
|
|
|
|
Vector2 rayStart = ConvertUnits.ToSimUnits(item.WorldPosition);
|
|
Vector2 rayEnd = ConvertUnits.ToSimUnits(targetPosition);
|
|
|
|
debugRayStartPos = item.WorldPosition;
|
|
debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
|
|
|
|
if (character.Submarine == null)
|
|
{
|
|
foreach (Submarine sub in Submarine.Loaded)
|
|
{
|
|
Rectangle subBorders = sub.Borders;
|
|
subBorders.Location += new Point((int)sub.WorldPosition.X, (int)sub.WorldPosition.Y - sub.Borders.Height);
|
|
if (!MathUtils.CircleIntersectsRectangle(item.WorldPosition, Range * 5.0f, subBorders))
|
|
{
|
|
continue;
|
|
}
|
|
Repair(rayStart - sub.SimPosition, rayEnd - sub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
|
}
|
|
Repair(rayStart, rayEnd, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
|
}
|
|
else
|
|
{
|
|
Repair(rayStart - character.Submarine.SimPosition, rayEnd - character.Submarine.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
|
|
}
|
|
|
|
UseProjSpecific(deltaTime);
|
|
|
|
return true;
|
|
}
|
|
|
|
partial void UseProjSpecific(float deltaTime);
|
|
|
|
private List<FireSource> fireSourcesInRange = new List<FireSource>();
|
|
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
|
|
{
|
|
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
|
|
if (RepairThroughWalls)
|
|
{
|
|
var bodies = Submarine.PickBodies(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true);
|
|
foreach (Body body in bodies)
|
|
{
|
|
FixBody(user, deltaTime, degreeOfSuccess, body);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FixBody(user, deltaTime, degreeOfSuccess, Submarine.PickBody(rayStart, rayEnd, ignoredBodies, collisionCategories, ignoreSensors: false, allowInsideFixture: true));
|
|
}
|
|
|
|
if (ExtinguishAmount > 0.0f && item.CurrentHull != null)
|
|
{
|
|
fireSourcesInRange.Clear();
|
|
//step along the ray in 10% intervals, collecting all fire sources in the range
|
|
for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f)
|
|
{
|
|
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * x);
|
|
if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; }
|
|
|
|
Hull hull = Hull.FindHull(displayPos, item.CurrentHull);
|
|
if (hull == null) continue;
|
|
foreach (FireSource fs in hull.FireSources)
|
|
{
|
|
if (fs.IsInDamageRange(displayPos, 100.0f) && !fireSourcesInRange.Contains(fs))
|
|
{
|
|
fireSourcesInRange.Add(fs);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (FireSource fs in fireSourcesInRange)
|
|
{
|
|
fs.Extinguish(deltaTime, ExtinguishAmount);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FixBody(Character user, float deltaTime, float degreeOfSuccess, Body targetBody)
|
|
{
|
|
if (targetBody?.UserData == null) { return; }
|
|
|
|
pickedPosition = Submarine.LastPickedPosition;
|
|
|
|
if (targetBody.UserData is Structure targetStructure)
|
|
{
|
|
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) return;
|
|
if (targetStructure.IsPlatform) return;
|
|
|
|
int sectionIndex = targetStructure.FindSectionIndex(ConvertUnits.ToDisplayUnits(pickedPosition));
|
|
if (sectionIndex < 0) return;
|
|
|
|
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
|
|
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user);
|
|
|
|
//if the next section is small enough, apply the effect to it as well
|
|
//(to make it easier to fix a small "left-over" section)
|
|
for (int i = -1; i < 2; i += 2)
|
|
{
|
|
int nextSectionLength = targetStructure.SectionLength(sectionIndex + i);
|
|
if ((sectionIndex == 1 && i == -1) ||
|
|
(sectionIndex == targetStructure.SectionCount - 2 && i == 1) ||
|
|
(nextSectionLength > 0 && nextSectionLength < Structure.WallSectionSize * 0.3f))
|
|
{
|
|
//targetStructure.HighLightSection(sectionIndex + i);
|
|
targetStructure.AddDamage(sectionIndex + i, -StructureFixAmount * degreeOfSuccess);
|
|
}
|
|
}
|
|
}
|
|
else if (targetBody.UserData is Character targetCharacter)
|
|
{
|
|
targetCharacter.LastDamageSource = item;
|
|
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List<ISerializableEntity>() { targetCharacter });
|
|
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
|
|
}
|
|
else if (targetBody.UserData is Limb targetLimb)
|
|
{
|
|
targetLimb.character.LastDamageSource = item;
|
|
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, new List<ISerializableEntity>() { targetLimb.character, targetLimb });
|
|
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
|
|
}
|
|
else if (targetBody.UserData is Item targetItem)
|
|
{
|
|
targetItem.IsHighlighted = true;
|
|
|
|
float prevCondition = targetItem.Condition;
|
|
|
|
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
|
|
|
|
var levelResource = targetItem.GetComponent<LevelResource>();
|
|
if (levelResource != null && levelResource.IsActive &&
|
|
levelResource.requiredItems.Any() &&
|
|
levelResource.HasRequiredItems(user, addMessage: false))
|
|
{
|
|
levelResource.DeattachTimer += deltaTime;
|
|
#if CLIENT
|
|
Character.Controlled?.UpdateHUDProgressBar(
|
|
this,
|
|
targetItem.WorldPosition,
|
|
levelResource.DeattachTimer / levelResource.DeattachDuration,
|
|
Color.Red, Color.Green);
|
|
#endif
|
|
}
|
|
FixItemProjSpecific(user, deltaTime, targetItem, prevCondition);
|
|
}
|
|
}
|
|
|
|
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
|
|
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
|
|
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition);
|
|
|
|
private float sinTime;
|
|
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
|
{
|
|
Gap leak = objective.OperateTarget as Gap;
|
|
if (leak == null) return true;
|
|
|
|
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
|
|
float dist = fromItemToLeak.Length();
|
|
|
|
//too far away -> consider this done and hope the AI is smart enough to move closer
|
|
if (dist > Range * 5.0f) return true;
|
|
|
|
// TODO: use the collider size?
|
|
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
|
Math.Abs(fromItemToLeak.X) < 100.0f && fromItemToLeak.Y < 0.0f && fromItemToLeak.Y > -150.0f)
|
|
{
|
|
((HumanoidAnimController)character.AnimController).Crouching = true;
|
|
}
|
|
|
|
//steer closer if almost in range
|
|
if (dist > Range)
|
|
{
|
|
Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f);
|
|
standPos = leak.WorldPosition + standPos * Range;
|
|
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
|
|
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
|
|
}
|
|
else
|
|
{
|
|
if (dist < Range / 2)
|
|
{
|
|
// Too close -> steer away
|
|
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
|
|
}
|
|
else
|
|
{
|
|
character.AIController.SteeringManager.Reset();
|
|
}
|
|
}
|
|
|
|
sinTime += deltaTime;
|
|
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
|
|
character.SetInput(InputType.Aim, false, true);
|
|
|
|
// Press the trigger only when the tool is approximately facing the target.
|
|
// If the character is climbing, ignore the check, because we cannot aim while climbing.
|
|
if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
|
|
{
|
|
Use(deltaTime, character);
|
|
}
|
|
else
|
|
{
|
|
sinTime -= deltaTime * 2;
|
|
}
|
|
|
|
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
|
|
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
|
|
|
|
if (leakFixed && leak.FlowTargetHull != null)
|
|
{
|
|
sinTime = 0;
|
|
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
|
|
{
|
|
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f);
|
|
}
|
|
else
|
|
{
|
|
character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leakfixed", 10.0f);
|
|
}
|
|
}
|
|
|
|
return leakFixed;
|
|
}
|
|
|
|
private void ApplyStatusEffectsOnTarget(Character user, float deltaTime, ActionType actionType, IEnumerable<ISerializableEntity> targets)
|
|
{
|
|
if (statusEffectLists == null) { return; }
|
|
if (!statusEffectLists.TryGetValue(actionType, out List<StatusEffect> statusEffects)) { return; }
|
|
|
|
foreach (StatusEffect effect in statusEffects)
|
|
{
|
|
effect.SetUser(user);
|
|
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
|
{
|
|
effect.Apply(actionType, deltaTime, item, targets);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|