feat(D-01/C): consumer migration — 85 files migrated to V2 helpers
Phase 1 (state): PlayerBindState, PlayerCaptorManager, PlayerEquipment, PlayerDataRetrieval, PlayerLifecycle, PlayerShockCollar, StruggleAccessory Phase 2 (client): AnimationTickHandler, NpcAnimationTickHandler, 5 render handlers, DamselModel, 3 client mixins, SelfBondageInputHandler, SlaveManagementScreen, ActionPanel, SlaveEntryWidget, ModKeybindings Phase 3 (entities): 28 entity/AI files migrated to CollarHelper, BindModeHelper, PoseTypeHelper, createStack() Phase 4 (network): PacketSlaveAction, PacketMasterEquip, PacketAssignCellToCollar, PacketNpcCommand, PacketFurnitureForcemount Phase 5 (events): RestraintTaskTickHandler, PetPlayRestrictionHandler, PlayerEnslavementHandler, ChatEventHandler, LaborAttackPunishmentHandler Phase 6 (commands): BondageSubCommand, CollarCommand, NPCCommand, KidnapSetCommand Phase 7 (compat): MCAKidnappedAdapter, MCA mixins Phase 8 (misc): GagTalkManager, PetRequestManager, HangingCagePiece, BondageItemBlockEntity, TrappedChestBlockEntity, DispenserBehaviors, BondageItemLoaderUtility, RestraintApplicator, StruggleSessionManager, MovementStyleResolver, CampLifecycleManager Some files retain dual V1/V2 checks (instanceof V1 || V2Helper) for coexistence — V1-only branches removed in Branch D.
This commit is contained in:
@@ -11,7 +11,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.EntityKidnapper.CaptivePriority;
|
||||
import com.tiedup.remake.entities.ai.StuckDetector;
|
||||
import com.tiedup.remake.entities.ai.WaypointNavigator;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.PrisonerRecord;
|
||||
import com.tiedup.remake.prison.PrisonerState;
|
||||
@@ -260,7 +260,7 @@ public class KidnapperBringToCellGoal extends Goal {
|
||||
);
|
||||
IRestrainable captive = this.kidnapper.getCaptive();
|
||||
if (captive != null) {
|
||||
ItemCollar.runWithSuppressedAlert(() ->
|
||||
CollarHelper.runWithSuppressedAlert(() ->
|
||||
captive.free(false)
|
||||
);
|
||||
this.kidnapper.removeCaptive(captive, false);
|
||||
@@ -1079,9 +1079,9 @@ public class KidnapperBringToCellGoal extends Goal {
|
||||
ItemStack collar = captive.getEquipment(BodyRegionV2.NECK);
|
||||
if (
|
||||
!collar.isEmpty() &&
|
||||
collar.getItem() instanceof ItemCollar collarItem
|
||||
CollarHelper.isCollar(collar)
|
||||
) {
|
||||
collarItem.setCellId(collar, this.targetCell.getId());
|
||||
CollarHelper.setCellId(collar, this.targetCell.getId());
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.StuckDetector;
|
||||
import com.tiedup.remake.items.ItemKey;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.action.PacketTying;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
@@ -849,11 +849,8 @@ public class KidnapperCaptureGoal extends Goal {
|
||||
// If already has collar, check ownership
|
||||
if (state.hasCollar()) {
|
||||
ItemStack existingCollar = state.getEquipment(BodyRegionV2.NECK);
|
||||
if (
|
||||
existingCollar.getItem() instanceof
|
||||
com.tiedup.remake.items.base.ItemCollar collarItem
|
||||
) {
|
||||
java.util.List<java.util.UUID> owners = collarItem.getOwners(
|
||||
if (CollarHelper.isCollar(existingCollar)) {
|
||||
java.util.List<java.util.UUID> owners = CollarHelper.getOwners(
|
||||
existingCollar
|
||||
);
|
||||
if (!owners.contains(this.kidnapper.getUUID())) {
|
||||
@@ -885,9 +882,9 @@ public class KidnapperCaptureGoal extends Goal {
|
||||
for (java.util.UUID oldOwner : new java.util.ArrayList<>(
|
||||
owners
|
||||
)) {
|
||||
collarItem.removeOwner(existingCollar, oldOwner);
|
||||
CollarHelper.removeOwner(existingCollar, oldOwner);
|
||||
}
|
||||
collarItem.addOwner(
|
||||
CollarHelper.addOwner(
|
||||
existingCollar,
|
||||
this.kidnapper.getUUID(),
|
||||
this.kidnapper.getNpcName()
|
||||
@@ -929,9 +926,9 @@ public class KidnapperCaptureGoal extends Goal {
|
||||
if (this.captureProgress >= COLLAR_APPLY_TIME) {
|
||||
// Create a copy of the collar and configure it
|
||||
ItemStack collarCopy = collar.copy();
|
||||
if (collarCopy.getItem() instanceof ItemCollar collarItem) {
|
||||
if (CollarHelper.isCollar(collarCopy)) {
|
||||
// Add kidnapper as owner
|
||||
collarItem.addOwner(
|
||||
CollarHelper.addOwner(
|
||||
collarCopy,
|
||||
this.kidnapper.getUUID(),
|
||||
this.kidnapper.getNpcName()
|
||||
@@ -941,12 +938,17 @@ public class KidnapperCaptureGoal extends Goal {
|
||||
ItemStack keyStack = new ItemStack(ModItems.COLLAR_KEY.get());
|
||||
if (keyStack.getItem() instanceof ItemKey keyItem) {
|
||||
UUID keyUUID = keyItem.getKeyUUID(keyStack);
|
||||
collarItem.setLockedByKeyUUID(collarCopy, keyUUID);
|
||||
// Lock via ILockable interface (collar must implement it)
|
||||
if (collarCopy.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
|
||||
lockable.setLockedByKeyUUID(collarCopy, keyUUID);
|
||||
}
|
||||
// Store the key on the kidnapper for potential drop on death
|
||||
this.kidnapper.addCollarKey(keyStack);
|
||||
} else {
|
||||
// Fallback: just lock without a key
|
||||
collarItem.setLocked(collarCopy, true);
|
||||
if (collarCopy.getItem() instanceof com.tiedup.remake.items.base.ILockable lockable) {
|
||||
lockable.setLocked(collarCopy, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import com.tiedup.remake.entities.AbstractTiedUpNpc;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.entities.ai.StuckDetector;
|
||||
import com.tiedup.remake.entities.ai.WaypointNavigator;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.BindVariant;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.util.KidnapperAIHelper;
|
||||
@@ -547,9 +547,7 @@ public class KidnapperWalkPrisonerGoal extends Goal {
|
||||
);
|
||||
|
||||
// 3. Change bind to DOGBINDER
|
||||
ItemStack dogBinder = new ItemStack(
|
||||
ModItems.getBind(BindVariant.DOGBINDER)
|
||||
);
|
||||
ItemStack dogBinder = DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder"));
|
||||
this.walkedPrisoner.equip(BodyRegionV2.ARMS, dogBinder);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
@@ -738,7 +736,7 @@ public class KidnapperWalkPrisonerGoal extends Goal {
|
||||
if (currentBind.isEmpty() || !prisoner.isTiedUp()) {
|
||||
// They freed themselves - put dogbinder back on
|
||||
ItemStack dogBinder = new ItemStack(
|
||||
ModItems.getBind(BindVariant.DOGBINDER)
|
||||
DataDrivenBondageItem.createStack(new ResourceLocation("tiedup", "dogbinder")).getItem()
|
||||
);
|
||||
prisoner.equip(BodyRegionV2.ARMS, dogBinder);
|
||||
}
|
||||
|
||||
@@ -338,10 +338,8 @@ public class MaidReturnGoal extends Goal {
|
||||
// Fallback: use basic rope if no snapshot
|
||||
cap.equip(
|
||||
BodyRegionV2.ARMS,
|
||||
new ItemStack(
|
||||
com.tiedup.remake.items.ModItems.getBind(
|
||||
com.tiedup.remake.items.base.BindVariant.ROPES
|
||||
)
|
||||
com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem.createStack(
|
||||
new net.minecraft.resources.ResourceLocation("tiedup", "ropes")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.tiedup.remake.entities.ai.master;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.DialogueBridge;
|
||||
import com.tiedup.remake.entities.EntityMaster;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.BindVariant;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
@@ -394,8 +394,8 @@ public class MasterHumanChairGoal extends Goal {
|
||||
|
||||
// Apply invisible dogbind for the pose animation
|
||||
if (!bindState.isTiedUp()) {
|
||||
ItemStack dogbind = new ItemStack(
|
||||
ModItems.getBind(BindVariant.DOGBINDER)
|
||||
ItemStack dogbind = DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "dogbinder")
|
||||
);
|
||||
CompoundTag tag = dogbind.getOrCreateTag();
|
||||
tag.putBoolean(NBT_HUMAN_CHAIR_BIND, true);
|
||||
|
||||
@@ -3,12 +3,9 @@ package com.tiedup.remake.entities.ai.master;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.DialogueBridge;
|
||||
import com.tiedup.remake.entities.EntityMaster;
|
||||
import com.tiedup.remake.items.ItemChokeCollar;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.BindVariant;
|
||||
import com.tiedup.remake.items.base.BlindfoldVariant;
|
||||
import com.tiedup.remake.items.base.GagVariant;
|
||||
import com.tiedup.remake.items.base.MittensVariant;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
||||
@@ -261,7 +258,7 @@ public class MasterPunishGoal extends Goal {
|
||||
// CHOKE: only if pet has choke collar
|
||||
if (bindState != null && bindState.hasCollar()) {
|
||||
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
|
||||
if (collar.getItem() instanceof ItemChokeCollar) {
|
||||
if (CollarHelper.isChokeCollar(collar)) {
|
||||
available.add(PunishmentType.CHOKE_COLLAR);
|
||||
}
|
||||
}
|
||||
@@ -393,8 +390,8 @@ public class MasterPunishGoal extends Goal {
|
||||
PlayerBindState bindState = PlayerBindState.getInstance(pet);
|
||||
if (bindState != null && bindState.hasCollar()) {
|
||||
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
|
||||
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) {
|
||||
chokeCollar.setChoking(collar, true);
|
||||
if (CollarHelper.isChokeCollar(collar)) {
|
||||
CollarHelper.setChoking(collar, true);
|
||||
this.activeChokeCollar = collar;
|
||||
this.chokeActiveTimer = 1;
|
||||
|
||||
@@ -457,12 +454,14 @@ public class MasterPunishGoal extends Goal {
|
||||
*/
|
||||
private ItemStack createAccessory(BodyRegionV2 region) {
|
||||
return switch (region) {
|
||||
case EYES -> new ItemStack(
|
||||
ModItems.getBlindfold(BlindfoldVariant.CLASSIC)
|
||||
case EYES -> DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "classic_blindfold")
|
||||
);
|
||||
case MOUTH -> new ItemStack(ModItems.getGag(GagVariant.BALL_GAG));
|
||||
case HANDS -> new ItemStack(
|
||||
ModItems.getMittens(MittensVariant.LEATHER)
|
||||
case MOUTH -> DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "ball_gag")
|
||||
);
|
||||
case HANDS -> DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "leather_mittens")
|
||||
);
|
||||
default -> ItemStack.EMPTY;
|
||||
};
|
||||
@@ -472,8 +471,8 @@ public class MasterPunishGoal extends Goal {
|
||||
* Apply armbinder as punishment.
|
||||
*/
|
||||
private void applyTighten(ServerPlayer pet) {
|
||||
ItemStack armbinder = new ItemStack(
|
||||
ModItems.getBind(BindVariant.ARMBINDER)
|
||||
ItemStack armbinder = DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "armbinder")
|
||||
);
|
||||
|
||||
// Mark as temporary
|
||||
@@ -545,9 +544,9 @@ public class MasterPunishGoal extends Goal {
|
||||
private void deactivateChoke() {
|
||||
if (
|
||||
!activeChokeCollar.isEmpty() &&
|
||||
activeChokeCollar.getItem() instanceof ItemChokeCollar chokeCollar
|
||||
CollarHelper.isChokeCollar(activeChokeCollar)
|
||||
) {
|
||||
chokeCollar.setChoking(activeChokeCollar, false);
|
||||
CollarHelper.setChoking(activeChokeCollar, false);
|
||||
}
|
||||
this.activeChokeCollar = ItemStack.EMPTY;
|
||||
this.chokeActiveTimer = 0;
|
||||
|
||||
@@ -3,11 +3,8 @@ package com.tiedup.remake.entities.ai.master;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.DialogueBridge;
|
||||
import com.tiedup.remake.entities.EntityMaster;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.BindVariant;
|
||||
import com.tiedup.remake.items.base.BlindfoldVariant;
|
||||
import com.tiedup.remake.items.base.GagVariant;
|
||||
import com.tiedup.remake.items.base.MittensVariant;
|
||||
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
||||
@@ -17,7 +14,6 @@ import java.util.List;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
@@ -249,15 +245,18 @@ public class MasterRandomEventGoal extends Goal {
|
||||
* Create a random accessory item for the given body region.
|
||||
*/
|
||||
private ItemStack createRandomAccessory(BodyRegionV2 region) {
|
||||
Item item = switch (region) {
|
||||
case EYES -> ModItems.getBlindfold(BlindfoldVariant.CLASSIC);
|
||||
case MOUTH -> ModItems.getGag(GagVariant.BALL_GAG);
|
||||
case HANDS -> ModItems.getMittens(MittensVariant.LEATHER);
|
||||
default -> null;
|
||||
return switch (region) {
|
||||
case EYES -> DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "classic_blindfold")
|
||||
);
|
||||
case MOUTH -> DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "ball_gag")
|
||||
);
|
||||
case HANDS -> DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "leather_mittens")
|
||||
);
|
||||
default -> ItemStack.EMPTY;
|
||||
};
|
||||
|
||||
if (item == null) return ItemStack.EMPTY;
|
||||
return new ItemStack(item);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,8 +303,8 @@ public class MasterRandomEventGoal extends Goal {
|
||||
// Put pet in dogbind if not already tied
|
||||
PlayerBindState bindState = PlayerBindState.getInstance(pet);
|
||||
if (bindState != null && !bindState.isTiedUp()) {
|
||||
ItemStack dogbind = new ItemStack(
|
||||
ModItems.getBind(BindVariant.DOGBINDER)
|
||||
ItemStack dogbind = DataDrivenBondageItem.createStack(
|
||||
new ResourceLocation("tiedup", "dogbinder")
|
||||
);
|
||||
bindState.equip(BodyRegionV2.ARMS, dogbind);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.tiedup.remake.entities.ai.master;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.DialogueBridge;
|
||||
import com.tiedup.remake.entities.EntityMaster;
|
||||
import com.tiedup.remake.items.ItemChokeCollar;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.util.MessageDispatcher;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
@@ -441,8 +441,8 @@ public class MasterTaskWatchGoal extends Goal {
|
||||
if (bindState != null && bindState.hasCollar()) {
|
||||
ItemStack collar = bindState.getEquipment(BodyRegionV2.NECK);
|
||||
|
||||
if (collar.getItem() instanceof ItemChokeCollar chokeCollar) {
|
||||
chokeCollar.setChoking(collar, true);
|
||||
if (CollarHelper.isChokeCollar(collar)) {
|
||||
CollarHelper.setChoking(collar, true);
|
||||
this.activeChokeCollar = collar;
|
||||
this.chokeTimer = CHOKE_DURATION;
|
||||
|
||||
@@ -475,9 +475,9 @@ public class MasterTaskWatchGoal extends Goal {
|
||||
private void deactivateChoke() {
|
||||
if (
|
||||
!activeChokeCollar.isEmpty() &&
|
||||
activeChokeCollar.getItem() instanceof ItemChokeCollar chokeCollar
|
||||
CollarHelper.isChokeCollar(activeChokeCollar)
|
||||
) {
|
||||
chokeCollar.setChoking(activeChokeCollar, false);
|
||||
CollarHelper.setChoking(activeChokeCollar, false);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[MasterTaskWatchGoal] {} deactivated choke",
|
||||
master.getNpcName()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.tiedup.remake.entities.ai.personality;
|
||||
|
||||
import com.tiedup.remake.entities.EntityDamsel;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||
import com.tiedup.remake.personality.NpcCommand;
|
||||
import com.tiedup.remake.personality.ToolMode;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
@@ -506,7 +506,7 @@ public class NpcFollowCommandGoal extends Goal {
|
||||
if (dist <= ATTACK_RANGE) {
|
||||
// Try to capture using bind item
|
||||
ItemStack bindItem = npc.getMainHandItem();
|
||||
if (bindItem.getItem() instanceof ItemBind) {
|
||||
if (BindModeHelper.isBindItem(bindItem)) {
|
||||
// Apply bind to target
|
||||
captureTarget.equip(BodyRegionV2.ARMS, bindItem.copy());
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.tiedup.remake.entities.ai.personality;
|
||||
|
||||
import com.tiedup.remake.entities.EntityDamsel;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.v2.bondage.BindModeHelper;
|
||||
import com.tiedup.remake.v2.bondage.CollarHelper;
|
||||
import com.tiedup.remake.personality.NpcCommand;
|
||||
import com.tiedup.remake.personality.PersonalityState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
@@ -426,7 +426,7 @@ public class NpcGuardCommandGoal extends Goal {
|
||||
NonNullList<ItemStack> inventory = npc.getNpcInventory();
|
||||
for (int i = 0; i < inventory.size(); i++) {
|
||||
ItemStack stack = inventory.get(i);
|
||||
if (stack.getItem() instanceof ItemBind) {
|
||||
if (BindModeHelper.isBindItem(stack)) {
|
||||
// Apply bind to slave
|
||||
slave.equip(BodyRegionV2.ARMS, stack.copy());
|
||||
stack.shrink(1);
|
||||
@@ -486,8 +486,8 @@ public class NpcGuardCommandGoal extends Goal {
|
||||
private UUID getCollarOwnerUUID(EntityDamsel slave) {
|
||||
if (!slave.hasCollar()) return null;
|
||||
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
|
||||
if (collar.getItem() instanceof ItemCollar collarItem) {
|
||||
java.util.List<UUID> owners = collarItem.getOwners(collar);
|
||||
if (CollarHelper.isCollar(collar)) {
|
||||
java.util.List<UUID> owners = CollarHelper.getOwners(collar);
|
||||
if (!owners.isEmpty()) {
|
||||
return owners.get(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user