mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-22 11:38:12 +01:00
Merge pull request #10381 from Zuhayr/hardsuitcontrol
Rig AI movement and module control buffs.
This commit is contained in:
@@ -95,7 +95,7 @@
|
||||
playsound(T, "sparks", 50, 1)
|
||||
anim(T,M,'icons/mob/mob.dmi',,"phaseout",,M.dir)
|
||||
|
||||
/obj/item/rig_module/teleporter/engage(atom/target)
|
||||
/obj/item/rig_module/teleporter/engage(var/atom/target, var/notify_ai)
|
||||
|
||||
if(!..()) return 0
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
var/interface_path = "hardsuit.tmpl"
|
||||
var/ai_interface_path = "hardsuit.tmpl"
|
||||
var/interface_title = "Hardsuit Controller"
|
||||
var/wearer_move_delay //Used for AI moving.
|
||||
var/ai_controlled_move_delay = 10
|
||||
|
||||
// Keeps track of what this rig should spawn with.
|
||||
var/suit_type = "hardsuit"
|
||||
@@ -179,8 +181,8 @@
|
||||
for(var/obj/item/piece in list(helmet,boots,gloves,chest))
|
||||
if(!piece) continue
|
||||
piece.icon_state = "[initial(icon_state)]"
|
||||
if(airtight)
|
||||
piece.item_flags &= ~(STOPPRESSUREDAMAGE|AIRTIGHT)
|
||||
if(airtight)
|
||||
piece.item_flags &= ~(STOPPRESSUREDAMAGE|AIRTIGHT)
|
||||
update_icon(1)
|
||||
|
||||
/obj/item/weapon/rig/proc/toggle_seals(var/mob/living/carbon/human/M,var/instant)
|
||||
@@ -268,9 +270,9 @@
|
||||
for(var/obj/item/piece in list(helmet,boots,gloves,chest))
|
||||
if(!piece) continue
|
||||
piece.icon_state = "[initial(icon_state)][!seal_target ? "" : "_sealed"]"
|
||||
canremove = !seal_target
|
||||
canremove = !seal_target
|
||||
if(airtight)
|
||||
update_component_sealed()
|
||||
update_component_sealed()
|
||||
update_icon(1)
|
||||
return 0
|
||||
|
||||
@@ -289,9 +291,9 @@
|
||||
for(var/obj/item/piece in list(helmet,boots,gloves,chest))
|
||||
if(canremove)
|
||||
piece.item_flags &= ~(STOPPRESSUREDAMAGE|AIRTIGHT)
|
||||
else
|
||||
else
|
||||
piece.item_flags |= (STOPPRESSUREDAMAGE|AIRTIGHT)
|
||||
update_icon(1)
|
||||
update_icon(1)
|
||||
|
||||
/obj/item/weapon/rig/process()
|
||||
|
||||
@@ -324,6 +326,8 @@
|
||||
else
|
||||
if(offline)
|
||||
offline = 0
|
||||
if(istype(wearer) && !wearer.wearing_rig)
|
||||
wearer.wearing_rig = src
|
||||
chest.slowdown = initial(slowdown)
|
||||
|
||||
if(offline)
|
||||
@@ -540,12 +544,10 @@
|
||||
return 0
|
||||
|
||||
/obj/item/weapon/rig/proc/notify_ai(var/message)
|
||||
if(!message || !installed_modules || !installed_modules.len)
|
||||
return
|
||||
for(var/obj/item/rig_module/module in installed_modules)
|
||||
for(var/mob/living/silicon/ai/ai in module.contents)
|
||||
if(ai && ai.client && !ai.stat)
|
||||
ai << "[message]"
|
||||
for(var/obj/item/rig_module/ai_container/module in installed_modules)
|
||||
if(module.integrated_ai && module.integrated_ai.client && !module.integrated_ai.stat)
|
||||
module.integrated_ai << "[message]"
|
||||
. = 1
|
||||
|
||||
/obj/item/weapon/rig/equipped(mob/living/carbon/human/M)
|
||||
..()
|
||||
@@ -562,6 +564,7 @@
|
||||
if(istype(M) && M.back == src)
|
||||
M.visible_message("<font color='blue'><b>[M] struggles into \the [src].</b></font>", "<font color='blue'><b>You struggle into \the [src].</b></font>")
|
||||
wearer = M
|
||||
wearer.wearing_rig = src
|
||||
update_icon()
|
||||
|
||||
/obj/item/weapon/rig/proc/toggle_piece(var/piece, var/mob/living/carbon/human/H, var/deploy_mode)
|
||||
@@ -673,6 +676,7 @@
|
||||
..()
|
||||
for(var/piece in list("helmet","gauntlets","chest","boots"))
|
||||
toggle_piece(piece, user, ONLY_RETRACT)
|
||||
wearer.wearing_rig = null
|
||||
wearer = null
|
||||
|
||||
//Todo
|
||||
@@ -754,14 +758,128 @@
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/*/obj/item/weapon/rig/proc/forced_move(dir)
|
||||
if(locked_down)
|
||||
/obj/item/weapon/rig/proc/ai_can_move_suit(var/mob/user, var/check_user_module = 0, var/check_for_ai = 0)
|
||||
|
||||
if(check_for_ai)
|
||||
if(!(locate(/obj/item/rig_module/ai_container) in contents))
|
||||
return 0
|
||||
var/found_ai
|
||||
for(var/obj/item/rig_module/ai_container/module in contents)
|
||||
if(module.damage >= 2)
|
||||
continue
|
||||
if(module.integrated_ai && module.integrated_ai.client && !module.integrated_ai.stat)
|
||||
found_ai = 1
|
||||
break
|
||||
if(!found_ai)
|
||||
return 0
|
||||
|
||||
if(check_user_module)
|
||||
if(!user || !user.loc || !user.loc.loc)
|
||||
return 0
|
||||
var/obj/item/rig_module/ai_container/module = user.loc.loc
|
||||
if(!istype(module) || module.damage >= 2)
|
||||
user << "<span class='warning'>Your host module is unable to interface with the suit.</span>"
|
||||
return 0
|
||||
|
||||
if(offline || !cell || !cell.charge || locked_down)
|
||||
if(user) user << "<span class='warning'>Your host rig is unpowered and unresponsive.</span>"
|
||||
return 0
|
||||
if(!control_overridden)
|
||||
return
|
||||
if(!wearer || wearer.back != src)
|
||||
if(user) user << "<span class='warning'>Your host rig is not being worn.</span>"
|
||||
return 0
|
||||
wearer.Move(null,dir)*/
|
||||
if(!wearer.stat && !control_overridden && !ai_override_enabled)
|
||||
if(user) user << "<span class='warning'>You are locked out of the suit servo controller.</span>"
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/obj/item/weapon/rig/proc/force_rest(var/mob/user)
|
||||
if(!ai_can_move_suit(user, check_user_module = 1))
|
||||
return
|
||||
wearer.lay_down()
|
||||
user << "<span class='notice'>\The [wearer] is now [wearer.resting ? "resting" : "getting up"].</span>"
|
||||
|
||||
/obj/item/weapon/rig/proc/forced_move(var/direction, var/mob/user)
|
||||
|
||||
// Why is all this shit in client/Move()? Who knows?
|
||||
if(world.time < wearer_move_delay)
|
||||
return
|
||||
|
||||
if(!wearer || !wearer.loc || !ai_can_move_suit(user, check_user_module = 1))
|
||||
return
|
||||
|
||||
//This is sota the goto stop mobs from moving var
|
||||
if(wearer.transforming || !wearer.canmove)
|
||||
return
|
||||
|
||||
if(locate(/obj/effect/stop/, wearer.loc))
|
||||
for(var/obj/effect/stop/S in wearer.loc)
|
||||
if(S.victim == wearer)
|
||||
return
|
||||
|
||||
if(!wearer.lastarea)
|
||||
wearer.lastarea = get_area(wearer.loc)
|
||||
|
||||
if((istype(wearer.loc, /turf/space)) || (wearer.lastarea.has_gravity == 0))
|
||||
if(!wearer.Process_Spacemove(0))
|
||||
return 0
|
||||
|
||||
if(malfunctioning)
|
||||
direction = pick(cardinal)
|
||||
|
||||
// Inside an object, tell it we moved.
|
||||
if(isobj(wearer.loc) || ismob(wearer.loc))
|
||||
var/atom/O = wearer.loc
|
||||
return O.relaymove(wearer, direction)
|
||||
|
||||
if(isturf(wearer.loc))
|
||||
if(wearer.restrained())//Why being pulled while cuffed prevents you from moving
|
||||
for(var/mob/M in range(wearer, 1))
|
||||
if(M.pulling == wearer)
|
||||
if(!M.restrained() && M.stat == 0 && M.canmove && wearer.Adjacent(M))
|
||||
user << "<span class='notice'>Your host is restrained! They can't move!</span>"
|
||||
return 0
|
||||
else
|
||||
M.stop_pulling()
|
||||
|
||||
if(wearer.pinned.len)
|
||||
src << "<span class='notice'>Your host is pinned to a wall by [wearer.pinned[1]]</span>!"
|
||||
return 0
|
||||
|
||||
// AIs are a bit slower than regular and ignore move intent.
|
||||
wearer.last_move_intent = world.time + ai_controlled_move_delay
|
||||
wearer_move_delay = world.time + ai_controlled_move_delay
|
||||
|
||||
var/tickcomp = 0
|
||||
if(config.Tickcomp)
|
||||
tickcomp = ((1/(world.tick_lag))*1.3) - 1.3
|
||||
wearer_move_delay += tickcomp
|
||||
|
||||
if(istype(wearer.buckled, /obj/vehicle))
|
||||
//manually set move_delay for vehicles so we don't inherit any mob movement penalties
|
||||
//specific vehicle move delays are set in code\modules\vehicles\vehicle.dm
|
||||
wearer_move_delay = world.time + tickcomp
|
||||
return wearer.buckled.relaymove(wearer, direction)
|
||||
|
||||
if(istype(wearer.machine, /obj/machinery))
|
||||
if(wearer.machine.relaymove(wearer, direction))
|
||||
return
|
||||
|
||||
if(wearer.pulledby || wearer.buckled) // Wheelchair driving!
|
||||
if(istype(wearer.loc, /turf/space))
|
||||
return // No wheelchair driving in space
|
||||
if(istype(wearer.pulledby, /obj/structure/bed/chair/wheelchair))
|
||||
return wearer.pulledby.relaymove(wearer, direction)
|
||||
else if(istype(wearer.buckled, /obj/structure/bed/chair/wheelchair))
|
||||
if(ishuman(wearer.buckled))
|
||||
var/obj/item/organ/external/l_hand = wearer.get_organ("l_hand")
|
||||
var/obj/item/organ/external/r_hand = wearer.get_organ("r_hand")
|
||||
if((!l_hand || (l_hand.status & ORGAN_DESTROYED)) && (!r_hand || (r_hand.status & ORGAN_DESTROYED)))
|
||||
return // No hands to drive your chair? Tough luck!
|
||||
wearer_move_delay += 2
|
||||
return wearer.buckled.relaymove(wearer,direction)
|
||||
|
||||
cell.use(200) //Arbitrary, TODO
|
||||
wearer.Move(get_step(get_turf(wearer),direction),direction)
|
||||
|
||||
// This returns the rig if you are contained inside one, but not if you are wearing it
|
||||
/atom/proc/get_rig()
|
||||
@@ -772,6 +890,9 @@
|
||||
/obj/item/weapon/rig/get_rig()
|
||||
return src
|
||||
|
||||
/mob/living/carbon/human/get_rig()
|
||||
return back
|
||||
|
||||
#undef ONLY_DEPLOY
|
||||
#undef ONLY_RETRACT
|
||||
#undef SEAL_DELAY
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
/obj/machinery/portable_atmospherics/hydroponics/AltClick()
|
||||
if(mechanical && !usr.stat && !usr.lying && Adjacent(usr))
|
||||
close_lid(usr)
|
||||
return
|
||||
return 1
|
||||
return ..()
|
||||
|
||||
/obj/machinery/portable_atmospherics/hydroponics/attack_ghost(var/mob/dead/observer/user)
|
||||
|
||||
@@ -122,6 +122,13 @@
|
||||
locked = 1
|
||||
return
|
||||
|
||||
/obj/item/device/mmi/relaymove(var/mob/user, var/direction)
|
||||
if(user.stat || user.stunned)
|
||||
return
|
||||
var/obj/item/weapon/rig/rig = src.get_rig()
|
||||
if(rig)
|
||||
rig.forced_move(direction, user)
|
||||
|
||||
/obj/item/device/mmi/Destroy()
|
||||
if(isrobot(loc))
|
||||
var/mob/living/silicon/robot/borg = loc
|
||||
|
||||
@@ -49,12 +49,12 @@
|
||||
return 1
|
||||
return ..()
|
||||
|
||||
|
||||
/mob/living/carbon/brain/update_canmove()
|
||||
if(in_contents_of(/obj/mecha))
|
||||
if(in_contents_of(/obj/mecha) || istype(loc, /obj/item/device/mmi))
|
||||
canmove = 1
|
||||
use_me = 1 //If it can move, let it emote
|
||||
else canmove = 0
|
||||
use_me = 1
|
||||
else
|
||||
canmove = 0
|
||||
return canmove
|
||||
|
||||
/mob/living/carbon/brain/binarycheck()
|
||||
|
||||
@@ -136,8 +136,10 @@
|
||||
|
||||
return shock_damage
|
||||
|
||||
/mob/proc/swap_hand()
|
||||
return
|
||||
|
||||
/mob/living/carbon/proc/swap_hand()
|
||||
/mob/living/carbon/swap_hand()
|
||||
var/obj/item/item_in_hand = src.get_active_hand()
|
||||
if(item_in_hand) //this segment checks if the item in your hand is twohanded.
|
||||
if(istype(item_in_hand,/obj/item/weapon/material/twohanded))
|
||||
|
||||
@@ -61,11 +61,13 @@
|
||||
if(!gibbed && species.death_sound)
|
||||
playsound(loc, species.death_sound, 80, 1, 1)
|
||||
|
||||
|
||||
if(ticker && ticker.mode)
|
||||
sql_report_death(src)
|
||||
ticker.mode.check_win()
|
||||
|
||||
if(wearing_rig)
|
||||
wearing_rig.notify_ai("<span class='danger'>Warning: user death event. Mobility control passed to integrated intelligence system.</span>")
|
||||
|
||||
return ..(gibbed,species.death_message)
|
||||
|
||||
/mob/living/carbon/human/proc/ChangeToHusk()
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
var/list/hud_list[10]
|
||||
var/embedded_flag //To check if we've need to roll for damage on movement while an item is imbedded in us.
|
||||
var/obj/item/weapon/rig/wearing_rig // This is very not good, but it's much much better than calling get_rig() every update_canmove() call.
|
||||
|
||||
/mob/living/carbon/human/New(var/new_loc, var/new_species = null)
|
||||
|
||||
@@ -1370,6 +1371,16 @@
|
||||
handle_regular_hud_updates()
|
||||
|
||||
/mob/living/carbon/human/Check_Shoegrip()
|
||||
if(istype(shoes, /obj/item/clothing/shoes/magboots) && (shoes.item_flags & NOSLIP)) //magboots + dense_object = no floating
|
||||
if((shoes.item_flags & NOSLIP) && istype(shoes, /obj/item/clothing/shoes/magboots)) //magboots + dense_object = no floating
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/mob/living/carbon/human/can_stand_overridden()
|
||||
if(wearing_rig && wearing_rig.ai_can_move_suit(check_for_ai = 1))
|
||||
// Actually missing a leg will screw you up. Everything else can be compensated for.
|
||||
for(var/limbcheck in list("l_leg","r_leg"))
|
||||
var/obj/item/organ/affecting = get_organ(limbcheck)
|
||||
if(!affecting)
|
||||
return 0
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
|
||||
/mob/living/carbon/human/Paralyse(amount)
|
||||
if(HULK in mutations) return
|
||||
// Notify our AI if they can now control the suit.
|
||||
if(wearing_rig && !stat && paralysis < amount) //We are passing out right this second.
|
||||
wearing_rig.notify_ai("<span class='danger'>Warning: user consciousness failure. Mobility control passed to integrated intelligence system.</span>")
|
||||
..()
|
||||
|
||||
/mob/living/carbon/human/getCloneLoss()
|
||||
|
||||
@@ -52,6 +52,10 @@
|
||||
// update the current life tick, can be used to e.g. only do something every 4 ticks
|
||||
life_tick++
|
||||
|
||||
// This is not an ideal place for this but it will do for now.
|
||||
if(wearing_rig && wearing_rig.offline)
|
||||
wearing_rig = null
|
||||
|
||||
in_stasis = istype(loc, /obj/structure/closet/body_bag/cryobag) && loc:opened == 0
|
||||
if(in_stasis) loc:used++
|
||||
|
||||
|
||||
@@ -657,7 +657,7 @@ default behaviour is:
|
||||
set category = "IC"
|
||||
|
||||
resting = !resting
|
||||
src << "\blue You are now [resting ? "resting" : "getting up"]"
|
||||
src << "<span class='notice'>You are now [resting ? "resting" : "getting up"].</span>"
|
||||
|
||||
/mob/living/proc/handle_ventcrawl(var/obj/machinery/atmospherics/unary/vent_pump/vent_found = null, var/ignore_items = 0) // -- TLE -- Merged by Carn
|
||||
if(stat)
|
||||
|
||||
@@ -81,7 +81,7 @@ var/list/ai_verbs_default = list(
|
||||
|
||||
var/datum/ai_icon/selected_sprite // The selected icon set
|
||||
var/custom_sprite = 0 // Whether the selected icon is custom
|
||||
|
||||
var/carded
|
||||
|
||||
/mob/living/silicon/ai/proc/add_ai_verbs()
|
||||
src.verbs |= ai_verbs_default
|
||||
@@ -684,5 +684,15 @@ var/list/ai_verbs_default = list(
|
||||
icon_state = selected_sprite.alive_icon
|
||||
set_light(1, 1, selected_sprite.alive_light)
|
||||
|
||||
// Pass lying down or getting up to our pet human, if we're in a rig.
|
||||
/mob/living/silicon/ai/lay_down()
|
||||
set name = "Rest"
|
||||
set category = "IC"
|
||||
|
||||
resting = 0
|
||||
var/obj/item/weapon/rig/rig = src.get_rig()
|
||||
if(rig)
|
||||
rig.force_rest(src)
|
||||
|
||||
#undef AI_CHECK_WIRELESS
|
||||
#undef AI_CHECK_RADIO
|
||||
|
||||
@@ -72,8 +72,6 @@
|
||||
var/current_pda_messaging = null
|
||||
|
||||
/mob/living/silicon/pai/New(var/obj/item/device/paicard)
|
||||
|
||||
canmove = 0
|
||||
src.loc = paicard
|
||||
card = paicard
|
||||
sradio = new(src)
|
||||
@@ -276,8 +274,6 @@
|
||||
var/obj/item/device/pda/holder = card.loc
|
||||
holder.pai = null
|
||||
|
||||
canmove = 1
|
||||
|
||||
src.client.perspective = EYE_PERSPECTIVE
|
||||
src.client.eye = src
|
||||
src.forceMove(get_turf(card))
|
||||
@@ -339,12 +335,16 @@
|
||||
set name = "Rest"
|
||||
set category = "IC"
|
||||
|
||||
// Pass lying down or getting up to our pet human, if we're in a rig.
|
||||
if(istype(src.loc,/obj/item/device/paicard))
|
||||
resting = 0
|
||||
var/obj/item/weapon/rig/rig = src.get_rig()
|
||||
if(istype(rig))
|
||||
rig.force_rest(src)
|
||||
else
|
||||
resting = !resting
|
||||
icon_state = resting ? "[chassis]_rest" : "[chassis]"
|
||||
src << "\blue You are now [resting ? "resting" : "getting up"]"
|
||||
src << "<span class='notice'>You are now [resting ? "resting" : "getting up"]</span>"
|
||||
|
||||
canmove = !resting
|
||||
|
||||
@@ -393,7 +393,8 @@
|
||||
card.loc = get_turf(card)
|
||||
src.forceMove(card)
|
||||
card.forceMove(card.loc)
|
||||
canmove = 0
|
||||
canmove = 1
|
||||
resting = 0
|
||||
icon_state = "[chassis]"
|
||||
|
||||
/mob/living/silicon/pai/start_pulling(var/atom/movable/AM)
|
||||
|
||||
+41
-29
@@ -692,40 +692,52 @@
|
||||
if(transforming) return 0
|
||||
return 1
|
||||
|
||||
// Not sure what to call this. Used to check if humans are wearing an AI-controlled exosuit and hence don't need to fall over yet.
|
||||
/mob/proc/can_stand_overridden()
|
||||
return 0
|
||||
|
||||
/mob/proc/cannot_stand()
|
||||
return incapacitated() || restrained() || resting || sleeping || (status_flags & FAKEDEATH)
|
||||
|
||||
//Updates canmove, lying and icons. Could perhaps do with a rename but I can't think of anything to describe it.
|
||||
/mob/proc/update_canmove()
|
||||
if(istype(buckled, /obj/vehicle))
|
||||
var/obj/vehicle/V = buckled
|
||||
if(stat || weakened || paralysis || resting || sleeping || (status_flags & FAKEDEATH))
|
||||
lying = 1
|
||||
canmove = 0
|
||||
pixel_y = V.mob_offset_y - 5
|
||||
else
|
||||
if(buckled.buckle_lying != -1) lying = buckled.buckle_lying
|
||||
canmove = 1
|
||||
pixel_y = V.mob_offset_y
|
||||
else if(buckled)
|
||||
anchored = 1
|
||||
canmove = 0
|
||||
if(istype(buckled))
|
||||
if(buckled.buckle_lying != -1)
|
||||
lying = buckled.buckle_lying
|
||||
if(buckled.buckle_movable)
|
||||
anchored = 0
|
||||
canmove = 1
|
||||
|
||||
else if( stat || weakened || paralysis || resting || sleeping || (status_flags & FAKEDEATH))
|
||||
lying = 1
|
||||
canmove = 0
|
||||
else if(stunned)
|
||||
canmove = 0
|
||||
else if(captured)
|
||||
anchored = 1
|
||||
canmove = 0
|
||||
lying = 0
|
||||
else
|
||||
if(!resting && cannot_stand() && can_stand_overridden())
|
||||
lying = 0
|
||||
canmove = 1
|
||||
else
|
||||
if(istype(buckled, /obj/vehicle))
|
||||
var/obj/vehicle/V = buckled
|
||||
if(cannot_stand())
|
||||
lying = 1
|
||||
canmove = 0
|
||||
pixel_y = V.mob_offset_y - 5
|
||||
else
|
||||
if(buckled.buckle_lying != -1) lying = buckled.buckle_lying
|
||||
canmove = 1
|
||||
pixel_y = V.mob_offset_y
|
||||
else if(buckled)
|
||||
anchored = 1
|
||||
canmove = 0
|
||||
if(istype(buckled))
|
||||
if(buckled.buckle_lying != -1)
|
||||
lying = buckled.buckle_lying
|
||||
if(buckled.buckle_movable)
|
||||
anchored = 0
|
||||
canmove = 1
|
||||
|
||||
else if(cannot_stand())
|
||||
lying = 1
|
||||
canmove = 0
|
||||
else if(stunned)
|
||||
canmove = 0
|
||||
else if(captured)
|
||||
anchored = 1
|
||||
canmove = 0
|
||||
lying = 0
|
||||
else
|
||||
lying = 0
|
||||
canmove = 1
|
||||
|
||||
if(lying)
|
||||
density = 0
|
||||
|
||||
@@ -233,7 +233,6 @@
|
||||
|
||||
if(Process_Grab()) return
|
||||
|
||||
|
||||
if(!mob.canmove)
|
||||
return
|
||||
|
||||
@@ -246,7 +245,6 @@
|
||||
if((istype(mob.loc, /turf/space)) || (mob.lastarea.has_gravity == 0))
|
||||
if(!mob.Process_Spacemove(0)) return 0
|
||||
|
||||
|
||||
if(isobj(mob.loc) || ismob(mob.loc))//Inside an object, tell it we moved
|
||||
var/atom/O = mob.loc
|
||||
return O.relaymove(mob, direct)
|
||||
|
||||
Reference in New Issue
Block a user