BoatScript.c
class BoatScriptOwnerStateclass BoatScriptMoveclass BoatScriptenum EBoatEffectsenum EBoatOperationalStateenum EBoatEngineSoundState
enum EBoatEffects
{
PTC_FRONT = 0,
PTC_BACK,
PTC_SIDE_L,
PTC_SIDE_R
}
enum EBoatOperationalState
{
OK = 0,
RUINED = 1,
NO_FUEL = 2,
NO_IGNITER = 4,
}
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
/*!
Base script class for boats
*/
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<typename, ref TInputActionMap> m_BoatTypeActionsMap = new map<typename, ref TInputActionMap>();
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 (g_Game.IsServer())
{
m_DecayTimer = new Timer( CALL_CATEGORY_SYSTEM );
m_DecayTimer.Run(DECAY_TICK_FREQUENCY, this, "DecayHealthTick", NULL, true);
}
if (g_Game.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 (!g_Game.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<ActionBase_Basic> 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 (g_Game.GetPlayer() && (!g_Game.GetPlayer().GetCommand_Vehicle() || g_Game.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);
}
/*!
Gets called everytime the game wants to start the engine.
\return true if the engine can start, false otherwise.
*/
override bool OnBeforeEngineStart()
{
EBoatOperationalState state = CheckOperationalRequirements();
return state == EBoatOperationalState.OK;
}
// Whether the car engine can be started
int CheckOperationalRequirements()
{
int state = EBoatOperationalState.OK;
EntityAI item = null;
if (GetHealthLevel("") >= GameConstants.STATE_RUINED || GetHealthLevel("Engine") >= GameConstants.STATE_RUINED)
{
state |= EBoatOperationalState.RUINED;
}
if (GetFluidFraction(BoatFluid.FUEL) <= 0)
{
state |= EBoatOperationalState.NO_FUEL;
}
if (IsVitalSparkPlug())
{
item = FindAttachmentBySlotName("SparkPlug");
if (!item || (item && item.IsRuined()))
state |= EBoatOperationalState.NO_IGNITER;
}
return state;
}
void OnIgnition()
{
EBoatOperationalState state = CheckOperationalRequirements();
if (state == EBoatOperationalState.RUINED)
{
return;
}
if (state & EBoatOperationalState.NO_IGNITER)
{
HandleEngineSound(EBoatEngineSoundState.START_NO_FUEL);
return;
}
if (state & EBoatOperationalState.NO_FUEL)
{
HandleEngineSound(EBoatEngineSoundState.START_NO_FUEL);
return;
}
}
override void OnEngineStart()
{
super.OnEngineStart();
if (g_Game.IsDedicatedServer())
return;
FadeEngineSound(true);
HandleEngineSound(EBoatEngineSoundState.START_OK);
ClearWaterEffects(); // in case they are still active
}
override void OnEngineStop()
{
super.OnEngineStop();
if (g_Game.IsDedicatedServer())
return;
FadeEngineSound(false);
HandleEngineSound(EBoatEngineSoundState.STOP_OK);
vector mat[4];
dBodyGetWorldTransform(this, mat);
vector pos = mat[3] + VectorToParent(PropellerGetPosition());
if (g_Game.GetWaterDepth(pos) < 0) // stop instantly
StopParticleUpdate();
else
g_Game.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 OnInput(float dt)
{
super.OnInput(dt);
}
override void EOnPostSimulate(IEntity other, float timeSlice)
{
if (!IsProxy())
{
if (EngineIsOn())
{
if (GetFluidFraction(BoatFluid.FUEL) <= 0)
{
if (g_Game.IsServer())
{
m_PlaySoundEngineStopNoFuel = true;
SetSynchDirty();
m_PlaySoundEngineStopNoFuel = false;
}
EngineStop();
}
}
}
if (g_Game.IsServer())
{
CheckContactCache();
m_VelocityPrevTick = GetVelocity(this);
m_MomentumPrevTick = GetMomentum();
}
if (!g_Game.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 (g_Game.GetWaterDepth(pos) < -0.2)
EngineStop();
}
}
}
override void EOnFrame(IEntity other, float timeSlice)
{
if (!g_Game.IsDedicatedServer())
{
if (m_UpdateParticles)
UpdateParticles(timeSlice);
if (m_IsEngineSoundFading)
{
m_EngineFadeTime -= timeSlice;
if (m_EngineFadeTime < 0)
m_IsEngineSoundFading = false;
}
}
}
//! WARNING: Can be called very frequently in one frame, use with caution
override void OnContact(string zoneName, vector localPos, IEntity other, Contact data)
{
if (g_Game.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(localPos, 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 MarkCrewMemberUnconscious(int crewMemberIndex)
{
if (!IsAuthority())
return;
if (crewMemberIndex == DayZPlayerConstants.VEHICLESEAT_DRIVER)
{
EngineStop();
}
}
override void MarkCrewMemberDead(int crewMemberIndex)
{
if (!IsAuthority())
return;
if (crewMemberIndex == DayZPlayerConstants.VEHICLESEAT_DRIVER)
{
EngineStop();
}
}
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<vector> locations = g_Game.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;
}
override void OnDriverExit(Human player)
{
super.OnDriverExit(player);
if (GetGear() != GetNeutralGear())
{
EngineStop();
}
}
// 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 (g_Game.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 = g_Game.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<ActionBase_Basic> actionArray = m_InputActionMap.Get(ai);
if (!actionArray)
{
actionArray = new array<ActionBase_Basic>;
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(g_Game.GetPlayer());
ActionBase action = player.GetActionManager().GetAction(actionName);
typename ai = action.GetInputType();
array<ActionBase_Basic> 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()
{
g_Game.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);
}
override bool OnAction(int action_id, Man player, ParamsReadContext ctx)
{
if (super.OnAction(action_id, player, ctx))
return true;
if (!g_Game.IsServer())
{
return false;
}
switch (action_id)
{
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 (g_Game.IsServer())
Fill(BoatFluid.FUEL, GetFluidCapacity(BoatFluid.FUEL));
}
#endif
}