Compare commits
2 commits
main
...
wolf-patch
| Author | SHA1 | Date | |
|---|---|---|---|
| a0bda5cdac | |||
| 3a493c1485 |
7 changed files with 395 additions and 114 deletions
58
enemy.cpp
Normal file
58
enemy.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include "enemy.hpp"
|
||||
|
||||
Enemy::Enemy(float x, float y, float leftB, float rightB)
|
||||
: shape(sf::Vector2f(40.f, 50.f)),
|
||||
leftBound(leftB),
|
||||
rightBound(rightB),
|
||||
speed(200.f),
|
||||
movingRight(true),
|
||||
aggro(false),
|
||||
platformY(y)
|
||||
{
|
||||
shape.setPosition(x, y);
|
||||
shape.setFillColor(sf::Color::Red); // просто колір, без текстури
|
||||
}
|
||||
|
||||
void Enemy::update(float dt, const sf::Vector2f& playerPos) {
|
||||
float x = shape.getPosition().x;
|
||||
|
||||
if (aggro) {
|
||||
if (playerPos.x < x)
|
||||
x -= speed * dt;
|
||||
else
|
||||
x += speed * dt;
|
||||
} else {
|
||||
if (movingRight) {
|
||||
x += speed * dt;
|
||||
if (x + shape.getSize().x >= rightBound) movingRight = false;
|
||||
} else {
|
||||
x -= speed * dt;
|
||||
if (x <= leftBound) movingRight = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (x < leftBound) x = leftBound;
|
||||
if (x + shape.getSize().x > rightBound) x = rightBound - shape.getSize().x;
|
||||
|
||||
shape.setPosition(x, platformY);
|
||||
}
|
||||
|
||||
void Enemy::draw(sf::RenderWindow& window) {
|
||||
window.draw(shape);
|
||||
}
|
||||
|
||||
void Enemy::setAggro(bool value) {
|
||||
aggro = value;
|
||||
}
|
||||
|
||||
float Enemy::getPlatformY() const {
|
||||
return platformY;
|
||||
}
|
||||
|
||||
sf::FloatRect Enemy::getBounds() const {
|
||||
return shape.getGlobalBounds();
|
||||
}
|
||||
|
||||
sf::RectangleShape& Enemy::getShape() {
|
||||
return shape;
|
||||
}
|
||||
25
enemy.hpp
Normal file
25
enemy.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
class Enemy {
|
||||
private:
|
||||
sf::RectangleShape shape;
|
||||
|
||||
float leftBound;
|
||||
float rightBound;
|
||||
float speed;
|
||||
bool movingRight;
|
||||
bool aggro;
|
||||
float platformY;
|
||||
|
||||
public:
|
||||
Enemy(float x, float y, float leftB, float rightB);
|
||||
|
||||
void update(float dt, const sf::Vector2f& playerPos);
|
||||
void draw(sf::RenderWindow& window);
|
||||
|
||||
void setAggro(bool value);
|
||||
float getPlatformY() const;
|
||||
sf::FloatRect getBounds() const;
|
||||
sf::RectangleShape& getShape();
|
||||
};
|
||||
143
game.cpp
143
game.cpp
|
|
@ -1,23 +1,52 @@
|
|||
#include <SFML/Graphics.hpp>
|
||||
#include "vokzal.hpp"
|
||||
#include "enemy.hpp"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
// Створюємо вікно 1950x1200
|
||||
sf::RenderWindow window(sf::VideoMode(1950, 1200), "Test Game Window");
|
||||
//
|
||||
// Завантаження фону
|
||||
sf::Texture backgroundTexture;
|
||||
if (!backgroundTexture.loadFromFile("img/background.png")) {
|
||||
cout << "ERROR: Cannot load background.png\n";
|
||||
}
|
||||
sf::Sprite backgroundSprite;
|
||||
backgroundSprite.setTexture(backgroundTexture);
|
||||
float scaleX = 1950.f / backgroundTexture.getSize().x;
|
||||
float scaleY = 1200.f / backgroundTexture.getSize().y;
|
||||
backgroundSprite.setScale(scaleX, scaleY);
|
||||
|
||||
sf::RenderWindow window(sf::VideoMode(1950, 1200), "2D Platformer");
|
||||
window.setFramerateLimit(65);
|
||||
|
||||
// Платформи
|
||||
vector<Platform> platforms;
|
||||
platforms.push_back(Platform(299.f, 900.f, 200.f, 40.f));
|
||||
platforms.push_back(Platform(700.f, 725.f, 200.f, 40.f));
|
||||
platforms.push_back(Platform(1300.f, 700.f, 300.f, 50.f));
|
||||
platforms.push_back(Platform(400.f, 600.f, 250.f, 30.f));
|
||||
|
||||
platforms.push_back(Platform(700.f, 900.f, 400.f, 30.f));
|
||||
Platform ground(100.f, 1050.f, 300.f, 50.f);
|
||||
|
||||
window.setFramerateLimit(90); //вище 200 не рокимендую піднімати частоту після 600 буде чути писк дроселів
|
||||
|
||||
// Гравець
|
||||
Player player;
|
||||
|
||||
// Вороги
|
||||
vector<Enemy> enemies;
|
||||
for (auto& platform : platforms) {
|
||||
float left = platform.getBounds().left;
|
||||
float top = platform.getBounds().top;
|
||||
float width = platform.getBounds().width;
|
||||
enemies.push_back(Enemy(left + 10.f, top - 50.f, left, left + width));
|
||||
}
|
||||
|
||||
// Кулі
|
||||
vector<Bullet> bullets;
|
||||
sf::Clock fireClock;
|
||||
float fireCooldown = 0.3f;
|
||||
|
||||
sf::Clock gameClock;
|
||||
|
||||
while (window.isOpen()) {
|
||||
|
|
@ -29,22 +58,102 @@ int main() {
|
|||
|
||||
float deltaTime = gameClock.restart().asSeconds();
|
||||
|
||||
player.update(deltaTime, platforms); // логіка гравця
|
||||
// Рух гравця
|
||||
player.update(deltaTime, platforms);
|
||||
|
||||
window.clear(sf::Color::Black); // очистка вікна
|
||||
|
||||
// малюємо платформи
|
||||
for (auto& platform : platforms) {
|
||||
platform.draw(window);
|
||||
// Створення кулі при натиску пробілу
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Space) &&
|
||||
fireClock.getElapsedTime().asSeconds() >= fireCooldown)
|
||||
{
|
||||
fireClock.restart();
|
||||
sf::Vector2f playerPos(
|
||||
player.getShape().getPosition().x + player.getShape().getSize().x / 2,
|
||||
player.getShape().getPosition().y + player.getShape().getSize().y / 2
|
||||
);
|
||||
bullets.push_back(Bullet(playerPos, player.isFacingRight()));
|
||||
}
|
||||
|
||||
// Оновлення і видалення куль
|
||||
for (auto it = bullets.begin(); it != bullets.end(); ) {
|
||||
it->update(deltaTime);
|
||||
if (it->isOffScreen() || it->collidesWithPlatform(platforms)) {
|
||||
it = bullets.erase(it);
|
||||
} else ++it;
|
||||
}
|
||||
|
||||
// Оновлення ворогів (агро на гравця)
|
||||
sf::Vector2f playerPosVec(player.getShape().getPosition().x, player.getShape().getPosition().y);
|
||||
for (auto& enemy : enemies) {
|
||||
float playerBottom = player.getShape().getPosition().y + player.getShape().getSize().y;
|
||||
float platformY = enemy.getPlatformY();
|
||||
float tolerance = 10.f;
|
||||
|
||||
// якщо гравець на тій же платформі
|
||||
if (playerBottom >= platformY - tolerance && playerBottom <= platformY + tolerance) {
|
||||
enemy.setAggro(true);
|
||||
} else {
|
||||
enemy.setAggro(false);
|
||||
}
|
||||
|
||||
enemy.update(deltaTime, playerPosVec);
|
||||
}
|
||||
|
||||
// Колізія куль з ворогами
|
||||
for (auto it = bullets.begin(); it != bullets.end(); ) {
|
||||
bool bulletRemoved = false;
|
||||
for (auto enemyIt = enemies.begin(); enemyIt != enemies.end(); ++enemyIt) {
|
||||
if (it->getBounds().intersects(enemyIt->getBounds())) {
|
||||
it = bullets.erase(it);
|
||||
enemies.erase(enemyIt);
|
||||
bulletRemoved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bulletRemoved) ++it;
|
||||
}
|
||||
|
||||
// Кінець гри, якщо всі вороги вбиті
|
||||
if (enemies.empty()) {
|
||||
cout << "Вітаємо! Ви перемогли всіх ворогів!\n";
|
||||
window.close();
|
||||
}
|
||||
|
||||
// Колізія ворогів з гравцем
|
||||
bool playerHit = false;
|
||||
for (auto& enemy : enemies) {
|
||||
if (enemy.getBounds().intersects(sf::FloatRect(player.getShape().getPosition(), player.getShape().getSize()))) {
|
||||
playerHit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ресет гри
|
||||
if (playerHit) {
|
||||
player.setPosition(sf::Vector2f(50.f, 1040.f));
|
||||
player.setVelocityY(0.f);
|
||||
player.setOnGround(true);
|
||||
|
||||
enemies.clear();
|
||||
for (auto& platform : platforms) {
|
||||
float left = platform.getBounds().left;
|
||||
float top = platform.getBounds().top;
|
||||
float width = platform.getBounds().width;
|
||||
enemies.push_back(Enemy(left + 10.f, top - 50.f, left, left + width));
|
||||
}
|
||||
|
||||
bullets.clear();
|
||||
}
|
||||
|
||||
// Відмалювання
|
||||
window.clear(sf::Color::Black);
|
||||
window.draw(backgroundSprite);
|
||||
for (auto& platform : platforms) platform.draw(window);
|
||||
ground.draw(window);
|
||||
|
||||
// малюємо гравця
|
||||
player.draw(window);
|
||||
|
||||
window.display(); // відображення
|
||||
for (auto& enemy : enemies) enemy.draw(window);
|
||||
for (auto& bullet : bullets) bullet.draw(window);
|
||||
window.display();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
37
main.cpp
Normal file
37
main.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include <SFML/Graphics.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
// Create the main window
|
||||
sf::RenderWindow app(sf::VideoMode(800, 600), "SFML window");
|
||||
|
||||
// Load a sprite to display
|
||||
sf::Texture texture;
|
||||
if (!texture.loadFromFile("cb.bmp"))
|
||||
return EXIT_FAILURE;
|
||||
sf::Sprite sprite(texture);
|
||||
|
||||
// Start the game loop
|
||||
while (app.isOpen())
|
||||
{
|
||||
// Process events
|
||||
sf::Event event;
|
||||
while (app.pollEvent(event))
|
||||
{
|
||||
// Close window : exit
|
||||
if (event.type == sf::Event::Closed)
|
||||
app.close();
|
||||
}
|
||||
|
||||
// Clear screen
|
||||
app.clear();
|
||||
|
||||
// Draw the sprite
|
||||
app.draw(sprite);
|
||||
|
||||
// Update the window
|
||||
app.display();
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
181
player.cpp
181
player.cpp
|
|
@ -1,99 +1,140 @@
|
|||
#include "player.hpp" // Включаємо заголовок класу Player, щоб компілятор знав про його методи та змінні.
|
||||
#include "player.hpp"
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
// Конструктор класу Player. Він автоматично викликається при створенні об'єкта Player.
|
||||
|
||||
Player::Player()
|
||||
: shape(sf::Vector2f(50.f, 50.f)), // тут лише "чисте" створення
|
||||
speed(500.f), //швидкість
|
||||
: shape(sf::Vector2f(60.f, 60.f)),
|
||||
speed(500.f),
|
||||
velocityY(0.f),
|
||||
jumpStrength(700.f), // пиржок
|
||||
jumpStrength(700.f),
|
||||
onGround(true),
|
||||
gravity(1500.f),
|
||||
groundLevel(1040.f)
|
||||
groundLevel(1040.f),
|
||||
facingRight(true),
|
||||
runFrames(),
|
||||
idleFrame(),
|
||||
sprite(),
|
||||
currentFrame(0),
|
||||
animationTimer(0.f),
|
||||
animationSpeed(0.1f),
|
||||
isMoving(false)
|
||||
{
|
||||
// А тут уже викликаємо методи, які налаштовують shape
|
||||
shape.setFillColor(sf::Color::Green);
|
||||
shape.setPosition(50.f, 1040.f);
|
||||
shape.setPosition(50.f, groundLevel);
|
||||
|
||||
loadIdleFrame();
|
||||
loadRunAnimation();
|
||||
|
||||
sprite.setTexture(idleFrame);
|
||||
sprite.setOrigin(idleFrame.getSize().x / 2.f, idleFrame.getSize().y / 2.f);
|
||||
|
||||
float scaleX = shape.getSize().x / idleFrame.getSize().x;
|
||||
float scaleY = shape.getSize().y / idleFrame.getSize().y;
|
||||
|
||||
// Початкове віддзеркалення: PNG дивиться вліво, потрібно вправо
|
||||
sprite.setScale(-scaleX, scaleY);
|
||||
|
||||
sprite.setPosition(
|
||||
shape.getPosition().x + shape.getSize().x / 2.f,
|
||||
shape.getPosition().y + shape.getSize().y / 2.f
|
||||
);
|
||||
}
|
||||
|
||||
void Player::loadIdleFrame() {
|
||||
if (!idleFrame.loadFromFile("img/FA_PENGUIN_Idle_000.png")) {
|
||||
cout << "Cannot load idle frame\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Player::loadRunAnimation() {
|
||||
runFrames.clear();
|
||||
for (size_t i = 0; i < 10; ++i) { // припустимо 10 кадрів
|
||||
sf::Texture tex;
|
||||
string filename = "img/04-Run/FA_PENGUIN_Run_00" + to_string(i) + ".png";
|
||||
if (!tex.loadFromFile(filename)) {
|
||||
cout << "Cannot load " << filename << "\n";
|
||||
} else {
|
||||
runFrames.push_back(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Закоментований метод `update` (робота на потім).
|
||||
// Він призначений для оновлення логіки гравця у кожному кадрі гри.
|
||||
// Усередині цього методу, ви будете обробляти рух, зіткнення, анімацію тощо.
|
||||
// `deltaTime` — це час, що минув з попереднього кадру, і він забезпечує плавний рух незалежно від швидкості комп'ютера.
|
||||
void Player::update(float deltaTime, const std::vector<Platform>& platforms) { // Тут буде логіка руху гравця
|
||||
void Player::update(float deltaTime, const std::vector<Platform>& platforms) {
|
||||
float moveX = 0.f;
|
||||
isMoving = false;
|
||||
|
||||
|
||||
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)){
|
||||
moveX -= speed * deltaTime;} //рух вліво
|
||||
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)){
|
||||
moveX += speed * deltaTime;} //рух вправо
|
||||
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) && onGround==true) {
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
|
||||
moveX -= speed * deltaTime;
|
||||
facingRight = false;
|
||||
isMoving = true;
|
||||
}
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
|
||||
moveX += speed * deltaTime;
|
||||
facingRight = true;
|
||||
isMoving = true;
|
||||
}
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) && onGround)
|
||||
velocityY = -jumpStrength;
|
||||
}
|
||||
|
||||
velocityY += gravity * deltaTime;
|
||||
|
||||
shape.move(moveX, 0.f);
|
||||
shape.move(0.f , velocityY * deltaTime);
|
||||
shape.move(0.f, velocityY * deltaTime);
|
||||
|
||||
//перевірка чи є земля та обмеження щоб фігура не вилитіла
|
||||
|
||||
if(shape.getPosition().y + shape.getSize().y >= groundLevel){
|
||||
shape.setPosition(shape.getPosition().x, groundLevel -shape.getSize().y);
|
||||
velocityY = 0;
|
||||
onGround = true;
|
||||
}else{
|
||||
onGround = false;
|
||||
}
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
sf::FloatRect playerBounds = shape.getGlobalBounds();
|
||||
|
||||
for (const auto& platform : platforms) {
|
||||
sf::FloatRect platformBounds = platform.getBounds();
|
||||
if (playerBounds.intersects(platformBounds)){
|
||||
if (playerBounds.intersects(platformBounds)) {
|
||||
// Додатковий if — перевіряємо, чи гравець зверху
|
||||
if (playerBounds.top + playerBounds.height <= platformBounds.top + 20.f) {
|
||||
shape.setPosition(shape.getPosition().x, platformBounds.top - shape.getSize().y);
|
||||
// Земля
|
||||
if (shape.getPosition().y + shape.getSize().y >= groundLevel) {
|
||||
shape.setPosition(shape.getPosition().x, groundLevel - shape.getSize().y);
|
||||
velocityY = 0.f;
|
||||
onGround = true;
|
||||
}else if(playerBounds.top> platformBounds.top){shape.setPosition(shape.getPosition().x, platformBounds.top +platformBounds.height);
|
||||
velocityY =0.f;}
|
||||
} else onGround = false;
|
||||
|
||||
// Колізії з платформами
|
||||
for (const auto& platform : platforms) {
|
||||
sf::FloatRect playerBounds = shape.getGlobalBounds();
|
||||
sf::FloatRect platBounds = platform.getBounds();
|
||||
if (playerBounds.intersects(platBounds)) {
|
||||
if (playerBounds.top + playerBounds.height <= platBounds.top + 20.f) {
|
||||
shape.setPosition(shape.getPosition().x, platBounds.top - shape.getSize().y);
|
||||
velocityY = 0.f;
|
||||
onGround = true;
|
||||
} else if (playerBounds.top > platBounds.top) {
|
||||
shape.setPosition(shape.getPosition().x, platBounds.top + platBounds.height);
|
||||
velocityY = 0.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Межі екрану
|
||||
if (shape.getPosition().x < 0.f) shape.setPosition(0.f, shape.getPosition().y);
|
||||
if (shape.getPosition().x + shape.getSize().x > 1925.f)
|
||||
shape.setPosition(1925.f - shape.getSize().x, shape.getPosition().y);
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
if(shape.getPosition().x < 0.f)
|
||||
shape.setPosition(0.f, shape.getPosition().y);
|
||||
|
||||
if(shape.getPosition().x+shape.getSize().x>1925.f)
|
||||
shape.setPosition(1925.f - shape.getSize().x ,shape.getPosition().y);
|
||||
|
||||
|
||||
if(shape.getPosition().y < 0.f)
|
||||
shape.setPosition(shape.getPosition().x,0.f);
|
||||
|
||||
if(shape.getPosition().y+shape.getSize().y>1200.f)
|
||||
shape.setPosition(shape.getPosition().x, 1200.f - shape.getSize().y);
|
||||
// Анімація
|
||||
animationTimer += deltaTime;
|
||||
if (isMoving && !runFrames.empty()) {
|
||||
if (animationTimer >= animationSpeed) {
|
||||
animationTimer = 0.f;
|
||||
currentFrame = (currentFrame + 1) % runFrames.size();
|
||||
sprite.setTexture(runFrames[currentFrame]);
|
||||
}
|
||||
} else {
|
||||
sprite.setTexture(idleFrame);
|
||||
}
|
||||
|
||||
// Центруємо спрайт по хітбоксу
|
||||
sprite.setOrigin(sprite.getTexture()->getSize().x / 2.f,
|
||||
sprite.getTexture()->getSize().y / 2.f);
|
||||
|
||||
// Метод `draw` для відмальовування гравця на екрані.
|
||||
// Приймає посилання на вікно, щоб знати, де малювати.
|
||||
void Player::draw(sf::RenderWindow& window)
|
||||
{
|
||||
// Викликаємо метод `draw` вікна, щоб намалювати наш об'єкт `shape`.
|
||||
window.draw(shape);
|
||||
sprite.setPosition(shape.getPosition().x + shape.getSize().x / 2.f,
|
||||
shape.getPosition().y + shape.getSize().y / 2.f);
|
||||
|
||||
float scaleX = shape.getSize().x / sprite.getTexture()->getSize().x;
|
||||
float scaleY = shape.getSize().y / sprite.getTexture()->getSize().y;
|
||||
|
||||
// Віддзеркалення за напрямком погляду
|
||||
sprite.setScale(facingRight ? -scaleX : scaleX, scaleY);
|
||||
}
|
||||
|
||||
void Player::draw(sf::RenderWindow& window) {
|
||||
window.draw(sprite);
|
||||
}
|
||||
|
||||
|
|
|
|||
48
player.hpp
48
player.hpp
|
|
@ -1,33 +1,45 @@
|
|||
|
||||
#pragma once
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <vector>
|
||||
#include "platform.hpp"
|
||||
|
||||
|
||||
//float velocityY;
|
||||
//float gravity;
|
||||
//float jumpStrength;
|
||||
//bool isOnGround;
|
||||
|
||||
|
||||
class Player {
|
||||
sf::RectangleShape shape;
|
||||
float speed;
|
||||
float velocityY;
|
||||
float jumpStrength;
|
||||
bool onGround;
|
||||
float gravity;
|
||||
float groundLevel;
|
||||
bool facingRight;
|
||||
|
||||
private:
|
||||
sf::RectangleShape shape; // форма гравця
|
||||
float speed; //швидкість гравця
|
||||
float velocityY; // швидкість по вертикалі
|
||||
float jumpStrength; // сила стрибка
|
||||
bool onGround; //чи стоїть на землі
|
||||
float gravity; // сила гравітації
|
||||
float groundLevel; // висота "землі"
|
||||
std::vector<sf::Texture> runFrames;
|
||||
sf::Texture idleFrame;
|
||||
sf::Sprite sprite;
|
||||
|
||||
size_t currentFrame;
|
||||
float animationTimer;
|
||||
float animationSpeed;
|
||||
bool isMoving;
|
||||
|
||||
void loadRunAnimation();
|
||||
void loadIdleFrame();
|
||||
|
||||
public:
|
||||
Player(); // порожній конструктор
|
||||
void update(float deltaTime, const std::vector<Platform>& platforms);
|
||||
Player();
|
||||
|
||||
void update(float deltaTime, const std::vector<Platform>& platforms);
|
||||
void draw(sf::RenderWindow& window);
|
||||
|
||||
// Геттери
|
||||
sf::RectangleShape& getShape() { return shape; }
|
||||
float getVelocityY() const { return velocityY; }
|
||||
bool isOnGround() const { return onGround; }
|
||||
|
||||
// Сеттери
|
||||
void setPosition(const sf::Vector2f& pos) { shape.setPosition(pos); }
|
||||
void setVelocityY(float v) { velocityY = v; }
|
||||
void setOnGround(bool val) { onGround = val; }
|
||||
|
||||
bool isFacingRight() const { return facingRight; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,4 +5,3 @@
|
|||
#include "platform.hpp"
|
||||
#include "bullet.hpp"
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue