mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2025-12-26 01:52:15 +00:00
Human icon tweaks (#3131)
Mostly efficiency changes to human icons, little bit of code deduplication in head icons. The human overlay list has been changed to accept /list so that multiple items can be added to a layer without having to resort to adding overlays to an intermediary object like an /image. changes: Human skin color (not tone) is now stored as a hex string instead of a 3-value list. Human body hair color (resomi only) is now stored as a hex string instead of a 3-value list. Body markings now play nice with the human icon cache (Fixes #3110) Hair properly uses its cache now in all cases. Merged head organs and human icon's implementations of hair icon gen into a proc on human. Replaced the individual hair and beard caches with a shared composited hair cache used by both head icon and mob icons. Lists in the human overlay list are now flattened out onto the human, allowing for multiple objects within a single human icon layer without using an intermediary image. Removed some unused caching lists. Damage overlays no longer utilize overlays on a blank image for drawing, instead using a list in the human overlay list. Shoe overlays no longer utilize overlays on the shoe image, instead using a list in the human overlay list. Surgery overlays no longer utilize overlays on a blank image, instead using a list in the human overlay list.
This commit is contained in:
@@ -4,12 +4,13 @@
|
||||
name = "Icon Cache"
|
||||
flags = SS_NO_FIRE | SS_NO_INIT
|
||||
|
||||
// Cached bloody overlays, key is object type.
|
||||
var/list/bloody_cache = list()
|
||||
|
||||
var/list/holo_multiplier_cache = list()
|
||||
var/list/holo_adder_cache = list()
|
||||
|
||||
var/list/image/fluidtrack_cache = list()
|
||||
// Cached holo effect multiplier images.
|
||||
var/list/holo_multiplier_cache = list() // 2-layer list: icon -> icon_state -> /image
|
||||
// Cached holo effect adder images.
|
||||
var/list/holo_adder_cache = list() // 2-layer list: icon -> icon_state -> /image
|
||||
|
||||
var/list/floor_decals = list()
|
||||
var/list/flooring_cache = list()
|
||||
@@ -32,14 +33,20 @@
|
||||
var/list/human_icon_cache = list()
|
||||
var/list/tail_icon_cache = list() //key is [species.race_key][r_skin][g_skin][b_skin]
|
||||
var/list/light_overlay_cache = list()
|
||||
// Cached body hair icons, used by resomi.
|
||||
var/list/body_hair_cache = list()
|
||||
// Cached human damage icons.
|
||||
var/list/damage_icon_parts = list()
|
||||
// [icon]-[icon_state]-[limb_name]-[color]
|
||||
var/list/markings_cache = list()
|
||||
// Cached human body markings.
|
||||
var/list/markings_cache = list() // [icon]-[icon_state]-[limb_name]-[color]
|
||||
var/list/human_eye_cache = list()
|
||||
var/list/human_lip_cache = list()
|
||||
// Cached composited human hair (beard & hair).
|
||||
// Key:
|
||||
// hair+beard: [beard_style][r_facial][g_facial][b_facial]_[hair_style][r_hair][g_hair][b_hair]
|
||||
// haironly: nobeard_[hair_style][r_hair][g_hair][b_hair]
|
||||
// beardonly: [beard_style][r_facial][g_facial][b_facial]_nohair
|
||||
var/list/human_hair_cache = list()
|
||||
var/list/human_beard_cache = list()
|
||||
var/list/human_underwear_cache = list()
|
||||
var/list/human_undershirt_cache = list()
|
||||
var/list/human_socks_cache = list()
|
||||
|
||||
@@ -145,7 +145,12 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
add_overlay(list(overlays_standing[L_HAND_LAYER], overlays_standing[R_HAND_LAYER]))
|
||||
else if (icon_update)
|
||||
icon = stand_icon
|
||||
var/list/ovr = overlays_standing.Copy()
|
||||
var/list/ovr = list()
|
||||
// We manually add each element instead of just using Copy() so that lists are appended instead of inserted.
|
||||
for (var/item in overlays_standing)
|
||||
if (item)
|
||||
ovr += item
|
||||
|
||||
if(species.has_floating_eyes)
|
||||
ovr += species.get_eyes(src)
|
||||
|
||||
@@ -187,9 +192,8 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
|
||||
previous_damage_appearance = damage_appearance
|
||||
|
||||
var/icon/standing = new /icon(species.damage_overlays, "00")
|
||||
|
||||
var/image/standing_image = new /image("icon" = standing)
|
||||
// The overlays we're going to add to the mob.
|
||||
var/list/ovr
|
||||
|
||||
// blend the individual damage states with our icons
|
||||
for(var/obj/item/organ/external/O in organs)
|
||||
@@ -198,22 +202,21 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
|
||||
O.update_icon()
|
||||
if(O.damage_state == "00") continue
|
||||
var/icon/DI
|
||||
var/cache_index = "[O.damage_state]/[O.icon_name]/[species.blood_color]/[species.get_bodytype()]"
|
||||
var/list/damage_icon_parts = SSicon_cache.damage_icon_parts
|
||||
if(!damage_icon_parts[cache_index])
|
||||
var/icon/DI = damage_icon_parts[cache_index]
|
||||
if(!DI)
|
||||
DI = new /icon(species.damage_overlays, O.damage_state) // the damage icon for whole human
|
||||
DI.Blend(new /icon(species.damage_mask, O.icon_name), ICON_MULTIPLY) // mask with this organ's pixels
|
||||
DI.Blend(species.blood_color, ICON_MULTIPLY)
|
||||
damage_icon_parts[cache_index] = DI
|
||||
else
|
||||
DI = damage_icon_parts[cache_index]
|
||||
|
||||
standing_image.overlays += DI
|
||||
LAZYADD(ovr, DI)
|
||||
|
||||
overlays_standing[DAMAGE_LAYER] = standing_image
|
||||
overlays_standing[DAMAGE_LAYER] = ovr
|
||||
|
||||
if(update_icons) update_icons()
|
||||
if(update_icons)
|
||||
update_icons()
|
||||
|
||||
//BASE MOB SPRITE
|
||||
/mob/living/carbon/human/proc/update_body(var/update_icons=1)
|
||||
@@ -261,13 +264,16 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
icon_key += "[part.species.race_key]"
|
||||
icon_key += "[part.dna.GetUIState(DNA_UI_GENDER)]"
|
||||
icon_key += "[part.dna.GetUIValue(DNA_UI_SKIN_TONE)]"
|
||||
if(part.s_col && part.s_col.len >= 3)
|
||||
icon_key += "[rgb(part.s_col[1],part.s_col[2],part.s_col[3])]"
|
||||
if(part.body_hair && part.h_col && part.h_col.len >= 3)
|
||||
icon_key += "[rgb(part.h_col[1],part.h_col[2],part.h_col[3])]"
|
||||
if(part.skin_color)
|
||||
icon_key += "[part.skin_color]"
|
||||
if(part.body_hair && part.hair_color)
|
||||
icon_key += "[part.hair_color]"
|
||||
else
|
||||
icon_key += "#000000"
|
||||
|
||||
for(var/M in part.markings)
|
||||
icon_key += "[M][part.markings[M]["color"]]"
|
||||
|
||||
icon_key = "[icon_key][!!husk][!!fat][!!hulk][!!skeleton]"
|
||||
var/icon/base_icon = SSicon_cache.human_icon_cache[icon_key]
|
||||
if (!base_icon) // Icon ain't in the cache, so generate it.
|
||||
@@ -347,11 +353,44 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
//tail
|
||||
update_tail_showing(0)
|
||||
|
||||
// This proc generates & returns an icon representing a human's hair, using a cached icon from SSicon_cache if possible.
|
||||
// If `hair_is_visible` is FALSE, only facial hair will be drawn.
|
||||
/mob/living/carbon/human/proc/generate_hair_icon(hair_is_visible = TRUE)
|
||||
var/cache_key = "[f_style ? "[f_style][r_facial][g_facial][b_facial]" : "nofacial"]_[(h_style && hair_is_visible) ? "[h_style][r_hair][g_hair][b_hair]" : "nohair"]"
|
||||
|
||||
var/icon/face_standing = SSicon_cache.human_hair_cache[cache_key]
|
||||
if (!face_standing) // Not cached, generate it from scratch.
|
||||
face_standing = new /icon('icons/mob/human_face.dmi',"bald_s")
|
||||
// Beard.
|
||||
if(f_style)
|
||||
var/datum/sprite_accessory/facial_hair_style = facial_hair_styles_list[f_style]
|
||||
if(facial_hair_style && facial_hair_style.species_allowed && (src.species.get_bodytype() in facial_hair_style.species_allowed))
|
||||
var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
|
||||
if(facial_hair_style.do_colouration)
|
||||
facial_s.Blend(rgb(r_facial, g_facial, b_facial), ICON_ADD)
|
||||
|
||||
face_standing.Blend(facial_s, ICON_OVERLAY)
|
||||
|
||||
// Hair.
|
||||
if(hair_is_visible)
|
||||
var/datum/sprite_accessory/hair_style = hair_styles_list[h_style]
|
||||
if(hair_style && (src.species.get_bodytype() in hair_style.species_allowed))
|
||||
var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s")
|
||||
if(hair_style.do_colouration)
|
||||
hair_s.Blend(rgb(r_hair, g_hair, b_hair), ICON_ADD)
|
||||
|
||||
face_standing.Blend(hair_s, ICON_OVERLAY)
|
||||
|
||||
// Add it to the cache.
|
||||
SSicon_cache.human_hair_cache[cache_key] = face_standing
|
||||
|
||||
return face_standing
|
||||
|
||||
//HAIR OVERLAY
|
||||
/mob/living/carbon/human/proc/update_hair(var/update_icons=1)
|
||||
if (QDELING(src))
|
||||
return
|
||||
|
||||
|
||||
//Reset our hair
|
||||
overlays_standing[HAIR_LAYER] = null
|
||||
|
||||
@@ -365,40 +404,24 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
if(update_icons) update_icons()
|
||||
return
|
||||
|
||||
//base icons
|
||||
var/icon/face_standing = new /icon('icons/mob/human_face.dmi',"bald_s")
|
||||
var/has_visible_hair = h_style && !(head && (head.flags_inv & BLOCKHEADHAIR))
|
||||
|
||||
if(f_style)
|
||||
var/datum/sprite_accessory/facial_hair_style = facial_hair_styles_list[f_style]
|
||||
if(facial_hair_style && facial_hair_style.species_allowed && (src.species.get_bodytype() in facial_hair_style.species_allowed))
|
||||
var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
|
||||
if(facial_hair_style.do_colouration)
|
||||
facial_s.Blend(rgb(r_facial, g_facial, b_facial), ICON_ADD)
|
||||
|
||||
face_standing.Blend(facial_s, ICON_OVERLAY)
|
||||
|
||||
if(h_style && !(head && (head.flags_inv & BLOCKHEADHAIR)))
|
||||
var/datum/sprite_accessory/hair_style = hair_styles_list[h_style]
|
||||
if(hair_style && (src.species.get_bodytype() in hair_style.species_allowed))
|
||||
var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s")
|
||||
if(hair_style.do_colouration)
|
||||
hair_s.Blend(rgb(r_hair, g_hair, b_hair), ICON_ADD)
|
||||
|
||||
face_standing.Blend(hair_s, ICON_OVERLAY)
|
||||
|
||||
if (species.light_range)
|
||||
var/col = species.get_light_color(h_style)
|
||||
if (!col)
|
||||
col = "#FFFFFF"
|
||||
|
||||
var/icon/hair_icon = generate_hair_icon(has_visible_hair)
|
||||
|
||||
// Handle light emission.
|
||||
if (species.light_range)
|
||||
if (has_visible_hair)
|
||||
var/datum/sprite_accessory/hair_style = hair_styles_list[h_style]
|
||||
if (hair_style)
|
||||
var/col = species.get_light_color(h_style) || "#FFFFFF"
|
||||
set_light(species.light_range, species.light_power, col, uv = 0, angle = LIGHT_WIDE)
|
||||
|
||||
else if (species.light_range)
|
||||
set_light(FALSE)
|
||||
|
||||
overlays_standing[HAIR_LAYER] = image(face_standing)
|
||||
else
|
||||
set_light(0)
|
||||
|
||||
if(update_icons) update_icons()
|
||||
overlays_standing[HAIR_LAYER] = hair_icon
|
||||
|
||||
if(update_icons)
|
||||
update_icons()
|
||||
|
||||
/mob/living/carbon/human/update_mutations(var/update_icons=1)
|
||||
if (QDELING(src))
|
||||
@@ -710,6 +733,7 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
return
|
||||
|
||||
overlays_standing[SHOES_LAYER] = null
|
||||
var/list/ovr
|
||||
if(check_draw_shoes())
|
||||
var/image/standing
|
||||
if(shoes.contained_sprite)
|
||||
@@ -731,19 +755,24 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
else
|
||||
standing = image("icon" = 'icons/mob/feet.dmi', "icon_state" = "[shoes.icon_state]")
|
||||
|
||||
standing.color = shoes.color
|
||||
|
||||
ovr = list(standing)
|
||||
|
||||
if(shoes.blood_DNA)
|
||||
var/image/bloodsies = image("icon" = species.blood_mask, "icon_state" = "shoeblood")
|
||||
bloodsies.color = shoes.blood_color
|
||||
standing.overlays += bloodsies
|
||||
standing.color = shoes.color
|
||||
overlays_standing[SHOES_LAYER] = standing
|
||||
ovr += bloodsies
|
||||
|
||||
overlays_standing[SHOES_LAYER] = ovr
|
||||
else
|
||||
if(feet_blood_DNA)
|
||||
var/image/bloodsies = image("icon" = species.blood_mask, "icon_state" = "shoeblood")
|
||||
bloodsies.color = feet_blood_color
|
||||
overlays_standing[SHOES_LAYER] = bloodsies
|
||||
|
||||
if(update_icons) update_icons()
|
||||
if(update_icons)
|
||||
update_icons()
|
||||
|
||||
/mob/living/carbon/human/update_inv_s_store(var/update_icons=1)
|
||||
if (QDELING(src))
|
||||
@@ -1301,15 +1330,15 @@ Please contact me on #coderbus IRC. ~Carn x
|
||||
|
||||
/mob/living/carbon/human/proc/update_surgery(var/update_icons=1)
|
||||
overlays_standing[SURGERY_LEVEL] = null
|
||||
var/image/total = new
|
||||
var/list/ovr
|
||||
for(var/obj/item/organ/external/E in organs)
|
||||
if(E.open)
|
||||
var/image/I = image("icon"='icons/mob/surgery.dmi', "icon_state"="[E.name][round(E.open)]", "layer"=-SURGERY_LEVEL)
|
||||
total.overlays += I
|
||||
overlays_standing[SURGERY_LEVEL] = total
|
||||
if(update_icons) update_icons()
|
||||
|
||||
LAZYADD(ovr, I)
|
||||
overlays_standing[SURGERY_LEVEL] = ovr
|
||||
|
||||
if(update_icons)
|
||||
update_icons()
|
||||
|
||||
//Drawcheck functions
|
||||
//These functions check if an item should be drawn, or if its covered up by something else
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
var/list/organ_cache = list()
|
||||
|
||||
/obj/item/organ
|
||||
name = "organ"
|
||||
icon = 'icons/obj/surgery.dmi'
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
var/cannot_amputate
|
||||
var/cannot_break
|
||||
var/s_tone
|
||||
var/list/s_col
|
||||
var/list/h_col
|
||||
var/skin_color
|
||||
var/hair_color
|
||||
var/list/wounds = list()
|
||||
var/number_wounds = 0 // cache the number of wounds, which is NOT wounds.len!
|
||||
var/perma_injury = 0
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
/obj/item/organ/external/proc/sync_colour_to_human(var/mob/living/carbon/human/human)
|
||||
s_tone = null
|
||||
s_col = null
|
||||
h_col = null
|
||||
skin_color = null
|
||||
hair_color = null
|
||||
if(status & ORGAN_ROBOT && !(isipc(human)))
|
||||
return
|
||||
if(species && human.species && species.name != human.species.name)
|
||||
@@ -22,20 +22,20 @@
|
||||
if(!isnull(human.s_tone) && (human.species.appearance_flags & HAS_SKIN_TONE))
|
||||
s_tone = human.s_tone
|
||||
if(human.species.appearance_flags & HAS_SKIN_COLOR)
|
||||
s_col = list(human.r_skin, human.g_skin, human.b_skin)
|
||||
h_col = list(human.r_hair, human.g_hair, human.b_hair)
|
||||
skin_color = rgb(human.r_skin, human.g_skin, human.b_skin)
|
||||
hair_color = rgb(human.r_hair, human.g_hair, human.b_hair)
|
||||
|
||||
/obj/item/organ/external/proc/sync_colour_to_dna()
|
||||
s_tone = null
|
||||
s_col = null
|
||||
h_col = null
|
||||
skin_color = null
|
||||
hair_color = null
|
||||
if(status & ORGAN_ROBOT && !force_skintone)
|
||||
return
|
||||
if(!isnull(dna.GetUIValue(DNA_UI_SKIN_TONE)) && (species.appearance_flags & HAS_SKIN_TONE))
|
||||
s_tone = dna.GetUIValue(DNA_UI_SKIN_TONE)
|
||||
if(species.appearance_flags & HAS_SKIN_COLOR)
|
||||
s_col = list(dna.GetUIValue(DNA_UI_SKIN_R), dna.GetUIValue(DNA_UI_SKIN_G), dna.GetUIValue(DNA_UI_SKIN_B))
|
||||
h_col = list(dna.GetUIValue(DNA_UI_HAIR_R),dna.GetUIValue(DNA_UI_HAIR_G),dna.GetUIValue(DNA_UI_HAIR_B))
|
||||
skin_color = rgb(dna.GetUIValue(DNA_UI_SKIN_R), dna.GetUIValue(DNA_UI_SKIN_G), dna.GetUIValue(DNA_UI_SKIN_B))
|
||||
hair_color = rgb(dna.GetUIValue(DNA_UI_HAIR_R),dna.GetUIValue(DNA_UI_HAIR_G),dna.GetUIValue(DNA_UI_HAIR_B))
|
||||
|
||||
/obj/item/organ/external/head/sync_colour_to_human(var/mob/living/carbon/human/human)
|
||||
..()
|
||||
@@ -102,41 +102,7 @@
|
||||
add_overlay(finished_icon) //So when it's not on your body, it has icons
|
||||
mob_icon.Blend(finished_icon, ICON_OVERLAY) //So when it's on your body, it has icons
|
||||
|
||||
if(owner.f_style)
|
||||
var/datum/sprite_accessory/facial_hair_style = facial_hair_styles_list[owner.f_style]
|
||||
if(facial_hair_style && facial_hair_style.species_allowed && (species.get_bodytype() in facial_hair_style.species_allowed))
|
||||
var/facialcolor
|
||||
if (facial_hair_style.do_colouration)
|
||||
facialcolor = rgb(owner.r_facial, owner.g_facial, owner.b_facial)
|
||||
|
||||
var/cache_key = "[facial_hair_style.icon]_[facial_hair_style.icon_state]_[facialcolor || "nocolor"]"
|
||||
|
||||
var/icon/facial_s = SSicon_cache.human_beard_cache[cache_key]
|
||||
if (!facial_s)
|
||||
facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
|
||||
if(facial_hair_style.do_colouration)
|
||||
facial_s.Blend(facialcolor, ICON_ADD)
|
||||
|
||||
SSicon_cache.human_beard_cache[cache_key] = facial_s
|
||||
|
||||
add_overlay(facial_s)
|
||||
|
||||
if(owner.h_style && !(owner.head && (owner.head.flags_inv & BLOCKHEADHAIR)))
|
||||
var/datum/sprite_accessory/hair_style = hair_styles_list[owner.h_style]
|
||||
if(hair_style && (species.get_bodytype() in hair_style.species_allowed))
|
||||
var/haircolor
|
||||
if (hair_style.do_colouration && istype(h_col) && h_col.len >= 3)
|
||||
haircolor = rgb(h_col[1], h_col[2], h_col[3])
|
||||
|
||||
var/cache_key = "[hair_style.icon]_[hair_style.icon_state]_[haircolor || "nocolor"]"
|
||||
var/icon/hair_s = SSicon_cache.human_hair_cache[cache_key]
|
||||
if (!hair_s)
|
||||
hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s")
|
||||
if(hair_style.do_colouration && islist(h_col) && h_col.len >= 3)
|
||||
hair_s.Blend(haircolor, ICON_ADD)
|
||||
|
||||
SSicon_cache.human_hair_cache[cache_key] = hair_s
|
||||
add_overlay(hair_s)
|
||||
add_overlay(owner.generate_hair_icon())
|
||||
|
||||
compile_overlays()
|
||||
|
||||
@@ -150,14 +116,12 @@
|
||||
|
||||
if(force_icon)
|
||||
mob_icon = new /icon(force_icon, "[icon_name][gendered_icon ? "_[gender]" : ""]")
|
||||
if(painted)
|
||||
if(s_col && s_col.len >= 3)
|
||||
mob_icon.Blend(rgb(s_col[1], s_col[2], s_col[3]), ICON_ADD)
|
||||
if(painted && skin_color)
|
||||
mob_icon.Blend(skin_color, ICON_ADD)
|
||||
else
|
||||
if(!dna)
|
||||
mob_icon = new /icon('icons/mob/human_races/r_human.dmi', "[icon_name][gendered_icon ? "_[gender]" : ""]")
|
||||
else
|
||||
|
||||
if(!gendered_icon)
|
||||
gender = null
|
||||
else
|
||||
@@ -185,8 +149,8 @@
|
||||
mob_icon.Blend(rgb(s_tone, s_tone, s_tone), ICON_ADD)
|
||||
else
|
||||
mob_icon.Blend(rgb(-s_tone, -s_tone, -s_tone), ICON_SUBTRACT)
|
||||
else if(s_col && s_col.len >= 3)
|
||||
mob_icon.Blend(rgb(s_col[1], s_col[2], s_col[3]), ICON_ADD)
|
||||
else if(skin_color)
|
||||
mob_icon.Blend(skin_color, ICON_ADD)
|
||||
|
||||
//Body markings, does not include head, duplicated (sadly) above.
|
||||
for(var/M in markings)
|
||||
@@ -203,12 +167,12 @@
|
||||
add_overlay(finished_icon) //So when it's not on your body, it has icons
|
||||
mob_icon.Blend(finished_icon, ICON_OVERLAY) //So when it's on your body, it has icons
|
||||
|
||||
if(body_hair && islist(h_col) && h_col.len >= 3)
|
||||
if(body_hair && hair_color)
|
||||
var/list/limb_icon_cache = SSicon_cache.body_hair_cache
|
||||
var/cache_key = "[body_hair]-[icon_name]-[h_col[1]][h_col[2]][h_col[3]]"
|
||||
var/cache_key = "[body_hair]-[icon_name]-[hair_color]"
|
||||
if(!limb_icon_cache[cache_key])
|
||||
var/icon/I = icon(species.icobase, "[icon_name]_[body_hair]")
|
||||
I.Blend(rgb(h_col[1],h_col[2],h_col[3]), ICON_ADD)
|
||||
I.Blend(hair_color, ICON_ADD)
|
||||
limb_icon_cache[cache_key] = I
|
||||
mob_icon.Blend(limb_icon_cache[cache_key], ICON_OVERLAY)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user