Use python enums for enums
I played a bit with getting the text instead of the magic number in the string representation but everything I tried got a bit ugly
This commit is contained in:
parent
e4ab0a7791
commit
67c54c4be4
|
@ -437,10 +437,10 @@ class CodeGenerator():
|
||||||
raise ValueError(f"Error: unknown data size {member['type']}")
|
raise ValueError(f"Error: unknown data size {member['type']}")
|
||||||
# {"name": ["mode"], "type": {"kind": "enum", "enum": ["chat"]}},
|
# {"name": ["mode"], "type": {"kind": "enum", "enum": ["chat"]}},
|
||||||
elif member['type']['kind'] == 'enum':
|
elif member['type']['kind'] == 'enum':
|
||||||
ftype = 'int'
|
enum_name: str = name_to_camel(member['type']['enum'])
|
||||||
enum_name: str = name_to_snake(member['type']['enum'])
|
ftype = f'int'
|
||||||
default = self.get_default_enum7(enum_name)
|
default = self.get_default_enum7(enum_name)
|
||||||
default = f"enum7.{default}"
|
default = f"enum7.{default}.value"
|
||||||
elif member['type']['kind'] in ('int32', 'tick'):
|
elif member['type']['kind'] in ('int32', 'tick'):
|
||||||
ftype = 'int'
|
ftype = 'int'
|
||||||
default = '0'
|
default = '0'
|
||||||
|
@ -593,7 +593,10 @@ class CodeGenerator():
|
||||||
name = name_to_snake(member["name"])
|
name = name_to_snake(member["name"])
|
||||||
if ftype != '':
|
if ftype != '':
|
||||||
ftype = f': {ftype}'
|
ftype = f': {ftype}'
|
||||||
out_file.write(f" self.{name}{ftype} = {name}\n")
|
if member['type']['kind'] == 'enum':
|
||||||
|
out_file.write(f" self.{name}{ftype} = {name}\n")
|
||||||
|
else:
|
||||||
|
out_file.write(f" self.{name}{ftype} = {name}\n")
|
||||||
out_file.write('\n')
|
out_file.write('\n')
|
||||||
out_file.write(' # first byte of data\n')
|
out_file.write(' # first byte of data\n')
|
||||||
out_file.write(' # has to be the first byte of the message payload\n')
|
out_file.write(' # has to be the first byte of the message payload\n')
|
||||||
|
@ -609,33 +612,34 @@ class CodeGenerator():
|
||||||
|
|
||||||
def get_default_enum7(self, enum_name: str) -> str:
|
def get_default_enum7(self, enum_name: str) -> str:
|
||||||
"""
|
"""
|
||||||
enum_name has to be snake case
|
enum_name has to be camel case
|
||||||
can be lower or upper case does not matter
|
|
||||||
|
|
||||||
If for example enum_name 'chat' is given
|
If for example enum_name 'chat' is given
|
||||||
it returns 'CHAT_NONE'
|
it returns 'CHAT_NONE'
|
||||||
"""
|
"""
|
||||||
enum_name = enum_name.upper()
|
|
||||||
enum: GameEnumJson
|
enum: GameEnumJson
|
||||||
for enum in self.game_enums:
|
for enum in self.game_enums:
|
||||||
base: str = name_to_snake(enum['name']).upper()
|
base: str = name_to_camel(enum['name'])
|
||||||
if base != enum_name:
|
if base != enum_name:
|
||||||
continue
|
continue
|
||||||
val: GameEnumValueJson
|
val: GameEnumValueJson
|
||||||
for val in enum['values']:
|
for val in enum['values']:
|
||||||
sub: str = name_to_snake(val['name']).upper()
|
sub: str = name_to_snake(val['name']).upper()
|
||||||
return f"{base}_{sub}"
|
return f"{base}.{sub}"
|
||||||
raise ValueError(f"Enum not found '{enum_name}'")
|
raise ValueError(f"Enum not found '{enum_name}'")
|
||||||
|
|
||||||
def gen_enum_file7(self) -> None:
|
def gen_enum_file7(self) -> None:
|
||||||
enum_code: str = ''
|
enum_code: str = 'from enum import Enum\n\n'
|
||||||
enum: GameEnumJson
|
enum: GameEnumJson
|
||||||
for enum in self.game_enums:
|
for enum in self.game_enums:
|
||||||
base: str = name_to_snake(enum['name']).upper()
|
base: str = name_to_camel(enum['name'])
|
||||||
|
enum_code += f'class {base}(Enum):\n'
|
||||||
val: GameEnumValueJson
|
val: GameEnumValueJson
|
||||||
for val in enum['values']:
|
for val in enum['values']:
|
||||||
sub: str = name_to_snake(val['name']).upper()
|
sub: str = name_to_snake(val['name']).upper()
|
||||||
enum_code += f"{base}_{sub}: int = {val['value']}\n"
|
enum_code += \
|
||||||
|
' ' \
|
||||||
|
f"{sub}: int = {val['value']}\n"
|
||||||
enum_code += "\n"
|
enum_code += "\n"
|
||||||
# cut off last doubled newline
|
# cut off last doubled newline
|
||||||
# because we do not split a section anymore
|
# because we do not split a section anymore
|
||||||
|
|
|
@ -1,122 +1,135 @@
|
||||||
PICKUP_HEALTH: int = 0
|
from enum import Enum
|
||||||
PICKUP_ARMOR: int = 1
|
|
||||||
PICKUP_GRENADE: int = 2
|
|
||||||
PICKUP_SHOTGUN: int = 3
|
|
||||||
PICKUP_LASER: int = 4
|
|
||||||
PICKUP_NINJA: int = 5
|
|
||||||
PICKUP_GUN: int = 6
|
|
||||||
PICKUP_HAMMER: int = 7
|
|
||||||
|
|
||||||
EMOTE_NORMAL: int = 0
|
class Pickup(Enum):
|
||||||
EMOTE_PAIN: int = 1
|
HEALTH: int = 0
|
||||||
EMOTE_HAPPY: int = 2
|
ARMOR: int = 1
|
||||||
EMOTE_SURPRISE: int = 3
|
GRENADE: int = 2
|
||||||
EMOTE_ANGRY: int = 4
|
SHOTGUN: int = 3
|
||||||
EMOTE_BLINK: int = 5
|
LASER: int = 4
|
||||||
|
NINJA: int = 5
|
||||||
|
GUN: int = 6
|
||||||
|
HAMMER: int = 7
|
||||||
|
|
||||||
EMOTICON_OOP: int = 0
|
class Emote(Enum):
|
||||||
EMOTICON_EXCLAMATION: int = 1
|
NORMAL: int = 0
|
||||||
EMOTICON_HEARTS: int = 2
|
PAIN: int = 1
|
||||||
EMOTICON_DROP: int = 3
|
HAPPY: int = 2
|
||||||
EMOTICON_DOTDOT: int = 4
|
SURPRISE: int = 3
|
||||||
EMOTICON_MUSIC: int = 5
|
ANGRY: int = 4
|
||||||
EMOTICON_SORRY: int = 6
|
BLINK: int = 5
|
||||||
EMOTICON_GHOST: int = 7
|
|
||||||
EMOTICON_SUSHI: int = 8
|
|
||||||
EMOTICON_SPLATTEE: int = 9
|
|
||||||
EMOTICON_DEVILTEE: int = 10
|
|
||||||
EMOTICON_ZOMG: int = 11
|
|
||||||
EMOTICON_ZZZ: int = 12
|
|
||||||
EMOTICON_WTF: int = 13
|
|
||||||
EMOTICON_EYES: int = 14
|
|
||||||
EMOTICON_QUESTION: int = 15
|
|
||||||
|
|
||||||
VOTE_UNKNOWN: int = 0
|
class Emoticon(Enum):
|
||||||
VOTE_START_OP: int = 1
|
OOP: int = 0
|
||||||
VOTE_START_KICK: int = 2
|
EXCLAMATION: int = 1
|
||||||
VOTE_START_SPEC: int = 3
|
HEARTS: int = 2
|
||||||
VOTE_END_ABORT: int = 4
|
DROP: int = 3
|
||||||
VOTE_END_PASS: int = 5
|
DOTDOT: int = 4
|
||||||
VOTE_END_FAIL: int = 6
|
MUSIC: int = 5
|
||||||
|
SORRY: int = 6
|
||||||
|
GHOST: int = 7
|
||||||
|
SUSHI: int = 8
|
||||||
|
SPLATTEE: int = 9
|
||||||
|
DEVILTEE: int = 10
|
||||||
|
ZOMG: int = 11
|
||||||
|
ZZZ: int = 12
|
||||||
|
WTF: int = 13
|
||||||
|
EYES: int = 14
|
||||||
|
QUESTION: int = 15
|
||||||
|
|
||||||
CHAT_NONE: int = 0
|
class Vote(Enum):
|
||||||
CHAT_ALL: int = 1
|
UNKNOWN: int = 0
|
||||||
CHAT_TEAM: int = 2
|
START_OP: int = 1
|
||||||
CHAT_WHISPER: int = 3
|
START_KICK: int = 2
|
||||||
|
START_SPEC: int = 3
|
||||||
|
END_ABORT: int = 4
|
||||||
|
END_PASS: int = 5
|
||||||
|
END_FAIL: int = 6
|
||||||
|
|
||||||
GAMEMSG_TEAM_SWAP: int = 0
|
class Chat(Enum):
|
||||||
GAMEMSG_SPEC_INVALIDID: int = 1
|
NONE: int = 0
|
||||||
GAMEMSG_TEAM_SHUFFLE: int = 2
|
ALL: int = 1
|
||||||
GAMEMSG_TEAM_BALANCE: int = 3
|
TEAM: int = 2
|
||||||
GAMEMSG_CTF_DROP: int = 4
|
WHISPER: int = 3
|
||||||
GAMEMSG_CTF_RETURN: int = 5
|
|
||||||
GAMEMSG_TEAM_ALL: int = 6
|
|
||||||
GAMEMSG_TEAM_BALANCE_VICTIM: int = 7
|
|
||||||
GAMEMSG_CTF_GRAB: int = 8
|
|
||||||
GAMEMSG_CTF_CAPTURE: int = 9
|
|
||||||
GAMEMSG_GAME_PAUSED: int = 10
|
|
||||||
|
|
||||||
WEAPON_HAMMER: int = 0
|
class Gamemsg(Enum):
|
||||||
WEAPON_PISTOL: int = 1
|
TEAM_SWAP: int = 0
|
||||||
WEAPON_SHOTGUN: int = 2
|
SPEC_INVALIDID: int = 1
|
||||||
WEAPON_GRENADE: int = 3
|
TEAM_SHUFFLE: int = 2
|
||||||
WEAPON_RIFLE: int = 4
|
TEAM_BALANCE: int = 3
|
||||||
WEAPON_NINJA: int = 5
|
CTF_DROP: int = 4
|
||||||
|
CTF_RETURN: int = 5
|
||||||
|
TEAM_ALL: int = 6
|
||||||
|
TEAM_BALANCE_VICTIM: int = 7
|
||||||
|
CTF_GRAB: int = 8
|
||||||
|
CTF_CAPTURE: int = 9
|
||||||
|
GAME_PAUSED: int = 10
|
||||||
|
|
||||||
TEAM_SPECTATORS: int = -1
|
class Weapon(Enum):
|
||||||
TEAM_RED: int = 0
|
HAMMER: int = 0
|
||||||
TEAM_BLUE: int = 1
|
PISTOL: int = 1
|
||||||
|
SHOTGUN: int = 2
|
||||||
|
GRENADE: int = 3
|
||||||
|
RIFLE: int = 4
|
||||||
|
NINJA: int = 5
|
||||||
|
|
||||||
SOUND_GUN_FIRE: int = 0
|
class Team(Enum):
|
||||||
SOUND_SHOTGUN_FIRE: int = 1
|
SPECTATORS: int = -1
|
||||||
SOUND_GRENADE_FIRE: int = 2
|
RED: int = 0
|
||||||
SOUND_HAMMER_FIRE: int = 3
|
BLUE: int = 1
|
||||||
SOUND_HAMMER_HIT: int = 4
|
|
||||||
SOUND_NINJA_FIRE: int = 5
|
|
||||||
SOUND_GRENADE_EXPLODE: int = 6
|
|
||||||
SOUND_NINJA_HIT: int = 7
|
|
||||||
SOUND_RIFLE_FIRE: int = 8
|
|
||||||
SOUND_RIFLE_BOUNCE: int = 9
|
|
||||||
SOUND_WEAPON_SWITCH: int = 10
|
|
||||||
SOUND_PLAYER_PAIN_SHORT: int = 11
|
|
||||||
SOUND_PLAYER_PAIN_LONG: int = 12
|
|
||||||
SOUND_BODY_LAND: int = 13
|
|
||||||
SOUND_PLAYER_AIRJUMP: int = 14
|
|
||||||
SOUND_PLAYER_JUMP: int = 15
|
|
||||||
SOUND_PLAYER_DIE: int = 16
|
|
||||||
SOUND_PLAYER_SPAWN: int = 17
|
|
||||||
SOUND_PLAYER_SKID: int = 18
|
|
||||||
SOUND_TEE_CRY: int = 19
|
|
||||||
SOUND_HOOK_LOOP: int = 20
|
|
||||||
SOUND_HOOK_ATTACH_GROUND: int = 21
|
|
||||||
SOUND_HOOK_ATTACH_PLAYER: int = 22
|
|
||||||
SOUND_HOOK_NOATTACH: int = 23
|
|
||||||
SOUND_PICKUP_HEALTH: int = 24
|
|
||||||
SOUND_PICKUP_ARMOR: int = 25
|
|
||||||
SOUND_PICKUP_GRENADE: int = 26
|
|
||||||
SOUND_PICKUP_SHOTGUN: int = 27
|
|
||||||
SOUND_PICKUP_NINJA: int = 28
|
|
||||||
SOUND_WEAPON_SPAWN: int = 29
|
|
||||||
SOUND_WEAPON_NOAMMO: int = 30
|
|
||||||
SOUND_HIT: int = 31
|
|
||||||
SOUND_CHAT_SERVER: int = 32
|
|
||||||
SOUND_CHAT_CLIENT: int = 33
|
|
||||||
SOUND_CHAT_HIGHLIGHT: int = 34
|
|
||||||
SOUND_CTF_DROP: int = 35
|
|
||||||
SOUND_CTF_RETURN: int = 36
|
|
||||||
SOUND_CTF_GRAB_PL: int = 37
|
|
||||||
SOUND_CTF_GRAB_EN: int = 38
|
|
||||||
SOUND_CTF_CAPTURE: int = 39
|
|
||||||
SOUND_MENU: int = 40
|
|
||||||
|
|
||||||
SPEC_FREEVIEW: int = 0
|
class Sound(Enum):
|
||||||
SPEC_PLAYER: int = 1
|
GUN_FIRE: int = 0
|
||||||
SPEC_FLAGRED: int = 2
|
SHOTGUN_FIRE: int = 1
|
||||||
SPEC_FLAGBLUE: int = 3
|
GRENADE_FIRE: int = 2
|
||||||
|
HAMMER_FIRE: int = 3
|
||||||
|
HAMMER_HIT: int = 4
|
||||||
|
NINJA_FIRE: int = 5
|
||||||
|
GRENADE_EXPLODE: int = 6
|
||||||
|
NINJA_HIT: int = 7
|
||||||
|
RIFLE_FIRE: int = 8
|
||||||
|
RIFLE_BOUNCE: int = 9
|
||||||
|
WEAPON_SWITCH: int = 10
|
||||||
|
PLAYER_PAIN_SHORT: int = 11
|
||||||
|
PLAYER_PAIN_LONG: int = 12
|
||||||
|
BODY_LAND: int = 13
|
||||||
|
PLAYER_AIRJUMP: int = 14
|
||||||
|
PLAYER_JUMP: int = 15
|
||||||
|
PLAYER_DIE: int = 16
|
||||||
|
PLAYER_SPAWN: int = 17
|
||||||
|
PLAYER_SKID: int = 18
|
||||||
|
TEE_CRY: int = 19
|
||||||
|
HOOK_LOOP: int = 20
|
||||||
|
HOOK_ATTACH_GROUND: int = 21
|
||||||
|
HOOK_ATTACH_PLAYER: int = 22
|
||||||
|
HOOK_NOATTACH: int = 23
|
||||||
|
PICKUP_HEALTH: int = 24
|
||||||
|
PICKUP_ARMOR: int = 25
|
||||||
|
PICKUP_GRENADE: int = 26
|
||||||
|
PICKUP_SHOTGUN: int = 27
|
||||||
|
PICKUP_NINJA: int = 28
|
||||||
|
WEAPON_SPAWN: int = 29
|
||||||
|
WEAPON_NOAMMO: int = 30
|
||||||
|
HIT: int = 31
|
||||||
|
CHAT_SERVER: int = 32
|
||||||
|
CHAT_CLIENT: int = 33
|
||||||
|
CHAT_HIGHLIGHT: int = 34
|
||||||
|
CTF_DROP: int = 35
|
||||||
|
CTF_RETURN: int = 36
|
||||||
|
CTF_GRAB_PL: int = 37
|
||||||
|
CTF_GRAB_EN: int = 38
|
||||||
|
CTF_CAPTURE: int = 39
|
||||||
|
MENU: int = 40
|
||||||
|
|
||||||
SKINPART_BODY: int = 0
|
class Spec(Enum):
|
||||||
SKINPART_MARKING: int = 1
|
FREEVIEW: int = 0
|
||||||
SKINPART_DECORATION: int = 2
|
PLAYER: int = 1
|
||||||
SKINPART_HANDS: int = 3
|
FLAGRED: int = 2
|
||||||
SKINPART_FEET: int = 4
|
FLAGBLUE: int = 3
|
||||||
SKINPART_EYES: int = 5
|
|
||||||
|
class Skinpart(Enum):
|
||||||
|
BODY: int = 0
|
||||||
|
MARKING: int = 1
|
||||||
|
DECORATION: int = 2
|
||||||
|
HANDS: int = 3
|
||||||
|
FEET: int = 4
|
||||||
|
EYES: int = 5
|
||||||
|
|
|
@ -9,7 +9,7 @@ import twnet_parser.enum7 as enum7
|
||||||
class MsgClEmoticon(PrettyPrint):
|
class MsgClEmoticon(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
emoticon: int = enum7.EMOTICON_OOP
|
emoticon: int = enum7.Emoticon.OOP.value
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message_name = 'cl_emoticon'
|
self.message_name = 'cl_emoticon'
|
||||||
self.system_message = False
|
self.system_message = False
|
||||||
|
|
|
@ -9,7 +9,7 @@ import twnet_parser.enum7 as enum7
|
||||||
class MsgClSay(PrettyPrint):
|
class MsgClSay(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mode: int = enum7.CHAT_NONE,
|
mode: int = enum7.Chat.NONE.value,
|
||||||
target: int = 0,
|
target: int = 0,
|
||||||
message: str = 'default'
|
message: str = 'default'
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -9,7 +9,7 @@ import twnet_parser.enum7 as enum7
|
||||||
class MsgClSetSpectatorMode(PrettyPrint):
|
class MsgClSetSpectatorMode(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
spec_mode: int = enum7.SPEC_FREEVIEW,
|
spec_mode: int = enum7.Spec.FREEVIEW.value,
|
||||||
spectator_id: int = 0
|
spectator_id: int = 0
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message_name = 'cl_set_spectator_mode'
|
self.message_name = 'cl_set_spectator_mode'
|
||||||
|
|
|
@ -9,7 +9,7 @@ import twnet_parser.enum7 as enum7
|
||||||
class MsgClSetTeam(PrettyPrint):
|
class MsgClSetTeam(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
team: int = enum7.TEAM_SPECTATORS
|
team: int = enum7.Team.SPECTATORS.value
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message_name = 'cl_set_team'
|
self.message_name = 'cl_set_team'
|
||||||
self.system_message = False
|
self.system_message = False
|
||||||
|
|
|
@ -11,7 +11,7 @@ class MsgDeClientEnter(PrettyPrint):
|
||||||
self,
|
self,
|
||||||
name: str = 'default',
|
name: str = 'default',
|
||||||
client_id: int = 0,
|
client_id: int = 0,
|
||||||
team: int = enum7.TEAM_SPECTATORS
|
team: int = enum7.Team.SPECTATORS.value
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message_name = 'de_client_enter'
|
self.message_name = 'de_client_enter'
|
||||||
self.system_message = False
|
self.system_message = False
|
||||||
|
|
|
@ -9,7 +9,7 @@ import twnet_parser.enum7 as enum7
|
||||||
class MsgSvChat(PrettyPrint):
|
class MsgSvChat(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mode: int = enum7.CHAT_NONE,
|
mode: int = enum7.Chat.NONE.value,
|
||||||
client_id: int = 0,
|
client_id: int = 0,
|
||||||
target_id: int = 0,
|
target_id: int = 0,
|
||||||
message: str = 'default'
|
message: str = 'default'
|
||||||
|
|
|
@ -12,7 +12,7 @@ class MsgSvClientInfo(PrettyPrint):
|
||||||
self,
|
self,
|
||||||
client_id: int = 0,
|
client_id: int = 0,
|
||||||
local: bool = False,
|
local: bool = False,
|
||||||
team: int = enum7.TEAM_SPECTATORS,
|
team: int = enum7.Team.SPECTATORS.value,
|
||||||
name: str = 'default',
|
name: str = 'default',
|
||||||
clan: str = 'default',
|
clan: str = 'default',
|
||||||
country: int = 0,
|
country: int = 0,
|
||||||
|
|
|
@ -10,7 +10,7 @@ class MsgSvEmoticon(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client_id: int = 0,
|
client_id: int = 0,
|
||||||
emoticon: int = enum7.EMOTICON_OOP
|
emoticon: int = enum7.Emoticon.OOP.value
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message_name = 'sv_emoticon'
|
self.message_name = 'sv_emoticon'
|
||||||
self.system_message = False
|
self.system_message = False
|
||||||
|
|
|
@ -10,7 +10,7 @@ class MsgSvTeam(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client_id: int = 0,
|
client_id: int = 0,
|
||||||
team: int = enum7.TEAM_SPECTATORS,
|
team: int = enum7.Team.SPECTATORS.value,
|
||||||
silent: bool = False,
|
silent: bool = False,
|
||||||
cooldown_tick: int = 0
|
cooldown_tick: int = 0
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -10,7 +10,7 @@ class MsgSvVoteSet(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
client_id: int = 0,
|
client_id: int = 0,
|
||||||
type: int = enum7.VOTE_UNKNOWN,
|
type: int = enum7.Vote.UNKNOWN.value,
|
||||||
timeout: int = 0,
|
timeout: int = 0,
|
||||||
description: str = 'default',
|
description: str = 'default',
|
||||||
reason: str = 'default'
|
reason: str = 'default'
|
||||||
|
|
|
@ -9,7 +9,7 @@ import twnet_parser.enum7 as enum7
|
||||||
class MsgSvWeaponPickup(PrettyPrint):
|
class MsgSvWeaponPickup(PrettyPrint):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
weapon: int = enum7.WEAPON_HAMMER
|
weapon: int = enum7.Weapon.HAMMER.value
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message_name = 'sv_weapon_pickup'
|
self.message_name = 'sv_weapon_pickup'
|
||||||
self.system_message = False
|
self.system_message = False
|
||||||
|
|
Loading…
Reference in a new issue