Tweaks
This commit is contained in:
parent
430e05e921
commit
7e968df4a6
11
Main.cs
11
Main.cs
@ -12,14 +12,17 @@ public class Main : Node2D
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_particleNodes = GetNode<Node2D>("ParticlesNodes");
|
||||
_particleNodes = GetNode<Node2D>("ParticleNodes");
|
||||
GD.Randomize();
|
||||
var randomSeed = GD.Randi();
|
||||
ulong randomSeed = GD.Randi();
|
||||
GD.Seed(randomSeed);
|
||||
GD.Print("Last Seed: " + randomSeed);
|
||||
var viewSize = GetViewportRect().Size;
|
||||
var zoom = GetNode<Camera2D>("Camera2D").Zoom;
|
||||
var spaceSize = new Vector2(viewSize.x * zoom.x, viewSize.y * zoom.y);
|
||||
_particleSimulation = new ParticleSimulation
|
||||
{
|
||||
SpaceSize = GetViewportRect().Size * 1.15f
|
||||
SpaceSize = spaceSize
|
||||
};
|
||||
_particleSimulation.Initialize();
|
||||
foreach (var id in _particleSimulation.LastParticlesAdded) CreateParticleNode(id);
|
||||
@ -51,7 +54,7 @@ public class Main : Node2D
|
||||
particleNode.CurrentSimulationPosition = simulationParticle.Position;
|
||||
particleNode.SetColor(simulationParticle.Type.Hue, simulationParticle.Health,
|
||||
Mathf.Clamp(simulationParticle.AverageSpeed / 1.5f, 1f, 1f));
|
||||
particleNode.ScreenWrappedLast = simulationParticle.ScreenWrappedLast;
|
||||
particleNode.WasTeleportedLast = simulationParticle.WasTeleportedLast;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
[node name="Main" type="Node2D"]
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="ParticlesNodes" type="Node2D" parent="."]
|
||||
[node name="ParticleNodes" type="Node2D" parent="."]
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
anchor_mode = 0
|
||||
current = true
|
||||
zoom = Vector2( 1.15, 1.15 )
|
||||
zoom = Vector2( 1.35, 1.35 )
|
||||
|
@ -9,7 +9,7 @@ public class ParticleNode : Node2D
|
||||
public Vector2 CurrentSimulationPosition = new Vector2();
|
||||
|
||||
public Vector2 LastSimulationPosition;
|
||||
public bool ScreenWrappedLast = false;
|
||||
public bool WasTeleportedLast = true;
|
||||
public int SimulationId;
|
||||
|
||||
public override void _Ready()
|
||||
@ -20,7 +20,7 @@ public class ParticleNode : Node2D
|
||||
|
||||
public override void _Process(float delta)
|
||||
{
|
||||
Position = ScreenWrappedLast == false
|
||||
Position = WasTeleportedLast == false
|
||||
? LastSimulationPosition.LinearInterpolate(CurrentSimulationPosition,
|
||||
GetParent<Node2D>().GetParent<Main>().PhysicsInterpolationFraction)
|
||||
: CurrentSimulationPosition;
|
||||
|
@ -1,7 +1,7 @@
|
||||
[gd_scene load_steps=3 format=2]
|
||||
|
||||
[ext_resource path="res://ParticleNode.cs" type="Script" id=1]
|
||||
[ext_resource path="res://textures/particle.png" type="Texture" id=2]
|
||||
[ext_resource path="res://textures/particle_noborder.png" type="Texture" id=2]
|
||||
|
||||
[node name="ParticleNode" type="Node2D"]
|
||||
script = ExtResource( 1 )
|
||||
|
@ -7,7 +7,7 @@ namespace Particles.ParticleSimulation
|
||||
private float _health = 1f;
|
||||
|
||||
public Vector2 Position = new Vector2();
|
||||
public bool ScreenWrappedLast = true;
|
||||
public bool WasTeleportedLast = true;
|
||||
public Vector2 Velocity = new Vector2();
|
||||
|
||||
public Particle(int id, ParticleType type)
|
||||
@ -33,5 +33,10 @@ namespace Particles.ParticleSimulation
|
||||
{
|
||||
AverageSpeed = 0.99f * AverageSpeed + 0.01f * speed;
|
||||
}
|
||||
|
||||
public void ResetAverageSpeed()
|
||||
{
|
||||
AverageSpeed = 1f;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
#define MULTITHREADED
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
#if MULTITHREADED
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace Particles.ParticleSimulation
|
||||
{
|
||||
public class ParticleSimulation
|
||||
{
|
||||
|
||||
// size of simulation space
|
||||
public Vector2 SpaceSize;
|
||||
|
||||
// dictionary of particles with particle Id being the key
|
||||
private readonly Dictionary<int, Particle> _particles = new Dictionary<int, Particle>();
|
||||
|
||||
private readonly List<ParticleType> _particleTypes = new List<ParticleType>();
|
||||
private readonly List<Task> _tasks = new List<Task>();
|
||||
|
||||
// task list if multi-threaded
|
||||
#if MULTITHREADED
|
||||
private readonly List<Task> _tasks = new List<Task>();
|
||||
#endif
|
||||
|
||||
// updated on every simulation update
|
||||
public List<int> LastParticlesAdded { get; private set; } = new List<int>();
|
||||
// ReSharper disable once CollectionNeverUpdated.Global
|
||||
public List<int> LastParticlesRemoved { get; private set; } = new List<int>();
|
||||
|
||||
// counts up for each particle added
|
||||
private int _idCount;
|
||||
|
||||
private const int MaxParticles = 1100;
|
||||
private const int MaxParticleTypes = 12;
|
||||
private const int MaxParticleTypes = 10;
|
||||
|
||||
private const float HealthDelta = 0.005f;
|
||||
private const float NegativeHealthMultiplier = 2f;
|
||||
private const float PositiveHealthMultiplier = 4f;
|
||||
@ -32,35 +44,41 @@ namespace Particles.ParticleSimulation
|
||||
{
|
||||
for (var i = 0; i < MaxParticleTypes; i++)
|
||||
CreateRandomParticleType();
|
||||
//for (var i = 0; i < MaxParticles; i++)
|
||||
// CreateRandomParticle();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
LastParticlesRemoved.Clear();
|
||||
LastParticlesAdded.Clear();
|
||||
_tasks.Clear();
|
||||
|
||||
// update all particles
|
||||
#if MULTITHREADED
|
||||
_tasks.Clear();
|
||||
foreach (var id in _particles.Keys)
|
||||
_tasks.Add(Task.Factory.StartNew(UpdateParticle, id));
|
||||
Task.WaitAll(_tasks.ToArray());
|
||||
#else
|
||||
foreach (var id in _particles.Keys)
|
||||
_tasks.Add(Task.Factory.StartNew(UpdateParticle, id));
|
||||
Task.WaitAll(_tasks.ToArray());
|
||||
var deletedParticle = false;
|
||||
foreach (var p in _particles)
|
||||
UpdateParticle(id);
|
||||
#endif
|
||||
|
||||
// used to ensure only one particle is moved per update
|
||||
var movedParticle = false;
|
||||
|
||||
foreach (var particle in _particles.Select(p => p.Value))
|
||||
{
|
||||
var particle = p.Value;
|
||||
particle.ScreenWrappedLast = false;
|
||||
if (deletedParticle == false && particle.Health == 0f)
|
||||
particle.WasTeleportedLast = false;
|
||||
if (movedParticle == false && particle.Health == 0f)
|
||||
{
|
||||
if (GD.Randf() < 0.2f)
|
||||
if (GD.Randf() < 0.1f)
|
||||
{
|
||||
particle.Position = GetRandomParticlePosition();
|
||||
particle.ScreenWrappedLast = true;
|
||||
//LastParticlesRemoved.Add(p.Key);
|
||||
deletedParticle = true;
|
||||
particle.WasTeleportedLast = true;
|
||||
particle.Health = 1f;
|
||||
movedParticle = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
var position = particle.Position;
|
||||
particle.Velocity = particle.Velocity.Clamped(5f);
|
||||
position += particle.Velocity;
|
||||
@ -68,52 +86,48 @@ namespace Particles.ParticleSimulation
|
||||
if (position.x > SpaceSize.x)
|
||||
{
|
||||
position.x -= SpaceSize.x;
|
||||
particle.ScreenWrappedLast = true;
|
||||
particle.WasTeleportedLast = true;
|
||||
}
|
||||
else if (position.x < 0)
|
||||
{
|
||||
position.x += SpaceSize.x;
|
||||
particle.ScreenWrappedLast = true;
|
||||
particle.WasTeleportedLast = true;
|
||||
}
|
||||
|
||||
if (position.y > SpaceSize.y)
|
||||
{
|
||||
position.y -= SpaceSize.y;
|
||||
particle.ScreenWrappedLast = true;
|
||||
particle.WasTeleportedLast = true;
|
||||
}
|
||||
else if (position.y < 0)
|
||||
{
|
||||
position.y += SpaceSize.y;
|
||||
particle.ScreenWrappedLast = true;
|
||||
particle.WasTeleportedLast = true;
|
||||
}
|
||||
|
||||
particle.AddAverageSpeedValue(particle.Position.DistanceTo(position));
|
||||
/*
|
||||
if (particle.AverageSpeed < 0.3f)
|
||||
particle.AddAverageSpeedValue(particle.Velocity.Length());
|
||||
|
||||
if (particle.AverageSpeed < 0.5f)
|
||||
particle.Health -= HealthDelta * NegativeHealthMultiplier;
|
||||
else
|
||||
particle.Health += HealthDelta;
|
||||
*/
|
||||
if (deletedParticle == false && particle.Health == 0f)
|
||||
particle.Health += HealthDelta * PositiveHealthMultiplier;
|
||||
|
||||
if (movedParticle == false && particle.Health == 0f)
|
||||
{
|
||||
if (GD.Randf() < 0.2f)
|
||||
if (GD.Randf() < 0.1f)
|
||||
{
|
||||
//LastParticlesRemoved.Add(p.Key);
|
||||
particle.Position = GetRandomParticlePosition();
|
||||
particle.ScreenWrappedLast = true;
|
||||
deletedParticle = true;
|
||||
particle.ResetAverageSpeed();
|
||||
particle.WasTeleportedLast = true;
|
||||
particle.Health = 1f;
|
||||
movedParticle = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
particle.Position = position;
|
||||
}
|
||||
|
||||
foreach (var id in LastParticlesRemoved)
|
||||
{
|
||||
_particles.Remove(id);
|
||||
}
|
||||
|
||||
// ReSharper disable once InvertIf
|
||||
if (_particles.Count < MaxParticles)
|
||||
{
|
||||
@ -132,6 +146,13 @@ namespace Particles.ParticleSimulation
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private void RemoveParticle(int id)
|
||||
{
|
||||
_particles.Remove(id);
|
||||
LastParticlesRemoved.Add(id);
|
||||
}
|
||||
|
||||
private void CreateRandomParticleType()
|
||||
{
|
||||
var type = new ParticleType()
|
||||
@ -192,7 +213,7 @@ namespace Particles.ParticleSimulation
|
||||
return _particles[id];
|
||||
}
|
||||
|
||||
private Task UpdateParticle(object i)
|
||||
private void UpdateParticle(object i)
|
||||
{
|
||||
var id = (int)i;
|
||||
var particle1 = _particles[id];
|
||||
@ -206,15 +227,17 @@ namespace Particles.ParticleSimulation
|
||||
var distanceSquared = particle1.Position.DistanceSquaredTo(position);
|
||||
if (distanceSquared > (55f * 55f))
|
||||
continue;
|
||||
var direction = particle1.Position.DirectionTo(position);
|
||||
|
||||
if (distanceSquared < (35f * 35f))
|
||||
closeCount++;
|
||||
|
||||
// collision force
|
||||
float distance;
|
||||
Vector2 direction;
|
||||
|
||||
if (distanceSquared < (ParticleCollisionRadius * ParticleCollisionRadius))
|
||||
{
|
||||
direction = particle1.Position.DirectionTo(position);
|
||||
distance = particle1.Position.DistanceTo(position);
|
||||
var collisionForce = 1f / (0.35f + Mathf.Pow(Mathf.E, -1.15f * (distance - 12f))) - 1f / 0.35f;
|
||||
particle1.Velocity += direction * collisionForce;
|
||||
@ -225,6 +248,7 @@ namespace Particles.ParticleSimulation
|
||||
if (props.Force != 0f && distanceSquared >= props.MinRadius * props.MinRadius &&
|
||||
distanceSquared <= props.MaxRadius * props.MaxRadius)
|
||||
{
|
||||
direction = particle1.Position.DirectionTo(position);
|
||||
distance = particle1.Position.DistanceTo(position);
|
||||
var mid = (props.MinRadius + props.MaxRadius) / 2f;
|
||||
float particleForce;
|
||||
@ -256,12 +280,10 @@ namespace Particles.ParticleSimulation
|
||||
}
|
||||
}
|
||||
|
||||
if (closeCount > 50)
|
||||
if (closeCount > 70)
|
||||
particle1.Health -= HealthDelta * NegativeHealthMultiplier;
|
||||
else
|
||||
particle1.Health += HealthDelta * PositiveHealthMultiplier;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
<Project Sdk="Godot.NET.Sdk/3.3.0">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<Configurations>Debug;ExportDebug;ExportRelease;Release</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -1,22 +1,19 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Particles", "Particles.csproj", "{E15A1F42-C497-4EDE-8944-84CB055B33CB}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Particles", "Particles.csproj", "{87802628-5B53-4FFD-8ACF-E7227985842E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.ExportRelease|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.ExportRelease|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E15A1F42-C497-4EDE-8944-84CB055B33CB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{87802628-5B53-4FFD-8ACF-E7227985842E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87802628-5B53-4FFD-8ACF-E7227985842E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87802628-5B53-4FFD-8ACF-E7227985842E}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{87802628-5B53-4FFD-8ACF-E7227985842E}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{87802628-5B53-4FFD-8ACF-E7227985842E}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{87802628-5B53-4FFD-8ACF-E7227985842E}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -1,2 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">INFO</s:String></wpf:ResourceDictionary>
|
7
default_env.tres
Normal file
7
default_env.tres
Normal file
@ -0,0 +1,7 @@
|
||||
[gd_resource type="Environment" load_steps=2 format=2]
|
||||
|
||||
[sub_resource type="ProceduralSky" id=1]
|
||||
|
||||
[resource]
|
||||
background_mode = 2
|
||||
background_sky = SubResource( 1 )
|
@ -13,26 +13,30 @@ config_version=4
|
||||
config/name="Particles"
|
||||
run/main_scene="res://Main.tscn"
|
||||
config/icon="res://icon.png"
|
||||
config/windows_native_icon="res://icon.ico"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/width=1920
|
||||
window/size/height=1080
|
||||
window/size/resizable=false
|
||||
window/size/borderless=true
|
||||
window/size/fullscreen=true
|
||||
|
||||
[input]
|
||||
|
||||
quit={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":16777217,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
reset={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":0,"physical_scancode":82,"unicode":0,"echo":false,"script":null)
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":82,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
quit={
|
||||
"deadzone": 0.5,
|
||||
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777217,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[mono]
|
||||
|
||||
profiler/enabled=true
|
||||
|
||||
[physics]
|
||||
|
||||
@ -45,3 +49,4 @@ quality/driver/driver_name="GLES2"
|
||||
vram_compression/import_etc=true
|
||||
vram_compression/import_etc2=false
|
||||
environment/default_clear_color=Color( 0, 0, 0, 1 )
|
||||
environment/default_environment="res://default_env.tres"
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
textures/particle_noborder.png
Normal file
BIN
textures/particle_noborder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
35
textures/particle_noborder.png.import
Normal file
35
textures/particle_noborder.png.import
Normal file
@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/particle_noborder.png-2c964d106064b04cd1632e2feec7d5d8.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/particle_noborder.png"
|
||||
dest_files=[ "res://.import/particle_noborder.png-2c964d106064b04cd1632e2feec7d5d8.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
BIN
textures/particle_old.png
Normal file
BIN
textures/particle_old.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
35
textures/particle_old.png.import
Normal file
35
textures/particle_old.png.import
Normal file
@ -0,0 +1,35 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/particle_old.png-70319fb8cbbe1fe7999a1d4ae575ed1d.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://textures/particle_old.png"
|
||||
dest_files=[ "res://.import/particle_old.png-70319fb8cbbe1fe7999a1d4ae575ed1d.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
process/normal_map_invert_y=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
Loading…
Reference in New Issue
Block a user