💻 MORE SYSTEMS - Barber, Lawyer, Zombie Miners
Added 3 additional game systems: 1. BARBER SHOP SYSTEM (BarberShopSystem.js): - 7 hairstyles (dreadlocks, mohawks, long hair, ponytail, bald) - 5 piercing types (ear gauges, nose ring, eyebrow, lip) - 9 hair dye colors + clothing dyes - Zombie makeover (cosmetic for workers, +20 loyalty) - NPC customization (requires 5+ hearts) - 5 saved look slots - Repeat customer discounts (5+ visits = 10% off) 2. LAWYER OFFICE SYSTEM (LawyerOfficeSystem.js): - Divorce processing (50,000g + 25% money loss) - Prenup system (10,000g, reduces loss to 10%) - Marriage counseling (5,000g, 3 tasks to save marriage) - Relationship crisis detection (auto-unlock at 3 hearts) - 28-day remarriage cooldown - Post-divorce quests 3. ZOMBIE MINER AUTOMATION (ZombieMinerAutomationSystem.js): - Hire zombie miners (5,000g each, max 10) - Assign to specific mine depths - Efficiency & loyalty mechanics (0-100%) - Passive resource generation (yield per hour) - Zombie leveling system (XP from mining) - Equipment upgrades (pickaxe tiers, lamps, oxygen, carts) - Feed zombies to restore loyalty Total new systems: 6 Total code: 3,100+ lines All systems include event emission for UI integration Next: Town Growth & NPC Privacy systems
This commit is contained in:
663
src/systems/BarberShopSystem.js
Normal file
663
src/systems/BarberShopSystem.js
Normal file
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* BARBER SHOP SYSTEM - Character Customization & Styling
|
||||
* Part of: New Town Buildings
|
||||
* Created: January 4, 2026
|
||||
*
|
||||
* Features:
|
||||
* - Hairstyle changes (dreadlocks, mohawk, long hair, ponytail, bald)
|
||||
* - Piercings & body modifications (ear gauges, nose ring, eyebrow, lip)
|
||||
* - Color customization (hair dye, clothing)
|
||||
* - Zombie makeover (cosmetic for workers)
|
||||
* - NPC customization (change other characters)
|
||||
* - Save/load favorite looks
|
||||
*/
|
||||
|
||||
export class BarberShopSystem {
|
||||
constructor(game) {
|
||||
this.game = game;
|
||||
this.player = game.player;
|
||||
|
||||
// Barber shop status
|
||||
this.isUnlocked = false;
|
||||
this.isOpen = false;
|
||||
this.barber = null;
|
||||
|
||||
// Hairstyle catalog
|
||||
this.hairstyles = this.initializeHairstyles();
|
||||
|
||||
// Piercing catalog
|
||||
this.piercings = this.initializePiercings();
|
||||
|
||||
// Hair dye colors
|
||||
this.dyeColors = this.initializeDyeColors();
|
||||
|
||||
// Player's current appearance
|
||||
this.playerAppearance = {
|
||||
hairstyle: 'default',
|
||||
hairColor: 'brown',
|
||||
piercings: [],
|
||||
clothing: {
|
||||
shirt: 'default',
|
||||
pants: 'default',
|
||||
colorShirt: 'blue',
|
||||
colorPants: 'brown'
|
||||
}
|
||||
};
|
||||
|
||||
// Saved looks (5 slots)
|
||||
this.savedLooks = [null, null, null, null, null];
|
||||
|
||||
// Visit counter (for discounts)
|
||||
this.visitCount = 0;
|
||||
this.lastVisitDate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hairstyle catalog
|
||||
*/
|
||||
initializeHairstyles() {
|
||||
return {
|
||||
default: {
|
||||
id: 'default',
|
||||
name: 'Default Hair',
|
||||
price: 0,
|
||||
unlocked: true,
|
||||
sprite: 'hair_default',
|
||||
description: 'Your natural hair.'
|
||||
},
|
||||
dreadlocks_pink_green: {
|
||||
id: 'dreadlocks_pink_green',
|
||||
name: 'Pink & Green Dreadlocks',
|
||||
price: 500,
|
||||
unlocked: true,
|
||||
signature: 'kai', // Kai's signature style
|
||||
sprite: 'hair_dreadlocks_pg',
|
||||
description: 'Kai\'s iconic pink and green dreads!'
|
||||
},
|
||||
mohawk_blue: {
|
||||
id: 'mohawk_blue',
|
||||
name: 'Blue Mohawk',
|
||||
price: 300,
|
||||
unlocked: true,
|
||||
sprite: 'hair_mohawk_blue',
|
||||
description: 'Stand tall with this punk mohawk.'
|
||||
},
|
||||
mohawk_purple: {
|
||||
id: 'mohawk_purple',
|
||||
name: 'Purple Mohawk',
|
||||
price: 300,
|
||||
unlocked: true,
|
||||
sprite: 'hair_mohawk_purple',
|
||||
description: 'Purple power!'
|
||||
},
|
||||
mohawk_red: {
|
||||
id: 'mohawk_red',
|
||||
name: 'Red Mohawk',
|
||||
price: 300,
|
||||
unlocked: true,
|
||||
sprite: 'hair_mohawk_red',
|
||||
description: 'Fiery red mohawk.'
|
||||
},
|
||||
long_hair: {
|
||||
id: 'long_hair',
|
||||
name: 'Long Hair',
|
||||
price: 400,
|
||||
unlocked: true,
|
||||
dyeable: true,
|
||||
sprite: 'hair_long',
|
||||
description: 'Flowing long hair, can be dyed any color.'
|
||||
},
|
||||
ponytail: {
|
||||
id: 'ponytail',
|
||||
name: 'Ponytail',
|
||||
price: 250,
|
||||
unlocked: true,
|
||||
dyeable: true,
|
||||
sprite: 'hair_ponytail',
|
||||
description: 'Practical and stylish.'
|
||||
},
|
||||
bald: {
|
||||
id: 'bald',
|
||||
name: 'Bald',
|
||||
price: 100,
|
||||
unlocked: true,
|
||||
reversible: true, // Can revert for free
|
||||
sprite: 'hair_none',
|
||||
description: 'Clean shaven head. Can reverse for FREE!'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize piercing catalog
|
||||
*/
|
||||
initializePiercings() {
|
||||
return {
|
||||
ear_gauges: {
|
||||
id: 'ear_gauges',
|
||||
name: 'Ear Gauges',
|
||||
price: 200,
|
||||
unlocked: true,
|
||||
signature: 'kai', // Kai's trademark
|
||||
sprite: 'piercing_gauges',
|
||||
description: 'Large ear gauges like Kai\'s!'
|
||||
},
|
||||
nose_ring: {
|
||||
id: 'nose_ring',
|
||||
name: 'Nose Ring',
|
||||
price: 150,
|
||||
unlocked: true,
|
||||
sprite: 'piercing_nose',
|
||||
description: 'Simple nose ring.'
|
||||
},
|
||||
eyebrow_piercing: {
|
||||
id: 'eyebrow_piercing',
|
||||
name: 'Eyebrow Piercing',
|
||||
price: 100,
|
||||
unlocked: true,
|
||||
sprite: 'piercing_eyebrow',
|
||||
description: 'Edgy eyebrow piercing.'
|
||||
},
|
||||
lip_ring: {
|
||||
id: 'lip_ring',
|
||||
name: 'Lip Ring',
|
||||
price: 150,
|
||||
unlocked: true,
|
||||
sprite: 'piercing_lip',
|
||||
description: 'Lip ring for that punk look.'
|
||||
},
|
||||
multiple_ear: {
|
||||
id: 'multiple_ear',
|
||||
name: 'Multiple Ear Piercings',
|
||||
price: 50,
|
||||
stackable: true, // Can have multiple
|
||||
sprite: 'piercing_ear_multiple',
|
||||
description: 'Each additional piercing costs 50g.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hair dye colors
|
||||
*/
|
||||
initializeDyeColors() {
|
||||
return {
|
||||
brown: { name: 'Brown', price: 0, hex: '#654321' },
|
||||
black: { name: 'Black', price: 100, hex: '#000000' },
|
||||
blonde: { name: 'Blonde', price: 150, hex: '#FFD700' },
|
||||
red: { name: 'Red', price: 200, hex: '#FF0000' },
|
||||
blue: { name: 'Blue', price: 200, hex: '#0000FF' },
|
||||
purple: { name: 'Purple', price: 200, hex: '#800080' },
|
||||
pink: { name: 'Pink', price: 200, hex: '#FF69B4' },
|
||||
green: { name: 'Green', price: 200, hex: '#00FF00' },
|
||||
white: { name: 'White', price: 250, hex: '#FFFFFF' }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock barber shop
|
||||
*/
|
||||
unlockBarberShop() {
|
||||
// Check requirements
|
||||
const npcCount = this.game.npcs.getPopulationCount();
|
||||
if (npcCount < 3) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Need at least 3 NPCs in town first!'
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.player.hasCompletedQuest('style_matters')) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Complete "Style Matters" quest first!'
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.player.hasMaterials({ wood: 75, iron: 25 })) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Need 75 Wood + 25 Iron to build barber shop!'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.player.money < 6000) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Need 6,000g to build barber shop!'
|
||||
};
|
||||
}
|
||||
|
||||
// Build barber shop
|
||||
this.player.removeMaterials({ wood: 75, iron: 25 });
|
||||
this.player.money -= 6000;
|
||||
this.isUnlocked = true;
|
||||
this.isOpen = true;
|
||||
|
||||
// Spawn barber NPC
|
||||
this.barber = this.game.npcs.spawn('barber', {
|
||||
name: 'Razor',
|
||||
position: { x: 1400, y: 900 },
|
||||
appearance: {
|
||||
hairstyle: 'mohawk_purple',
|
||||
piercings: ['ear_gauges', 'nose_ring', 'lip_ring']
|
||||
}
|
||||
});
|
||||
|
||||
this.game.showMessage('💇 Barber Shop is now open!');
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Change hairstyle
|
||||
*/
|
||||
changeHairstyle(hairstyleId) {
|
||||
const hairstyle = this.hairstyles[hairstyleId];
|
||||
|
||||
if (!hairstyle) {
|
||||
return { success: false, message: 'Hairstyle not found!' };
|
||||
}
|
||||
|
||||
// Check if shop is open
|
||||
if (!this.isOpen) {
|
||||
return { success: false, message: 'Barber shop is closed!' };
|
||||
}
|
||||
|
||||
// Check price (with discount)
|
||||
const discount = this.getVisitDiscount();
|
||||
const price = Math.floor(hairstyle.price * (1 - discount));
|
||||
|
||||
// Free reversal from bald
|
||||
if (hairstyle.reversible && this.playerAppearance.hairstyle === 'bald') {
|
||||
// Revert to previous hairstyle for free
|
||||
this.playerAppearance.hairstyle = this.previousHairstyle || 'default';
|
||||
this.game.showMessage('Hair restored for FREE!');
|
||||
return { success: true, price: 0 };
|
||||
}
|
||||
|
||||
// Check money
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Save previous hairstyle (for bald reversal)
|
||||
this.previousHairstyle = this.playerAppearance.hairstyle;
|
||||
|
||||
// Change hairstyle
|
||||
this.player.money -= price;
|
||||
this.playerAppearance.hairstyle = hairstyleId;
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
// Track visit
|
||||
this.incrementVisitCount();
|
||||
|
||||
let message = `Changed to ${hairstyle.name}! ${price}g`;
|
||||
if (discount > 0) {
|
||||
message += ` (${Math.floor(discount * 100)}% repeat customer discount!)`;
|
||||
}
|
||||
|
||||
this.game.showMessage(message);
|
||||
|
||||
return { success: true, price: price, discount: discount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Add piercing
|
||||
*/
|
||||
addPiercing(piercingId) {
|
||||
const piercing = this.piercings[piercingId];
|
||||
|
||||
if (!piercing) {
|
||||
return { success: false, message: 'Piercing not found!' };
|
||||
}
|
||||
|
||||
// Check if already have (unless stackable)
|
||||
if (!piercing.stackable && this.playerAppearance.piercings.includes(piercingId)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'You already have this piercing!'
|
||||
};
|
||||
}
|
||||
|
||||
// Check price
|
||||
const price = piercing.price;
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Add piercing
|
||||
this.player.money -= price;
|
||||
|
||||
if (piercing.stackable) {
|
||||
// Count how many of this type
|
||||
const count = this.playerAppearance.piercings.filter(p => p === piercingId).length;
|
||||
this.playerAppearance.piercings.push(`${piercingId}_${count + 1}`);
|
||||
} else {
|
||||
this.playerAppearance.piercings.push(piercingId);
|
||||
}
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
// Track visit
|
||||
this.incrementVisitCount();
|
||||
|
||||
this.game.showMessage(`Added ${piercing.name}! ${price}g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove piercing
|
||||
*/
|
||||
removePiercing(piercingId) {
|
||||
const index = this.playerAppearance.piercings.indexOf(piercingId);
|
||||
|
||||
if (index === -1) {
|
||||
return { success: false, message: 'You don\'t have this piercing!' };
|
||||
}
|
||||
|
||||
// Remove free of charge
|
||||
this.playerAppearance.piercings.splice(index, 1);
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
this.game.showMessage('Piercing removed (free).');
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Dye hair color
|
||||
*/
|
||||
dyeHair(colorId) {
|
||||
const color = this.dyeColors[colorId];
|
||||
|
||||
if (!color) {
|
||||
return { success: false, message: 'Color not found!' };
|
||||
}
|
||||
|
||||
// Check if current hairstyle is dyeable
|
||||
const currentHairstyle = this.hairstyles[this.playerAppearance.hairstyle];
|
||||
if (!currentHairstyle.dyeable && currentHairstyle.id !== 'default') {
|
||||
return {
|
||||
success: false,
|
||||
message: 'This hairstyle cannot be dyed!'
|
||||
};
|
||||
}
|
||||
|
||||
// Check price
|
||||
if (this.player.money < color.price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${color.price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Dye hair
|
||||
this.player.money -= color.price;
|
||||
this.playerAppearance.hairColor = colorId;
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
// Track visit
|
||||
this.incrementVisitCount();
|
||||
|
||||
this.game.showMessage(`Hair dyed ${color.name}! ${color.price}g`);
|
||||
|
||||
return { success: true, price: color.price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Dye clothing
|
||||
*/
|
||||
dyeClothing(clothingPart, colorId) {
|
||||
const color = this.dyeColors[colorId];
|
||||
|
||||
if (!color) {
|
||||
return { success: false, message: 'Color not found!' };
|
||||
}
|
||||
|
||||
// Clothing dye is cheaper (half price)
|
||||
const price = Math.floor(color.price / 2);
|
||||
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Not enough money! Need ${price}g`
|
||||
};
|
||||
}
|
||||
|
||||
// Dye clothing
|
||||
this.player.money -= price;
|
||||
|
||||
if (clothingPart === 'shirt') {
|
||||
this.playerAppearance.clothing.colorShirt = colorId;
|
||||
} else if (clothingPart === 'pants') {
|
||||
this.playerAppearance.clothing.colorPants = colorId;
|
||||
}
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
this.game.showMessage(`${clothingPart} dyed ${color.name}! ${price}g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Zombie makeover (cosmetic for workers)
|
||||
*/
|
||||
zombieMakeover(zombieId) {
|
||||
const zombie = this.game.zombieWorkers.get(zombieId);
|
||||
|
||||
if (!zombie) {
|
||||
return { success: false, message: 'Zombie not found!' };
|
||||
}
|
||||
|
||||
const price = 1000;
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Zombie makeover costs 1,000g!'
|
||||
};
|
||||
}
|
||||
|
||||
// Apply makeover
|
||||
this.player.money -= price;
|
||||
|
||||
// Random fancy appearance for zombie
|
||||
const fancyStyles = ['mohawk_blue', 'mohawk_purple', 'dreadlocks_pink_green'];
|
||||
const style = Phaser.Utils.Array.GetRandom(fancyStyles);
|
||||
|
||||
zombie.appearance = {
|
||||
hairstyle: style,
|
||||
accessories: ['fancy_bow_tie', 'top_hat']
|
||||
};
|
||||
|
||||
// Update zombie sprite
|
||||
this.game.zombieWorkers.updateZombieSprite(zombieId);
|
||||
|
||||
// Loyalty bonus (zombies appreciate looking good!)
|
||||
zombie.addLoyalty(20);
|
||||
|
||||
this.game.showMessage(`${zombie.name} looks fabulous! +20 Loyalty! 1,000g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize NPC appearance
|
||||
*/
|
||||
customizeNPC(npcId, changes) {
|
||||
const npc = this.game.npcs.get(npcId);
|
||||
|
||||
if (!npc) {
|
||||
return { success: false, message: 'NPC not found!' };
|
||||
}
|
||||
|
||||
// Check relationship (need 5+ hearts)
|
||||
if (this.player.getRelationshipLevel(npcId) < 5) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Need 5+ hearts with ${npc.name} first!`
|
||||
};
|
||||
}
|
||||
|
||||
const price = 500;
|
||||
if (this.player.money < price) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'NPC customization costs 500g!'
|
||||
};
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
this.player.money -= price;
|
||||
|
||||
if (changes.hairstyle) {
|
||||
npc.appearance.hairstyle = changes.hairstyle;
|
||||
}
|
||||
if (changes.hairColor) {
|
||||
npc.appearance.hairColor = changes.hairColor;
|
||||
}
|
||||
|
||||
// Update NPC sprite
|
||||
this.game.npcs.updateNPCSprite(npcId);
|
||||
|
||||
// NPC reaction
|
||||
npc.addRelationshipPoints(10);
|
||||
this.game.showDialogue(npc.name, "Wow, I love it! Thank you!");
|
||||
|
||||
this.game.showMessage(`Styled ${npc.name}! +10 ❤️ 500g`);
|
||||
|
||||
return { success: true, price: price };
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current look to slot
|
||||
*/
|
||||
saveLook(slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= 5) {
|
||||
return { success: false, message: 'Invalid slot!' };
|
||||
}
|
||||
|
||||
this.savedLooks[slotIndex] = {
|
||||
hairstyle: this.playerAppearance.hairstyle,
|
||||
hairColor: this.playerAppearance.hairColor,
|
||||
piercings: [...this.playerAppearance.piercings],
|
||||
clothing: { ...this.playerAppearance.clothing },
|
||||
name: `Look ${slotIndex + 1}`
|
||||
};
|
||||
|
||||
this.game.showMessage(`Saved look to slot ${slotIndex + 1}!`);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load saved look from slot
|
||||
*/
|
||||
loadLook(slotIndex) {
|
||||
if (slotIndex < 0 || slotIndex >= 5) {
|
||||
return { success: false, message: 'Invalid slot!' };
|
||||
}
|
||||
|
||||
const savedLook = this.savedLooks[slotIndex];
|
||||
if (!savedLook) {
|
||||
return { success: false, message: 'No look saved in this slot!' };
|
||||
}
|
||||
|
||||
// Apply saved look (free!)
|
||||
this.playerAppearance = {
|
||||
hairstyle: savedLook.hairstyle,
|
||||
hairColor: savedLook.hairColor,
|
||||
piercings: [...savedLook.piercings],
|
||||
clothing: { ...savedLook.clothing }
|
||||
};
|
||||
|
||||
// Update sprite
|
||||
this.updatePlayerSprite();
|
||||
|
||||
this.game.showMessage(`Loaded ${savedLook.name}!`);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get visit discount (repeat customers)
|
||||
*/
|
||||
getVisitDiscount() {
|
||||
// 5+ visits = 10% off
|
||||
if (this.visitCount >= 5) {
|
||||
return 0.10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment visit count
|
||||
*/
|
||||
incrementVisitCount() {
|
||||
const today = this.game.time.currentDate;
|
||||
|
||||
// Only count once per day
|
||||
if (this.lastVisitDate !== today) {
|
||||
this.visitCount++;
|
||||
this.lastVisitDate = today;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update player sprite with new appearance
|
||||
*/
|
||||
updatePlayerSprite() {
|
||||
// Emit event for sprite manager to update
|
||||
this.game.emit('playerAppearanceChanged', {
|
||||
appearance: this.playerAppearance
|
||||
});
|
||||
|
||||
// Rebuild player sprite
|
||||
this.player.rebuildSprite(this.playerAppearance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get preview of appearance change
|
||||
*/
|
||||
getPreview(changes) {
|
||||
return {
|
||||
...this.playerAppearance,
|
||||
...changes
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shop UI data
|
||||
*/
|
||||
getShopUIData() {
|
||||
return {
|
||||
isUnlocked: this.isUnlocked,
|
||||
isOpen: this.isOpen,
|
||||
hairstyles: this.hairstyles,
|
||||
piercings: this.piercings,
|
||||
dyeColors: this.dyeColors,
|
||||
currentAppearance: this.playerAppearance,
|
||||
savedLooks: this.savedLooks,
|
||||
visitDiscount: this.getVisitDiscount(),
|
||||
barber: this.barber
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default BarberShopSystem;
|
||||
Reference in New Issue
Block a user