minor adjustments to LevelState
This commit is contained in:
parent
c2f76d4222
commit
f44d7c7858
4 changed files with 345 additions and 33 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
*pycache*
|
*pycache*
|
||||||
|
*guy_originale.py*
|
||||||
|
|
|
||||||
327
gui_originale.py
Normal file
327
gui_originale.py
Normal file
|
|
@ -0,0 +1,327 @@
|
||||||
|
import pygame
|
||||||
|
from constants import *
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class ButtonTypes(Enum):
|
||||||
|
PLAY = "play"
|
||||||
|
RESUME = "resume"
|
||||||
|
MENU = "menu"
|
||||||
|
DELETE = "delete"
|
||||||
|
QUIT = "quit"
|
||||||
|
RETRY = "retry"
|
||||||
|
FW = "fw"
|
||||||
|
BW = "bw"
|
||||||
|
GO = "go"
|
||||||
|
|
||||||
|
class MenuType(Enum):
|
||||||
|
PAUSE = 0
|
||||||
|
COMPLETED = 1
|
||||||
|
START = 2
|
||||||
|
SELECT = 3
|
||||||
|
|
||||||
|
|
||||||
|
# Button class that can store a value
|
||||||
|
class Button:
|
||||||
|
def __init__(self, x, y, value, type=ButtonTypes.PLAY):
|
||||||
|
# value is a utility field that can store something useful for the use of the button
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
self.original_image, self.dark_image = self.load_image(type)
|
||||||
|
self.image = self.original_image.convert_alpha()
|
||||||
|
|
||||||
|
# Collide rect measures based on the type
|
||||||
|
if type == ButtonTypes.FW or type == ButtonTypes.BW:
|
||||||
|
width = 105
|
||||||
|
height = 82
|
||||||
|
elif type == ButtonTypes.GO:
|
||||||
|
width = 83
|
||||||
|
height = 63
|
||||||
|
else:
|
||||||
|
width = 232
|
||||||
|
height = 82
|
||||||
|
|
||||||
|
self.rect = pygame.Rect(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
int(width * SCALE_FACTOR),
|
||||||
|
int(height * SCALE_FACTOR)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.image_rect = self.image.get_rect(
|
||||||
|
center=self.rect.center
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_image(self, type):
|
||||||
|
if type == ButtonTypes.MENU:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/menu_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/menu_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.QUIT:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/quit_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/quit_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.DELETE:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/delete_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/delete_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.RESUME:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/resume_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/resume_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.PLAY:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/play_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/play_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.RETRY:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/retry_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/retry_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.FW:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/fw_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/fw_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.BW:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/bw_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/bw_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == ButtonTypes.GO:
|
||||||
|
image = pygame.transform.scale_by(pygame.image.load('assets/go_btn.png'), SCALE_FACTOR)
|
||||||
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/go_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
|
||||||
|
return image, dark_image
|
||||||
|
|
||||||
|
def draw(self, screen, mouse, font):
|
||||||
|
#text_surface = text_outline_perfect(font, self.text, WHITE, DARK)
|
||||||
|
#text_rect = text_surface.get_rect(center=self.rect.center)
|
||||||
|
# shadow_rect = sahdow_surface.get_rect(center=self.rect.center)
|
||||||
|
|
||||||
|
if self.rect.collidepoint(mouse):
|
||||||
|
screen.blit(self.dark_image, self.image_rect)
|
||||||
|
else:
|
||||||
|
screen.blit(self.image, self.image_rect)
|
||||||
|
|
||||||
|
#pygame.draw.rect(screen, (255, 0, 255), self.rect, 2)
|
||||||
|
#screen.blit(text_surface, text_rect)
|
||||||
|
|
||||||
|
def clicked(self, mouse):
|
||||||
|
return self.rect.collidepoint(mouse)
|
||||||
|
|
||||||
|
# Easy, text area
|
||||||
|
class TextArea:
|
||||||
|
def __init__(self, x, y, text, color):
|
||||||
|
self.text = text
|
||||||
|
self.rect = pygame.Rect(x, y, 0, 0)
|
||||||
|
self.color = color
|
||||||
|
def draw(self, screen, font):
|
||||||
|
text_surface = font.render(self.text, True, self.color)
|
||||||
|
self.rect = text_surface.get_rect(center=self.rect.center)
|
||||||
|
screen.blit(text_surface, self.rect)
|
||||||
|
|
||||||
|
class EntryState(Enum):
|
||||||
|
UNLOCKED = 0
|
||||||
|
LOCKED = 1
|
||||||
|
|
||||||
|
class SelectEntry:
|
||||||
|
def __init__(self, x, y, text, value, state):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.text = text
|
||||||
|
self.btn = Button(0, 0, value, ButtonTypes.GO)
|
||||||
|
self.area = TextArea(0, 0, text, DARK)
|
||||||
|
self.icon = self.load_icon(state)
|
||||||
|
self.icon_rect = self.icon.get_rect()
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
# Calculate full width
|
||||||
|
#self.width = self.btn.rect.width + self.area.rect.width + self.icon_rect.width
|
||||||
|
self.width = 500
|
||||||
|
self.height = self.btn.rect.height
|
||||||
|
self.rect = pygame.Rect(x, y, self.width, self.height)
|
||||||
|
|
||||||
|
def clicked(self, mouse):
|
||||||
|
return self.btn.clicked(mouse)
|
||||||
|
|
||||||
|
def load_icon(self, state):
|
||||||
|
if state == EntryState.LOCKED:
|
||||||
|
return pygame.image.load("assets/locked.png").convert_alpha()
|
||||||
|
elif state == EntryState.UNLOCKED:
|
||||||
|
return pygame.image.load("assets/unlocked.png").convert_alpha()
|
||||||
|
|
||||||
|
def draw(self, screen, mouse, font):
|
||||||
|
|
||||||
|
self.btn.rect.y = self.rect.y
|
||||||
|
self.btn.image_rect.y = self.rect.y
|
||||||
|
self.area.rect.y = self.rect.y
|
||||||
|
self.icon_rect.y = self.rect.y
|
||||||
|
|
||||||
|
x = self.rect.x + 20
|
||||||
|
self.btn.rect.x = x
|
||||||
|
self.btn.image_rect.x = x
|
||||||
|
x += self.btn.rect.width*2
|
||||||
|
|
||||||
|
self.area.rect.x = x
|
||||||
|
|
||||||
|
self.icon_rect.x = 800
|
||||||
|
|
||||||
|
self.btn.draw(screen, mouse, font)
|
||||||
|
self.area.draw(screen, font)
|
||||||
|
screen.blit(self.icon, self.icon_rect)
|
||||||
|
|
||||||
|
|
||||||
|
# Single page menu class
|
||||||
|
class MenuPage:
|
||||||
|
def __init__(self, type):
|
||||||
|
self.top_text_area = None
|
||||||
|
self.bottom_text_area = None
|
||||||
|
self.buttons = []
|
||||||
|
self.entries = []
|
||||||
|
self.bg_image = self.load_card(type)
|
||||||
|
|
||||||
|
def load_card(self, type):
|
||||||
|
if type == MenuType.PAUSE:
|
||||||
|
return pygame.transform.scale_by(pygame.image.load('assets/paused_card.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == MenuType.COMPLETED:
|
||||||
|
return pygame.transform.scale_by(pygame.image.load('assets/completed_card.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == MenuType.START:
|
||||||
|
return pygame.transform.scale_by(pygame.image.load('assets/menu_card.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
elif type == MenuType.SELECT:
|
||||||
|
return pygame.transform.scale_by(pygame.image.load('assets/select_card.png').convert_alpha(), SCALE_FACTOR)
|
||||||
|
|
||||||
|
# Button methods
|
||||||
|
def add_button(self, value, type):
|
||||||
|
self.buttons.append(Button(0, 0, value, type))
|
||||||
|
def add_entry(self, text, value, state):
|
||||||
|
self.entries.append(SelectEntry(0, 0, text, value, state))
|
||||||
|
|
||||||
|
def add_top_text(self, text):
|
||||||
|
self.top_text_area = TextArea(0, 0, text, DARK)
|
||||||
|
def add_bottom_text(self, text):
|
||||||
|
self.bottom_text_area = TextArea(0, 0, text, DARK)
|
||||||
|
def draw(self, screen, mouse, btn_font, text_font):
|
||||||
|
center_x, center_y = screen.get_rect().center
|
||||||
|
|
||||||
|
# Draw the card image
|
||||||
|
screen.blit(self.bg_image, (center_x-self.bg_image.get_width()/2, center_y-self.bg_image.get_height()/2))
|
||||||
|
|
||||||
|
spacing = BTN_HEIGHT * 0.5
|
||||||
|
elements = []
|
||||||
|
|
||||||
|
if self.top_text_area is not None:
|
||||||
|
elements.append(self.top_text_area)
|
||||||
|
|
||||||
|
elements.extend(self.buttons)
|
||||||
|
elements.extend(self.entries)
|
||||||
|
|
||||||
|
if self.bottom_text_area is not None:
|
||||||
|
elements.append(self.bottom_text_area)
|
||||||
|
|
||||||
|
# Calculate the dimensions
|
||||||
|
heights = []
|
||||||
|
for element in elements:
|
||||||
|
if isinstance(element, TextArea):
|
||||||
|
text_surface = text_font.render(element.text, True, WHITE)
|
||||||
|
heights.append(text_surface.get_height())
|
||||||
|
elif isinstance(element, Button):
|
||||||
|
heights.append(element.image.get_height()/2)
|
||||||
|
else:
|
||||||
|
heights.append(element.height)
|
||||||
|
|
||||||
|
self.total_height = sum(heights) + spacing * (len(elements) - 1)
|
||||||
|
y = center_y - self.total_height / 2
|
||||||
|
|
||||||
|
for element, height in zip(elements, heights):
|
||||||
|
element.rect.centerx = center_x
|
||||||
|
element.rect.y = y
|
||||||
|
|
||||||
|
if isinstance(element, Button):
|
||||||
|
element.image_rect.center = element.rect.center
|
||||||
|
element.draw(screen, mouse, btn_font)
|
||||||
|
elif isinstance(element, TextArea):
|
||||||
|
element.draw(screen, text_font)
|
||||||
|
else:
|
||||||
|
element.draw(screen, mouse, text_font)
|
||||||
|
|
||||||
|
y += height + spacing
|
||||||
|
|
||||||
|
def handle_event(self, event, mouse):
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
return "quit_game"
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
for button in self.buttons:
|
||||||
|
if button.clicked(mouse):
|
||||||
|
return button.value
|
||||||
|
for entry in self.entries:
|
||||||
|
if entry.clicked(mouse):
|
||||||
|
return entry.value
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Multi-page menu, automatically creates new page 3 buttons per page
|
||||||
|
class Menu:
|
||||||
|
def __init__(self, menu_type):
|
||||||
|
self.bottom_buttons = [Button(0, 0, None, ButtonTypes.BW), Button(0, 0, "menu", ButtonTypes.MENU), Button(0, 0, None, ButtonTypes.FW)]
|
||||||
|
|
||||||
|
self.pages = []
|
||||||
|
self.menu_type = menu_type
|
||||||
|
# Creating the first page
|
||||||
|
self.pages.append(MenuPage(self.menu_type))
|
||||||
|
self.current_page_i = 0
|
||||||
|
|
||||||
|
|
||||||
|
# For now the paged menu can only have pages with just buttons, no top or bottom text
|
||||||
|
def add_button(self, value, type):
|
||||||
|
current_page = self.pages[-1]
|
||||||
|
if len(current_page.buttons) >= MAX_BTN:
|
||||||
|
current_page = MenuPage(self.menu_type)
|
||||||
|
self.pages.append(current_page)
|
||||||
|
|
||||||
|
current_page.add_button(value, type)
|
||||||
|
|
||||||
|
def add_entry(self, text, value, state):
|
||||||
|
current_page = self.pages[-1]
|
||||||
|
if len(current_page.entries) >= MAX_BTN:
|
||||||
|
current_page = MenuPage(self.menu_type)
|
||||||
|
self.pages.append(current_page)
|
||||||
|
|
||||||
|
current_page.add_entry(text, value, state)
|
||||||
|
|
||||||
|
def draw(self, screen, mouse, btn_font, text_font):
|
||||||
|
center_x, center_y = screen.get_rect().center
|
||||||
|
spacing = BTN_HEIGHT * 0.5
|
||||||
|
|
||||||
|
self.pages[self.current_page_i].draw(screen, mouse, btn_font, text_font)
|
||||||
|
|
||||||
|
# bottom y, OK
|
||||||
|
bottom_y = center_y + self.pages[self.current_page_i].total_height / 2 + spacing
|
||||||
|
|
||||||
|
# Calculate total width
|
||||||
|
group_width = 0
|
||||||
|
for button in self.bottom_buttons:
|
||||||
|
group_width += button.rect.width
|
||||||
|
|
||||||
|
x = center_x - group_width / 2.5
|
||||||
|
for button in self.bottom_buttons:
|
||||||
|
button.rect.centerx = x
|
||||||
|
button.rect.y = bottom_y
|
||||||
|
button.image_rect.center = button.rect.center
|
||||||
|
x += group_width / 2.5
|
||||||
|
|
||||||
|
for button in self.bottom_buttons:
|
||||||
|
button.draw(screen, mouse, btn_font)
|
||||||
|
|
||||||
|
def handle_event(self, event, mouse):
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
return "quit_game"
|
||||||
|
|
||||||
|
# Maybe in the future the index can wrap around
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
if self.bottom_buttons[0].clicked(mouse):
|
||||||
|
if self.current_page_i > 0:
|
||||||
|
self.current_page_i -= 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.bottom_buttons[1].clicked(mouse):
|
||||||
|
return self.bottom_buttons[1].value
|
||||||
|
|
||||||
|
if self.bottom_buttons[2].clicked(mouse):
|
||||||
|
if self.current_page_i < len(self.pages) - 1:
|
||||||
|
self.current_page_i += 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.pages[self.current_page_i].handle_event(event, mouse)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
@ -12,16 +12,16 @@ from new_gui import *
|
||||||
# Enum and Tables
|
# Enum and Tables
|
||||||
# -------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------
|
||||||
class TileTypes(Enum):
|
class TileTypes(Enum):
|
||||||
FLOOR = 0
|
FLOOR = '.'
|
||||||
WALL = 1
|
WALL = '#'
|
||||||
RED_PLATE = 2
|
RED_PLATE = 'r'
|
||||||
RED_DOOR = 3
|
RED_DOOR = 'R'
|
||||||
ORANGE_PLATE = 4
|
ORANGE_PLATE = 'o'
|
||||||
ORANGE_DOOR = 5
|
ORANGE_DOOR = 'O'
|
||||||
TEAL_PLATE = 6
|
TEAL_PLATE = 't'
|
||||||
TEAL_DOOR = 7
|
TEAL_DOOR = 'T'
|
||||||
BLUE_ARRIVE = 8
|
BLUE_ARRIVE = 'B'
|
||||||
GREEN_ARRIVE = 9
|
GREEN_ARRIVE = 'G'
|
||||||
|
|
||||||
TILE_FILES = {
|
TILE_FILES = {
|
||||||
TileTypes.FLOOR: "assets/floor.svg",
|
TileTypes.FLOOR: "assets/floor.svg",
|
||||||
|
|
@ -93,22 +93,17 @@ class Level:
|
||||||
doors = {}
|
doors = {}
|
||||||
plates = {}
|
plates = {}
|
||||||
for (row, col), character in np.ndenumerate(self.map):
|
for (row, col), character in np.ndenumerate(self.map):
|
||||||
if character in ('R'):
|
if character in ('R', 'T', 'O'):
|
||||||
# Todo: choose type based on character
|
# Todo: choose type based on character
|
||||||
doors[character] = Door(col, row, TileTypes.RED_DOOR)
|
doors[character] = Door(col, row, TileTypes(character))
|
||||||
if character.lower() in plates:
|
if character.lower() in plates:
|
||||||
plates[character.lower()].set_door(doors[character])
|
plates[character.lower()].set_door(doors[character])
|
||||||
if character in ('r'):
|
if character in ('r', 't', 'o'):
|
||||||
# Todo: choose type based on character
|
# Todo: choose type based on character
|
||||||
plates[character] = PressurePlate(col, row, TileTypes.RED_PLATE)
|
plates[character] = PressurePlate(col, row, TileTypes(character))
|
||||||
if character.upper() in doors:
|
if character.upper() in doors:
|
||||||
plates[character].set_door(doors[character.upper()])
|
plates[character].set_door(doors[character.upper()])
|
||||||
|
|
||||||
|
|
||||||
#for (row, col), character in np.ndenumerate(self.map):
|
|
||||||
# if character == 'r':
|
|
||||||
# plates.append(PressurePlate(col, row, TileTypes.RED_PLATE))
|
|
||||||
|
|
||||||
return doors, plates
|
return doors, plates
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -128,10 +123,6 @@ class Level:
|
||||||
# Draw the appropriate tile color
|
# Draw the appropriate tile color
|
||||||
if character == '#':
|
if character == '#':
|
||||||
screen.blit(self.wall_tile, rect)
|
screen.blit(self.wall_tile, rect)
|
||||||
# elif character == 'R' and pressing != "red":
|
|
||||||
# screen.blit(self.red_door, rect)
|
|
||||||
# elif character == 'r':
|
|
||||||
# screen.blit(self.red_press, rect)
|
|
||||||
elif character == 'B':
|
elif character == 'B':
|
||||||
screen.blit(self.blue_arrive, rect)
|
screen.blit(self.blue_arrive, rect)
|
||||||
elif character == 'G':
|
elif character == 'G':
|
||||||
|
|
@ -194,7 +185,7 @@ class LevelState:
|
||||||
if dir == Direction.HORIZ:
|
if dir == Direction.HORIZ:
|
||||||
next_x = x + speed
|
next_x = x + speed
|
||||||
if (self.level.map[y, next_x] == '#' or
|
if (self.level.map[y, next_x] == '#' or
|
||||||
(self.level.map[y, next_x] == 'R' and self.pressing != "red") or
|
(self.level.map[y, next_x] == 'R' and not self.level.plates['r'].is_pressed()) or
|
||||||
(next_x == self.players[1-self.curr_player_i].x and y == self.players[1-self.curr_player_i].y)):
|
(next_x == self.players[1-self.curr_player_i].x and y == self.players[1-self.curr_player_i].y)):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
@ -202,7 +193,7 @@ class LevelState:
|
||||||
else:
|
else:
|
||||||
next_y = y + speed
|
next_y = y + speed
|
||||||
if (self.level.map[next_y, x] == '#' or
|
if (self.level.map[next_y, x] == '#' or
|
||||||
(self.level.map[next_y, x] == 'R' and self.pressing != "red") or
|
(self.level.map[next_y, x] == 'R' and not self.level.plates['r'].is_pressed()) or
|
||||||
(next_y == self.players[1-self.curr_player_i].y and x == self.players[1-self.curr_player_i].x)):
|
(next_y == self.players[1-self.curr_player_i].y and x == self.players[1-self.curr_player_i].x)):
|
||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
@ -213,14 +204,7 @@ class LevelState:
|
||||||
p1 = self.players[0]
|
p1 = self.players[0]
|
||||||
p2 = self.players[1]
|
p2 = self.players[1]
|
||||||
|
|
||||||
# check for pressure on plates
|
# Pressure plate check
|
||||||
# TODO: Add the other types of plates to press
|
|
||||||
if (self.level.map[p1.y, p1.x] == "r" and p1.powers[p1.current_power_i] == Powers.PRESS.value) or \
|
|
||||||
(self.level.map[p2.y, p2.x] == "r" and p2.powers[p2.current_power_i] == Powers.PRESS.value):
|
|
||||||
self.pressing = "red"
|
|
||||||
else:
|
|
||||||
self.pressing = "none"
|
|
||||||
|
|
||||||
for plate in self.level.plates.values():
|
for plate in self.level.plates.values():
|
||||||
if((p1.rect.x, p1.rect.y) == (plate.x, plate.y) and
|
if((p1.rect.x, p1.rect.y) == (plate.x, plate.y) and
|
||||||
p1.powers[p1.current_power_i] == Powers.PRESS.value) or ((p2.rect.x, p2.rect.y) == (plate.x, plate.y) and
|
p1.powers[p1.current_power_i] == Powers.PRESS.value) or ((p2.rect.x, p2.rect.y) == (plate.x, plate.y) and
|
||||||
|
|
|
||||||
BIN
progress.db
BIN
progress.db
Binary file not shown.
Loading…
Add table
Reference in a new issue