boatscript.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. enum EBoatEffects
  2. {
  3. PTC_FRONT = 0,
  4. PTC_BACK,
  5. PTC_SIDE_L,
  6. PTC_SIDE_R
  7. }
  8. enum EBoatOperationalState
  9. {
  10. OK = 0,
  11. RUINED = 1,
  12. NO_FUEL = 2,
  13. NO_IGNITER = 4,
  14. }
  15. enum EBoatEngineSoundState
  16. {
  17. NONE,
  18. START_OK,
  19. START_NO_FUEL,
  20. STOP_OK,
  21. STOP_NO_FUEL
  22. }
  23. class BoatScriptOwnerState : BoatOwnerState
  24. {
  25. };
  26. class BoatScriptMove : BoatMove
  27. {
  28. };
  29. #ifdef DIAG_DEVELOPER
  30. BoatScript _boat;
  31. #endif
  32. /*!
  33. Base script class for boats
  34. */
  35. class BoatScript : Boat
  36. {
  37. protected const int DECAY_PLAYER_RANGE = 300; // when a player is found within this range, decay is deactivated
  38. protected const int DECAY_FLAG_RANGE = 100; // when a boat is parked around a territoryflag, decay is deactivated
  39. protected const float DECAY_TICK_FREQUENCY = 10; // seconds
  40. protected const float DECAY_DAMAGE = 0.017;
  41. protected const float DECAY_FLIPPED_MULT = 4;
  42. protected const float SOUND_ENGINE_FADE = 0.2;
  43. protected const float SPLASH_THRESHOLD_CONDITION = 0.08; // diff between sea surface and propeller pos which determines if the splash should happen
  44. protected const float SPLASH_THRESHOLD = 0.18; // diff between sea surface and propeller pos which determines when the splash happens
  45. private static ref map<typename, ref TInputActionMap> m_BoatTypeActionsMap = new map<typename, ref TInputActionMap>();
  46. private TInputActionMap m_InputActionMap;
  47. private bool m_ActionsInitialized;
  48. protected vector m_VelocityPrevTick;
  49. protected float m_MomentumPrevTick;
  50. protected ref VehicleContactData m_ContactData;
  51. protected ref Timer m_DecayTimer;
  52. // particles
  53. protected bool m_UpdateParticles;
  54. protected ref EffectBoatWaterBase m_WaterEffects[4];
  55. // sounds
  56. protected bool m_SplashIncoming; // boat is high enough for the splash to trigger when it falls back down to sea level
  57. protected bool m_PlaySoundEngineStopNoFuel;
  58. protected bool m_PlaySoundImpactLight;
  59. protected bool m_PlaySoundImpactHeavy;
  60. protected bool m_PlaySoundPushBoat;
  61. protected bool m_IsEngineSoundFading;
  62. protected bool m_EngineFadeDirection; // true>in | false>out
  63. protected float m_EngineFadeTime;
  64. protected string m_SoundImpactLight;
  65. protected string m_SoundImpactHeavy;
  66. protected string m_SoundPushBoat;
  67. protected string m_SoundWaterSplash;
  68. protected string m_SoundEngineStart;
  69. protected string m_SoundEngineStop;
  70. protected string m_SoundEngineStartNoFuel;
  71. protected string m_SoundEngineStopNoFuel;
  72. // caches to avoid multiple triggers
  73. protected ref EffectSound m_SoundPushBoatEffect;
  74. protected ref EffectSound m_SoundWaterSplashEffect;
  75. protected ref EffectSound m_SoundEngineEffect;
  76. protected ref EffectSound m_SoundEngineEffectDeletion; // hold engine sound which is being stopped & deleted while another is starting
  77. void BoatScript()
  78. {
  79. #ifdef DIAG_DEVELOPER
  80. _boat = this;
  81. #endif
  82. SetEventMask(EntityEvent.POSTSIMULATE);
  83. SetEventMask(EntityEvent.FRAME);
  84. m_PlaySoundImpactLight = false;
  85. m_PlaySoundImpactHeavy = false;
  86. RegisterNetSyncVariableBool("m_PlaySoundEngineStopNoFuel");
  87. RegisterNetSyncVariableBool("m_PlaySoundPushBoat");
  88. RegisterNetSyncVariableBoolSignal("m_PlaySoundImpactLight");
  89. RegisterNetSyncVariableBoolSignal("m_PlaySoundImpactHeavy");
  90. m_SoundImpactHeavy = "boat_01_hit_light_SoundSet";
  91. m_SoundImpactLight = "boat_01_hit_heavy_SoundSet";
  92. m_SoundPushBoat = "boat_01_push_SoundSet";
  93. m_SoundWaterSplash = "boat_01_splash_SoundSet";
  94. m_SoundEngineStart = "boat_01_engine_start_SoundSet";
  95. m_SoundEngineStop = "boat_01_engine_stop_SoundSet";
  96. m_SoundEngineStartNoFuel = "boat_01_engine_start_no_fuel_SoundSet";
  97. m_SoundEngineStopNoFuel = "boat_01_engine_stop_no_fuel_SoundSet";
  98. if (GetGame().IsServer())
  99. {
  100. m_DecayTimer = new Timer( CALL_CATEGORY_SYSTEM );
  101. m_DecayTimer.Run(DECAY_TICK_FREQUENCY, this, "DecayHealthTick", NULL, true);
  102. }
  103. if (GetGame().IsDedicatedServer())
  104. return;
  105. m_WaterEffects[EBoatEffects.PTC_FRONT] = new EffectBoatWaterFront();
  106. m_WaterEffects[EBoatEffects.PTC_BACK] = new EffectBoatWaterBack();
  107. m_WaterEffects[EBoatEffects.PTC_SIDE_L] = new EffectBoatWaterSide();
  108. m_WaterEffects[EBoatEffects.PTC_SIDE_R] = new EffectBoatWaterSide();
  109. if (MemoryPointExists("ptcFxFront"))
  110. m_WaterEffects[EBoatEffects.PTC_FRONT].AttachTo(this, GetMemoryPointPos("ptcFxFront"));
  111. if (MemoryPointExists("ptcFxBack"))
  112. m_WaterEffects[EBoatEffects.PTC_BACK].AttachTo(this, GetMemoryPointPos("ptcFxBack"));
  113. if (MemoryPointExists("ptcFxSide1"))
  114. m_WaterEffects[EBoatEffects.PTC_SIDE_L].AttachTo(this, GetMemoryPointPos("ptcFxSide1"));
  115. if (MemoryPointExists("ptcFxSide2"))
  116. m_WaterEffects[EBoatEffects.PTC_SIDE_R].AttachTo(this, GetMemoryPointPos("ptcFxSide2"));
  117. }
  118. void ~BoatScript()
  119. {
  120. if (m_DecayTimer)
  121. m_DecayTimer.Stop();
  122. #ifndef SERVER
  123. CleanupEffects();
  124. #endif
  125. }
  126. override void EEDelete(EntityAI parent)
  127. {
  128. if (!GetGame().IsDedicatedServer())
  129. CleanupEffects();
  130. }
  131. void InitializeActions()
  132. {
  133. m_InputActionMap = m_BoatTypeActionsMap.Get(this.Type());
  134. if (!m_InputActionMap)
  135. {
  136. TInputActionMap iam = new TInputActionMap();
  137. m_InputActionMap = iam;
  138. SetActions();
  139. m_BoatTypeActionsMap.Insert(this.Type(), m_InputActionMap);
  140. }
  141. }
  142. override void GetActions(typename action_input_type, out array<ActionBase_Basic> actions)
  143. {
  144. if (!m_ActionsInitialized)
  145. {
  146. m_ActionsInitialized = true;
  147. InitializeActions();
  148. }
  149. actions = m_InputActionMap.Get(action_input_type);
  150. }
  151. override bool DisableVicinityIcon()
  152. {
  153. return true;
  154. }
  155. override string GetVehicleType()
  156. {
  157. return "VehicleTypeBoat";
  158. }
  159. override bool IsInventoryVisible()
  160. {
  161. return (GetGame().GetPlayer() && (!GetGame().GetPlayer().GetCommand_Vehicle() || GetGame().GetPlayer().GetCommand_Vehicle().GetTransport() == this));
  162. }
  163. override float GetTransportCameraDistance()
  164. {
  165. return 4.5;
  166. }
  167. override vector GetTransportCameraOffset()
  168. {
  169. return "0 1.4 0";
  170. }
  171. override int GetAnimInstance()
  172. {
  173. return VehicleAnimInstances.ZODIAC;
  174. }
  175. override int Get3rdPersonCameraType()
  176. {
  177. return DayZPlayerCameras.DAYZCAMERA_3RD_VEHICLE;
  178. }
  179. override bool CrewCanGetThrough(int posIdx)
  180. {
  181. return true;
  182. }
  183. override bool CanReachSeatFromSeat(int currentSeat, int nextSeat)
  184. {
  185. return true;
  186. }
  187. override bool CanReachSeatFromDoors(string pSeatSelection, vector pFromPos, float pDistance = 1.0)
  188. {
  189. return true;
  190. }
  191. override bool CanReachDoorsFromSeat(string pDoorsSelection, int pCurrentSeat)
  192. {
  193. return true;
  194. }
  195. override bool IsAreaAtDoorFree(int currentSeat, float maxAllowedObjHeight, inout vector extents, out vector transform[4])
  196. {
  197. return super.IsAreaAtDoorFree(currentSeat, maxAllowedObjHeight, extents, transform);
  198. }
  199. /*!
  200. Gets called everytime the game wants to start the engine.
  201. \return true if the engine can start, false otherwise.
  202. */
  203. override bool OnBeforeEngineStart()
  204. {
  205. EBoatOperationalState state = CheckOperationalRequirements();
  206. return state == EBoatOperationalState.OK;
  207. }
  208. // Whether the car engine can be started
  209. int CheckOperationalRequirements()
  210. {
  211. int state = EBoatOperationalState.OK;
  212. EntityAI item = null;
  213. if (GetHealthLevel("") >= GameConstants.STATE_RUINED || GetHealthLevel("Engine") >= GameConstants.STATE_RUINED)
  214. {
  215. state |= EBoatOperationalState.RUINED;
  216. }
  217. if (GetFluidFraction(BoatFluid.FUEL) <= 0)
  218. {
  219. state |= EBoatOperationalState.NO_FUEL;
  220. }
  221. if (IsVitalSparkPlug())
  222. {
  223. item = FindAttachmentBySlotName("SparkPlug");
  224. if (!item || (item && item.IsRuined()))
  225. state |= EBoatOperationalState.NO_IGNITER;
  226. }
  227. return state;
  228. }
  229. void OnIgnition()
  230. {
  231. EBoatOperationalState state = CheckOperationalRequirements();
  232. if (state == EBoatOperationalState.RUINED)
  233. {
  234. return;
  235. }
  236. if (state & EBoatOperationalState.NO_IGNITER)
  237. {
  238. HandleEngineSound(EBoatEngineSoundState.START_NO_FUEL);
  239. return;
  240. }
  241. if (state & EBoatOperationalState.NO_FUEL)
  242. {
  243. HandleEngineSound(EBoatEngineSoundState.START_NO_FUEL);
  244. return;
  245. }
  246. }
  247. override void OnEngineStart()
  248. {
  249. super.OnEngineStart();
  250. if (GetGame().IsDedicatedServer())
  251. return;
  252. FadeEngineSound(true);
  253. HandleEngineSound(EBoatEngineSoundState.START_OK);
  254. ClearWaterEffects(); // in case they are still active
  255. }
  256. override void OnEngineStop()
  257. {
  258. super.OnEngineStop();
  259. if (GetGame().IsDedicatedServer())
  260. return;
  261. FadeEngineSound(false);
  262. HandleEngineSound(EBoatEngineSoundState.STOP_OK);
  263. vector mat[4];
  264. dBodyGetWorldTransform(this, mat);
  265. vector pos = mat[3] + VectorToParent(PropellerGetPosition());
  266. if (GetGame().GetWaterDepth(pos) < 0) // stop instantly
  267. StopParticleUpdate();
  268. else
  269. GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(StopParticleUpdate, 3000);
  270. }
  271. override void EEOnCECreate()
  272. {
  273. float maxVolume = GetFluidCapacity(BoatFluid.FUEL);
  274. float amount = Math.RandomFloat(0.0, maxVolume * 0.35);
  275. Fill(BoatFluid.FUEL, amount);
  276. }
  277. override void OnInput(float dt)
  278. {
  279. super.OnInput(dt);
  280. }
  281. override void EOnPostSimulate(IEntity other, float timeSlice)
  282. {
  283. if (!IsProxy())
  284. {
  285. if (EngineIsOn())
  286. {
  287. if (GetFluidFraction(BoatFluid.FUEL) <= 0)
  288. {
  289. if (GetGame().IsServer())
  290. {
  291. m_PlaySoundEngineStopNoFuel = true;
  292. SetSynchDirty();
  293. m_PlaySoundEngineStopNoFuel = false;
  294. }
  295. EngineStop();
  296. }
  297. }
  298. }
  299. if (GetGame().IsServer())
  300. {
  301. CheckContactCache();
  302. m_VelocityPrevTick = GetVelocity(this);
  303. m_MomentumPrevTick = GetMomentum();
  304. }
  305. if (!GetGame().IsDedicatedServer())
  306. {
  307. if (EngineIsOn())
  308. m_UpdateParticles = true;
  309. if (m_UpdateParticles)
  310. HandleBoatSplashSound();
  311. }
  312. }
  313. override void EOnSimulate(IEntity other, float dt)
  314. {
  315. if (!IsProxy())
  316. {
  317. if (EngineIsOn())
  318. {
  319. vector mat[4];
  320. dBodyGetWorldTransform(this, mat);
  321. vector pos = mat[3] + VectorToParent(PropellerGetPosition());
  322. if (GetGame().GetWaterDepth(pos) < -0.2)
  323. EngineStop();
  324. }
  325. }
  326. }
  327. override void EOnFrame(IEntity other, float timeSlice)
  328. {
  329. if (!GetGame().IsDedicatedServer())
  330. {
  331. if (m_UpdateParticles)
  332. UpdateParticles(timeSlice);
  333. if (m_IsEngineSoundFading)
  334. {
  335. m_EngineFadeTime -= timeSlice;
  336. if (m_EngineFadeTime < 0)
  337. m_IsEngineSoundFading = false;
  338. }
  339. }
  340. }
  341. //! WARNING: Can be called very frequently in one frame, use with caution
  342. override void OnContact(string zoneName, vector localPos, IEntity other, Contact data)
  343. {
  344. if (GetGame().IsServer())
  345. {
  346. if (m_ContactData)
  347. return;
  348. float momentumDelta = GetMomentum() - m_MomentumPrevTick;
  349. float dot = vector.Dot(m_VelocityPrevTick.Normalized(), GetVelocity(this).Normalized());
  350. if (dot < 0)
  351. momentumDelta = m_MomentumPrevTick;
  352. m_ContactData = new VehicleContactData();
  353. m_ContactData.SetData(localPos, other, momentumDelta); // change to local pos
  354. }
  355. if (!IsProxy())
  356. {
  357. if (EngineIsOn() && !CheckOperationalState())
  358. EngineStop();
  359. }
  360. }
  361. override void EEHitBy(TotalDamageResult damageResult, int damageType, EntityAI source, int component, string dmgZone, string ammo, vector modelPos, float speedCoef)
  362. {
  363. super.EEHitBy(damageResult, damageType, source, component, dmgZone, ammo, modelPos, speedCoef);
  364. if (!IsProxy())
  365. {
  366. if (EngineIsOn() && !CheckOperationalState())
  367. EngineStop();
  368. }
  369. }
  370. override void OnVariablesSynchronized()
  371. {
  372. super.OnVariablesSynchronized();
  373. if (m_PlaySoundImpactHeavy)
  374. {
  375. EffectSound impactHeavy;
  376. PlaySound(m_SoundImpactHeavy, impactHeavy, GetPosition());
  377. m_PlaySoundImpactHeavy = false;
  378. }
  379. if (m_PlaySoundImpactLight)
  380. {
  381. EffectSound impactLight;
  382. PlaySound(m_SoundImpactLight, impactLight, GetPosition());
  383. m_PlaySoundImpactLight = false;
  384. }
  385. if (m_PlaySoundPushBoat)
  386. PlaySound(m_SoundPushBoat, m_SoundPushBoatEffect, GetPosition());
  387. else if (m_SoundPushBoatEffect && m_SoundPushBoatEffect.IsPlaying())
  388. m_SoundPushBoatEffect.Stop();
  389. if (m_PlaySoundEngineStopNoFuel)
  390. {
  391. HandleEngineSound(EBoatEngineSoundState.STOP_NO_FUEL);
  392. m_PlaySoundEngineStopNoFuel = false;
  393. }
  394. }
  395. protected override bool DetectFlipped(VehicleFlippedContext ctx)
  396. {
  397. if (!DetectFlippedUsingSurface(ctx, GameConstants.VEHICLE_FLIP_ANGLE_TOLERANCE))
  398. return false;
  399. return true;
  400. }
  401. override float OnSound(BoatSoundCtrl ctrl, float oldValue)
  402. {
  403. if (m_IsEngineSoundFading)
  404. {
  405. if (m_EngineFadeDirection)
  406. oldValue = Math.InverseLerp(SOUND_ENGINE_FADE, 0, m_EngineFadeTime);
  407. else
  408. oldValue = Math.InverseLerp(0, SOUND_ENGINE_FADE, m_EngineFadeTime);
  409. return oldValue;
  410. }
  411. return super.OnSound(ctrl, oldValue);
  412. }
  413. override void MarkCrewMemberUnconscious(int crewMemberIndex)
  414. {
  415. if (!IsAuthority())
  416. return;
  417. if (crewMemberIndex == DayZPlayerConstants.VEHICLESEAT_DRIVER)
  418. {
  419. EngineStop();
  420. }
  421. }
  422. override void MarkCrewMemberDead(int crewMemberIndex)
  423. {
  424. if (!IsAuthority())
  425. return;
  426. if (crewMemberIndex == DayZPlayerConstants.VEHICLESEAT_DRIVER)
  427. {
  428. EngineStop();
  429. }
  430. }
  431. protected void DecayHealthTick()
  432. {
  433. if ( (GetCEApi() && !GetCEApi().AvoidPlayer(GetPosition(), DECAY_PLAYER_RANGE)) || CfgGameplayHandler.GetBoatDecayMultiplier() <= 0 )
  434. return;
  435. float damage = DECAY_DAMAGE * CfgGameplayHandler.GetBoatDecayMultiplier();
  436. if (IsFlipped())
  437. damage *= DECAY_FLIPPED_MULT;
  438. if (IsInFlagRange())
  439. damage *= 0.3;
  440. AddHealth("Engine","Health", -damage);
  441. }
  442. protected bool IsInFlagRange()
  443. {
  444. array<vector> locations = GetGame().GetMission().GetActiveRefresherLocations();
  445. if (!locations)
  446. return false;
  447. int count = locations.Count();
  448. if (count > 0)
  449. {
  450. vector pos = GetWorldPosition();
  451. for (int i = 0; i < count; i++)
  452. {
  453. if (vector.Distance(pos, locations.Get(i)) < DECAY_FLAG_RANGE)
  454. return true;
  455. }
  456. return false;
  457. }
  458. else
  459. return false;
  460. }
  461. bool CheckOperationalState()
  462. {
  463. if (GetHealthLevel("") >= GameConstants.STATE_RUINED || GetHealthLevel("Engine") >= GameConstants.STATE_RUINED)
  464. return false;
  465. if (IsVitalSparkPlug())
  466. {
  467. EntityAI item = FindAttachmentBySlotName("SparkPlug");
  468. if (!item || (item && item.IsRuined()))
  469. return false;
  470. }
  471. return true;
  472. }
  473. override void OnDriverExit(Human player)
  474. {
  475. super.OnDriverExit(player);
  476. if (GetGear() != GetNeutralGear())
  477. {
  478. EngineStop();
  479. }
  480. }
  481. // Server side event for jump out processing
  482. void OnVehicleJumpOutServer(GetOutTransportActionData data)
  483. {
  484. vector posMS = data.m_Player.WorldToModel(data.m_Player.GetPosition());
  485. float healthCoef = Math.InverseLerp(ActionGetOutTransport.HEALTH_LOW_SPEED_VALUE, ActionGetOutTransport.HEALTH_HIGH_SPEED_VALUE, data.m_Speed) * 0.5;
  486. healthCoef = Math.Clamp(healthCoef, 0.0, 1.0);
  487. data.m_Player.ProcessDirectDamage(DamageType.CUSTOM, data.m_Player, "", "FallDamageHealth", posMS, healthCoef);
  488. }
  489. protected void HandleEngineSound(EBoatEngineSoundState state)
  490. {
  491. if (GetGame().IsDedicatedServer())
  492. return;
  493. string soundset;
  494. switch (state)
  495. {
  496. case EBoatEngineSoundState.START_OK:
  497. soundset = m_SoundEngineStart;
  498. break;
  499. case EBoatEngineSoundState.STOP_OK:
  500. soundset = m_SoundEngineStop;
  501. break;
  502. case EBoatEngineSoundState.START_NO_FUEL:
  503. soundset = m_SoundEngineStartNoFuel;
  504. break;
  505. case EBoatEngineSoundState.STOP_NO_FUEL:
  506. soundset = m_SoundEngineStopNoFuel;
  507. break;
  508. }
  509. // already playing same type of sound
  510. if (m_SoundEngineEffect)
  511. {
  512. if (m_SoundEngineEffect.GetSoundSet() == soundset) // already playing same type of sound
  513. return;
  514. else
  515. {
  516. m_SoundEngineEffectDeletion = m_SoundEngineEffect;
  517. m_SoundEngineEffectDeletion.SoundStop(); // stop the existing sound
  518. m_SoundEngineEffect = null;
  519. }
  520. }
  521. PlaySound(soundset, m_SoundEngineEffect, ModelToWorld(PropellerGetPosition()));
  522. }
  523. protected void PlaySound(string soundset, inout EffectSound sound, vector position = vector.Zero)
  524. {
  525. #ifndef SERVER
  526. if (position == vector.Zero)
  527. position = GetPosition();
  528. if (!sound)
  529. {
  530. sound = SEffectManager.PlaySoundCachedParams(soundset, position);
  531. sound.SetAttachmentParent(this);
  532. sound.SetAutodestroy(true); // SoundWaveObjects tend to null themselves for unknown reasons, breaking the effect in the process
  533. }
  534. else
  535. {
  536. if (!sound.IsSoundPlaying())
  537. {
  538. sound.SetCurrentPosition(position);
  539. sound.SoundPlay();
  540. }
  541. }
  542. #endif
  543. }
  544. protected void HandleBoatSplashSound()
  545. {
  546. float propPosRelative = GetGame().SurfaceGetSeaLevel() - CoordToParent(PropellerGetPosition())[1];
  547. if (propPosRelative < SPLASH_THRESHOLD_CONDITION)
  548. m_SplashIncoming = true;
  549. if (m_SplashIncoming && propPosRelative > SPLASH_THRESHOLD)
  550. {
  551. m_SoundWaterSplashEffect = SEffectManager.CreateSound(m_SoundWaterSplash, GetPosition());
  552. m_SoundWaterSplashEffect.SetAttachmentParent(this);
  553. m_SoundWaterSplashEffect.SetLocalPosition(PropellerGetPosition());
  554. m_SoundWaterSplashEffect.SetAutodestroy(true);
  555. vector velocity = dBodyGetVelocityAt(this, CoordToParent(PropellerGetPosition()));
  556. float speed = velocity.Normalize() * 3.6; // to km/h
  557. float lerp = Math.InverseLerp(0, 30, speed);
  558. m_SoundWaterSplashEffect.SetSoundVolume(lerp);
  559. m_SoundWaterSplashEffect.Start();
  560. m_SplashIncoming = false;
  561. }
  562. }
  563. protected void SyncSoundImpactLight()
  564. {
  565. if (!m_PlaySoundImpactLight)
  566. {
  567. m_PlaySoundImpactLight = true;
  568. SetSynchDirty();
  569. }
  570. }
  571. protected void SyncSoundImpactHeavy()
  572. {
  573. if (!m_PlaySoundImpactHeavy)
  574. {
  575. m_PlaySoundImpactHeavy = true;
  576. SetSynchDirty();
  577. }
  578. }
  579. void SyncSoundPushBoat(bool play)
  580. {
  581. if (m_PlaySoundPushBoat != play)
  582. {
  583. m_PlaySoundPushBoat = play;
  584. SetSynchDirty();
  585. }
  586. }
  587. // Set engine sound to fade on start/stop
  588. protected void FadeEngineSound(bool fadeIn)
  589. {
  590. m_IsEngineSoundFading = true;
  591. m_EngineFadeDirection = fadeIn;
  592. m_EngineFadeTime = SOUND_ENGINE_FADE;
  593. }
  594. protected void FlipVehicle()
  595. {
  596. vector orient = GetOrientation();
  597. orient[2] = orient[2] + 180;
  598. SetOrientation(orient);
  599. vector pos = GetPosition();
  600. pos[1] = pos[1] + 0.5;
  601. SetPosition(pos);
  602. }
  603. protected void CheckContactCache()
  604. {
  605. if (!m_ContactData)
  606. return;
  607. float impulse = Math.AbsInt(m_ContactData.m_Impulse);
  608. m_ContactData = null;
  609. if (impulse < GameConstants.CARS_CONTACT_DMG_MIN)
  610. return;
  611. else if (impulse < GameConstants.CARS_CONTACT_DMG_THRESHOLD)
  612. SyncSoundImpactLight();
  613. else
  614. SyncSoundImpactHeavy();
  615. }
  616. protected void SetActions()
  617. {
  618. AddAction(ActionGetInTransport);
  619. AddAction(ActionPushBoat);
  620. }
  621. void AddAction(typename actionName)
  622. {
  623. ActionBase action = ActionManagerBase.GetAction(actionName);
  624. if (!action)
  625. {
  626. Debug.LogError("Action " + actionName + " dosn't exist!");
  627. return;
  628. }
  629. typename ai = action.GetInputType();
  630. if (!ai)
  631. {
  632. m_ActionsInitialized = false;
  633. return;
  634. }
  635. array<ActionBase_Basic> actionArray = m_InputActionMap.Get(ai);
  636. if (!actionArray)
  637. {
  638. actionArray = new array<ActionBase_Basic>;
  639. m_InputActionMap.Insert(ai, actionArray);
  640. }
  641. if (LogManager.IsActionLogEnable())
  642. {
  643. Debug.ActionLog(action.ToString() + " -> " + ai, this.ToString() , "n/a", "Add action" );
  644. }
  645. actionArray.Insert(action);
  646. }
  647. void RemoveAction(typename actionName)
  648. {
  649. PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
  650. ActionBase action = player.GetActionManager().GetAction(actionName);
  651. typename ai = action.GetInputType();
  652. array<ActionBase_Basic> actionArray = m_InputActionMap.Get(ai);
  653. if (actionArray)
  654. {
  655. actionArray.RemoveItem(action);
  656. }
  657. }
  658. protected void UpdateParticles(float timeSlice = 0)
  659. {
  660. for (int i; i < 4; i++)
  661. {
  662. m_WaterEffects[i].Update(timeSlice);
  663. }
  664. }
  665. protected void StopParticleUpdate()
  666. {
  667. m_UpdateParticles = false;
  668. ClearWaterEffects();
  669. }
  670. protected void ClearWaterEffects()
  671. {
  672. GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).Remove(StopParticleUpdate);
  673. for (int i; i < 4; i++)
  674. {
  675. if (m_WaterEffects[i].IsPlaying())
  676. {
  677. if (m_WaterEffects[i].GetParticle())
  678. {
  679. m_WaterEffects[i].GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE, 0);
  680. m_WaterEffects[i].GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE_RND, 0);
  681. }
  682. m_WaterEffects[i].Stop();
  683. }
  684. }
  685. }
  686. // Called on destroy/stream out
  687. protected void CleanupEffects()
  688. {
  689. for (int i; i < 4; i++)
  690. {
  691. if (m_WaterEffects[i].IsRegistered())
  692. {
  693. m_WaterEffects[i].Stop();
  694. SEffectManager.EffectUnregisterEx(m_WaterEffects[i]);
  695. }
  696. }
  697. SEffectManager.DestroyEffect(m_SoundPushBoatEffect);
  698. SEffectManager.DestroyEffect(m_SoundWaterSplashEffect);
  699. }
  700. override void GetDebugActions(out TSelectableActionInfoArrayEx outputList)
  701. {
  702. outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.FLIP_ENTITY, "Flip vehicle", FadeColors.LIGHT_GREY));
  703. super.GetDebugActions(outputList);
  704. }
  705. override bool OnAction(int action_id, Man player, ParamsReadContext ctx)
  706. {
  707. if (super.OnAction(action_id, player, ctx))
  708. return true;
  709. if (!GetGame().IsServer())
  710. {
  711. return false;
  712. }
  713. switch (action_id)
  714. {
  715. case EActions.FLIP_ENTITY:
  716. FlipVehicle();
  717. return true;
  718. }
  719. return false;
  720. }
  721. protected override event typename GetOwnerStateType()
  722. {
  723. return BoatScriptOwnerState;
  724. }
  725. protected override event typename GetMoveType()
  726. {
  727. return BoatScriptMove;
  728. }
  729. #ifdef DIAG_DEVELOPER
  730. override void FixEntity()
  731. {
  732. super.FixEntity();
  733. if (GetGame().IsServer())
  734. Fill(BoatFluid.FUEL, GetFluidCapacity(BoatFluid.FUEL));
  735. }
  736. #endif
  737. }