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 { }; #ifdef DIAG_DEVELOPER BoatScript _boat; #endif class BoatScript : Boat { protected const int DECAY_PLAYER_RANGE = 300; // when a player is found within this range, decay is deactivated protected const int DECAY_FLAG_RANGE = 100; // when a boat is parked around a territoryflag, decay is deactivated protected const float DECAY_TICK_FREQUENCY = 10; // seconds protected const float DECAY_DAMAGE = 0.017; protected const float DECAY_FLIPPED_MULT = 4; 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; protected ref Timer m_DecayTimer; // 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 string m_SoundEngineStart; protected string m_SoundEngineStop; protected string m_SoundEngineStartNoFuel; protected string m_SoundEngineStopNoFuel; // caches to avoid multiple triggers protected ref EffectSound m_SoundPushBoatEffect; protected ref EffectSound m_SoundWaterSplashEffect; protected ref EffectSound m_SoundEngineEffect; protected ref EffectSound m_SoundEngineEffectDeletion; // hold engine sound which is being stopped & deleted while another is starting void BoatScript() { #ifdef DIAG_DEVELOPER _boat = this; #endif 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"; m_SoundEngineStart = "boat_01_engine_start_SoundSet"; m_SoundEngineStop = "boat_01_engine_stop_SoundSet"; m_SoundEngineStartNoFuel = "boat_01_engine_start_no_fuel_SoundSet"; m_SoundEngineStopNoFuel = "boat_01_engine_stop_no_fuel_SoundSet"; if (GetGame().IsServer()) { m_DecayTimer = new Timer( CALL_CATEGORY_SYSTEM ); m_DecayTimer.Run(DECAY_TICK_FREQUENCY, this, "DecayHealthTick", NULL, true); } 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() { if (m_DecayTimer) m_DecayTimer.Stop(); #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 (IsVitalSparkPlug()) { EntityAI item = FindAttachmentBySlotName("SparkPlug"); if (!item || (item && item.IsRuined())) { HandleEngineSound(EBoatEngineSoundState.START_NO_FUEL); return false; } } 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 (!IsProxy()) { HandleByCrewMemberState(ECrewMemberState.UNCONSCIOUS); HandleByCrewMemberState(ECrewMemberState.DEAD); if (EngineIsOn()) { if (GetFluidFraction(BoatFluid.FUEL) <= 0) { if (GetGame().IsServer()) { m_PlaySoundEngineStopNoFuel = true; SetSynchDirty(); m_PlaySoundEngineStopNoFuel = false; } EngineStop(); } } } if (GetGame().IsServer()) { CheckContactCache(); m_VelocityPrevTick = GetVelocity(this); m_MomentumPrevTick = GetMomentum(); } if (!GetGame().IsDedicatedServer()) { if (EngineIsOn()) m_UpdateParticles = true; if (m_UpdateParticles) HandleBoatSplashSound(); } } override void EOnSimulate(IEntity other, float dt) { 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(timeSlice); 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 (!IsProxy()) { 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 (!IsProxy()) { if (EngineIsOn() && !CheckOperationalState()) EngineStop(); } } override void OnVariablesSynchronized() { super.OnVariablesSynchronized(); if (m_PlaySoundImpactHeavy) { EffectSound impactHeavy; PlaySound(m_SoundImpactHeavy, impactHeavy, GetPosition()); m_PlaySoundImpactHeavy = false; } if (m_PlaySoundImpactLight) { EffectSound impactLight; PlaySound(m_SoundImpactLight, impactLight, GetPosition()); m_PlaySoundImpactLight = false; } if (m_PlaySoundPushBoat) PlaySound(m_SoundPushBoat, m_SoundPushBoatEffect, GetPosition()); else if (m_SoundPushBoatEffect && m_SoundPushBoatEffect.IsPlaying()) m_SoundPushBoatEffect.Stop(); if (m_PlaySoundEngineStopNoFuel) { HandleEngineSound(EBoatEngineSoundState.STOP_NO_FUEL); m_PlaySoundEngineStopNoFuel = false; } } protected override bool DetectFlipped(VehicleFlippedContext ctx) { if (!DetectFlippedUsingSurface(ctx, GameConstants.VEHICLE_FLIP_ANGLE_TOLERANCE)) return false; return true; } 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; } } protected void DecayHealthTick() { if ( (GetCEApi() && !GetCEApi().AvoidPlayer(GetPosition(), DECAY_PLAYER_RANGE)) || CfgGameplayHandler.GetBoatDecayMultiplier() <= 0 ) return; float damage = DECAY_DAMAGE * CfgGameplayHandler.GetBoatDecayMultiplier(); if (IsFlipped()) damage *= DECAY_FLIPPED_MULT; if (IsInFlagRange()) damage *= 0.3; AddHealth("Engine","Health", -damage); } protected bool IsInFlagRange() { array locations = GetGame().GetMission().GetActiveRefresherLocations(); if (!locations) return false; int count = locations.Count(); if (count > 0) { vector pos = GetWorldPosition(); for (int i = 0; i < count; i++) { if (vector.Distance(pos, locations.Get(i)) < DECAY_FLAG_RANGE) return true; } return false; } else return false; } 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; string soundset; switch (state) { case EBoatEngineSoundState.START_OK: soundset = m_SoundEngineStart; break; case EBoatEngineSoundState.STOP_OK: soundset = m_SoundEngineStop; break; case EBoatEngineSoundState.START_NO_FUEL: soundset = m_SoundEngineStartNoFuel; break; case EBoatEngineSoundState.STOP_NO_FUEL: soundset = m_SoundEngineStopNoFuel; break; } // already playing same type of sound if (m_SoundEngineEffect) { if (m_SoundEngineEffect.GetSoundSet() == soundset) // already playing same type of sound return; else { m_SoundEngineEffectDeletion = m_SoundEngineEffect; m_SoundEngineEffectDeletion.SoundStop(); // stop the existing sound m_SoundEngineEffect = null; } } PlaySound(soundset, m_SoundEngineEffect, ModelToWorld(PropellerGetPosition())); } protected void PlaySound(string soundset, inout EffectSound sound, vector position = vector.Zero) { #ifndef SERVER if (position == vector.Zero) position = GetPosition(); if (!sound) { sound = SEffectManager.PlaySoundCachedParams(soundset, position); 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(position); 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 FlipVehicle() { vector orient = GetOrientation(); orient[2] = orient[2] + 180; SetOrientation(orient); vector pos = GetPosition(); pos[1] = pos[1] + 0.5; SetPosition(pos); } 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(float timeSlice = 0) { for (int i; i < 4; i++) { m_WaterEffects[i].Update(timeSlice); } } 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_SoundPushBoatEffect); SEffectManager.DestroyEffect(m_SoundWaterSplashEffect); } override void GetDebugActions(out TSelectableActionInfoArrayEx outputList) { outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.FLIP_ENTITY, "Flip vehicle", FadeColors.LIGHT_GREY)); 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; case EActions.FLIP_ENTITY: FlipVehicle(); 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 }