Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs
2025-03-12 12:56:27 +00:00

330 lines
13 KiB
C#

using Barotrauma.Networking;
using Barotrauma.Particles;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Sprayer : RangedWeapon, IDrawableComponent
{
#if DEBUG
private Vector2 debugRayStartPos, debugRayEndPos;
#endif
public Vector2 DrawSize
{
get { return Vector2.Zero; }
}
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private Hull targetHull;
private Vector2 rayStartWorldPosition;
private Color color;
partial void InitProjSpecific(ContentXElement element)
{
currentCrossHairPointerScale = element.GetAttributeFloat("crosshairscale", 0.1f);
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
}
}
}
private readonly List<BackgroundSection> targetSections = new List<BackgroundSection>();
// 0 = 1x1, 1 = 2x2, 2 = 3x3
private int spraySetting = 0;
private readonly Point[] sprayArray = new Point[8];
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
{
if (character == null || !character.IsKeyDown(InputType.Aim)) return;
if (PlayerInput.KeyHit(InputType.PreviousFireMode))
{
if (spraySetting > 0)
{
spraySetting--;
}
else
{
spraySetting = 2;
}
targetSections.Clear();
}
if (PlayerInput.KeyHit(InputType.NextFireMode))
{
if (spraySetting < 2)
{
spraySetting++;
}
else
{
spraySetting = 0;
}
targetSections.Clear();
}
crosshairPointerPos = PlayerInput.MousePosition;
Vector2 rayStart;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = item.SimPosition + TransformedBarrelPos;
//make sure there's no obstacles between the base of the item (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall) == null)
{
//no obstacles -> we start the raycast at the end of the barrel
rayStart = ConvertUnits.ToSimUnits(item.WorldPosition) + TransformedBarrelPos;
}
else
{
targetHull = null;
targetSections.Clear();
return;
}
Vector2 pos = character.CursorWorldPosition;
Vector2 rayEnd = ConvertUnits.ToSimUnits(pos);
rayStartWorldPosition = ConvertUnits.ToDisplayUnits(rayStart);
if (Vector2.Distance(rayStartWorldPosition, pos) > Range)
{
targetHull = null;
targetSections.Clear();
return;
}
#if DEBUG
debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStart);
debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
#endif
Submarine parentSub = character.Submarine ?? item.Submarine;
if (parentSub != null)
{
rayStart -= parentSub.SimPosition;
rayEnd -= parentSub.SimPosition;
}
var obstacles = Submarine.PickBodies(rayStart, rayEnd, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
foreach (var body in obstacles)
{
if (body.UserData is Item item)
{
var door = item.GetComponent<Door>();
if (door != null && (door.IsOpen || door.IsBroken)) { continue; }
}
targetHull = null;
targetSections.Clear();
return;
}
targetHull = Hull.GetCleanTarget(pos);
if (targetHull == null)
{
targetSections.Clear();
return;
}
BackgroundSection mousedOverSection = targetHull.GetBackgroundSection(pos);
if (mousedOverSection == null)
{
targetSections.Clear();
return;
}
// No need to refresh
if (targetSections.Count > 0 && mousedOverSection == targetSections[0])
{
return;
}
targetSections.Clear();
targetSections.Add(mousedOverSection);
int mousedOverIndex = mousedOverSection.Index;
// Start with 2x2
if (spraySetting > 0)
{
sprayArray[0].X = mousedOverIndex + 1;
sprayArray[0].Y = mousedOverSection.RowIndex;
sprayArray[1].X = mousedOverIndex + targetHull.xBackgroundMax;
sprayArray[1].Y = mousedOverSection.RowIndex + 1;
sprayArray[2].X = sprayArray[1].X + 1;
sprayArray[2].Y = sprayArray[1].Y;
for (int i = 0; i < 3; i++)
{
if (targetHull.DoesSectionMatch(sprayArray[i].X, sprayArray[i].Y))
{
targetSections.Add(targetHull.BackgroundSections[sprayArray[i].X]);
}
}
// Add more if it's 3x3
if (spraySetting == 2)
{
sprayArray[3].X = mousedOverIndex - 1;
sprayArray[3].Y = mousedOverSection.RowIndex;
sprayArray[4].X = sprayArray[1].X - 1;
sprayArray[4].Y = sprayArray[1].Y;
sprayArray[5].X = sprayArray[3].X - targetHull.xBackgroundMax;
sprayArray[5].Y = sprayArray[3].Y - 1;
sprayArray[6].X = sprayArray[5].X + 1;
sprayArray[6].Y = sprayArray[5].Y;
sprayArray[7].X = sprayArray[6].X + 1;
sprayArray[7].Y = sprayArray[6].Y;
for (int i = 3; i < sprayArray.Length; i++)
{
if (targetHull.DoesSectionMatch(sprayArray[i].X, sprayArray[i].Y))
{
targetSections.Add(targetHull.BackgroundSections[sprayArray[i].X]);
}
}
}
}
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
base.DrawHUD(spriteBatch, character);
GUI.HideCursor = targetSections.Count > 0;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null) { return false; }
if (character == Character.Controlled)
{
Spray(character, deltaTime, applyColors: targetSections.Count > 0);
return true;
}
else
{
//allow remote players to use the sprayer, but don't actually color the walls (we'll receive the data from the server)
Spray(character, deltaTime, applyColors: false);
return true;
}
}
public void Spray(Character user, float deltaTime, bool applyColors)
{
Item liquidItem = liquidContainer?.Inventory.FirstOrDefault();
if (liquidItem == null) { return; }
bool isCleaning = false;
liquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color);
if (applyColors && targetSections.Any())
{
// Ethanol or other cleaning solvent
if (color.A == 0) { isCleaning = true; }
float sizeAdjustedSprayStrength = SprayStrength / targetSections.Count;
if (!isCleaning)
{
for (int i = 0; i < targetSections.Count; i++)
{
targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false);
}
if (GameMain.GameSession != null)
{
GameMain.GameSession.TimeSpentCleaning += deltaTime;
}
}
else
{
for (int i = 0; i < targetSections.Count; i++)
{
targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true);
}
if (GameMain.GameSession != null)
{
GameMain.GameSession.TimeSpentPainting += deltaTime;
}
}
}
Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
Vector2 particleEndPos = user.CursorWorldPosition;
//the cursor position is not exact for remote players, we only know the direction they're aiming at but not the distance
// -> use 50% range, looks good enough
float dist = Math.Min(Vector2.Distance(particleStartPos, particleEndPos), Range * 0.5f);
foreach (ParticleEmitter particleEmitter in particleEmitters)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
float particleRange = particleEmitter.Prefab.Properties.VelocityMax * particleEmitter.Prefab.ParticlePrefab.LifeTime;
particleEmitter.Emit(
deltaTime, particleStartPos,
item.CurrentHull, particleAngle, particleEmitter.Prefab.Properties.CopyEntityAngle ? -particleAngle : 0, velocityMultiplier: dist / particleRange * 1.5f,
colorMultiplier: new Color(color.R, color.G, color.B, (byte)255));
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{
#if DEBUG
if (GameMain.DebugDraw && Character.Controlled != null && Character.Controlled.IsKeyDown(InputType.Aim))
{
GUI.DrawLine(spriteBatch,
new Vector2(debugRayStartPos.X, -debugRayStartPos.Y),
new Vector2(debugRayEndPos.X, -debugRayEndPos.Y),
Color.Yellow);
}
#endif
if (Character.Controlled == null || !Character.Controlled.HasEquippedItem(item) || !Character.Controlled.IsKeyDown(InputType.Aim) || targetHull == null || targetSections.Count == 0) return;
Vector2 drawOffset = targetHull.Submarine == null ? Vector2.Zero : targetHull.Submarine.DrawPosition;
Point sectionSize = targetSections[0].Rect.Size;
Rectangle drawPositionRect = new Rectangle((int)(drawOffset.X + targetHull.Rect.X), (int)(drawOffset.Y + targetHull.Rect.Y), sectionSize.X, sectionSize.Y);
if (crosshairSprite == null && crosshairPointerSprite == null)
{
for (int i = 0; i < targetSections.Count; i++)
{
GUI.DrawRectangle(spriteBatch, new Vector2(drawPositionRect.X + targetSections[i].Rect.X, -(drawPositionRect.Y + targetSections[i].Rect.Y)), new Vector2(sectionSize.X, sectionSize.Y), Color.White, false, 0.0f, 1);
}
}
else if (targetSections.Count > 0)
{
Vector2 drawPos = Vector2.Zero;
for (int i = 0; i < targetSections.Count; i++)
{
drawPos += new Vector2(drawPositionRect.X + targetSections[i].Rect.X + sectionSize.X / 2, -(drawPositionRect.Y + targetSections[i].Rect.Y - sectionSize.Y / 2));
}
drawPos /= targetSections.Count;
crosshairSprite?.Draw(spriteBatch, drawPos, scale: sectionSize.X * 3 / crosshairSprite.size.X);
crosshairPointerSprite?.Draw(spriteBatch, drawPos, scale: sectionSize.X * (spraySetting + 1) / crosshairPointerSprite.size.X);
}
}
}
}