mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
Support for Atlas with spine-starling in addition to Starling's texture atlas.
This commit is contained in:
parent
51a0876561
commit
725fd49c77
@ -87,7 +87,7 @@ public class Atlas {
|
|||||||
else if (direction == "xy")
|
else if (direction == "xy")
|
||||||
page.uWrap = page.vWrap = TextureWrap.repeat;
|
page.uWrap = page.vWrap = TextureWrap.repeat;
|
||||||
|
|
||||||
textureLoader.load(page, line);
|
textureLoader.loadPage(page, line);
|
||||||
|
|
||||||
pages.push(page);
|
pages.push(page);
|
||||||
|
|
||||||
@ -139,6 +139,7 @@ public class Atlas {
|
|||||||
|
|
||||||
region.index = parseInt(reader.readValue());
|
region.index = parseInt(reader.readValue());
|
||||||
|
|
||||||
|
textureLoader.loadRegion(region);
|
||||||
regions.push(region);
|
regions.push(region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +157,7 @@ public class Atlas {
|
|||||||
|
|
||||||
public function dispose () : void {
|
public function dispose () : void {
|
||||||
for (var i:int = 0, n:int = pages.length; i < n; i++)
|
for (var i:int = 0, n:int = pages.length; i < n; i++)
|
||||||
textureLoader.unload(pages[i].rendererObject);
|
textureLoader.unloadPage(pages[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@ public class AtlasRegion {
|
|||||||
public var rotate:Boolean;
|
public var rotate:Boolean;
|
||||||
public var splits:Vector.<int>;
|
public var splits:Vector.<int>;
|
||||||
public var pads:Vector.<int>;
|
public var pads:Vector.<int>;
|
||||||
|
public var rendererObject:Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,8 +34,9 @@
|
|||||||
package spine.atlas {
|
package spine.atlas {
|
||||||
|
|
||||||
public interface TextureLoader {
|
public interface TextureLoader {
|
||||||
function load (page:AtlasPage, path:String) : void;
|
function loadPage (page:AtlasPage, path:String) : void;
|
||||||
function unload (texture:Object) : void;
|
function loadRegion (region:AtlasRegion) : void;
|
||||||
|
function unloadPage (page:AtlasPage) : void;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import flash.display.Bitmap;
|
|||||||
import flash.display.BitmapData;
|
import flash.display.BitmapData;
|
||||||
|
|
||||||
import spine.atlas.AtlasPage;
|
import spine.atlas.AtlasPage;
|
||||||
|
import spine.atlas.AtlasRegion;
|
||||||
import spine.atlas.TextureLoader;
|
import spine.atlas.TextureLoader;
|
||||||
|
|
||||||
public class SingleTextureLoader implements TextureLoader {
|
public class SingleTextureLoader implements TextureLoader {
|
||||||
@ -51,14 +52,17 @@ public class SingleTextureLoader implements TextureLoader {
|
|||||||
throw new ArgumentError("object must be a Bitmap or BitmapData.");
|
throw new ArgumentError("object must be a Bitmap or BitmapData.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function load (page:AtlasPage, path:String) : void {
|
public function loadPage (page:AtlasPage, path:String) : void {
|
||||||
page.rendererObject = pageBitmapData;
|
page.rendererObject = pageBitmapData;
|
||||||
page.width = pageBitmapData.width;
|
page.width = pageBitmapData.width;
|
||||||
page.height = pageBitmapData.height;
|
page.height = pageBitmapData.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadRegion (region:AtlasRegion) : void {
|
||||||
|
}
|
||||||
|
|
||||||
public function unload (texture:Object) : void {
|
public function unloadPage (page:AtlasPage) : void {
|
||||||
BitmapData(texture).dispose();
|
BitmapData(page.rendererObject).dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
79
spine-starling/spine-starling-example/src/AtlasExample.as
Normal file
79
spine-starling/spine-starling-example/src/AtlasExample.as
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package {
|
||||||
|
|
||||||
|
import spine.Event;
|
||||||
|
import spine.SkeletonData;
|
||||||
|
import spine.SkeletonJson;
|
||||||
|
import spine.animation.AnimationStateData;
|
||||||
|
import spine.atlas.Atlas;
|
||||||
|
import spine.attachments.AtlasAttachmentLoader;
|
||||||
|
import spine.starling.SingleTextureLoader;
|
||||||
|
import spine.starling.SkeletonAnimation;
|
||||||
|
import spine.starling.StarlingAtlasAttachmentLoader;
|
||||||
|
|
||||||
|
import starling.core.Starling;
|
||||||
|
import starling.display.Sprite;
|
||||||
|
import starling.events.Touch;
|
||||||
|
import starling.events.TouchEvent;
|
||||||
|
import starling.events.TouchPhase;
|
||||||
|
import starling.textures.Texture;
|
||||||
|
import starling.textures.TextureAtlas;
|
||||||
|
|
||||||
|
public class AtlasExample extends Sprite {
|
||||||
|
[Embed(source = "spineboy-atlas.atlas", mimeType = "application/octet-stream")]
|
||||||
|
static public const SpineboyAtlasFile:Class;
|
||||||
|
|
||||||
|
[Embed(source = "spineboy-atlas.png")]
|
||||||
|
static public const SpineboyAtlasTexture:Class;
|
||||||
|
|
||||||
|
[Embed(source = "spineboy.json", mimeType = "application/octet-stream")]
|
||||||
|
static public const SpineboyJson:Class;
|
||||||
|
|
||||||
|
private var skeleton:SkeletonAnimation;
|
||||||
|
|
||||||
|
public function AtlasExample () {
|
||||||
|
var atlas:Atlas = new Atlas(new SpineboyAtlasFile(), new SingleTextureLoader(new SpineboyAtlasTexture()));
|
||||||
|
var json:SkeletonJson = new SkeletonJson(new AtlasAttachmentLoader(atlas));
|
||||||
|
var skeletonData:SkeletonData = json.readSkeletonData(new SpineboyJson());
|
||||||
|
|
||||||
|
var stateData:AnimationStateData = new AnimationStateData(skeletonData);
|
||||||
|
stateData.setMixByName("walk", "jump", 0.2);
|
||||||
|
stateData.setMixByName("jump", "walk", 0.4);
|
||||||
|
stateData.setMixByName("jump", "jump", 0.2);
|
||||||
|
|
||||||
|
skeleton = new SkeletonAnimation(skeletonData, stateData);
|
||||||
|
skeleton.x = 320;
|
||||||
|
skeleton.y = 420;
|
||||||
|
|
||||||
|
skeleton.state.onStart = function (trackIndex:int) : void {
|
||||||
|
trace(trackIndex + " start: " + skeleton.state.getCurrent(trackIndex));
|
||||||
|
};
|
||||||
|
skeleton.state.onEnd = function (trackIndex:int) : void {
|
||||||
|
trace(trackIndex + " end: " + skeleton.state.getCurrent(trackIndex));
|
||||||
|
};
|
||||||
|
skeleton.state.onComplete = function (trackIndex:int, count:int) : void {
|
||||||
|
trace(trackIndex + " complete: " + skeleton.state.getCurrent(trackIndex) + ", " + count);
|
||||||
|
};
|
||||||
|
skeleton.state.onEvent = function (trackIndex:int, event:Event) : void {
|
||||||
|
trace(trackIndex + " event: " + skeleton.state.getCurrent(trackIndex) + ", "
|
||||||
|
+ event.data.name + ": " + event.intValue + ", " + event.floatValue + ", " + event.stringValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
skeleton.state.setAnimationByName(0, "walk", true);
|
||||||
|
skeleton.state.addAnimationByName(0, "jump", false, 3);
|
||||||
|
skeleton.state.addAnimationByName(0, "walk", true, 0);
|
||||||
|
|
||||||
|
addChild(skeleton);
|
||||||
|
Starling.juggler.add(skeleton);
|
||||||
|
|
||||||
|
addEventListener(TouchEvent.TOUCH, onClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function onClick (event:TouchEvent) : void {
|
||||||
|
var touch:Touch = event.getTouch(this);
|
||||||
|
if (touch && touch.phase == TouchPhase.BEGAN) {
|
||||||
|
skeleton.state.setAnimationByName(0, "jump", false);
|
||||||
|
skeleton.state.addAnimationByName(0, "walk", true, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,7 +9,7 @@ public class Main extends Sprite {
|
|||||||
private var _starling:Starling;
|
private var _starling:Starling;
|
||||||
|
|
||||||
public function Main () {
|
public function Main () {
|
||||||
_starling = new Starling(Game, stage);
|
_starling = new Starling(AtlasExample, stage);
|
||||||
_starling.start();
|
_starling.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,11 +15,11 @@ import starling.events.TouchPhase;
|
|||||||
import starling.textures.Texture;
|
import starling.textures.Texture;
|
||||||
import starling.textures.TextureAtlas;
|
import starling.textures.TextureAtlas;
|
||||||
|
|
||||||
public class Game extends Sprite {
|
public class StarlingAtlasExample extends Sprite {
|
||||||
[Embed(source = "spineboy.xml", mimeType = "application/octet-stream")]
|
[Embed(source = "spineboy-starling.xml", mimeType = "application/octet-stream")]
|
||||||
static public const SpineboyAtlasXml:Class;
|
static public const SpineboyAtlasXml:Class;
|
||||||
|
|
||||||
[Embed(source = "spineboy.png")]
|
[Embed(source = "spineboy-starling.png")]
|
||||||
static public const SpineboyAtlasTexture:Class;
|
static public const SpineboyAtlasTexture:Class;
|
||||||
|
|
||||||
[Embed(source = "spineboy.json", mimeType = "application/octet-stream")]
|
[Embed(source = "spineboy.json", mimeType = "application/octet-stream")]
|
||||||
@ -27,7 +27,7 @@ public class Game extends Sprite {
|
|||||||
|
|
||||||
private var skeleton:SkeletonAnimation;
|
private var skeleton:SkeletonAnimation;
|
||||||
|
|
||||||
public function Game () {
|
public function StarlingAtlasExample () {
|
||||||
var texture:Texture = Texture.fromBitmap(new SpineboyAtlasTexture());
|
var texture:Texture = Texture.fromBitmap(new SpineboyAtlasTexture());
|
||||||
var xml:XML = XML(new SpineboyAtlasXml());
|
var xml:XML = XML(new SpineboyAtlasXml());
|
||||||
var atlas:TextureAtlas = new TextureAtlas(texture, xml);
|
var atlas:TextureAtlas = new TextureAtlas(texture, xml);
|
||||||
131
spine-starling/spine-starling-example/src/spineboy-atlas.atlas
Normal file
131
spine-starling/spine-starling-example/src/spineboy-atlas.atlas
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
spineboy.png
|
||||||
|
format: RGBA8888
|
||||||
|
filter: Nearest,Nearest
|
||||||
|
repeat: none
|
||||||
|
eyes
|
||||||
|
rotate: false
|
||||||
|
xy: 312, 24
|
||||||
|
size: 34, 27
|
||||||
|
orig: 34, 27
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
eyes-closed
|
||||||
|
rotate: false
|
||||||
|
xy: 348, 24
|
||||||
|
size: 34, 27
|
||||||
|
orig: 34, 27
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
head
|
||||||
|
rotate: false
|
||||||
|
xy: 2, 2
|
||||||
|
size: 121, 132
|
||||||
|
orig: 121, 132
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
left-arm
|
||||||
|
rotate: true
|
||||||
|
xy: 217, 10
|
||||||
|
size: 35, 29
|
||||||
|
orig: 35, 29
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
left-foot
|
||||||
|
rotate: false
|
||||||
|
xy: 386, 55
|
||||||
|
size: 65, 30
|
||||||
|
orig: 65, 30
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
left-hand
|
||||||
|
rotate: false
|
||||||
|
xy: 125, 2
|
||||||
|
size: 35, 38
|
||||||
|
orig: 35, 38
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
left-lower-leg
|
||||||
|
rotate: true
|
||||||
|
xy: 320, 85
|
||||||
|
size: 49, 64
|
||||||
|
orig: 49, 64
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
left-shoulder
|
||||||
|
rotate: true
|
||||||
|
xy: 162, 6
|
||||||
|
size: 34, 53
|
||||||
|
orig: 34, 53
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
left-upper-leg
|
||||||
|
rotate: true
|
||||||
|
xy: 248, 55
|
||||||
|
size: 33, 67
|
||||||
|
orig: 33, 67
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
neck
|
||||||
|
rotate: true
|
||||||
|
xy: 282, 19
|
||||||
|
size: 34, 28
|
||||||
|
orig: 34, 28
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
pelvis
|
||||||
|
rotate: false
|
||||||
|
xy: 386, 87
|
||||||
|
size: 63, 47
|
||||||
|
orig: 63, 47
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
right-arm
|
||||||
|
rotate: true
|
||||||
|
xy: 195, 47
|
||||||
|
size: 21, 45
|
||||||
|
orig: 21, 45
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
right-foot
|
||||||
|
rotate: false
|
||||||
|
xy: 317, 53
|
||||||
|
size: 67, 30
|
||||||
|
orig: 67, 30
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
right-hand
|
||||||
|
rotate: false
|
||||||
|
xy: 248, 21
|
||||||
|
size: 32, 32
|
||||||
|
orig: 32, 32
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
right-lower-leg
|
||||||
|
rotate: false
|
||||||
|
xy: 195, 70
|
||||||
|
size: 51, 64
|
||||||
|
orig: 51, 64
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
right-shoulder
|
||||||
|
rotate: false
|
||||||
|
xy: 386, 2
|
||||||
|
size: 52, 51
|
||||||
|
orig: 52, 51
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
right-upper-leg
|
||||||
|
rotate: true
|
||||||
|
xy: 248, 90
|
||||||
|
size: 44, 70
|
||||||
|
orig: 44, 70
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
|
torso
|
||||||
|
rotate: false
|
||||||
|
xy: 125, 42
|
||||||
|
size: 68, 92
|
||||||
|
orig: 68, 92
|
||||||
|
offset: 0, 0
|
||||||
|
index: -1
|
||||||
BIN
spine-starling/spine-starling-example/src/spineboy-atlas.png
Normal file
BIN
spine-starling/spine-starling-example/src/spineboy-atlas.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
@ -0,0 +1,87 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* Spine Runtime Software License - Version 1.1
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013, Esoteric Software
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms in whole or in part, with
|
||||||
|
* or without modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* 1. A Spine Essential, Professional, Enterprise, or Education License must
|
||||||
|
* be purchased from Esoteric Software and the license must remain valid:
|
||||||
|
* http://esotericsoftware.com/
|
||||||
|
* 2. Redistributions of source code must retain this license, which is the
|
||||||
|
* above copyright notice, this declaration of conditions and the following
|
||||||
|
* disclaimer.
|
||||||
|
* 3. Redistributions in binary form must reproduce this license, which is the
|
||||||
|
* above copyright notice, this declaration of conditions and the following
|
||||||
|
* disclaimer, in the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
package spine.starling {
|
||||||
|
import flash.display.Bitmap;
|
||||||
|
import flash.display.BitmapData;
|
||||||
|
import flash.geom.Point;
|
||||||
|
import flash.geom.Rectangle;
|
||||||
|
|
||||||
|
import spine.atlas.AtlasPage;
|
||||||
|
import spine.atlas.AtlasRegion;
|
||||||
|
import spine.atlas.TextureLoader;
|
||||||
|
|
||||||
|
import starling.textures.SubTexture;
|
||||||
|
import starling.textures.Texture;
|
||||||
|
|
||||||
|
public class SingleTextureLoader implements TextureLoader {
|
||||||
|
private var pageBitmapData:BitmapData;
|
||||||
|
|
||||||
|
/** @param object A Bitmap or BitmapData. */
|
||||||
|
public function SingleTextureLoader (object:*) {
|
||||||
|
if (object is BitmapData)
|
||||||
|
pageBitmapData = BitmapData(object);
|
||||||
|
else if (object is Bitmap)
|
||||||
|
pageBitmapData = Bitmap(object).bitmapData;
|
||||||
|
else
|
||||||
|
throw new ArgumentError("object must be a Bitmap or BitmapData.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadPage (page:AtlasPage, path:String) : void {
|
||||||
|
page.rendererObject = Texture.fromBitmapData(pageBitmapData);
|
||||||
|
page.width = pageBitmapData.width;
|
||||||
|
page.height = pageBitmapData.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadRegion (region:AtlasRegion) : void {
|
||||||
|
var image:SkeletonImage = new SkeletonImage(Texture(region.page.rendererObject));
|
||||||
|
if (region.rotate) {
|
||||||
|
image.setTexCoordsTo(0, region.u, region.v2);
|
||||||
|
image.setTexCoordsTo(1, region.u, region.v);
|
||||||
|
image.setTexCoordsTo(2, region.u2, region.v2);
|
||||||
|
image.setTexCoordsTo(3, region.u2, region.v);
|
||||||
|
} else {
|
||||||
|
image.setTexCoordsTo(0, region.u, region.v);
|
||||||
|
image.setTexCoordsTo(1, region.u2, region.v);
|
||||||
|
image.setTexCoordsTo(2, region.u, region.v2);
|
||||||
|
image.setTexCoordsTo(3, region.u2, region.v2);
|
||||||
|
}
|
||||||
|
region.rendererObject = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unloadPage (page:AtlasPage) : void {
|
||||||
|
BitmapData(pageBitmapData).dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -40,6 +40,8 @@ import spine.Bone;
|
|||||||
import spine.Skeleton;
|
import spine.Skeleton;
|
||||||
import spine.SkeletonData;
|
import spine.SkeletonData;
|
||||||
import spine.Slot;
|
import spine.Slot;
|
||||||
|
import spine.atlas.AtlasPage;
|
||||||
|
import spine.atlas.AtlasRegion;
|
||||||
import spine.attachments.RegionAttachment;
|
import spine.attachments.RegionAttachment;
|
||||||
|
|
||||||
import starling.animation.IAnimatable;
|
import starling.animation.IAnimatable;
|
||||||
@ -87,12 +89,15 @@ public class SkeletonSprite extends DisplayObject implements IAnimatable {
|
|||||||
var a:Number = slot.a;
|
var a:Number = slot.a;
|
||||||
var rgb:uint = Color.rgb(r * slot.r, g * slot.g, b * slot.b);
|
var rgb:uint = Color.rgb(r * slot.r, g * slot.g, b * slot.b);
|
||||||
|
|
||||||
var image:SkeletonImage = regionAttachment.rendererObject as SkeletonImage;
|
var image:SkeletonImage;
|
||||||
|
image = regionAttachment.rendererObject as SkeletonImage;
|
||||||
|
if (image == null) image = SkeletonImage(AtlasRegion(regionAttachment.rendererObject).rendererObject);
|
||||||
|
|
||||||
var vertexData:VertexData = image.vertexData;
|
var vertexData:VertexData = image.vertexData;
|
||||||
|
|
||||||
vertexData.setPosition(0, vertices[2], vertices[3]);
|
vertexData.setPosition(0, vertices[2], vertices[3]);
|
||||||
vertexData.setColorAndAlpha(0, rgb, a);
|
vertexData.setColorAndAlpha(0, rgb, a);
|
||||||
|
|
||||||
vertexData.setPosition(1, vertices[4], vertices[5]);
|
vertexData.setPosition(1, vertices[4], vertices[5]);
|
||||||
vertexData.setColorAndAlpha(1, rgb, a);
|
vertexData.setColorAndAlpha(1, rgb, a);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user