diff --git a/GamecraftModdingAPI/Block.cs b/GamecraftModdingAPI/Block.cs
index 862db3f..33a1623 100644
--- a/GamecraftModdingAPI/Block.cs
+++ b/GamecraftModdingAPI/Block.cs
@@ -240,6 +240,8 @@ namespace GamecraftModdingAPI
set
{
MovementEngine.MoveBlock(Id, InitData, value);
+ if (blockGroup != null)
+ blockGroup.PosAndRotCalculated = false;
}
}
@@ -252,6 +254,8 @@ namespace GamecraftModdingAPI
set
{
RotationEngine.RotateBlock(Id, InitData, value);
+ if (blockGroup != null)
+ blockGroup.PosAndRotCalculated = false;
}
}
@@ -354,15 +358,31 @@ namespace GamecraftModdingAPI
}
}
+ private BlockGroup blockGroup;
///
/// Returns the block group this block is a part of. Block groups can be placed using blueprints.
- /// Returns null if not part of a group.
+ /// Returns null if not part of a group.
+ /// Setting the group after the block has been initialized will not update everything properly.
+ /// You should only set this property on blocks newly placed by your code.
///
public BlockGroup BlockGroup
{
- get => BlockEngine.GetBlockInfo(this,
- (BlockGroupEntityComponent bgec) =>
- bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this));
+ get
+ {
+ if (blockGroup != null) return blockGroup;
+ return blockGroup = BlockEngine.GetBlockInfo(this,
+ (BlockGroupEntityComponent bgec) =>
+ bgec.currentBlockGroup == -1 ? null : new BlockGroup(bgec.currentBlockGroup, this));
+ }
+ set
+ {
+ blockGroup?.RemoveInternal(this);
+ BlockEngine.SetBlockInfo(this,
+ (ref BlockGroupEntityComponent bgec, BlockGroup val) => bgec.currentBlockGroup = val?.Id ?? -1,
+ value);
+ value?.AddInternal(this);
+ blockGroup = value;
+ }
}
///
diff --git a/GamecraftModdingAPI/BlockGroup.cs b/GamecraftModdingAPI/BlockGroup.cs
index 8d9e761..d660236 100644
--- a/GamecraftModdingAPI/BlockGroup.cs
+++ b/GamecraftModdingAPI/BlockGroup.cs
@@ -1,4 +1,8 @@
-using Gamecraft.Blocks.BlockGroups;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using Gamecraft.Blocks.BlockGroups;
using Unity.Mathematics;
using UnityEngine;
@@ -10,11 +14,14 @@ namespace GamecraftModdingAPI
///
/// A group of blocks that can be selected together. The placed version of blueprints.
///
- public class BlockGroup
+ public class BlockGroup : ICollection
{
internal static BlueprintEngine _engine = new BlueprintEngine();
public int Id { get; }
private readonly Block sourceBlock;
+ private readonly List blocks;
+ private float3 position, rotation;
+ internal bool PosAndRotCalculated;
internal BlockGroup(int id, Block block)
{
@@ -22,41 +29,155 @@ namespace GamecraftModdingAPI
throw new BlockException("Cannot create a block group for blocks without a group!");
Id = id;
sourceBlock = block;
+ blocks = new List(GetBlocks());
}
///
- /// The position of the block group. Calculated when GetBlocks() is used.
+ /// The position of the block group (center). Recalculated if blocks have been added/removed since the last query.
///
- public float3 Position { get; private set; }
-
+ public float3 Position
+ {
+ get
+ {
+ if (!PosAndRotCalculated)
+ Refresh();
+ return position;
+ }
+ set
+ {
+ var diff = value - position;
+ foreach (var block in blocks)
+ block.Position += diff;
+ if (!PosAndRotCalculated) //The condition can only be true if a block has been added/removed manually
+ Refresh(); //So the blocks array is up to date
+ else
+ position += diff;
+ }
+ }
+
+ ///
+ /// The rotation of the block group. Recalculated if blocks have been added/removed since the last query.
+ ///
+ public float3 Rotation
+ {
+ get
+ {
+ if (!PosAndRotCalculated)
+ Refresh();
+ return rotation;
+ }
+ set
+ {
+ var diff = value - rotation;
+ var qdiff = Quaternion.Euler(diff);
+ foreach (var block in blocks)
+ {
+ block.Rotation += diff;
+ block.Position = qdiff * block.Position;
+ }
+ if (!PosAndRotCalculated)
+ Refresh();
+ else
+ rotation += diff;
+ }
+ }
+
+ /*///
+ /// Removes all of the blocks in this group from the world.
+ ///
+ public void RemoveBlocks()
+ {
+ _engine.RemoveBlockGroup(Id); - TODO: Causes a hard crash
+ }*/
+
///
- /// The rotation of the block group. Calculated when GetBlocks() is used.
+ /// Creates a new block group consisting of a single block.
+ /// You can add more blocks using the Add() method or by setting the BlockGroup property of the blocks.
+ /// Note that only newly placed blocks should be added to groups.
///
- public float3 Rotation { get; private set; }
+ /// The block to add
+ /// A new block group containing the given block
+ public static BlockGroup Create(Block block)
+ {
+ return new BlockGroup(_engine.CreateBlockGroup(default, default), block);
+ }
///
/// Collects each block that is a part of this group. Also sets the position and rotation.
///
/// An array of blocks
- public Block[] GetBlocks()
+ private Block[] GetBlocks()
{
+ if (!sourceBlock.Exists) return new[] {sourceBlock}; //The block must exist to get the others
var ret = _engine.GetBlocksFromGroup(sourceBlock.Id, out var pos, out var rot);
- Position = pos;
- Rotation = ((Quaternion) rot).eulerAngles;
+ position = pos;
+ rotation = ((Quaternion) rot).eulerAngles;
+ PosAndRotCalculated = true;
return ret;
}
+ private void Refresh()
+ {
+ blocks.Clear();
+ blocks.AddRange(GetBlocks());
+ }
+
+ internal static void Init()
+ {
+ GameEngineManager.AddGameEngine(_engine);
+ }
+
+ public IEnumerator GetEnumerator() => blocks.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => blocks.GetEnumerator();
+
///
- /// Removes all of the blocks in this group from the world.
+ /// Adds a block to the group. You should only add newly placed blocks
+ /// so that the game initializes the group membership properly.
///
- public void Remove()
+ ///
+ ///
+ public void Add(Block item)
{
- _engine.RemoveBlockGroup(Id);
+ if (item == null) throw new NullReferenceException("Cannot add null to a block group");
+ item.BlockGroup = this; //Calls AddInternal
}
- public static void Init()
+ internal void AddInternal(Block item) => blocks.Add(item);
+
+ ///
+ /// Removes all blocks from this group.
+ /// You should not remove blocks that have been initialized, only those that you placed recently.
+ ///
+ public void Clear()
{
- GameEngineManager.AddGameEngine(_engine);
+ while (blocks.Count > 0)
+ Remove(blocks[blocks.Count - 1]);
}
+
+ public bool Contains(Block item) => blocks.Contains(item);
+ public void CopyTo(Block[] array, int arrayIndex) => blocks.CopyTo(array, arrayIndex);
+
+ ///
+ /// Removes a block from this group.
+ /// You should not remove blocks that have been initialized, only those that you placed recently.
+ ///
+ ///
+ ///
+ ///
+ public bool Remove(Block item)
+ {
+ if (item == null) throw new NullReferenceException("Cannot remove null from a block group");
+ bool ret = item.BlockGroup == this;
+ if (ret)
+ item.BlockGroup = null; //Calls RemoveInternal
+ return ret;
+ }
+
+ internal void RemoveInternal(Block item) => blocks.Remove(item);
+
+ public int Count => blocks.Count;
+ public bool IsReadOnly { get; } = false;
+
+ public Block this[int index] => blocks[index]; //Setting is not supported, since the order doesn't matter
}
}
\ No newline at end of file
diff --git a/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs b/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs
index 1e5ce21..d1c2639 100644
--- a/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs
+++ b/GamecraftModdingAPI/Blocks/BlockEventsEngine.cs
@@ -5,10 +5,11 @@ using Svelto.ECS;
using GamecraftModdingAPI.Engines;
using GamecraftModdingAPI.Utility;
+using RobocraftX.Blocks;
namespace GamecraftModdingAPI.Blocks
{
- public class BlockEventsEngine : IReactionaryEngine
+ public class BlockEventsEngine : IReactionaryEngine
{
public event EventHandler Placed;
public event EventHandler Removed;
@@ -27,20 +28,20 @@ namespace GamecraftModdingAPI.Blocks
public bool isRemovable { get; } = false;
private bool shouldAddRemove;
- public void Add(ref DBEntityStruct entityComponent, EGID egid)
+ public void Add(ref BlockTagEntityStruct entityComponent, EGID egid)
{
if (!(shouldAddRemove = !shouldAddRemove))
return;
ExceptionUtil.InvokeEvent(Placed, this,
- new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
+ new BlockPlacedRemovedEventArgs {ID = egid});
}
- public void Remove(ref DBEntityStruct entityComponent, EGID egid)
+ public void Remove(ref BlockTagEntityStruct entityComponent, EGID egid)
{
if (!(shouldAddRemove = !shouldAddRemove))
return;
ExceptionUtil.InvokeEvent(Removed, this,
- new BlockPlacedRemovedEventArgs {ID = egid, Type = (BlockIDs) entityComponent.DBID});
+ new BlockPlacedRemovedEventArgs {ID = egid});
}
}
diff --git a/GamecraftModdingAPI/Blocks/BlueprintEngine.cs b/GamecraftModdingAPI/Blocks/BlueprintEngine.cs
index 4104ae6..e46ad50 100644
--- a/GamecraftModdingAPI/Blocks/BlueprintEngine.cs
+++ b/GamecraftModdingAPI/Blocks/BlueprintEngine.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Reflection;
using Gamecraft.Blocks.BlockGroups;
using Gamecraft.GUI.Blueprints;
@@ -21,7 +22,7 @@ using Allocator = Svelto.Common.Allocator;
namespace GamecraftModdingAPI.Blocks
{
- public class BlueprintEngine : IApiEngine
+ public class BlueprintEngine : IFactoryEngine
{
private readonly MethodInfo getBlocksFromGroup =
AccessTools.Method("RobocraftX.CR.MachineEditing.PlaceBlockUtility:GetBlocksSharingBlockgroup");
@@ -77,12 +78,29 @@ namespace GamecraftModdingAPI.Blocks
connectionFactory, default).Complete();
}
- public void SelectBlueprint(uint resourceID)
+ public int CreateBlockGroup(float3 position, quaternion rotation)
{
- BlueprintUtil.SelectBlueprint(null, new BlueprintInventoryItemEntityStruct
+ int nextFilterId = BlockGroupUtility.NextFilterId;
+ Factory.BuildEntity((uint) nextFilterId,
+ BlockGroupExclusiveGroups.BlockGroupEntityGroup).Init(new BlockGroupTransformEntityComponent
{
- blueprintResourceId = resourceID,
+ blockGroupGridRotation = rotation,
+ blockGroupGridPosition = position
});
+ return nextFilterId;
+ }
+
+ public void SelectBlueprint(uint resourceID)
+ {
+ if (resourceID == uint.MaxValue)
+ BlueprintUtil.UnselectBlueprint(entitiesDB);
+ else
+ {
+ BlueprintUtil.SelectBlueprint(entitiesDB, new BlueprintInventoryItemEntityStruct
+ {
+ blueprintResourceId = resourceID,
+ });
+ }
}
public uint CreateBlueprint()
@@ -91,28 +109,27 @@ namespace GamecraftModdingAPI.Blocks
return index;
}
- public void ReplaceBlueprint(uint playerID, uint blueprintID, Block[] selected, float3 pos, quaternion rot)
+ public void ReplaceBlueprint(uint playerID, uint blueprintID, ICollection selected, float3 pos, quaternion rot)
{
- var blockIDs = new EGID[selected.Length];
- for (var i = 0; i < selected.Length; i++)
+ var blockIDs = new EGID[selected.Count];
+ using (var enumerator = selected.GetEnumerator())
{
- var block = selected[i];
- blockIDs[i] = block.Id;
+ for (var i = 0; enumerator.MoveNext(); i++)
+ {
+ var block = enumerator.Current;
+ blockIDs[i] = block.Id;
+ }
}
var serializationData = clipboardManager.GetSerializationData(blueprintID);
SelectionSerializationUtility.ClearClipboard(playerID, entitiesDB, entityFunctions, serializationData.blueprintData);
- if (selected.Length == 0)
+ if (selected.Count == 0)
return;
//ref BlockGroupTransformEntityComponent groupTransform = ref EntityNativeDBExtensions.QueryEntity(entitiesDb, (uint) local1.currentBlockGroup, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
//ref ColliderAabb collider = ref EntityNativeDBExtensions.QueryEntity(entitiesDB, (uint) groupID, BlockGroupExclusiveGroups.BlockGroupEntityGroup);
//float3 bottomOffset = PlaceBlockUtility.GetBottomOffset(collider);
//var rootPosition = math.mul(groupTransform.blockGroupGridRotation, bottomOffset) + groupTransform.blockGroupGridPosition;
//var rootRotation = groupTransform.blockGroupGridRotation;
- if (math.all(pos == default))
- pos = selected[0].Position;
- if (math.all(rot.value == default))
- rot = Quaternion.Euler(selected[0].Rotation);
clipboardManager.SetGhostSerialized(blueprintID, false);
SelectionSerializationUtility.CopySelectionToClipboard(playerID, entitiesDB,
@@ -189,7 +206,7 @@ namespace GamecraftModdingAPI.Blocks
}
public string Name { get; } = "GamecraftModdingAPIBlueprintGameEngine";
- public bool isRemovable { get; }
+ public bool isRemovable { get; } = false;
[HarmonyPatch]
private static class RemoveEnginePatch
@@ -225,5 +242,7 @@ namespace GamecraftModdingAPI.Blocks
return AccessTools.GetDeclaredConstructors(AccessTools.TypeByName("RobocraftX.CR.MachineEditing.SelectBlockEngine"))[0];
}
}
+
+ public IEntityFactory Factory { get; set; }
}
}
\ No newline at end of file
diff --git a/GamecraftModdingAPI/Blocks/PlacementEngine.cs b/GamecraftModdingAPI/Blocks/PlacementEngine.cs
index cf9a80f..5357e7f 100644
--- a/GamecraftModdingAPI/Blocks/PlacementEngine.cs
+++ b/GamecraftModdingAPI/Blocks/PlacementEngine.cs
@@ -53,15 +53,15 @@ namespace GamecraftModdingAPI.Blocks
private EntityComponentInitializer BuildBlock(ushort block, byte color, float3 position, int uscale, float3 scale, float3 rot, uint playerId)
{
if (_blockEntityFactory == null)
- throw new Exception("The factory is null.");
+ throw new BlockException("The factory is null.");
if (uscale < 1)
- throw new Exception("Scale needs to be at least 1");
+ throw new BlockException("Scale needs to be at least 1");
if (scale.x < 4e-5) scale.x = uscale;
if (scale.y < 4e-5) scale.y = uscale;
if (scale.z < 4e-5) scale.z = uscale;
uint dbid = block;
- if (!PrefabsID.DBIDMAP.ContainsKey(dbid))
- throw new Exception("Block with ID " + dbid + " not found!");
+ if (!PrefabsID.HasPrefabRegistered(dbid, 0))
+ throw new BlockException("Block with ID " + dbid + " not found!");
//RobocraftX.CR.MachineEditing.PlaceBlockEngine
ScalingEntityStruct scaling = new ScalingEntityStruct {scale = scale};
Quaternion rotQ = Quaternion.Euler(rot);
diff --git a/GamecraftModdingAPI/Blueprint.cs b/GamecraftModdingAPI/Blueprint.cs
index 26876f6..458c56c 100644
--- a/GamecraftModdingAPI/Blueprint.cs
+++ b/GamecraftModdingAPI/Blueprint.cs
@@ -1,5 +1,6 @@
using System;
using Unity.Mathematics;
+using UnityEngine;
namespace GamecraftModdingAPI
{
@@ -35,16 +36,27 @@ namespace GamecraftModdingAPI
///
/// Set the blocks that the blueprint contains.
+ /// Use the BlockGroup overload for automatically calculated position and rotation.
///
/// The array of blocks to use
/// The anchor position of the blueprint
- /// The rotation of the blueprint
- public void SetStoredBlocks(Block[] blocks, float3 position = default, float3 rotation = default)
+ /// The base rotation of the blueprint
+ public void StoreBlocks(Block[] blocks, float3 position, float3 rotation)
{
BlockGroup._engine.ReplaceBlueprint(Player.LocalPlayer.Id, Id, blocks, position,
quaternion.Euler(rotation));
}
+ ///
+ /// Store the blocks from the given group in the blueprint with correct position and rotation for the blueprint.
+ ///
+ /// The block group to store
+ public void StoreBlocks(BlockGroup group)
+ {
+ BlockGroup._engine.ReplaceBlueprint(Player.LocalPlayer.Id, Id, group, group.Position,
+ Quaternion.Euler(group.Rotation));
+ }
+
///
/// Places the blocks the blueprint contains at the specified position and rotation.
///
diff --git a/GamecraftModdingAPI/Player.cs b/GamecraftModdingAPI/Player.cs
index a4b7064..14892a3 100644
--- a/GamecraftModdingAPI/Player.cs
+++ b/GamecraftModdingAPI/Player.cs
@@ -1,5 +1,5 @@
using System;
-
+using Gamecraft.GUI.Blueprints;
using Unity.Mathematics;
using RobocraftX.Common;
using RobocraftX.Common.Players;
@@ -343,6 +343,17 @@ namespace GamecraftModdingAPI
}
}
+ ///
+ /// The player's selected blueprint in their hand. Set to null to clear. Dispose after usage.
+ ///
+ public Blueprint SelectedBlueprint
+ {
+ get => playerEngine.GetPlayerStruct(Id, out BlueprintInventoryItemEntityStruct biies)
+ ? new Blueprint(biies.blueprintResourceId)
+ : null;
+ set => BlockGroup._engine.SelectBlueprint(value?.Id ?? uint.MaxValue);
+ }
+
// object methods
///
diff --git a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
index f0334aa..fb806ea 100644
--- a/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
+++ b/GamecraftModdingAPI/Tests/GamecraftModdingAPIPluginTest.cs
@@ -1,24 +1,29 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
+using System.Reflection.Emit;
using System.Text;
using HarmonyLib;
using IllusionInjector;
// test
+using GPUInstancer;
using Svelto.ECS;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.SimulationModeState;
using RobocraftX.FrontEnd;
using Unity.Mathematics;
+using UnityEngine;
using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Events;
using GamecraftModdingAPI.Utility;
using GamecraftModdingAPI.Blocks;
using GamecraftModdingAPI.Players;
+using EventType = GamecraftModdingAPI.Events.EventType;
namespace GamecraftModdingAPI.Tests
{
@@ -269,6 +274,20 @@ namespace GamecraftModdingAPI.Tests
Logging.CommandLog("Health set to: " + val);
}).Build();
+ CommandBuilder.Builder("placeBlockGroup", "Places some blocks in a group")
+ .Action((float x, float y, float z) =>
+ {
+ var pos = new float3(x, y, z);
+ var group = BlockGroup.Create(Block.PlaceNew(BlockIDs.AluminiumCube, pos,
+ color: BlockColors.Aqua));
+ Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Blue)
+ .BlockGroup = group;
+ Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Green)
+ .BlockGroup = group;
+ Block.PlaceNew(BlockIDs.AluminiumCube, pos += new float3(1, 0, 0), color: BlockColors.Lime)
+ .BlockGroup = group;
+ }).Build();
+
GameClient.SetDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);
@@ -391,6 +410,88 @@ namespace GamecraftModdingAPI.Tests
return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
}
}
+
+ [HarmonyPatch]
+ public class BugHuntPatch
+ {
+ public static MethodInfo method =
+ SymbolExtensions.GetMethodInfo(str => Console.WriteLine(str));
+
+ public static IEnumerable Transpiler(IEnumerable instructions)
+ {
+ int i = 0;
+ foreach (var instruction in instructions)
+ {
+ i++;
+ yield return instruction; //Return the instruction first
+ //stloc, dup, callvirt
+ if (instruction.opcode.Name.ToLower().StartsWith("stloc")
+ || instruction.opcode == OpCodes.Dup
+ || instruction.opcode == OpCodes.Callvirt)
+ {
+ yield return new CodeInstruction(OpCodes.Ldstr,
+ "Just ran the " + i + ". instruction ending with " + instruction.opcode.Name);
+ yield return new CodeInstruction(OpCodes.Call, method);
+ }
+ }
+ }
+
+ public static MethodInfo TargetMethod()
+ {
+ return AccessTools.Method("RobocraftX.CR.MachineEditing.BoxSelect.CopySelectionEngine:GenerateThumbnail");
+ }
+ }
+
+ [HarmonyPatch]
+ public class BugHuntPatch2
+ {
+ public static void Prefix(int width, float fieldOfView, Vector3 cameraDirection, Vector3 lightDirection)
+ {
+ Console.WriteLine("TakeThumbnail invoked with parameters: " + width + ", " + fieldOfView + ", " +
+ cameraDirection + ", " + lightDirection);
+
+ GPUInstancerManager manager = GPUInstancerAPI.GetActiveManagers().Find(m => m is GPUInstancerPrefabManager);
+ Bounds instancesBounds = manager.ComputeInstancesBounds(2);
+ Console.WriteLine("Bounds: " + instancesBounds);
+ Console.WriteLine("Size: " + instancesBounds.size);
+ Console.WriteLine("Size.x < 0: " + (instancesBounds.size.x < 0));
+ }
+
+ public static void Postfix(Texture2D __result)
+ {
+ Console.WriteLine("TakeThumbnail returned: " + (__result == null ? null : __result.name));
+ }
+
+ private delegate Texture2D TakeThumbnailDel(int width, float fieldOfView, Vector3 cameraDirection,
+ Vector3 lightDirection);
+
+ public static MethodInfo TargetMethod()
+ {
+ return ((TakeThumbnailDel) ThumbnailUtility.TakeThumbnail).Method;
+ }
+ }
+
+ [HarmonyPatch]
+ public class BugHuntPatch3
+ {
+ public static void Prefix(int width, int filterLayerMask, GPUInstancerManager manager,
+ Vector3 cameraPosition, Quaternion cameraRotation, float cameraFov, Vector3 lightDirection,
+ int cullingLayer)
+ {
+ Console.WriteLine("Inner TakeThumbnail invoked with parameters: " + width + ", " + filterLayerMask +
+ ", " + (manager != null ? manager.name : null) + ", " + cameraPosition + ", " +
+ cameraRotation + ", " + cameraFov + ", " + lightDirection + ", " + cullingLayer);
+ }
+
+ private delegate Texture2D TakeThumbnailDel(int width, int filterLayerMask, GPUInstancerManager manager,
+ Vector3 cameraPosition, Quaternion cameraRotation, float cameraFov, Vector3 lightDirection,
+ int cullingLayer);
+
+ public static MethodInfo TargetMethod()
+ {
+ return ((TakeThumbnailDel) ThumbnailUtility.TakeThumbnail).Method;
+ }
+ }
}
#endif
}