Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/Holdable/Sprayer.cs
3 using FarseerPhysics;
4 using FarseerPhysics.Dynamics;
5 using Microsoft.Xna.Framework;
6 using Microsoft.Xna.Framework.Graphics;
7 using System;
8 using System.Collections.Generic;
9 using System.Linq;
10 using System.Xml.Linq;
11 
13 {
14 
15  partial class Sprayer : RangedWeapon, IDrawableComponent
16  {
17 #if DEBUG
18  private Vector2 debugRayStartPos, debugRayEndPos;
19 #endif
20 
21  public Vector2 DrawSize
22  {
23  get { return Vector2.Zero; }
24  }
25 
26  private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
27  private Hull targetHull;
28 
29  private Vector2 rayStartWorldPosition;
30 
31  private Color color;
32 
33  partial void InitProjSpecific(ContentXElement element)
34  {
35  currentCrossHairPointerScale = element.GetAttributeFloat("crosshairscale", 0.1f);
36 
37  foreach (var subElement in element.Elements())
38  {
39  switch (subElement.Name.ToString().ToLowerInvariant())
40  {
41  case "particleemitter":
42  particleEmitters.Add(new ParticleEmitter(subElement));
43  break;
44  }
45  }
46  }
47 
48  private readonly List<BackgroundSection> targetSections = new List<BackgroundSection>();
49 
50  // 0 = 1x1, 1 = 2x2, 2 = 3x3
51  private int spraySetting = 0;
52  private readonly Point[] sprayArray = new Point[8];
53 
54  public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
55  {
56  if (character == null || !character.IsKeyDown(InputType.Aim)) return;
57 
58  if (PlayerInput.KeyHit(InputType.PreviousFireMode))
59  {
60  if (spraySetting > 0)
61  {
62  spraySetting--;
63  }
64  else
65  {
66  spraySetting = 2;
67  }
68 
69  targetSections.Clear();
70  }
71 
72  if (PlayerInput.KeyHit(InputType.NextFireMode))
73  {
74  if (spraySetting < 2)
75  {
76  spraySetting++;
77  }
78  else
79  {
80  spraySetting = 0;
81  }
82 
83  targetSections.Clear();
84  }
85 
86  crosshairPointerPos = PlayerInput.MousePosition;
87 
88  Vector2 rayStart;
89  Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
90  Vector2 barrelPos = item.SimPosition + TransformedBarrelPos;
91  //make sure there's no obstacles between the base of the item (or the shoulder of the character) and the end of the barrel
92  if (Submarine.PickBody(sourcePos, barrelPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall) == null)
93  {
94  //no obstacles -> we start the raycast at the end of the barrel
95  rayStart = ConvertUnits.ToSimUnits(item.WorldPosition) + TransformedBarrelPos;
96  }
97  else
98  {
99  targetHull = null;
100  targetSections.Clear();
101  return;
102  }
103 
104  Vector2 pos = character.CursorWorldPosition;
105  Vector2 rayEnd = ConvertUnits.ToSimUnits(pos);
106  rayStartWorldPosition = ConvertUnits.ToDisplayUnits(rayStart);
107 
108  if (Vector2.Distance(rayStartWorldPosition, pos) > Range)
109  {
110  targetHull = null;
111  targetSections.Clear();
112  return;
113  }
114 
115 #if DEBUG
116  debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStart);
117  debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
118 #endif
119 
120  Submarine parentSub = character.Submarine ?? item.Submarine;
121  if (parentSub != null)
122  {
123  rayStart -= parentSub.SimPosition;
124  rayEnd -= parentSub.SimPosition;
125  }
126 
127  var obstacles = Submarine.PickBodies(rayStart, rayEnd, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
128  foreach (var body in obstacles)
129  {
130  if (body.UserData is Item item)
131  {
132  var door = item.GetComponent<Door>();
133  if (door != null && (door.IsOpen || door.IsBroken)) { continue; }
134  }
135 
136  targetHull = null;
137  targetSections.Clear();
138  return;
139  }
140 
141  targetHull = Hull.GetCleanTarget(pos);
142  if (targetHull == null)
143  {
144  targetSections.Clear();
145  return;
146  }
147 
148  BackgroundSection mousedOverSection = targetHull.GetBackgroundSection(pos);
149 
150  if (mousedOverSection == null)
151  {
152  targetSections.Clear();
153  return;
154  }
155 
156  // No need to refresh
157  if (targetSections.Count > 0 && mousedOverSection == targetSections[0])
158  {
159  return;
160  }
161 
162  targetSections.Clear();
163 
164  targetSections.Add(mousedOverSection);
165  int mousedOverIndex = mousedOverSection.Index;
166 
167  // Start with 2x2
168  if (spraySetting > 0)
169  {
170  sprayArray[0].X = mousedOverIndex + 1;
171  sprayArray[0].Y = mousedOverSection.RowIndex;
172 
173  sprayArray[1].X = mousedOverIndex + targetHull.xBackgroundMax;
174  sprayArray[1].Y = mousedOverSection.RowIndex + 1;
175 
176  sprayArray[2].X = sprayArray[1].X + 1;
177  sprayArray[2].Y = sprayArray[1].Y;
178 
179  for (int i = 0; i < 3; i++)
180  {
181  if (targetHull.DoesSectionMatch(sprayArray[i].X, sprayArray[i].Y))
182  {
183  targetSections.Add(targetHull.BackgroundSections[sprayArray[i].X]);
184  }
185  }
186 
187  // Add more if it's 3x3
188  if (spraySetting == 2)
189  {
190  sprayArray[3].X = mousedOverIndex - 1;
191  sprayArray[3].Y = mousedOverSection.RowIndex;
192 
193  sprayArray[4].X = sprayArray[1].X - 1;
194  sprayArray[4].Y = sprayArray[1].Y;
195 
196  sprayArray[5].X = sprayArray[3].X - targetHull.xBackgroundMax;
197  sprayArray[5].Y = sprayArray[3].Y - 1;
198 
199  sprayArray[6].X = sprayArray[5].X + 1;
200  sprayArray[6].Y = sprayArray[5].Y;
201 
202  sprayArray[7].X = sprayArray[6].X + 1;
203  sprayArray[7].Y = sprayArray[6].Y;
204 
205  for (int i = 3; i < sprayArray.Length; i++)
206  {
207  if (targetHull.DoesSectionMatch(sprayArray[i].X, sprayArray[i].Y))
208  {
209  targetSections.Add(targetHull.BackgroundSections[sprayArray[i].X]);
210  }
211  }
212  }
213  }
214  }
215 
216  public override void DrawHUD(SpriteBatch spriteBatch, Character character)
217  {
218  if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
219  GUI.HideCursor = targetSections.Count > 0;
220  }
221 
222  public override bool Use(float deltaTime, Character character = null)
223  {
224  if (character == null) { return false; }
225  if (character == Character.Controlled)
226  {
227  Spray(character, deltaTime, applyColors: targetSections.Count > 0);
228  return true;
229  }
230  else
231  {
232  //allow remote players to use the sprayer, but don't actually color the walls (we'll receive the data from the server)
233  Spray(character, deltaTime, applyColors: false);
234  return true;
235  }
236  }
237 
238  public void Spray(Character user, float deltaTime, bool applyColors)
239  {
240  Item liquidItem = liquidContainer?.Inventory.FirstOrDefault();
241  if (liquidItem == null) { return; }
242 
243  bool isCleaning = false;
244  liquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color);
245 
246  if (applyColors && targetSections.Any())
247  {
248  // Ethanol or other cleaning solvent
249  if (color.A == 0) { isCleaning = true; }
250  float sizeAdjustedSprayStrength = SprayStrength / targetSections.Count;
251  if (!isCleaning)
252  {
253  for (int i = 0; i < targetSections.Count; i++)
254  {
255  targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false);
256  }
257  if (GameMain.GameSession != null)
258  {
260  }
261  }
262  else
263  {
264  for (int i = 0; i < targetSections.Count; i++)
265  {
266  targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true);
267  }
268  if (GameMain.GameSession != null)
269  {
270  GameMain.GameSession.TimeSpentPainting += deltaTime;
271  }
272  }
273  }
274 
275  Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
276  Vector2 particleEndPos = user.CursorWorldPosition;
277  //the cursor position is not exact for remote players, we only know the direction they're aiming at but not the distance
278  // -> use 50% range, looks good enough
279  float dist = Math.Min(Vector2.Distance(particleStartPos, particleEndPos), Range * 0.5f);
280  foreach (ParticleEmitter particleEmitter in particleEmitters)
281  {
282  float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
283  float particleRange = particleEmitter.Prefab.Properties.VelocityMax * particleEmitter.Prefab.ParticlePrefab.LifeTime;
284  particleEmitter.Emit(
285  deltaTime, particleStartPos,
286  item.CurrentHull, particleAngle, particleEmitter.Prefab.Properties.CopyEntityAngle ? -particleAngle : 0, velocityMultiplier: dist / particleRange * 1.5f,
287  colorMultiplier: new Color(color.R, color.G, color.B, (byte)255));
288  }
289  }
290 
291  public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
292  {
293 #if DEBUG
295  {
296  GUI.DrawLine(spriteBatch,
297  new Vector2(debugRayStartPos.X, -debugRayStartPos.Y),
298  new Vector2(debugRayEndPos.X, -debugRayEndPos.Y),
299  Color.Yellow);
300  }
301 #endif
302  if (Character.Controlled == null || !Character.Controlled.HasEquippedItem(item) || !Character.Controlled.IsKeyDown(InputType.Aim) || targetHull == null || targetSections.Count == 0) return;
303 
304  Vector2 drawOffset = targetHull.Submarine == null ? Vector2.Zero : targetHull.Submarine.DrawPosition;
305  Point sectionSize = targetSections[0].Rect.Size;
306  Rectangle drawPositionRect = new Rectangle((int)(drawOffset.X + targetHull.Rect.X), (int)(drawOffset.Y + targetHull.Rect.Y), sectionSize.X, sectionSize.Y);
307 
308  if (crosshairSprite == null && crosshairPointerSprite == null)
309  {
310  for (int i = 0; i < targetSections.Count; i++)
311  {
312  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);
313  }
314  }
315  else if (targetSections.Count > 0)
316  {
317  Vector2 drawPos = Vector2.Zero;
318  for (int i = 0; i < targetSections.Count; i++)
319  {
320  drawPos += new Vector2(drawPositionRect.X + targetSections[i].Rect.X + sectionSize.X / 2, -(drawPositionRect.Y + targetSections[i].Rect.Y - sectionSize.Y / 2));
321  }
322  drawPos /= targetSections.Count;
323  crosshairSprite?.Draw(spriteBatch, drawPos, scale: sectionSize.X * 3 / crosshairSprite.size.X);
324  crosshairPointerSprite?.Draw(spriteBatch, drawPos, scale: sectionSize.X * (spraySetting + 1) / crosshairPointerSprite.size.X);
325  }
326  }
327  }
328 }
virtual Vector2 AimSourceSimPos
bool HasEquippedItem(Item item, InvSlotType? slotType=null, Func< InvSlotType, bool > predicate=null)
float GetAttributeFloat(string key, float def)
IEnumerable< ContentXElement > Elements()
Submarine Submarine
Definition: Entity.cs:53
static GameSession?? GameSession
Definition: GameMain.cs:88
static bool DebugDraw
Definition: GameMain.cs:29
void IncreaseSectionColorOrStrength(BackgroundSection section, Color? color, float? strength, bool requiresUpdate, bool isCleaning)
BackgroundSection GetBackgroundSection(Vector2 worldPosition)
static Hull GetCleanTarget(Vector2 worldPosition)
bool DoesSectionMatch(int index, int row)
List< BackgroundSection > BackgroundSections
void CleanSection(BackgroundSection section, float cleanVal, bool updateRequired)
Item FirstOrDefault()
Return the first item in the inventory, or null if the inventory is empty.
override void DrawHUD(SpriteBatch spriteBatch, Character character)
override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth=-1, Color? overrideColor=null)
readonly ParticleEmitterPrefab Prefab
void Emit(float deltaTime, Vector2 position, Hull? hullGuess=null, float angle=0.0f, float particleRotation=0.0f, float velocityMultiplier=1.0f, float sizeMultiplier=1.0f, float amountMultiplier=1.0f, Color? colorMultiplier=null, ParticlePrefab? overrideParticle=null, bool mirrorAngle=false, Tuple< Vector2, Vector2 >? tracerPoints=null)
readonly ParticleEmitterProperties Properties
readonly Identifier Identifier
Definition: Prefab.cs:34
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
static IEnumerable< Body > PickBodies(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)
Returns a list of physics bodies the ray intersects with, sorted according to distance (the closest b...
static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable< Body > ignoredBodies=null, Category? collisionCategory=null, bool ignoreSensors=true, Predicate< Fixture > customPredicate=null, bool allowInsideFixture=false)