Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-05 11:00:05 +01:00
Merge pull request #268 from sk89q/blocktype-adjustments
Added data values to the BlockType methods and used those in a few places.
Dieser Commit ist enthalten in:
Commit
b891660263
@ -620,7 +620,8 @@ public class EditSession {
|
|||||||
for (int y = maxY; y >= minY; --y) {
|
for (int y = maxY; y >= minY; --y) {
|
||||||
Vector pt = new Vector(x, y, z);
|
Vector pt = new Vector(x, y, z);
|
||||||
int id = getBlockType(pt);
|
int id = getBlockType(pt);
|
||||||
if (naturalOnly ? BlockType.isNaturalTerrainBlock(id) : !BlockType.canPassThrough(id)) {
|
int data = getBlockData(pt);
|
||||||
|
if (naturalOnly ? BlockType.isNaturalTerrainBlock(id, data) : !BlockType.canPassThrough(id, data)) {
|
||||||
return y;
|
return y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2480,6 +2481,7 @@ public class EditSession {
|
|||||||
loop: for (int y = world.getMaxY(); y >= 1; --y) {
|
loop: for (int y = world.getMaxY(); y >= 1; --y) {
|
||||||
final Vector pt = new Vector(x, y, z);
|
final Vector pt = new Vector(x, y, z);
|
||||||
final int id = getBlockType(pt);
|
final int id = getBlockType(pt);
|
||||||
|
final int data = getBlockData(pt);
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case BlockID.DIRT:
|
case BlockID.DIRT:
|
||||||
@ -2497,7 +2499,7 @@ public class EditSession {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// ...and all non-passable blocks
|
// ...and all non-passable blocks
|
||||||
if (!BlockType.canPassThrough(id)) {
|
if (!BlockType.canPassThrough(id, data)) {
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2971,7 +2973,7 @@ public class EditSession {
|
|||||||
|
|
||||||
while (!queue.isEmpty()) {
|
while (!queue.isEmpty()) {
|
||||||
final BlockVector current = queue.removeFirst();
|
final BlockVector current = queue.removeFirst();
|
||||||
if (!BlockType.canPassThrough(getBlockType(current))) {
|
if (!BlockType.canPassThrough(getBlockType(current), getBlockData(current))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ package com.sk89q.worldedit;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import com.sk89q.worldedit.bags.BlockBag;
|
import com.sk89q.worldedit.bags.BlockBag;
|
||||||
|
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||||
import com.sk89q.worldedit.blocks.BlockID;
|
import com.sk89q.worldedit.blocks.BlockID;
|
||||||
import com.sk89q.worldedit.blocks.BlockType;
|
import com.sk89q.worldedit.blocks.BlockType;
|
||||||
import com.sk89q.worldedit.blocks.ItemID;
|
import com.sk89q.worldedit.blocks.ItemID;
|
||||||
@ -79,7 +80,7 @@ public abstract class LocalPlayer {
|
|||||||
byte free = 0;
|
byte free = 0;
|
||||||
|
|
||||||
while (y <= world.getMaxY() + 2) {
|
while (y <= world.getMaxY() + 2) {
|
||||||
if (BlockType.canPassThrough(world.getBlockType(new Vector(x, y, z)))) {
|
if (BlockType.canPassThrough(world.getBlock(new Vector(x, y, z)))) {
|
||||||
++free;
|
++free;
|
||||||
} else {
|
} else {
|
||||||
free = 0;
|
free = 0;
|
||||||
@ -114,8 +115,8 @@ public abstract class LocalPlayer {
|
|||||||
while (y >= 0) {
|
while (y >= 0) {
|
||||||
final Vector pos = new Vector(x, y, z);
|
final Vector pos = new Vector(x, y, z);
|
||||||
final int id = world.getBlockType(pos);
|
final int id = world.getBlockType(pos);
|
||||||
if (!BlockType.canPassThrough(id)) {
|
|
||||||
final int data = world.getBlockData(pos);
|
final int data = world.getBlockData(pos);
|
||||||
|
if (!BlockType.canPassThrough(id, data)) {
|
||||||
setPosition(new Vector(x + 0.5, y + BlockType.centralTopLimit(id, data), z + 0.5));
|
setPosition(new Vector(x + 0.5, y + BlockType.centralTopLimit(id, data), z + 0.5));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -140,17 +141,17 @@ public abstract class LocalPlayer {
|
|||||||
* @return true if a spot was found
|
* @return true if a spot was found
|
||||||
*/
|
*/
|
||||||
public boolean ascendLevel() {
|
public boolean ascendLevel() {
|
||||||
Vector pos = getBlockIn();
|
final WorldVector pos = getBlockIn();
|
||||||
int x = pos.getBlockX();
|
final int x = pos.getBlockX();
|
||||||
int y = Math.max(0, pos.getBlockY());
|
int y = Math.max(0, pos.getBlockY());
|
||||||
int z = pos.getBlockZ();
|
final int z = pos.getBlockZ();
|
||||||
LocalWorld world = getPosition().getWorld();
|
final LocalWorld world = pos.getWorld();
|
||||||
|
|
||||||
byte free = 0;
|
byte free = 0;
|
||||||
byte spots = 0;
|
byte spots = 0;
|
||||||
|
|
||||||
while (y <= world.getMaxY() + 2) {
|
while (y <= world.getMaxY() + 2) {
|
||||||
if (BlockType.canPassThrough(world.getBlockType(new Vector(x, y, z)))) {
|
if (BlockType.canPassThrough(world.getBlock(new Vector(x, y, z)))) {
|
||||||
++free;
|
++free;
|
||||||
} else {
|
} else {
|
||||||
free = 0;
|
free = 0;
|
||||||
@ -159,14 +160,16 @@ public abstract class LocalPlayer {
|
|||||||
if (free == 2) {
|
if (free == 2) {
|
||||||
++spots;
|
++spots;
|
||||||
if (spots == 2) {
|
if (spots == 2) {
|
||||||
int type = world.getBlockType(new Vector(x, y - 2, z));
|
final Vector platform = new Vector(x, y - 2, z);
|
||||||
|
final BaseBlock block = world.getBlock(platform);
|
||||||
|
final int type = block.getId();
|
||||||
|
|
||||||
// Don't get put in lava!
|
// Don't get put in lava!
|
||||||
if (type == BlockID.LAVA || type == BlockID.STATIONARY_LAVA) {
|
if (type == BlockID.LAVA || type == BlockID.STATIONARY_LAVA) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPosition(new Vector(x + 0.5, y - 1, z + 0.5));
|
setPosition(platform.add(0.5, BlockType.centralTopLimit(block), 0.5));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,16 +186,16 @@ public abstract class LocalPlayer {
|
|||||||
* @return true if a spot was found
|
* @return true if a spot was found
|
||||||
*/
|
*/
|
||||||
public boolean descendLevel() {
|
public boolean descendLevel() {
|
||||||
Vector pos = getBlockIn();
|
final WorldVector pos = getBlockIn();
|
||||||
int x = pos.getBlockX();
|
final int x = pos.getBlockX();
|
||||||
int y = Math.max(0, pos.getBlockY() - 1);
|
int y = Math.max(0, pos.getBlockY() - 1);
|
||||||
int z = pos.getBlockZ();
|
final int z = pos.getBlockZ();
|
||||||
LocalWorld world = getPosition().getWorld();
|
final LocalWorld world = pos.getWorld();
|
||||||
|
|
||||||
byte free = 0;
|
byte free = 0;
|
||||||
|
|
||||||
while (y >= 1) {
|
while (y >= 1) {
|
||||||
if (BlockType.canPassThrough(world.getBlockType(new Vector(x, y, z)))) {
|
if (BlockType.canPassThrough(world.getBlock(new Vector(x, y, z)))) {
|
||||||
++free;
|
++free;
|
||||||
} else {
|
} else {
|
||||||
free = 0;
|
free = 0;
|
||||||
@ -203,12 +206,14 @@ public abstract class LocalPlayer {
|
|||||||
// lightly and also check to see if there's something to
|
// lightly and also check to see if there's something to
|
||||||
// stand upon
|
// stand upon
|
||||||
while (y >= 0) {
|
while (y >= 0) {
|
||||||
int type = world.getBlockType(new Vector(x, y, z));
|
final Vector platform = new Vector(x, y, z);
|
||||||
|
final BaseBlock block = world.getBlock(platform);
|
||||||
|
final int type = block.getId();
|
||||||
|
|
||||||
// Don't want to end up in lava
|
// Don't want to end up in lava
|
||||||
if (type != BlockID.AIR && type != BlockID.LAVA && type != BlockID.STATIONARY_LAVA) {
|
if (type != BlockID.AIR && type != BlockID.LAVA && type != BlockID.STATIONARY_LAVA) {
|
||||||
// Found a block!
|
// Found a block!
|
||||||
setPosition(new Vector(x + 0.5, y + 1, z + 0.5));
|
setPosition(platform.add(0.5, BlockType.centralTopLimit(block), 0.5));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +250,7 @@ public abstract class LocalPlayer {
|
|||||||
|
|
||||||
while (y <= world.getMaxY()) {
|
while (y <= world.getMaxY()) {
|
||||||
// Found a ceiling!
|
// Found a ceiling!
|
||||||
if (!BlockType.canPassThrough(world.getBlockType(new Vector(x, y, z)))) {
|
if (!BlockType.canPassThrough(world.getBlock(new Vector(x, y, z)))) {
|
||||||
int platformY = Math.max(initialY, y - 3 - clearance);
|
int platformY = Math.max(initialY, y - 3 - clearance);
|
||||||
world.setBlockType(new Vector(x, platformY, z), BlockID.GLASS);
|
world.setBlockType(new Vector(x, platformY, z), BlockID.GLASS);
|
||||||
setPosition(new Vector(x + 0.5, platformY + 1, z + 0.5));
|
setPosition(new Vector(x + 0.5, platformY + 1, z + 0.5));
|
||||||
@ -274,7 +279,7 @@ public abstract class LocalPlayer {
|
|||||||
LocalWorld world = getPosition().getWorld();
|
LocalWorld world = getPosition().getWorld();
|
||||||
|
|
||||||
while (y <= world.getMaxY() + 2) {
|
while (y <= world.getMaxY() + 2) {
|
||||||
if (!BlockType.canPassThrough(world.getBlockType(new Vector(x, y, z)))) {
|
if (!BlockType.canPassThrough(world.getBlock(new Vector(x, y, z)))) {
|
||||||
break; // Hit something
|
break; // Hit something
|
||||||
} else if (y > maxY + 1) {
|
} else if (y > maxY + 1) {
|
||||||
break;
|
break;
|
||||||
@ -488,7 +493,7 @@ public abstract class LocalPlayer {
|
|||||||
boolean inFree = false;
|
boolean inFree = false;
|
||||||
|
|
||||||
while ((block = hitBlox.getNextBlock()) != null) {
|
while ((block = hitBlox.getNextBlock()) != null) {
|
||||||
boolean free = BlockType.canPassThrough(world.getBlockType(block));
|
boolean free = BlockType.canPassThrough(world.getBlock(block));
|
||||||
|
|
||||||
if (firstBlock) {
|
if (firstBlock) {
|
||||||
firstBlock = false;
|
firstBlock = false;
|
||||||
|
@ -28,6 +28,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import com.sk89q.util.StringUtil;
|
import com.sk89q.util.StringUtil;
|
||||||
import com.sk89q.worldedit.PlayerDirection;
|
import com.sk89q.worldedit.PlayerDirection;
|
||||||
|
import com.sk89q.worldedit.foundation.Block;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block types.
|
* Block types.
|
||||||
@ -436,7 +437,8 @@ public enum BlockType {
|
|||||||
canPassThrough.add(BlockID.REDSTONE_TORCH_OFF);
|
canPassThrough.add(BlockID.REDSTONE_TORCH_OFF);
|
||||||
canPassThrough.add(BlockID.REDSTONE_TORCH_ON);
|
canPassThrough.add(BlockID.REDSTONE_TORCH_ON);
|
||||||
canPassThrough.add(BlockID.STONE_BUTTON);
|
canPassThrough.add(BlockID.STONE_BUTTON);
|
||||||
canPassThrough.add(BlockID.SNOW);
|
canPassThrough.add(-16*BlockID.SNOW-0);
|
||||||
|
canPassThrough.add(-16*BlockID.SNOW-8);
|
||||||
canPassThrough.add(BlockID.REED);
|
canPassThrough.add(BlockID.REED);
|
||||||
canPassThrough.add(BlockID.PORTAL);
|
canPassThrough.add(BlockID.PORTAL);
|
||||||
canPassThrough.add(BlockID.REDSTONE_REPEATER_OFF);
|
canPassThrough.add(BlockID.REDSTONE_REPEATER_OFF);
|
||||||
@ -460,16 +462,40 @@ public enum BlockType {
|
|||||||
canPassThrough.add(BlockID.CARPET);
|
canPassThrough.add(BlockID.CARPET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a block can be passed through.
|
* Checks whether a block can be passed through.
|
||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
* @return
|
* @return
|
||||||
|
* @deprecated Use {@link #canPassThrough(int,int)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static boolean canPassThrough(int id) {
|
public static boolean canPassThrough(int id) {
|
||||||
return canPassThrough.contains(id);
|
return canPassThrough.contains(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a block can be passed through.
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
* @param data
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean canPassThrough(int id, int data) {
|
||||||
|
return canPassThrough.contains(-16*id-data) || canPassThrough.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a block can be passed through.
|
||||||
|
*
|
||||||
|
* @param block
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean canPassThrough(Block block) {
|
||||||
|
return canPassThrough(block.getId(), block.getData());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a block can be passed through.
|
* Checks whether a block can be passed through.
|
||||||
*
|
*
|
||||||
@ -487,6 +513,9 @@ public enum BlockType {
|
|||||||
centralTopLimit.put(BlockID.BED, 0.5625);
|
centralTopLimit.put(BlockID.BED, 0.5625);
|
||||||
centralTopLimit.put(BlockID.BREWING_STAND, 0.875);
|
centralTopLimit.put(BlockID.BREWING_STAND, 0.875);
|
||||||
centralTopLimit.put(BlockID.CAKE_BLOCK, 0.4375);
|
centralTopLimit.put(BlockID.CAKE_BLOCK, 0.4375);
|
||||||
|
for (int data = 6; data < 16; ++data) {
|
||||||
|
centralTopLimit.put(-16*BlockID.CAKE_BLOCK-data, 0.0);
|
||||||
|
}
|
||||||
centralTopLimit.put(BlockID.CAULDRON, 0.3125);
|
centralTopLimit.put(BlockID.CAULDRON, 0.3125);
|
||||||
centralTopLimit.put(BlockID.COCOA_PLANT, 0.750);
|
centralTopLimit.put(BlockID.COCOA_PLANT, 0.750);
|
||||||
centralTopLimit.put(BlockID.ENCHANTMENT_TABLE, 0.75);
|
centralTopLimit.put(BlockID.ENCHANTMENT_TABLE, 0.75);
|
||||||
@ -499,13 +528,10 @@ public enum BlockType {
|
|||||||
}
|
}
|
||||||
centralTopLimit.put(-16*BlockID.HEAD-data, 0.75);
|
centralTopLimit.put(-16*BlockID.HEAD-data, 0.75);
|
||||||
}
|
}
|
||||||
// Default value to be used if no data value is given
|
|
||||||
centralTopLimit.put(BlockID.HEAD, 0.75);
|
|
||||||
// Heads on the floor are lower
|
// Heads on the floor are lower
|
||||||
centralTopLimit.put(-16*BlockID.HEAD-1, 0.5);
|
centralTopLimit.put(-16*BlockID.HEAD-1, 0.5);
|
||||||
centralTopLimit.put(-16*BlockID.HEAD-9, 0.5);
|
centralTopLimit.put(-16*BlockID.HEAD-9, 0.5);
|
||||||
centralTopLimit.put(BlockID.FENCE, 1.5);
|
centralTopLimit.put(BlockID.FENCE, 1.5);
|
||||||
centralTopLimit.put(BlockID.FENCE_GATE, 1.5);
|
|
||||||
for (int data = 0; data < 8; ++data) {
|
for (int data = 0; data < 8; ++data) {
|
||||||
centralTopLimit.put(-16*BlockID.STEP-data, 0.5);
|
centralTopLimit.put(-16*BlockID.STEP-data, 0.5);
|
||||||
centralTopLimit.put(-16*BlockID.WOODEN_STEP-data, 0.5);
|
centralTopLimit.put(-16*BlockID.WOODEN_STEP-data, 0.5);
|
||||||
@ -515,13 +541,28 @@ public enum BlockType {
|
|||||||
centralTopLimit.put(BlockID.LILY_PAD, 0.015625);
|
centralTopLimit.put(BlockID.LILY_PAD, 0.015625);
|
||||||
centralTopLimit.put(BlockID.REDSTONE_REPEATER_ON, .125);
|
centralTopLimit.put(BlockID.REDSTONE_REPEATER_ON, .125);
|
||||||
centralTopLimit.put(BlockID.REDSTONE_REPEATER_OFF, .125);
|
centralTopLimit.put(BlockID.REDSTONE_REPEATER_OFF, .125);
|
||||||
centralTopLimit.put(BlockID.TRAP_DOOR, 0.1875);
|
for (int data = 0; data < 4; ++data) {
|
||||||
|
centralTopLimit.put(-16*BlockID.TRAP_DOOR-(data+ 0), 0.1875); // closed lower trap doors
|
||||||
|
centralTopLimit.put(-16*BlockID.TRAP_DOOR-(data+ 4), 0.0); // opened lower trap doors
|
||||||
|
centralTopLimit.put(-16*BlockID.TRAP_DOOR-(data+ 8), 1.0); // closed upper trap doors
|
||||||
|
centralTopLimit.put(-16*BlockID.TRAP_DOOR-(data+12), 0.0); // opened upper trap doors
|
||||||
|
|
||||||
|
centralTopLimit.put(-16*BlockID.FENCE_GATE-(data+ 0), 1.5);
|
||||||
|
centralTopLimit.put(-16*BlockID.FENCE_GATE-(data+ 4), 0.0);
|
||||||
|
centralTopLimit.put(-16*BlockID.FENCE_GATE-(data+ 8), 1.5);
|
||||||
|
centralTopLimit.put(-16*BlockID.FENCE_GATE-(data+12), 0.0);
|
||||||
|
}
|
||||||
centralTopLimit.put(BlockID.SLOW_SAND, 0.875);
|
centralTopLimit.put(BlockID.SLOW_SAND, 0.875);
|
||||||
centralTopLimit.put(BlockID.COBBLESTONE_WALL, 1.5);
|
centralTopLimit.put(BlockID.COBBLESTONE_WALL, 1.5);
|
||||||
centralTopLimit.put(BlockID.FLOWER_POT, 0.375);
|
centralTopLimit.put(BlockID.FLOWER_POT, 0.375);
|
||||||
centralTopLimit.put(BlockID.COMPARATOR_OFF, .125);
|
centralTopLimit.put(BlockID.COMPARATOR_OFF, .125);
|
||||||
centralTopLimit.put(BlockID.COMPARATOR_ON, .125);
|
centralTopLimit.put(BlockID.COMPARATOR_ON, .125);
|
||||||
centralTopLimit.put(BlockID.DAYLIGHT_SENSOR, 0.375);
|
centralTopLimit.put(BlockID.DAYLIGHT_SENSOR, 0.375);
|
||||||
|
|
||||||
|
// Some default values to be used if no data value is given
|
||||||
|
centralTopLimit.put(BlockID.HEAD, 0.75);
|
||||||
|
centralTopLimit.put(BlockID.TRAP_DOOR, 1.0);
|
||||||
|
centralTopLimit.put(BlockID.FENCE_GATE, 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -541,6 +582,16 @@ public enum BlockType {
|
|||||||
return canPassThrough(id) ? 0 : 1;
|
return canPassThrough(id) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the y offset a player falls to when falling onto the top of a block at xp+0.5/zp+0.5.
|
||||||
|
*
|
||||||
|
* @param block
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static double centralTopLimit(Block block) {
|
||||||
|
return centralTopLimit(block.getId(), block.getData());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the y offset a player falls to when falling onto the top of a block at xp+0.5/zp+0.5.
|
* Returns the y offset a player falls to when falling onto the top of a block at xp+0.5/zp+0.5.
|
||||||
*
|
*
|
||||||
@ -912,6 +963,26 @@ public enum BlockType {
|
|||||||
return isNaturalTerrainBlock.contains(id);
|
return isNaturalTerrainBlock.contains(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the block type is naturally occuring
|
||||||
|
*
|
||||||
|
* @param block
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isNaturalTerrainBlock(int id, int data) {
|
||||||
|
return isNaturalTerrainBlock.contains(-16*id-data) || isNaturalTerrainBlock.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the block type is naturally occuring
|
||||||
|
*
|
||||||
|
* @param block
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isNaturalTerrainBlock(Block block) {
|
||||||
|
return isNaturalTerrainBlock(block.getId(), block.getData());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the block type is naturally occuring
|
* Checks if the block type is naturally occuring
|
||||||
*
|
*
|
||||||
|
@ -145,7 +145,7 @@ public class TargetBlock {
|
|||||||
* @return Block
|
* @return Block
|
||||||
*/
|
*/
|
||||||
public BlockWorldVector getSolidTargetBlock() {
|
public BlockWorldVector getSolidTargetBlock() {
|
||||||
while (getNextBlock() != null && BlockType.canPassThrough(world.getBlockType(getCurrentBlock()))) ;
|
while (getNextBlock() != null && BlockType.canPassThrough(world.getBlock(getCurrentBlock()))) ;
|
||||||
return getCurrentBlock();
|
return getCurrentBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren