Changed task triggers to be stored differently so that attribute check and damage triggers don't overlap
This commit is contained in:
@@ -1,139 +0,0 @@
|
|||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|
||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
|
||||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions';
|
|
||||||
|
|
||||||
const damageProperty = new ValidatedMethod({
|
|
||||||
name: 'creatureProperties.damage',
|
|
||||||
validate: new SimpleSchema({
|
|
||||||
_id: SimpleSchema.RegEx.Id,
|
|
||||||
operation: {
|
|
||||||
type: String,
|
|
||||||
allowedValues: ['set', 'increment']
|
|
||||||
},
|
|
||||||
value: Number,
|
|
||||||
}).validator(),
|
|
||||||
mixins: [RateLimiterMixin],
|
|
||||||
rateLimit: {
|
|
||||||
numRequests: 20,
|
|
||||||
timeInterval: 5000,
|
|
||||||
},
|
|
||||||
run({ _id, operation, value }) {
|
|
||||||
|
|
||||||
// Get action context
|
|
||||||
let prop = CreatureProperties.findOne(_id);
|
|
||||||
if (!prop) throw new Meteor.Error(
|
|
||||||
'Damage property failed', 'Property doesn\'t exist'
|
|
||||||
);
|
|
||||||
const creatureId = prop.root.id;
|
|
||||||
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
|
||||||
|
|
||||||
// Check permissions
|
|
||||||
assertEditPermission(actionContext.creature, this.userId);
|
|
||||||
|
|
||||||
// Check if property can take damage
|
|
||||||
let schema = CreatureProperties.simpleSchema(prop);
|
|
||||||
if (!schema.allowsKey('damage')) {
|
|
||||||
throw new Meteor.Error(
|
|
||||||
'Damage property failed',
|
|
||||||
`Property of type "${prop.type}" can't be damaged`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the prop by its actionContext counterpart if possible
|
|
||||||
if (prop.variableName) {
|
|
||||||
const actionContextProp = actionContext.scope[prop.variableName];
|
|
||||||
if (actionContextProp?._id === prop._id) {
|
|
||||||
prop = actionContextProp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
|
||||||
|
|
||||||
// Insert the log
|
|
||||||
actionContext.writeLog();
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function damagePropertyWork({ prop, operation, value, actionContext, logFunction = undefined }) {
|
|
||||||
|
|
||||||
// Save the value to the scope before applying the before triggers
|
|
||||||
if (operation === 'increment') {
|
|
||||||
if (value >= 0) {
|
|
||||||
actionContext.scope['~damage'] = { value };
|
|
||||||
} else {
|
|
||||||
actionContext.scope['~healing'] = { value: -value };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
actionContext.scope['~set'] = { value };
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTriggers(actionContext.triggers?.damageProperty?.before, prop, actionContext);
|
|
||||||
|
|
||||||
// fetch the value from the scope after the before triggers, in case they changed them
|
|
||||||
if (operation === 'increment') {
|
|
||||||
if (value >= 0) {
|
|
||||||
value = actionContext.scope['~damage']?.value;
|
|
||||||
} else {
|
|
||||||
value = -actionContext.scope['~healing']?.value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = actionContext.scope['~set']?.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let damage, newValue, increment;
|
|
||||||
if (operation === 'set') {
|
|
||||||
const total = prop.total || 0;
|
|
||||||
// Set represents what we want the value to be after damage
|
|
||||||
// So we need the actual damage to get to that value
|
|
||||||
damage = total - value;
|
|
||||||
// Damage can't exceed total value
|
|
||||||
if (damage > total && !prop.ignoreLowerLimit) damage = total;
|
|
||||||
// Damage must be positive
|
|
||||||
if (damage < 0 && !prop.ignoreUpperLimit) damage = 0;
|
|
||||||
newValue = prop.total - damage;
|
|
||||||
// Write the results
|
|
||||||
CreatureProperties.update(prop._id, {
|
|
||||||
$set: { damage, value: newValue, dirty: true }
|
|
||||||
}, {
|
|
||||||
selector: prop
|
|
||||||
});
|
|
||||||
// Also write it straight to the prop so that it is updated in the actionContext
|
|
||||||
prop.damage = damage;
|
|
||||||
prop.value = newValue;
|
|
||||||
logFunction?.(newValue);
|
|
||||||
} else if (operation === 'increment') {
|
|
||||||
let currentValue = prop.value || 0;
|
|
||||||
let currentDamage = prop.damage || 0;
|
|
||||||
increment = value;
|
|
||||||
// Can't increase damage above the remaining value
|
|
||||||
if (increment > currentValue && !prop.ignoreLowerLimit) increment = currentValue;
|
|
||||||
// Can't decrease damage below zero
|
|
||||||
if (-increment > currentDamage && !prop.ignoreUpperLimit) increment = -currentDamage;
|
|
||||||
damage = currentDamage + increment;
|
|
||||||
newValue = prop.total - damage;
|
|
||||||
// Write the results
|
|
||||||
CreatureProperties.update(prop._id, {
|
|
||||||
$inc: { damage: increment, value: -increment },
|
|
||||||
$set: { dirty: true },
|
|
||||||
}, {
|
|
||||||
selector: prop
|
|
||||||
});
|
|
||||||
// Also write it straight to the prop so that it is updated in the actionContext
|
|
||||||
prop.damage += increment;
|
|
||||||
prop.value -= increment;
|
|
||||||
logFunction?.(increment);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext);
|
|
||||||
|
|
||||||
if (operation === 'set') {
|
|
||||||
return damage;
|
|
||||||
} else if (operation === 'increment') {
|
|
||||||
return increment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default damageProperty;
|
|
||||||
@@ -131,7 +131,7 @@ export async function applyAfterPropTasksForSomeChildren(
|
|||||||
export async function applyTriggers(
|
export async function applyTriggers(
|
||||||
action: EngineAction, prop, targetIds: string[], triggerPath: string, inputProvider: InputProvider
|
action: EngineAction, prop, targetIds: string[], triggerPath: string, inputProvider: InputProvider
|
||||||
) {
|
) {
|
||||||
const triggerIds = get(prop?.triggerIds, triggerPath);
|
const triggerIds = get(prop, triggerPath);
|
||||||
if (!triggerIds) return;
|
if (!triggerIds) return;
|
||||||
for (const triggerId of triggerIds) {
|
for (const triggerId of triggerIds) {
|
||||||
const trigger = await getSingleProperty(action.creatureId, triggerId);
|
const trigger = await getSingleProperty(action.creatureId, triggerId);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
} from '/imports/api/engine/loadCreatures';
|
} from '/imports/api/engine/loadCreatures';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions';
|
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions';
|
||||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty';
|
|
||||||
|
|
||||||
// TODO Migrate this to the new action engine
|
// TODO Migrate this to the new action engine
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { applyDefaultAfterPropTasks } from '/imports/api/engine/action/functions/applyTaskGroups';
|
import { applyTriggers } from '/imports/api/engine/action/functions/applyTaskGroups';
|
||||||
import { CheckTask } from '/imports/api/engine/action/tasks/Task';
|
import { CheckTask } from '/imports/api/engine/action/tasks/Task';
|
||||||
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
import { EngineAction } from '/imports/api/engine/action/EngineActions';
|
||||||
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
import { getEffectiveActionScope } from '/imports/api/engine/action/functions/getEffectiveActionScope';
|
||||||
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
import { getFromScope } from '/imports/api/creature/creatures/CreatureVariables';
|
||||||
import { getVariables } from '/imports/api/engine/loadCreatures';
|
import { getVariables } from '/imports/api/engine/loadCreatures';
|
||||||
import InputProvider, { CheckParams } from '/imports/api/engine/action/functions/userInput/InputProvider';
|
import InputProvider from '/imports/api/engine/action/functions/userInput/InputProvider';
|
||||||
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
import numberToSignedString from '/imports/api/utility/numberToSignedString';
|
||||||
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
import TaskResult from '/imports/api/engine/action/tasks/TaskResult';
|
||||||
|
|
||||||
@@ -38,6 +38,17 @@ export default async function applyCheckTask(
|
|||||||
const ability = checkParams.abilityVariableName && getFromScope(checkParams.abilityVariableName, scope) || null;
|
const ability = checkParams.abilityVariableName && getFromScope(checkParams.abilityVariableName, scope) || null;
|
||||||
const abilityModifier = ability?.modifier || 0;
|
const abilityModifier = ability?.modifier || 0;
|
||||||
|
|
||||||
|
|
||||||
|
// Run the before triggers which may change scope properties
|
||||||
|
if (skill) await applyTriggers(action, skill, [targetId], 'checkTriggerIds.before', userInput);
|
||||||
|
if (ability) await applyTriggers(action, ability, [targetId], 'checkTriggerIds.before', userInput);
|
||||||
|
|
||||||
|
if (skill || ability) {
|
||||||
|
// Create a new result after before triggers have run
|
||||||
|
result = new TaskResult(task.prop._id, task.targetIds);
|
||||||
|
action.results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
const totalModifier = skillBonus + abilityModifier;
|
const totalModifier = skillBonus + abilityModifier;
|
||||||
const rollModifierText = numberToSignedString(totalModifier);
|
const rollModifierText = numberToSignedString(totalModifier);
|
||||||
|
|
||||||
@@ -100,9 +111,15 @@ export default async function applyCheckTask(
|
|||||||
inline: true,
|
inline: true,
|
||||||
...prop?.silent && { silenced: prop.silent }
|
...prop?.silent && { silenced: prop.silent }
|
||||||
}, [targetId]);
|
}, [targetId]);
|
||||||
}
|
|
||||||
|
|
||||||
return applyDefaultAfterPropTasks(action, prop, targetIds, userInput);
|
// After check triggers
|
||||||
|
if (skill) await applyTriggers(action, skill, [targetId], 'checkTriggerIds.after', userInput);
|
||||||
|
if (ability) await applyTriggers(action, ability, [targetId], 'checkTriggerIds.after', userInput);
|
||||||
|
|
||||||
|
// After children check triggers
|
||||||
|
if (skill) await applyTriggers(action, skill, [targetId], 'checkTriggerIds.afterChildren', userInput);
|
||||||
|
if (ability) await applyTriggers(action, ability, [targetId], 'checkTriggerIds.afterChildren', userInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO set these and potentially read them again if triggers can change them
|
// TODO set these and potentially read them again if triggers can change them
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default async function applyDamagePropTask(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run the before triggers which may change scope properties
|
// Run the before triggers which may change scope properties
|
||||||
await applyTriggers(action, targetProp, [action.creatureId], 'before', userInput);
|
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.before', userInput);
|
||||||
|
|
||||||
// Create a new result after triggers have run
|
// Create a new result after triggers have run
|
||||||
result = new TaskResult(task.prop._id, task.targetIds);
|
result = new TaskResult(task.prop._id, task.targetIds);
|
||||||
@@ -136,8 +136,8 @@ export default async function applyDamagePropTask(
|
|||||||
});
|
});
|
||||||
setScope(result, targetProp, newValue, damage);
|
setScope(result, targetProp, newValue, damage);
|
||||||
}
|
}
|
||||||
await applyTriggers(action, targetProp, [action.creatureId], 'after', userInput);
|
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.after', userInput);
|
||||||
await applyTriggers(action, targetProp, [action.creatureId], 'afterChildren', userInput);
|
await applyTriggers(action, targetProp, [targetId], 'damageTriggerIds.afterChildren', userInput);
|
||||||
return increment;
|
return increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
|
|||||||
result.pushScope = { ['~ammoConsumed']: { value } };
|
result.pushScope = { ['~ammoConsumed']: { value } };
|
||||||
|
|
||||||
// Apply the before triggers
|
// Apply the before triggers
|
||||||
await applyTriggers(action, item, [action.creatureId], 'ammo.before', userInput);
|
await applyTriggers(action, item, task.targetIds, 'ammoTriggerIds.before', userInput);
|
||||||
|
|
||||||
|
// Create a new result after before triggers have run
|
||||||
|
result = new TaskResult(task.prop._id, task.targetIds);
|
||||||
|
action.results.push(result);
|
||||||
|
|
||||||
// Refetch the scope properties
|
// Refetch the scope properties
|
||||||
const scope = await getEffectiveActionScope(action);
|
const scope = await getEffectiveActionScope(action);
|
||||||
@@ -52,11 +56,12 @@ export default async function applyItemAsAmmoTask(task: ItemAsAmmoTask, action:
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await applyTriggers(action, item, [action.creatureId], 'ammo.after', userInput);
|
await applyTriggers(action, item, task.targetIds, 'ammoTriggerIds.after', userInput);
|
||||||
|
|
||||||
if (task.params.skipChildren) {
|
if (task.params.skipChildren) {
|
||||||
return applyAfterTasksSkipChildren(action, item, task.targetIds, userInput);
|
await applyAfterTasksSkipChildren(action, item, task.targetIds, userInput);
|
||||||
} else {
|
} else {
|
||||||
return applyDefaultAfterPropTasks(action, item, task.targetIds, userInput);
|
await applyDefaultAfterPropTasks(action, item, task.targetIds, userInput);
|
||||||
}
|
}
|
||||||
|
return applyTriggers(action, item, task.targetIds, 'ammoTriggerIds.afterChildren', userInput);
|
||||||
}
|
}
|
||||||
@@ -9,31 +9,40 @@ export default function computeTrigger(computation, node) {
|
|||||||
|
|
||||||
// Link triggers to all the properties that would fire them when applied
|
// Link triggers to all the properties that would fire them when applied
|
||||||
const tagTargets = getEffectTagTargets(prop, computation);
|
const tagTargets = getEffectTagTargets(prop, computation);
|
||||||
switch (prop.event) {
|
for (const targetId of tagTargets) {
|
||||||
case 'doActionProperty':
|
const targetProp = computation.propsById[targetId];
|
||||||
tagTargets.forEach(targetId => {
|
switch (prop.event) {
|
||||||
const targetProp = computation.propsById[targetId];
|
case 'doActionProperty':
|
||||||
// Only apply if the trigger matches this property type
|
// Only apply if the trigger matches this property type
|
||||||
if (targetProp.type !== prop.actionPropertyType) return;
|
if (targetProp.type === prop.actionPropertyType) {
|
||||||
setTrigger(prop, targetProp);
|
setTrigger(prop, targetProp, 'triggerIds');
|
||||||
});
|
}
|
||||||
break;
|
// Or on an item used as ammo
|
||||||
case 'damageProperty':
|
else if (prop.actionPropertyType === 'ammo' && targetProp.type === 'item') {
|
||||||
tagTargets.forEach(targetId => {
|
setTrigger(prop, targetProp, 'ammoTriggerIds');
|
||||||
const targetProp = computation.propsById[targetId];
|
}
|
||||||
|
break;
|
||||||
|
case 'damageProperty':
|
||||||
// Only apply to attributes
|
// Only apply to attributes
|
||||||
if (targetProp.type !== 'attribute') return;
|
if (targetProp.type === 'attribute') {
|
||||||
setTrigger(prop, targetProp);
|
setTrigger(prop, targetProp, 'damageTriggerIds');
|
||||||
});
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'check':
|
||||||
|
// Only apply to attributes and skills
|
||||||
|
if (targetProp.type === 'attribute' || targetProp.type === 'skill') {
|
||||||
|
setTrigger(prop, targetProp, 'checkTriggerIds');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTrigger(prop, targetProp) {
|
function setTrigger(prop, targetProp, field = 'triggerIds') {
|
||||||
let triggerIdArray = get(targetProp, `triggerIds.${prop.timing}`);
|
let triggerIdArray = get(targetProp, `${field}.${prop.timing}`);
|
||||||
if (!triggerIdArray) {
|
if (!triggerIdArray) {
|
||||||
triggerIdArray = [];
|
triggerIdArray = [];
|
||||||
set(targetProp, `triggerIds.${prop.timing}`, triggerIdArray);
|
set(targetProp, `${field}.${prop.timing}`, triggerIdArray);
|
||||||
}
|
}
|
||||||
triggerIdArray.push(prop._id);
|
triggerIdArray.push(prop._id);
|
||||||
}
|
}
|
||||||
@@ -280,9 +280,69 @@ const ComputedOnlyAttributeSchema = createPropertySchema({
|
|||||||
type: Number,
|
type: Number,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Triggers that fire when this property is damaged
|
||||||
|
'damageTriggerIds': {
|
||||||
|
type: Object,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'damageTriggerIds.before': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'damageTriggerIds.before.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
'damageTriggerIds.after': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'damageTriggerIds.after.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
'damageTriggerIds.afterChildren': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'damageTriggerIds.afterChildren.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
// Triggers that fire when this property is used to make a check
|
||||||
|
'checkTriggerIds': {
|
||||||
|
type: Object,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'checkTriggerIds.before': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'checkTriggerIds.before.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
'checkTriggerIds.after': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'checkTriggerIds.after.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
'checkTriggerIds.afterChildren': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'checkTriggerIds.afterChildren.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedAttributeSchema = new SimpleSchema()
|
const ComputedAttributeSchema = new SimpleSchema({})
|
||||||
.extend(ComputedOnlyAttributeSchema)
|
.extend(ComputedOnlyAttributeSchema)
|
||||||
.extend(AttributeSchema);
|
.extend(AttributeSchema);
|
||||||
|
|
||||||
|
|||||||
@@ -68,16 +68,46 @@ const ItemSchema = createPropertySchema({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Triggers that fire when this property is used as ammo
|
||||||
|
'ammoTriggerIds': {
|
||||||
|
type: Object,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'ammoTriggerIds.before': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'ammoTriggerIds.before.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
'ammoTriggerIds.after': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'ammoTriggerIds.after.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
'ammoTriggerIds.afterChildren': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'ammoTriggerIds.afterChildren.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let ComputedOnlyItemSchema = createPropertySchema({
|
const ComputedOnlyItemSchema = createPropertySchema({
|
||||||
description: {
|
description: {
|
||||||
type: 'computedOnlyInlineCalculationField',
|
type: 'computedOnlyInlineCalculationField',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedItemSchema = new SimpleSchema()
|
const ComputedItemSchema = new SimpleSchema({})
|
||||||
.extend(ItemSchema)
|
.extend(ItemSchema)
|
||||||
.extend(ComputedOnlyItemSchema);
|
.extend(ComputedOnlyItemSchema);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user