enum EBoatEffects { PTC_FRONT = 0, PTC_BACK, PTC_SIDE_L, PTC_SIDE_R } enum EBoatEngineSoundState { NONE, START_OK, START_NO_FUEL, STOP_OK, STOP_NO_FUEL } class BoatScriptOwnerState : BoatOwnerState { }; class BoatScriptMove : BoatMove { }; class BoatScript : Boat { protected const float SOUND_ENGINE_FADE = 0.2; protected const float SPLASH_THRESHOLD_CONDITION = 0.08; // diff between sea surface and propeller pos which determines if the splash should happen protected const float SPLASH_THRESHOLD = 0.18; // diff between sea surface and propeller pos which determines when the splash happens private static ref map m_BoatTypeActionsMap = new map(); private TInputActionMap m_InputActionMap; private bool m_ActionsInitialized; protected vector m_VelocityPrevTick; protected float m_MomentumPrevTick; protected ref VehicleContactData m_ContactData; // particles protected bool m_UpdateParticles; protected ref EffectBoatWaterBase m_WaterEffects[4]; // sounds protected bool m_SplashIncoming; // boat is high enough for the splash to trigger when it falls back down to sea level protected bool m_PlaySoundEngineStopNoFuel; protected bool m_PlaySoundImpactLight; protected bool m_PlaySoundImpactHeavy; protected bool m_PlaySoundPushBoat; protected bool m_IsEngineSoundFading; protected bool m_EngineFadeDirection; // true>in | false>out protected float m_EngineFadeTime; protected string m_SoundImpactLight; protected string m_SoundImpactHeavy; protected string m_SoundPushBoat; protected string m_SoundWaterSplash; protected ref EffectSound m_SoundImpactLightEffect; protected ref EffectSound m_SoundImpactHeavyEffect; protected ref EffectSound m_SoundPushBoatEffect; protected ref EffectSound m_SoundWaterSplashEffect; void BoatScript() { SetEventMask(EntityEvent.POSTSIMULATE); SetEventMask(EntityEvent.FRAME); m_PlaySoundImpactLight = false; m_PlaySoundImpactHeavy = false; RegisterNetSyncVariableBool("m_PlaySoundEngineStopNoFuel"); RegisterNetSyncVariableBool("m_PlaySoundPushBoat"); RegisterNetSyncVariableBoolSignal("m_PlaySoundImpactLight"); RegisterNetSyncVariableBoolSignal("m_PlaySoundImpactHeavy"); m_SoundImpactHeavy = "boat_01_hit_light_SoundSet"; m_SoundImpactLight = "boat_01_hit_heavy_SoundSet"; m_SoundPushBoat = "boat_01_push_SoundSet"; m_SoundWaterSplash = "boat_01_splash_SoundSet"; if (GetGame().IsDedicatedServer()) return; m_WaterEffects[EBoatEffects.PTC_FRONT] = new EffectBoatWaterFront(); m_WaterEffects[EBoatEffects.PTC_BACK] = new EffectBoatWaterBack(); m_WaterEffects[EBoatEffects.PTC_SIDE_L] = new EffectBoatWaterSide(); m_WaterEffects[EBoatEffects.PTC_SIDE_R] = new EffectBoatWaterSide(); if (MemoryPointExists("ptcFxFront")) m_WaterEffects[EBoatEffects.PTC_FRONT].AttachTo(this, GetMemoryPointPos("ptcFxFront")); if (MemoryPointExists("ptcFxBack")) m_WaterEffects[EBoatEffects.PTC_BACK].AttachTo(this, GetMemoryPointPos("ptcFxBack")); if (MemoryPointExists("ptcFxSide1")) m_WaterEffects[EBoatEffects.PTC_SIDE_L].AttachTo(this, GetMemoryPointPos("ptcFxSide1")); if (MemoryPointExists("ptcFxSide2")) m_WaterEffects[EBoatEffects.PTC_SIDE_R].AttachTo(this, GetMemoryPointPos("ptcFxSide2")); } void ~BoatScript() { #ifndef SERVER CleanupEffects(); #endif } override void EEDelete(EntityAI parent) { if (!GetGame().IsDedicatedServer()) CleanupEffects(); } void InitializeActions() { m_InputActionMap = m_BoatTypeActionsMap.Get(this.Type()); if (!m_InputActionMap) { TInputActionMap iam = new TInputActionMap(); m_InputActionMap = iam; SetActions(); m_BoatTypeActionsMap.Insert(this.Type(), m_InputActionMap); } } override void GetActions(typename action_input_type, out array actions) { if (!m_ActionsInitialized) { m_ActionsInitialized = true; InitializeActions(); } actions = m_InputActionMap.Get(action_input_type); } override bool DisableVicinityIcon() { return true; } override string GetVehicleType() { return "VehicleTypeBoat"; } override bool IsInventoryVisible() { return (GetGame().GetPlayer() && (!GetGame().GetPlayer().GetCommand_Vehicle() || GetGame().GetPlayer().GetCommand_Vehicle().GetTransport() == this)); } override float GetTransportCameraDistance() { return 4.5; } override vector GetTransportCameraOffset() { return "0 1.4 0"; } override int GetAnimInstance() { return VehicleAnimInstances.ZODIAC; } override int Get3rdPersonCameraType() { return DayZPlayerCameras.DAYZCAMERA_3RD_VEHICLE; } override bool CrewCanGetThrough(int posIdx) { return true; } override bool CanReachSeatFromSeat(int currentSeat, int nextSeat) { return true; } override bool CanReachSeatFromDoors(string pSeatSelection, vector pFromPos, float pDistance = 1.0) { return true; } override bool CanReachDoorsFromSeat(string pDoorsSelection, int pCurrentSeat) { return true; } override bool IsAreaAtDoorFree(int currentSeat, float maxAllowedObjHeight, inout vector extents, out vector transform[4]) { return super.IsAreaAtDoorFree(currentSeat, maxAllowedObjHeight, extents, transform); } override bool OnBeforeEngineStart() { if (GetFluidFraction(BoatFluid.FUEL) <= 0) { HandleEngineSound(EBoatEngineSoundState.START_NO_FUEL); return false; } return true; } override void OnEngineStart() { super.OnEngineStart(); if (GetGame().IsDedicatedServer()) return; FadeEngineSound(true); HandleEngineSound(EBoatEngineSoundState.START_OK); ClearWaterEffects(); // in case they are still active } override void OnEngineStop() { super.OnEngineStop(); if (GetGame().IsDedicatedServer()) return; FadeEngineSound(false); HandleEngineSound(EBoatEngineSoundState.STOP_OK); vector mat[4]; dBodyGetWorldTransform(this, mat); vector pos = mat[3] + VectorToParent(PropellerGetPosition()); if (GetGame().GetWaterDepth(pos) < 0) // stop instantly StopParticleUpdate(); else GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(StopParticleUpdate, 3000); } override void EEOnCECreate() { float maxVolume = GetFluidCapacity(BoatFluid.FUEL); float amount = Math.RandomFloat(0.0, maxVolume * 0.35); Fill(BoatFluid.FUEL, amount); } override void EOnPostSimulate(IEntity other, float timeSlice) { if (GetGame().IsServer()) { HandleByCrewMemberState(ECrewMemberState.UNCONSCIOUS); HandleByCrewMemberState(ECrewMemberState.DEAD); if (EngineIsOn()) { if (GetFluidFraction(BoatFluid.FUEL) <= 0) { m_PlaySoundEngineStopNoFuel = true; SetSynchDirty(); m_PlaySoundEngineStopNoFuel = false; EngineStop(); } } CheckContactCache(); m_VelocityPrevTick = GetVelocity(this); m_MomentumPrevTick = GetMomentum(); } else if (EngineIsOn()) m_UpdateParticles = true; if (!GetGame().IsDedicatedServer() && m_UpdateParticles) HandleBoatSplashSound(); } override void EOnSimulate(IEntity other, float timeSlice) { if (!IsProxy()) { if (EngineIsOn()) { vector mat[4]; dBodyGetWorldTransform(this, mat); vector pos = mat[3] + VectorToParent(PropellerGetPosition()); if (GetGame().GetWaterDepth(pos) < -0.2) EngineStop(); } } } override void EOnFrame(IEntity other, float timeSlice) { if (!GetGame().IsDedicatedServer()) { if (m_UpdateParticles) UpdateParticles(); if (m_IsEngineSoundFading) { m_EngineFadeTime -= timeSlice; if (m_EngineFadeTime < 0) m_IsEngineSoundFading = false; } } } override void EOnContact(IEntity other, Contact extra) { if (GetGame().IsServer()) { if (m_ContactData) return; float momentumDelta = GetMomentum() - m_MomentumPrevTick; float dot = vector.Dot(m_VelocityPrevTick.Normalized(), GetVelocity(this).Normalized()); if (dot < 0) momentumDelta = m_MomentumPrevTick; m_ContactData = new VehicleContactData(); m_ContactData.SetData(extra.Position, other, momentumDelta); // change to local pos if (EngineIsOn() && !CheckOperationalState()) EngineStop(); } } override void EEHitBy(TotalDamageResult damageResult, int damageType, EntityAI source, int component, string dmgZone, string ammo, vector modelPos, float speedCoef) { super.EEHitBy(damageResult, damageType, source, component, dmgZone, ammo, modelPos, speedCoef); if (GetGame().IsServer()) { if (EngineIsOn() && !CheckOperationalState()) EngineStop(); } } override void OnVariablesSynchronized() { super.OnVariablesSynchronized(); if (m_PlaySoundImpactHeavy) { PlaySound(m_SoundImpactHeavy, m_SoundImpactHeavyEffect); m_PlaySoundImpactHeavy = false; } if (m_PlaySoundImpactLight) { PlaySound(m_SoundImpactLight, m_SoundImpactLightEffect); m_PlaySoundImpactLight = false; } if (m_PlaySoundPushBoat) PlaySound(m_SoundPushBoat, m_SoundPushBoatEffect); else if (m_SoundPushBoatEffect && m_SoundPushBoatEffect.IsPlaying()) m_SoundPushBoatEffect.Stop(); if (m_PlaySoundEngineStopNoFuel) { HandleEngineSound(EBoatEngineSoundState.STOP_NO_FUEL); m_PlaySoundEngineStopNoFuel = false; } } override float OnSound(BoatSoundCtrl ctrl, float oldValue) { if (m_IsEngineSoundFading) { if (m_EngineFadeDirection) oldValue = Math.InverseLerp(SOUND_ENGINE_FADE, 0, m_EngineFadeTime); else oldValue = Math.InverseLerp(0, SOUND_ENGINE_FADE, m_EngineFadeTime); return oldValue; } return super.OnSound(ctrl, oldValue); } override void HandleByCrewMemberState(ECrewMemberState state) { switch (state) { case ECrewMemberState.UNCONSCIOUS: foreach (int unconsciousCrewMemberIndex : m_UnconsciousCrewMemberIndices) { if (unconsciousCrewMemberIndex == DayZPlayerConstants.VEHICLESEAT_DRIVER) EngineStop(); m_UnconsciousCrewMemberIndices.RemoveItem(unconsciousCrewMemberIndex); } break; case ECrewMemberState.DEAD: foreach (int deadCrewMemberIndex : m_DeadCrewMemberIndices) { if (deadCrewMemberIndex == DayZPlayerConstants.VEHICLESEAT_DRIVER) EngineStop(); m_DeadCrewMemberIndices.RemoveItem(deadCrewMemberIndex); } break; } } bool CheckOperationalState() { if (GetHealthLevel("") >= GameConstants.STATE_RUINED || GetHealthLevel("Engine") >= GameConstants.STATE_RUINED) return false; if (IsVitalSparkPlug()) { EntityAI item = FindAttachmentBySlotName("SparkPlug"); if (!item || (item && item.IsRuined())) return false; } return true; } // Server side event for jump out processing void OnVehicleJumpOutServer(GetOutTransportActionData data) { vector posMS = data.m_Player.WorldToModel(data.m_Player.GetPosition()); float healthCoef = Math.InverseLerp(ActionGetOutTransport.HEALTH_LOW_SPEED_VALUE, ActionGetOutTransport.HEALTH_HIGH_SPEED_VALUE, data.m_Speed) * 0.5; healthCoef = Math.Clamp(healthCoef, 0.0, 1.0); data.m_Player.ProcessDirectDamage(DamageType.CUSTOM, data.m_Player, "", "FallDamageHealth", posMS, healthCoef); } protected void HandleEngineSound(EBoatEngineSoundState state) { if (GetGame().IsDedicatedServer()) return; EffectSound sound = null; switch (state) { case EBoatEngineSoundState.START_OK: sound = SEffectManager.PlaySound("boat_01_engine_start_SoundSet", ModelToWorld(PropellerGetPosition())); sound.SetAttachmentParent(this); sound.SetAutodestroy(true); break; case EBoatEngineSoundState.STOP_OK: sound = SEffectManager.PlaySound("boat_01_engine_stop_SoundSet", ModelToWorld(PropellerGetPosition())); sound.SetAttachmentParent(this); sound.SetAutodestroy(true); break; case EBoatEngineSoundState.START_NO_FUEL: sound = SEffectManager.PlaySound("boat_01_engine_start_no_fuel_SoundSet", ModelToWorld(PropellerGetPosition())); sound.SetAttachmentParent(this); sound.SetAutodestroy(true); break; case EBoatEngineSoundState.STOP_NO_FUEL: sound = SEffectManager.PlaySound("boat_01_engine_stop_no_fuel_SoundSet", ModelToWorld(PropellerGetPosition())); sound.SetAttachmentParent(this); sound.SetAutodestroy(true); break; } } protected void PlaySound(string soundset, inout EffectSound sound) { #ifndef SERVER //Print(this.GetPosition().ToString() + " playing " + soundset + " using "+ sound); if (!sound) { sound = SEffectManager.PlaySoundCachedParams(soundset, GetPosition()); sound.SetAttachmentParent(this); sound.SetAutodestroy(true); // SoundWaveObjects tend to null themselves for unknown reasons, breaking the effect in the process } else { if (!sound.IsSoundPlaying()) { sound.SetCurrentPosition(GetPosition()); sound.SoundPlay(); } } #endif } protected void HandleBoatSplashSound() { float propPosRelative = GetGame().SurfaceGetSeaLevel() - CoordToParent(PropellerGetPosition())[1]; if (propPosRelative < SPLASH_THRESHOLD_CONDITION) m_SplashIncoming = true; if (m_SplashIncoming && propPosRelative > SPLASH_THRESHOLD) { m_SoundWaterSplashEffect = SEffectManager.CreateSound(m_SoundWaterSplash, GetPosition()); m_SoundWaterSplashEffect.SetAttachmentParent(this); m_SoundWaterSplashEffect.SetLocalPosition(PropellerGetPosition()); m_SoundWaterSplashEffect.SetAutodestroy(true); vector velocity = dBodyGetVelocityAt(this, CoordToParent(PropellerGetPosition())); float speed = velocity.Normalize() * 3.6; // to km/h float lerp = Math.InverseLerp(0, 30, speed); m_SoundWaterSplashEffect.SetSoundVolume(lerp); m_SoundWaterSplashEffect.Start(); m_SplashIncoming = false; } } protected void SyncSoundImpactLight() { if (!m_PlaySoundImpactLight) { m_PlaySoundImpactLight = true; SetSynchDirty(); } } protected void SyncSoundImpactHeavy() { if (!m_PlaySoundImpactHeavy) { m_PlaySoundImpactHeavy = true; SetSynchDirty(); } } void SyncSoundPushBoat(bool play) { if (m_PlaySoundPushBoat != play) { m_PlaySoundPushBoat = play; SetSynchDirty(); } } // Set engine sound to fade on start/stop protected void FadeEngineSound(bool fadeIn) { m_IsEngineSoundFading = true; m_EngineFadeDirection = fadeIn; m_EngineFadeTime = SOUND_ENGINE_FADE; } protected void CheckContactCache() { if (!m_ContactData) return; float impulse = Math.AbsInt(m_ContactData.m_Impulse); m_ContactData = null; if (impulse < GameConstants.CARS_CONTACT_DMG_MIN) return; else if (impulse < GameConstants.CARS_CONTACT_DMG_THRESHOLD) SyncSoundImpactLight(); else SyncSoundImpactHeavy(); } protected void SetActions() { AddAction(ActionGetInTransport); AddAction(ActionPushBoat); } void AddAction(typename actionName) { ActionBase action = ActionManagerBase.GetAction(actionName); if (!action) { Debug.LogError("Action " + actionName + " dosn't exist!"); return; } typename ai = action.GetInputType(); if (!ai) { m_ActionsInitialized = false; return; } array actionArray = m_InputActionMap.Get(ai); if (!actionArray) { actionArray = new array; m_InputActionMap.Insert(ai, actionArray); } if (LogManager.IsActionLogEnable()) { Debug.ActionLog(action.ToString() + " -> " + ai, this.ToString() , "n/a", "Add action" ); } actionArray.Insert(action); } void RemoveAction(typename actionName) { PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer()); ActionBase action = player.GetActionManager().GetAction(actionName); typename ai = action.GetInputType(); array actionArray = m_InputActionMap.Get(ai); if (actionArray) { actionArray.RemoveItem(action); } } protected void UpdateParticles() { for (int i; i < 4; i++) { m_WaterEffects[i].Update(); } } protected void StopParticleUpdate() { m_UpdateParticles = false; ClearWaterEffects(); } protected void ClearWaterEffects() { GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).Remove(StopParticleUpdate); for (int i; i < 4; i++) { if (m_WaterEffects[i].IsPlaying()) { if (m_WaterEffects[i].GetParticle()) { m_WaterEffects[i].GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE, 0); m_WaterEffects[i].GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE_RND, 0); } m_WaterEffects[i].Stop(); } } } // Called on destroy/stream out protected void CleanupEffects() { for (int i; i < 4; i++) { if (m_WaterEffects[i].IsRegistered()) { m_WaterEffects[i].Stop(); SEffectManager.EffectUnregisterEx(m_WaterEffects[i]); } } SEffectManager.DestroyEffect(m_SoundImpactLightEffect); SEffectManager.DestroyEffect(m_SoundImpactHeavyEffect); SEffectManager.DestroyEffect(m_SoundPushBoatEffect); SEffectManager.DestroyEffect(m_SoundWaterSplashEffect); } override void GetDebugActions(out TSelectableActionInfoArrayEx outputList) { super.GetDebugActions(outputList); outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED)); outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.DELETE, "Delete", FadeColors.RED)); } override bool OnAction(int action_id, Man player, ParamsReadContext ctx) { if (super.OnAction(action_id, player, ctx)) return true; if (!GetGame().IsServer()) { return false; } switch (action_id) { case EActions.DELETE: Delete(); return true; } return false; } protected override event typename GetOwnerStateType() { return BoatScriptOwnerState; } protected override event typename GetMoveType() { return BoatScriptMove; } #ifdef DIAG_DEVELOPER override void FixEntity() { super.FixEntity(); if (GetGame().IsServer()) Fill(BoatFluid.FUEL, GetFluidCapacity(BoatFluid.FUEL)); } #endif }