Compare commits
10 commits
24cc3cfd6b
...
28d2a9da91
Author | SHA1 | Date | |
---|---|---|---|
28d2a9da91 | |||
7cfe033668 | |||
c912784a7d | |||
95562a9da7 | |||
ac435f8e4f | |||
36aa2fa245 | |||
851e591c3c | |||
f50a346e22 | |||
7703128749 | |||
15c9632941 |
|
@ -138,7 +138,7 @@ set(SOURCES
|
||||||
src/typography/font_manager.cpp
|
src/typography/font_manager.cpp
|
||||||
src/typography/font_manager.hpp
|
src/typography/font_manager.hpp
|
||||||
src/screens/winner_screen.cpp
|
src/screens/winner_screen.cpp
|
||||||
src/screens/winner_screen.hpp src/screens/screen.cpp src/screens/screen.hpp)
|
src/screens/winner_screen.hpp src/screens/screen.cpp src/screens/screen.hpp src/screens/join_screen.cpp src/screens/join_screen.hpp src/game/level/level_generator.cpp src/game/level/level_generator.hpp src/screens/loading_screen.cpp src/screens/loading_screen.hpp)
|
||||||
|
|
||||||
set(PHYSICS_00_SOURCES
|
set(PHYSICS_00_SOURCES
|
||||||
src/prototypes/physics_00.cpp)
|
src/prototypes/physics_00.cpp)
|
||||||
|
|
BIN
assets/collectables/lantern.png
Normal file
After Width: | Height: | Size: 630 B |
BIN
assets/edge.png
Before Width: | Height: | Size: 19 KiB |
BIN
assets/grass-tiles.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/player/player-0.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/player/player-1.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/player/player-2.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/player/player-3.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/player/player.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
|
@ -9,9 +9,10 @@ std::map<std::string, CollectableConfig> const all_collectables = {
|
||||||
{"rose", CollectableConfig("rose", 1)},
|
{"rose", CollectableConfig("rose", 1)},
|
||||||
{"rosebush", CollectableConfig("rosebush", 3)},
|
{"rosebush", CollectableConfig("rosebush", 3)},
|
||||||
{"stone", CollectableConfig("stone", 5)},
|
{"stone", CollectableConfig("stone", 5)},
|
||||||
{"bike", CollectableConfig("bike", 10)},
|
{"bike", CollectableConfig("bike", 15)},
|
||||||
{"small-tree", CollectableConfig("small-tree", 20)},
|
{"small-tree", CollectableConfig("small-tree", 20)},
|
||||||
{"tram", CollectableConfig("tram", 50)}
|
{"tram", CollectableConfig("tram", 50)},
|
||||||
|
{"lantern", CollectableConfig("lantern", 10)}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //HOLESOME_COLLECTABLES_HPP
|
#endif //HOLESOME_COLLECTABLES_HPP
|
||||||
|
|
21
src/config.h
|
@ -4,7 +4,10 @@
|
||||||
#include <SFML/Graphics.hpp>
|
#include <SFML/Graphics.hpp>
|
||||||
#include <box2d/box2d.h>
|
#include <box2d/box2d.h>
|
||||||
|
|
||||||
#define DEVELOPER_MODE true
|
#define DEVELOPER_MODE false
|
||||||
|
|
||||||
|
#define GAME_NAME "Holesome"
|
||||||
|
#define CLOSE_GAME_BTN sf::Keyboard::Escape
|
||||||
|
|
||||||
// Player
|
// Player
|
||||||
#define DEFAULT_PLAYER_SPEED 5.f // World units per second
|
#define DEFAULT_PLAYER_SPEED 5.f // World units per second
|
||||||
|
@ -12,16 +15,22 @@
|
||||||
#define PLAYER_MIN_RADIUS 0.5f // World units
|
#define PLAYER_MIN_RADIUS 0.5f // World units
|
||||||
#define PLAYER_RADIUS_PER_LEVEL 0.25f
|
#define PLAYER_RADIUS_PER_LEVEL 0.25f
|
||||||
#define DEFAULT_MAX_PLAYER_NUMBER 4
|
#define DEFAULT_MAX_PLAYER_NUMBER 4
|
||||||
|
#define MIN_PLAYER_COUNT 2
|
||||||
|
#define POINT_DELTA_FOR_ATTACK 20
|
||||||
|
#define ATTACK_PER_SECOND 1.f
|
||||||
|
#define PLAYER_ALIVE_THRESHOLD (-10)
|
||||||
|
#define PLAYER_RUN_SPEED (DEFAULT_PLAYER_SPEED * 2.f)
|
||||||
|
#define PLAYER_RUN_SPEED_COST_PER_SECOND 10.f
|
||||||
|
|
||||||
// World
|
// World
|
||||||
#define WORLD_GRAVITY b2Vec2(0.f, -9.8f)
|
#define WORLD_GRAVITY b2Vec2(0.f, -9.8f)
|
||||||
#define SKY_HEIGHT_SCALE 2.f
|
#define SKY_HEIGHT_SCALE 5.f
|
||||||
#define CONSIDER_COLLECTABLE_DEPTH_MOVEMENT false // Might cost perfomance and is currently not in use
|
#define CONSIDER_COLLECTABLE_DEPTH_MOVEMENT false // Might cost performance and is currently not in use
|
||||||
|
|
||||||
// FPS
|
// FPS
|
||||||
#define FRAME_RATE 60
|
#define FRAME_RATE 60
|
||||||
#define FRAME_TIME sf::Time(sf::seconds(1.0f / FRAME_RATE))
|
#define FRAME_TIME sf::Time(sf::seconds(1.0f / FRAME_RATE))
|
||||||
#define FRAME_LIMIT_ENABLED false
|
#define FRAME_LIMIT_ENABLED true
|
||||||
|
|
||||||
// Window settings
|
// Window settings
|
||||||
#define ANTIALIASINGLEVEL 8
|
#define ANTIALIASINGLEVEL 8
|
||||||
|
@ -35,7 +44,6 @@
|
||||||
#define MASKED_HOLE_BORDER_TRANSITION_SIZE 0.2f
|
#define MASKED_HOLE_BORDER_TRANSITION_SIZE 0.2f
|
||||||
#define MASKED_HOLE_DARKNESS_LIMIT 0.5f
|
#define MASKED_HOLE_DARKNESS_LIMIT 0.5f
|
||||||
#define COLLECTABLE_SCALE 5.f
|
#define COLLECTABLE_SCALE 5.f
|
||||||
#define MASKED_SPRITE_LOWER_BOUND -3.f
|
|
||||||
|
|
||||||
// Tracking view defaults
|
// Tracking view defaults
|
||||||
#define DEF_TV_IS_ABSOLUTE_FREE_MOVE_THRESHOLD false
|
#define DEF_TV_IS_ABSOLUTE_FREE_MOVE_THRESHOLD false
|
||||||
|
@ -45,9 +53,8 @@
|
||||||
#define DEF_TV_MIN_VIEW_SIZE sf::Vector2f(6, 6) * WORLD_TO_ISO_SCALE
|
#define DEF_TV_MIN_VIEW_SIZE sf::Vector2f(6, 6) * WORLD_TO_ISO_SCALE
|
||||||
#define DEF_TV_MAX_VIEW_SIZE sf::Vector2f(0, 0)
|
#define DEF_TV_MAX_VIEW_SIZE sf::Vector2f(0, 0)
|
||||||
#define DEF_TV_IS_ABSOLUTE_VIEW_SIZE_PADDING false
|
#define DEF_TV_IS_ABSOLUTE_VIEW_SIZE_PADDING false
|
||||||
#define DEF_TV_VIEW_SIZE_PADDING sf::Vector2f(3.5f, 3.5f)
|
#define DEF_TV_VIEW_SIZE_PADDING sf::Vector2f(2.f, 2.f)
|
||||||
#define MP_VIEW_ADD_NEW_PLAYERS true
|
#define MP_VIEW_ADD_NEW_PLAYERS true
|
||||||
#define MP_VIEW_REMOVE_DISCONNECTED_PLAYERS true
|
|
||||||
#define MP_VIEW_BORDER_COLOR sf::Color::Black
|
#define MP_VIEW_BORDER_COLOR sf::Color::Black
|
||||||
|
|
||||||
// Simulations
|
// Simulations
|
||||||
|
|
|
@ -27,15 +27,6 @@ void MultiplayerView::update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old
|
|
||||||
if (MP_VIEW_REMOVE_DISCONNECTED_PLAYERS)
|
|
||||||
{
|
|
||||||
for (const auto &player: PlayerCollection::getInstance()->getRemovedPlayers())
|
|
||||||
{
|
|
||||||
removePlayer(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GameObject::update();
|
GameObject::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,3 +143,8 @@ MultiplayerView::MultiplayerView()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MultiplayerView::clear()
|
||||||
|
{
|
||||||
|
viewsForPlayers.clear();
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ public:
|
||||||
|
|
||||||
void update() override;
|
void update() override;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
void addPlayer(const std::shared_ptr<Player>& player);
|
void addPlayer(const std::shared_ptr<Player>& player);
|
||||||
|
|
||||||
void removePlayer(const std::shared_ptr<Player>& player);
|
void removePlayer(const std::shared_ptr<Player>& player);
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
#include "layer/global_layer.hpp"
|
#include "layer/global_layer.hpp"
|
||||||
#include "player/player_collection.hpp"
|
#include "player/player_collection.hpp"
|
||||||
#include "../screens/winner_screen.hpp"
|
#include "../screens/winner_screen.hpp"
|
||||||
|
#include "../screens/join_screen.hpp"
|
||||||
|
#include "level/level_generator.hpp"
|
||||||
|
#include "../screens/loading_screen.hpp"
|
||||||
|
|
||||||
Game::Game(std::shared_ptr<sf::RenderWindow> window) : window(std::move(window))
|
Game::Game(std::shared_ptr<sf::RenderWindow> window) : window(std::move(window))
|
||||||
{
|
{
|
||||||
|
@ -159,17 +162,17 @@ void Game::startCountdown(int durationInSeconds)
|
||||||
|
|
||||||
std::shared_ptr<Player> Game::checkForWinner()
|
std::shared_ptr<Player> Game::checkForWinner()
|
||||||
{
|
{
|
||||||
std::vector<std::shared_ptr<Player>> players = PlayerCollection::getInstance()->getPlayers();
|
std::vector<std::shared_ptr<Player>> remainingPlayers = PlayerCollection::getInstance()->getRemainingPlayers();
|
||||||
|
|
||||||
// Has timer run out or is only one player left?
|
// Has timer run out or is only one player left?
|
||||||
if (!countdown->isFinished() && players.size() > 1)
|
if (!countdown->isFinished() && remainingPlayers.size() > 1)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return player with highest score as winner
|
// Return player with highest score as winner
|
||||||
std::shared_ptr<Player> winner = nullptr;
|
std::shared_ptr<Player> winner = nullptr;
|
||||||
for (const auto &player: players)
|
for (const auto &player: remainingPlayers)
|
||||||
{
|
{
|
||||||
if (winner == nullptr || player->getPoints() > winner->getPoints())
|
if (winner == nullptr || player->getPoints() > winner->getPoints())
|
||||||
{
|
{
|
||||||
|
@ -202,8 +205,47 @@ void Game::handleWinning()
|
||||||
LOG(INFO) << "Player " << winner->getPlayerId() << " won the game with " << winner->getPoints() << " points.";
|
LOG(INFO) << "Player " << winner->getPlayerId() << " won the game with " << winner->getPoints() << " points.";
|
||||||
|
|
||||||
// Stop game and show winner
|
// Stop game and show winner
|
||||||
|
auto players = PlayerCollection::getInstance()->getPlayers();
|
||||||
LevelLoader::cleanUp();
|
LevelLoader::cleanUp();
|
||||||
auto winnerScreen = std::make_shared<WinnerScreen>(winner);
|
auto winnerScreen = std::make_shared<WinnerScreen>(players);
|
||||||
GlobalLayer::getInstance()->add(winnerScreen);
|
GlobalLayer::getInstance()->add(winnerScreen);
|
||||||
addGameObject(GlobalLayer::getInstance());
|
addGameObject(GlobalLayer::getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Game::showJoinScreen()
|
||||||
|
{
|
||||||
|
GlobalLayer::getInstance()->clear();
|
||||||
|
clearGameObjects();
|
||||||
|
|
||||||
|
InputMapper::getInstance()->allowNewInputIdentities = true;
|
||||||
|
PlayerCollection::getInstance()->deactivatePlayers();
|
||||||
|
|
||||||
|
auto joinScreen = std::make_shared<JoinScreen>();
|
||||||
|
GlobalLayer::getInstance()->add(joinScreen);
|
||||||
|
|
||||||
|
addGameObject(GlobalLayer::getInstance());
|
||||||
|
addGameObject(PlayerCollection::getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::generateNewLevel()
|
||||||
|
{
|
||||||
|
showLoadingScreen("Generating new level ...");
|
||||||
|
|
||||||
|
LevelConfig newLevel = LevelGenerator::generateLevel("Procedural Level");
|
||||||
|
LevelLoader::loadLevel(newLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Game::showLoadingScreen(const std::string &message)
|
||||||
|
{
|
||||||
|
GlobalLayer::getInstance()->clear();
|
||||||
|
clearGameObjects();
|
||||||
|
|
||||||
|
auto loadingScreen = std::make_shared<LoadingScreen>(message);
|
||||||
|
GlobalLayer::getInstance()->add(loadingScreen);
|
||||||
|
|
||||||
|
addGameObject(GlobalLayer::getInstance());
|
||||||
|
|
||||||
|
// Mini loop
|
||||||
|
update();
|
||||||
|
drawFrame();
|
||||||
|
}
|
||||||
|
|
|
@ -38,6 +38,11 @@ public:
|
||||||
void addGameObject(const std::shared_ptr<GameObject> &gameObject);
|
void addGameObject(const std::shared_ptr<GameObject> &gameObject);
|
||||||
|
|
||||||
std::shared_ptr<sf::RenderWindow> window;
|
std::shared_ptr<sf::RenderWindow> window;
|
||||||
|
|
||||||
|
void showJoinScreen();
|
||||||
|
|
||||||
|
void generateNewLevel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static inline std::shared_ptr<Game> singletonInstance = nullptr;
|
static inline std::shared_ptr<Game> singletonInstance = nullptr;
|
||||||
std::vector<std::shared_ptr<GameObject>> gameObjects = {};
|
std::vector<std::shared_ptr<GameObject>> gameObjects = {};
|
||||||
|
@ -55,6 +60,8 @@ private:
|
||||||
std::shared_ptr<Player> checkForWinner();
|
std::shared_ptr<Player> checkForWinner();
|
||||||
|
|
||||||
void handleWinning();
|
void handleWinning();
|
||||||
|
|
||||||
|
void showLoadingScreen(const std::string &message = "Loading...");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,7 @@
|
||||||
enum GameAction
|
enum GameAction
|
||||||
{
|
{
|
||||||
CONFIRM,
|
CONFIRM,
|
||||||
CANCEL,
|
RUN
|
||||||
MENU,
|
|
||||||
|
|
||||||
GROW,
|
|
||||||
SHRINK
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //HOLESOME_GAME_ACTION_HPP
|
#endif //HOLESOME_GAME_ACTION_HPP
|
||||||
|
|
|
@ -52,8 +52,8 @@ void InputMapper::processEvents()
|
||||||
|
|
||||||
void InputMapper::handleKeyPress(sf::Event::KeyEvent event)
|
void InputMapper::handleKeyPress(sf::Event::KeyEvent event)
|
||||||
{
|
{
|
||||||
// Close game on Escape or Q in DEV Mode
|
// Close game on Special button
|
||||||
if (DEVELOPER_MODE && event.code == sf::Keyboard::Escape)
|
if (event.code == CLOSE_GAME_BTN)
|
||||||
{
|
{
|
||||||
Game::getInstance()->exit();
|
Game::getInstance()->exit();
|
||||||
return;
|
return;
|
||||||
|
|
134
src/game/level/level_generator.cpp
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
#include "level_generator.hpp"
|
||||||
|
|
||||||
|
LevelConfig LevelGenerator::generateLevel(const std::string &name)
|
||||||
|
{
|
||||||
|
int levelSize = 50;
|
||||||
|
|
||||||
|
TileMapConfig map = generateRandomMap(levelSize, levelSize);
|
||||||
|
std::vector<CollectableInLevel> collectables = generateRandomCollectables(levelSize, levelSize);
|
||||||
|
|
||||||
|
return LevelConfig(name,
|
||||||
|
90,
|
||||||
|
{
|
||||||
|
{1, 2},
|
||||||
|
{2, 1},
|
||||||
|
{0, 3},
|
||||||
|
{3, 0}
|
||||||
|
},
|
||||||
|
collectables,
|
||||||
|
{
|
||||||
|
// Blues
|
||||||
|
sf::Color(2, 100, 234),
|
||||||
|
sf::Color(2, 100, 234),
|
||||||
|
sf::Color(2, 100, 234),
|
||||||
|
sf::Color(2, 195, 234),
|
||||||
|
|
||||||
|
// Neutral
|
||||||
|
sf::Color::White,
|
||||||
|
|
||||||
|
// Browns
|
||||||
|
sf::Color(163, 128, 68),
|
||||||
|
sf::Color(100, 80, 40),
|
||||||
|
sf::Color(20, 18, 11),
|
||||||
|
sf::Color::Black
|
||||||
|
},
|
||||||
|
map
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileMapConfig LevelGenerator::generateRandomMap(int width, int height)
|
||||||
|
{
|
||||||
|
int tileVariants = 6 * 2;
|
||||||
|
std::vector<std::vector<int>> tiles;
|
||||||
|
for (int h = 0; h < height; h++)
|
||||||
|
{
|
||||||
|
std::vector<int> row;
|
||||||
|
for (int w = 0; w < width; w++)
|
||||||
|
{
|
||||||
|
row.push_back(rand() % tileVariants);
|
||||||
|
}
|
||||||
|
tiles.push_back(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TileMapConfig("iso-tiles", tiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CollectableInLevel> LevelGenerator::generateRandomCollectables(int width, int height)
|
||||||
|
{
|
||||||
|
std::vector<CollectableInLevel> collectables;
|
||||||
|
|
||||||
|
// Add rose fields
|
||||||
|
for (int i = 0; i < 20; i++)
|
||||||
|
{
|
||||||
|
// Random coordinates for center of rose field with enough distance from edges
|
||||||
|
int x = rand() % int(width - 2 * roseFieldRadius) + roseFieldRadius;
|
||||||
|
int y = rand() % int(height - 2 * roseFieldRadius) + roseFieldRadius;
|
||||||
|
|
||||||
|
GridCoordinates fieldCenter = GridCoordinates(x, y);
|
||||||
|
|
||||||
|
// Add rose field by creating a dense field of roses around the center
|
||||||
|
int roseCount = rand() % 15 + 5;
|
||||||
|
for (int j = 0; j < roseCount; j++)
|
||||||
|
{
|
||||||
|
// Use cosine distribution to get a more dense field around the center
|
||||||
|
float angle = rand() % 360;
|
||||||
|
float radius = rand() % 1000 / 1000.f;
|
||||||
|
|
||||||
|
// Skew radius to avoid too dense at center
|
||||||
|
radius = pow(radius, 0.3f) * roseFieldRadius;
|
||||||
|
|
||||||
|
float xOffset = radius * cos(angle);
|
||||||
|
float yOffset = radius * sin(angle);
|
||||||
|
|
||||||
|
GridCoordinates roseCoordinates = GridCoordinates(fieldCenter.x + xOffset, fieldCenter.y + yOffset);
|
||||||
|
collectables.push_back(CollectableInLevel("rose", roseCoordinates));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place rows of lanterns
|
||||||
|
int rowCount = rand() % 3 + 2;
|
||||||
|
int heightDelta = height / (rowCount + 1);
|
||||||
|
int stepSize = 7;
|
||||||
|
for (int i = 0; i < rowCount; i++)
|
||||||
|
{
|
||||||
|
int y = (i + 1) * heightDelta;
|
||||||
|
for (int j = stepSize; j < width; j += stepSize)
|
||||||
|
{
|
||||||
|
collectables.push_back(CollectableInLevel("lantern", GridCoordinates(j, y)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place random bikes
|
||||||
|
int bikeCount = rand() % 5 + 5;
|
||||||
|
for (int i = 0; i < bikeCount; i++)
|
||||||
|
{
|
||||||
|
int x = rand() % width;
|
||||||
|
int y = rand() % height;
|
||||||
|
collectables.push_back(CollectableInLevel("bike", GridCoordinates(x, y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place random rose bushes
|
||||||
|
int roseBushCount = rand() % 40 + 40;
|
||||||
|
for (int i = 0; i < roseBushCount; i++)
|
||||||
|
{
|
||||||
|
int x = rand() % width;
|
||||||
|
int y = rand() % height;
|
||||||
|
collectables.push_back(CollectableInLevel("rose-bush", GridCoordinates(x, y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place random stones
|
||||||
|
int stoneCount = rand() % 20 + 20;
|
||||||
|
for (int i = 0; i < stoneCount; i++)
|
||||||
|
{
|
||||||
|
int x = rand() % width;
|
||||||
|
int y = rand() % height;
|
||||||
|
collectables.push_back(CollectableInLevel("stone", GridCoordinates(x, y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place a single tree
|
||||||
|
int x = rand() % width;
|
||||||
|
int y = rand() % height;
|
||||||
|
collectables.push_back(CollectableInLevel("small-tree", GridCoordinates(x, y)));
|
||||||
|
|
||||||
|
return collectables;
|
||||||
|
}
|
21
src/game/level/level_generator.hpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef HOLESOME_LEVEL_GENERATOR_HPP
|
||||||
|
#define HOLESOME_LEVEL_GENERATOR_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include "level_config.hpp"
|
||||||
|
|
||||||
|
class LevelGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static LevelConfig generateLevel(const std::string& name);
|
||||||
|
|
||||||
|
static TileMapConfig generateRandomMap(int width, int height);
|
||||||
|
|
||||||
|
static std::vector<CollectableInLevel> generateRandomCollectables(int width, int height);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const inline float roseFieldRadius = 3.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //HOLESOME_LEVEL_GENERATOR_HPP
|
|
@ -21,11 +21,14 @@ void LevelLoader::loadLevel(const LevelConfig &levelConfig)
|
||||||
LOG(INFO) << "Loading level '" << levelConfig.name << "' ...";
|
LOG(INFO) << "Loading level '" << levelConfig.name << "' ...";
|
||||||
|
|
||||||
game->setLevel(levelConfig);
|
game->setLevel(levelConfig);
|
||||||
|
InputMapper::getInstance()->allowNewInputIdentities = false;
|
||||||
|
|
||||||
MapSimulation::getInstance()->resetMap(levelConfig.worldMapSize);
|
MapSimulation::getInstance()->resetMap(levelConfig.worldMapSize);
|
||||||
HolesSimulation::getInstance()->clear();
|
HolesSimulation::getInstance()->clear();
|
||||||
PlayerCollection::getInstance()->clear();
|
|
||||||
GlobalLayer::getInstance()->clear();
|
GlobalLayer::getInstance()->clear();
|
||||||
HoleLayout::getInstance()->clear();
|
HoleLayout::getInstance()->clear();
|
||||||
|
MultiplayerView::getInstance()->clear();
|
||||||
|
|
||||||
|
|
||||||
// Add views
|
// Add views
|
||||||
game->addGameObject(MultiplayerView::getInstance());
|
game->addGameObject(MultiplayerView::getInstance());
|
||||||
|
@ -44,6 +47,8 @@ void LevelLoader::loadLevel(const LevelConfig &levelConfig)
|
||||||
|
|
||||||
levelRenderer->addChild(PlayerCollection::getInstance());
|
levelRenderer->addChild(PlayerCollection::getInstance());
|
||||||
PlayerCollection::getInstance()->setSpawnPoints(levelConfig.playerSpawnPoints);
|
PlayerCollection::getInstance()->setSpawnPoints(levelConfig.playerSpawnPoints);
|
||||||
|
PlayerCollection::getInstance()->resetPlayers();
|
||||||
|
PlayerCollection::getInstance()->activatePlayers();
|
||||||
|
|
||||||
// Prepare collectables framework
|
// Prepare collectables framework
|
||||||
auto maxDepth = (int) levelConfig.worldMapSize.x * 2;
|
auto maxDepth = (int) levelConfig.worldMapSize.x * 2;
|
||||||
|
@ -97,7 +102,7 @@ void LevelLoader::cleanUp()
|
||||||
game->clearGameObjects();
|
game->clearGameObjects();
|
||||||
game->setLevel(LevelConfig());
|
game->setLevel(LevelConfig());
|
||||||
HolesSimulation::getInstance()->clear();
|
HolesSimulation::getInstance()->clear();
|
||||||
PlayerCollection::getInstance()->clear();
|
PlayerCollection::getInstance()->resetPlayers();
|
||||||
GlobalLayer::getInstance()->clear();
|
GlobalLayer::getInstance()->clear();
|
||||||
HoleLayout::getInstance()->clear();
|
HoleLayout::getInstance()->clear();
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ void MapPlayer::updateCollidingWithPlayers()
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<MapPlayer> mapPlayerA = MapSimulation::getInstance()->getMapPlayerByBody(bodyA);
|
std::shared_ptr<MapPlayer> mapPlayerA = MapSimulation::getInstance()->getMapPlayerByBody(bodyA);
|
||||||
std::shared_ptr<MapPlayer> mapPlayerB = MapSimulation::getInstance()->getMapPlayerByBody(bodyA);
|
std::shared_ptr<MapPlayer> mapPlayerB = MapSimulation::getInstance()->getMapPlayerByBody(bodyB);
|
||||||
|
|
||||||
if (mapPlayerA == nullptr || mapPlayerB == nullptr)
|
if (mapPlayerA == nullptr || mapPlayerB == nullptr)
|
||||||
{
|
{
|
||||||
|
@ -84,11 +84,13 @@ void MapPlayer::updateCollidingWithPlayers()
|
||||||
if (mapPlayerA->player->getPlayerId() == player->getPlayerId())
|
if (mapPlayerA->player->getPlayerId() == player->getPlayerId())
|
||||||
{
|
{
|
||||||
collidingWithPlayers.push_back(mapPlayerB->player->getPlayerId());
|
collidingWithPlayers.push_back(mapPlayerB->player->getPlayerId());
|
||||||
} else if (mapPlayerB->player->getPlayerId() == player->getPlayerId())
|
} else
|
||||||
{
|
{
|
||||||
collidingWithPlayers.push_back(mapPlayerA->player->getPlayerId());
|
collidingWithPlayers.push_back(mapPlayerA->player->getPlayerId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player->setCollidingPlayers(collidingWithPlayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapPlayer::updatePlayer()
|
void MapPlayer::updatePlayer()
|
||||||
|
|
|
@ -36,10 +36,11 @@ void MapSimulation::physicsUpdate()
|
||||||
void MapSimulation::resetMap(sf::Vector2<int> worldMapSize)
|
void MapSimulation::resetMap(sf::Vector2<int> worldMapSize)
|
||||||
{
|
{
|
||||||
// Clear all players
|
// Clear all players
|
||||||
for (auto &mapPlayer: mapPlayersById)
|
for (auto &[id, _]: mapPlayersById)
|
||||||
{
|
{
|
||||||
removePlayer(mapPlayer.second->player);
|
world->DestroyBody(mapPlayersById[id]->body);
|
||||||
}
|
}
|
||||||
|
mapPlayersById.clear();
|
||||||
|
|
||||||
// No gravity, since this a top-down view of the map
|
// No gravity, since this a top-down view of the map
|
||||||
world = std::make_shared<b2World>(b2Vec2(0.0f, 0.0f));
|
world = std::make_shared<b2World>(b2Vec2(0.0f, 0.0f));
|
||||||
|
@ -116,3 +117,15 @@ std::shared_ptr<MapPlayer> MapSimulation::getMapPlayerByBody(b2Body *body) const
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MapSimulation::removePlayer(int playerId)
|
||||||
|
{
|
||||||
|
// Still in simulation?
|
||||||
|
if (mapPlayersById.find(playerId) == mapPlayersById.end())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
world->DestroyBody(mapPlayersById[playerId]->body);
|
||||||
|
mapPlayersById.erase(playerId);
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ public:
|
||||||
|
|
||||||
void addPlayer(const std::shared_ptr<Player>& player);
|
void addPlayer(const std::shared_ptr<Player>& player);
|
||||||
|
|
||||||
|
void removePlayer(int playerId);
|
||||||
|
|
||||||
[[nodiscard]] std::shared_ptr<MapPlayer> getMapPlayerByBody(b2Body* body) const;
|
[[nodiscard]] std::shared_ptr<MapPlayer> getMapPlayerByBody(b2Body* body) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include "../../logging/easylogging++.h"
|
#include "../../logging/easylogging++.h"
|
||||||
|
#include "player_collection.hpp"
|
||||||
|
#include "../../typography/font_manager.hpp"
|
||||||
|
#include "../physics/map/map_simulation.hpp"
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
Player::Player(std::shared_ptr<InputIdentity> assignedInput, const std::string &skinRessourceName,
|
Player::Player(std::shared_ptr<InputIdentity> assignedInput, const std::string &skinRessourceName,
|
||||||
GridCoordinates initCoordinates)
|
GridCoordinates initCoordinates)
|
||||||
: spawnPosition(initCoordinates)
|
: spawnPosition(initCoordinates), skinName(skinRessourceName)
|
||||||
{
|
{
|
||||||
playerId = playerCreationCounter++;
|
playerId = playerCreationCounter++;
|
||||||
coordinates->setTranslated(spawnPosition);
|
coordinates->setTranslated(spawnPosition);
|
||||||
|
@ -37,9 +40,12 @@ void Player::update()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto moveDirection = input->direction.asIsometricVector();
|
if (!passiveMode)
|
||||||
auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds();
|
{
|
||||||
coordinates->move(moveDelta);
|
performInteractiveUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePlayerCollisions();
|
||||||
|
|
||||||
GameObject::update();
|
GameObject::update();
|
||||||
}
|
}
|
||||||
|
@ -83,23 +89,152 @@ void Player::setWorldRadius(float newWorldRadius)
|
||||||
skinSprite->coordinates->setScreenOffset(IsometricCoordinates(-newSize / 2.f));
|
skinSprite->coordinates->setScreenOffset(IsometricCoordinates(-newSize / 2.f));
|
||||||
}
|
}
|
||||||
|
|
||||||
long Player::getPoints() const
|
int Player::getPoints() const
|
||||||
{
|
{
|
||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::updateRadiusBasedOnPoints()
|
void Player::updateRadiusBasedOnPoints()
|
||||||
{
|
{
|
||||||
long points = getPoints();
|
int points = getPoints();
|
||||||
float newWorldRadius = PLAYER_MIN_RADIUS + PLAYER_RADIUS_PER_LEVEL * points / 10.f;
|
float newWorldRadius = PLAYER_MIN_RADIUS + PLAYER_RADIUS_PER_LEVEL * points / 10.f;
|
||||||
|
|
||||||
setWorldRadius(newWorldRadius);
|
setWorldRadius(newWorldRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::consume(int points)
|
void Player::consume(int consumedPoints)
|
||||||
{
|
{
|
||||||
this->points += points;
|
setPoints(getPoints() + consumedPoints);
|
||||||
LOG(INFO) << "Player " << playerId << " consumed " << points << " points. Total: " << this->points;
|
LOG(INFO) << "Player " << playerId << " consumed " << consumedPoints << " points. Total: " << this->points;
|
||||||
|
}
|
||||||
updateRadiusBasedOnPoints();
|
|
||||||
|
std::string Player::getSkinName() const
|
||||||
|
{
|
||||||
|
return skinName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<InputIdentity> Player::getInput() const
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::setPassiveMode(bool newPassiveMode)
|
||||||
|
{
|
||||||
|
passiveMode = newPassiveMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::performInteractiveUpdates()
|
||||||
|
{
|
||||||
|
if (input->isPerformingAction(GameAction::RUN))
|
||||||
|
{
|
||||||
|
speed = PLAYER_RUN_SPEED;
|
||||||
|
|
||||||
|
// Subtract cost
|
||||||
|
setPoints(points - PLAYER_RUN_SPEED_COST_PER_SECOND * FRAME_TIME.asSeconds());
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
speed = DEFAULT_PLAYER_SPEED;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto moveDirection = input->direction.asIsometricVector();
|
||||||
|
auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds();
|
||||||
|
coordinates->move(moveDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::setCollidingPlayers(const std::vector<int> &collidingPlayerIds)
|
||||||
|
{
|
||||||
|
collidingPlayers.clear();
|
||||||
|
for (auto collidingPlayerId: collidingPlayerIds)
|
||||||
|
{
|
||||||
|
collidingPlayers.push_back(PlayerCollection::getInstance()->getPlayerById(collidingPlayerId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::handlePlayerCollisions()
|
||||||
|
{
|
||||||
|
if (collidingPlayers.empty() || !isAlive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count number of players that are bigger than this player by the required margin
|
||||||
|
float numAttackingPlayers = 0;
|
||||||
|
for (const auto &collidingPlayer: collidingPlayers)
|
||||||
|
{
|
||||||
|
if (collidingPlayer->getPoints() - getPoints() >= POINT_DELTA_FOR_ATTACK)
|
||||||
|
{
|
||||||
|
numAttackingPlayers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce points if necessary
|
||||||
|
float damage = numAttackingPlayers * ATTACK_PER_SECOND * FRAME_TIME.asSeconds() *
|
||||||
|
(points + 1.f + fabs(PLAYER_ALIVE_THRESHOLD));
|
||||||
|
if (numAttackingPlayers > 0)
|
||||||
|
{
|
||||||
|
LOG(INFO) << "Player " << playerId << " is getting attacked by " << numAttackingPlayers << "players and lost "
|
||||||
|
<< damage << " points.";
|
||||||
|
}
|
||||||
|
|
||||||
|
setPoints(points - damage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Player::getAlive() const
|
||||||
|
{
|
||||||
|
return isAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::setAlive(bool newAlive)
|
||||||
|
{
|
||||||
|
if (isAlive == newAlive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newAlive)
|
||||||
|
{
|
||||||
|
setPassiveMode(true);
|
||||||
|
MapSimulation::getInstance()->removePlayer(getPlayerId());
|
||||||
|
}
|
||||||
|
isAlive = newAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::draw(sf::RenderWindow *window)
|
||||||
|
{
|
||||||
|
if (isAlive)
|
||||||
|
{
|
||||||
|
GameObject::draw(window);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print "You were consumed" message
|
||||||
|
sf::Text text;
|
||||||
|
text.setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
text.setString("x-x");
|
||||||
|
text.setCharacterSize(15);
|
||||||
|
text.setFillColor(sf::Color::Red);
|
||||||
|
text.setOutlineColor(sf::Color::Black);
|
||||||
|
text.setOutlineThickness(2);
|
||||||
|
text.setPosition(coordinates->isometric().x - text.getLocalBounds().width / 2.f,
|
||||||
|
coordinates->isometric().y - text.getLocalBounds().height / 2.f);
|
||||||
|
window->draw(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::setPoints(float newPoints)
|
||||||
|
{
|
||||||
|
points = newPoints;
|
||||||
|
updateRadiusBasedOnPoints();
|
||||||
|
|
||||||
|
if (points > highestPoints)
|
||||||
|
{
|
||||||
|
highestPoints = points;
|
||||||
|
} else if (points <= PLAYER_ALIVE_THRESHOLD)
|
||||||
|
{
|
||||||
|
setAlive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Player::getMaxPoints() const
|
||||||
|
{
|
||||||
|
return highestPoints;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,17 +29,42 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] float getWorldRadius() const;
|
[[nodiscard]] float getWorldRadius() const;
|
||||||
|
|
||||||
[[nodiscard]] long getPoints() const;
|
[[nodiscard]] int getPoints() const;
|
||||||
|
|
||||||
void consume(int points);
|
int getMaxPoints() const;
|
||||||
|
|
||||||
|
void consume(int consumedPoints);
|
||||||
|
|
||||||
|
std::string getSkinName() const;
|
||||||
|
|
||||||
|
void setAlive(bool newAlive);
|
||||||
|
|
||||||
|
[[nodiscard]] bool getAlive() const;
|
||||||
|
|
||||||
|
std::shared_ptr<InputIdentity> getInput() const;
|
||||||
|
|
||||||
|
void setPassiveMode(bool newPassiveMode);
|
||||||
|
|
||||||
|
void setCollidingPlayers(const std::vector<int>& collidingPlayerIds);
|
||||||
|
|
||||||
|
void draw(sf::RenderWindow *window) override;
|
||||||
|
|
||||||
TranslatedCoordinates spawnPosition;
|
TranslatedCoordinates spawnPosition;
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<InputIdentity> input;
|
std::shared_ptr<InputIdentity> input;
|
||||||
float radiusInWorld = 0.5f; // In world units
|
float radiusInWorld = 0.5f; // In world units
|
||||||
std::shared_ptr<VersatileSprite> skinSprite;
|
std::shared_ptr<VersatileSprite> skinSprite;
|
||||||
|
std::vector<std::shared_ptr<Player>> collidingPlayers;
|
||||||
|
|
||||||
long points = DEFAULT_PLAYER_POINTS;
|
void setPoints(float newPoints);
|
||||||
|
|
||||||
|
bool passiveMode = true;
|
||||||
|
bool isAlive = true;
|
||||||
|
|
||||||
|
std::string skinName;
|
||||||
|
|
||||||
|
float points = DEFAULT_PLAYER_POINTS;
|
||||||
|
float highestPoints = DEFAULT_PLAYER_POINTS;
|
||||||
|
|
||||||
int playerId;
|
int playerId;
|
||||||
static inline int playerCreationCounter = 0;
|
static inline int playerCreationCounter = 0;
|
||||||
|
@ -47,6 +72,10 @@ private:
|
||||||
void setWorldRadius(float newWorldRadius);
|
void setWorldRadius(float newWorldRadius);
|
||||||
|
|
||||||
void updateRadiusBasedOnPoints();
|
void updateRadiusBasedOnPoints();
|
||||||
|
|
||||||
|
void performInteractiveUpdates();
|
||||||
|
|
||||||
|
void handlePlayerCollisions();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ PlayerCollection::PlayerCollection(int maxPlayerCount)
|
||||||
// Create player for existing input identities
|
// Create player for existing input identities
|
||||||
for (auto &inputIdentity: InputMapper::getInstance()->getInputIdentities())
|
for (auto &inputIdentity: InputMapper::getInstance()->getInputIdentities())
|
||||||
{
|
{
|
||||||
spawnPlayer(inputIdentity);
|
spawnPlayer(inputIdentity, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ void PlayerCollection::addPlayer(const std::shared_ptr<Player> &player)
|
||||||
{
|
{
|
||||||
newPlayerBuffer.push_back(player);
|
newPlayerBuffer.push_back(player);
|
||||||
addDetachedChild(player);
|
addDetachedChild(player);
|
||||||
updateInputIdentityAllowance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerCollection::clear()
|
void PlayerCollection::clear()
|
||||||
|
@ -38,6 +37,8 @@ void PlayerCollection::clear()
|
||||||
newPlayerBuffer.clear();
|
newPlayerBuffer.clear();
|
||||||
removedPlayerBuffer.clear();
|
removedPlayerBuffer.clear();
|
||||||
|
|
||||||
|
nextSpawnPointIndex = 0;
|
||||||
|
|
||||||
// Fill in removed players
|
// Fill in removed players
|
||||||
for (auto &child: getChildren())
|
for (auto &child: getChildren())
|
||||||
{
|
{
|
||||||
|
@ -49,7 +50,6 @@ void PlayerCollection::clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
clearChildren();
|
clearChildren();
|
||||||
updateInputIdentityAllowance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerCollection::lateUpdate()
|
void PlayerCollection::lateUpdate()
|
||||||
|
@ -80,7 +80,6 @@ void PlayerCollection::removePlayer(const std::shared_ptr<Player> &player)
|
||||||
{
|
{
|
||||||
removedPlayerBuffer.push_back(player);
|
removedPlayerBuffer.push_back(player);
|
||||||
removeChild(player);
|
removeChild(player);
|
||||||
updateInputIdentityAllowance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Player>> PlayerCollection::getNewPlayers() const
|
std::vector<std::shared_ptr<Player>> PlayerCollection::getNewPlayers() const
|
||||||
|
@ -99,13 +98,18 @@ void PlayerCollection::setSpawnPoints(std::vector<GridCoordinates> newSpawnPoint
|
||||||
nextSpawnPointIndex = 0;
|
nextSpawnPointIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerCollection::spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity)
|
void PlayerCollection::spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity, std::string skin)
|
||||||
{
|
{
|
||||||
// Get proper Spawn point, if available
|
// Get proper Spawn point, if available
|
||||||
auto spawn = spawnPoints[nextSpawnPointIndex];
|
auto spawn = spawnPoints[nextSpawnPointIndex];
|
||||||
nextSpawnPointIndex = static_cast<int>((nextSpawnPointIndex + 1) % spawnPoints.size());
|
nextSpawnPointIndex = static_cast<int>((nextSpawnPointIndex + 1) % spawnPoints.size());
|
||||||
|
|
||||||
auto player = std::make_shared<Player>(inputIdentity, PLAYER_SKIN, spawn);
|
if (skin.empty())
|
||||||
|
{
|
||||||
|
skin = getNextSkin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto player = std::make_shared<Player>(inputIdentity, skin, spawn);
|
||||||
addPlayer(player);
|
addPlayer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,12 +144,7 @@ int PlayerCollection::getMaxPlayerCount() const
|
||||||
return maxPlayerCount;
|
return maxPlayerCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerCollection::updateInputIdentityAllowance() const
|
std::shared_ptr<Player> PlayerCollection::getClosestPlayer(const TranslatedCoordinates &point) const
|
||||||
{
|
|
||||||
InputMapper::getInstance()->allowNewInputIdentities = getPlayers().size() < maxPlayerCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Player> PlayerCollection::getClosestPlayer(const TranslatedCoordinates& point) const
|
|
||||||
{
|
{
|
||||||
std::shared_ptr<Player> closestPlayer = nullptr;
|
std::shared_ptr<Player> closestPlayer = nullptr;
|
||||||
float closestDistance = INFINITY;
|
float closestDistance = INFINITY;
|
||||||
|
@ -163,3 +162,79 @@ std::shared_ptr<Player> PlayerCollection::getClosestPlayer(const TranslatedCoord
|
||||||
}
|
}
|
||||||
return closestPlayer;
|
return closestPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string PlayerCollection::getNextSkin()
|
||||||
|
{
|
||||||
|
int numberOfSkins = static_cast<int>(PLAYER_SKINS.size());
|
||||||
|
|
||||||
|
// Count how often each skin index is taken
|
||||||
|
std::map<int, int> skinIndexCount;
|
||||||
|
for (int i = 0; i < numberOfSkins; i++)
|
||||||
|
{
|
||||||
|
skinIndexCount[i] = 0;
|
||||||
|
|
||||||
|
for (auto &takenSkinIndex: takenSkinIndices)
|
||||||
|
{
|
||||||
|
if (takenSkinIndex == i)
|
||||||
|
{
|
||||||
|
skinIndexCount[i]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find skin index with lowest count
|
||||||
|
int skinIndex = takenSkinIndices.size() % numberOfSkins;
|
||||||
|
int lowestCount = takenSkinIndices.size();
|
||||||
|
for (auto &pair: skinIndexCount)
|
||||||
|
{
|
||||||
|
if (pair.second < lowestCount)
|
||||||
|
{
|
||||||
|
lowestCount = pair.second;
|
||||||
|
skinIndex = pair.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
takenSkinIndices.push_back(skinIndex);
|
||||||
|
return PLAYER_SKINS[skinIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerCollection::activatePlayers()
|
||||||
|
{
|
||||||
|
for (auto player: getPlayers())
|
||||||
|
{
|
||||||
|
player->setPassiveMode(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerCollection::deactivatePlayers()
|
||||||
|
{
|
||||||
|
for (auto player: getPlayers())
|
||||||
|
{
|
||||||
|
player->setPassiveMode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerCollection::resetPlayers()
|
||||||
|
{
|
||||||
|
// Recreate players
|
||||||
|
auto oldPlayers = getPlayers();
|
||||||
|
clear();
|
||||||
|
removedPlayerBuffer.clear();
|
||||||
|
for (auto &player: oldPlayers)
|
||||||
|
{
|
||||||
|
spawnPlayer(player->getInput(), player->getSkinName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Player>> PlayerCollection::getRemainingPlayers() const
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<Player>> remainingPlayers = {};
|
||||||
|
for (auto &player: getPlayers())
|
||||||
|
{
|
||||||
|
if (player->getAlive())
|
||||||
|
{
|
||||||
|
remainingPlayers.push_back(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remainingPlayers;
|
||||||
|
}
|
||||||
|
|
|
@ -28,12 +28,19 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] std::vector<std::shared_ptr<Player>> getRemovedPlayers() const;
|
[[nodiscard]] std::vector<std::shared_ptr<Player>> getRemovedPlayers() const;
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<std::shared_ptr<Player>> getRemainingPlayers() const;
|
||||||
|
|
||||||
[[nodiscard]] int getMaxPlayerCount() const;
|
[[nodiscard]] int getMaxPlayerCount() const;
|
||||||
|
|
||||||
void lateUpdate() override;
|
void lateUpdate() override;
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
void resetPlayers();
|
||||||
|
|
||||||
|
void activatePlayers();
|
||||||
|
void deactivatePlayers();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static inline std::shared_ptr<PlayerCollection> singletonInstance = nullptr;
|
static inline std::shared_ptr<PlayerCollection> singletonInstance = nullptr;
|
||||||
|
|
||||||
|
@ -43,11 +50,13 @@ private:
|
||||||
std::vector<GridCoordinates> spawnPoints;
|
std::vector<GridCoordinates> spawnPoints;
|
||||||
int nextSpawnPointIndex = 0;
|
int nextSpawnPointIndex = 0;
|
||||||
|
|
||||||
void spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity);
|
void spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity, std::string skin = "");
|
||||||
|
|
||||||
|
std::vector<int> takenSkinIndices = {};
|
||||||
|
|
||||||
|
std::string getNextSkin();
|
||||||
|
|
||||||
int maxPlayerCount;
|
int maxPlayerCount;
|
||||||
|
|
||||||
void updateInputIdentityAllowance() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <string>
|
||||||
#include "game/input/input_device_group.h"
|
#include "game/input/input_device_group.h"
|
||||||
#include "game/input/button_config.hpp"
|
#include "game/input/button_config.hpp"
|
||||||
#include "game/input/game_action_config.hpp"
|
#include "game/input/game_action_config.hpp"
|
||||||
|
@ -17,36 +18,68 @@ const std::map<sf::Keyboard::Key, ButtonConfig> KEY_CONFIGS = {
|
||||||
{sf::Keyboard::Down, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::DOWN)},
|
{sf::Keyboard::Down, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::DOWN)},
|
||||||
{sf::Keyboard::Left, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::LEFT)},
|
{sf::Keyboard::Left, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::LEFT)},
|
||||||
{sf::Keyboard::Right, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::RIGHT)},
|
{sf::Keyboard::Right, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::RIGHT)},
|
||||||
{sf::Keyboard::RControl, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::GROW})},
|
{sf::Keyboard::RShift, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::RUN})},
|
||||||
{sf::Keyboard::Numpad0, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::SHRINK})},
|
{sf::Keyboard::RControl, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::RUN})},
|
||||||
|
{sf::Keyboard::Numpad0, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::RUN})},
|
||||||
|
{sf::Keyboard::Space, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::CONFIRM})},
|
||||||
|
|
||||||
{sf::Keyboard::W, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::UP)},
|
{sf::Keyboard::W, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::UP)},
|
||||||
{sf::Keyboard::S, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::DOWN)},
|
{sf::Keyboard::S, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::DOWN)},
|
||||||
{sf::Keyboard::A, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::LEFT)},
|
{sf::Keyboard::A, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::LEFT)},
|
||||||
{sf::Keyboard::D, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::RIGHT)},
|
{sf::Keyboard::D, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::RIGHT)},
|
||||||
{sf::Keyboard::Space, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::GROW})},
|
{sf::Keyboard::Q, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::CONFIRM})},
|
||||||
{sf::Keyboard::Q, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::SHRINK})},
|
{sf::Keyboard::E, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::CONFIRM})},
|
||||||
|
{sf::Keyboard::LShift, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::RUN})},
|
||||||
|
{sf::Keyboard::LControl, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::RUN})},
|
||||||
|
|
||||||
{sf::Keyboard::I, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::UP)},
|
{sf::Keyboard::I, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::UP)},
|
||||||
{sf::Keyboard::K, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::DOWN)},
|
{sf::Keyboard::K, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::DOWN)},
|
||||||
{sf::Keyboard::J, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::LEFT)},
|
{sf::Keyboard::J, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::LEFT)},
|
||||||
{sf::Keyboard::L, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::RIGHT)},
|
{sf::Keyboard::L, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::RIGHT)},
|
||||||
{sf::Keyboard::U, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::GROW})},
|
{sf::Keyboard::U, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::CONFIRM})},
|
||||||
{sf::Keyboard::O, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::SHRINK})}
|
{sf::Keyboard::O, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::CONFIRM})},
|
||||||
|
{sf::Keyboard::RAlt, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::RUN})},
|
||||||
|
{sf::Keyboard::B, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::RUN})}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Gamepad buttons
|
// Gamepad buttons
|
||||||
const std::map<int, ButtonConfig> GAMEPAD_BUTTON_CONFIGS = {
|
const std::map<int, ButtonConfig> GAMEPAD_BUTTON_CONFIGS = {
|
||||||
{GamepadButton::EAST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::GROW})},
|
{GamepadButton::EAST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::CONFIRM})},
|
||||||
{GamepadButton::SOUTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::SHRINK})}
|
{GamepadButton::SOUTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::CONFIRM})},
|
||||||
|
{GamepadButton::RIGHT_SHOULDER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||||
|
{GamepadButton::LEFT_SHOULDER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||||
|
{GamepadButton::RIGHT_TRIGGER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||||
|
{GamepadButton::LEFT_TRIGGER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||||
|
{GamepadButton::NORTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||||
|
{GamepadButton::WEST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
const std::map<GameAction, GameActionConfig> GAME_ACTION_CONFIGS = {
|
const std::map<GameAction, GameActionConfig> GAME_ACTION_CONFIGS = {
|
||||||
{GameAction::GROW, GameActionConfig(InteractionMode::HOLD)},
|
{GameAction::CONFIRM, GameActionConfig(InteractionMode::PRESS)},
|
||||||
{GameAction::SHRINK, GameActionConfig(InteractionMode::HOLD)}
|
{GameAction::RUN, GameActionConfig(InteractionMode::HOLD)}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const std::map<InputDeviceGroup, std::string> DEVICE_GROUP_NAMES = {
|
||||||
|
{InputDeviceGroup::KEYBOARD_WASD, "WASD"},
|
||||||
|
{InputDeviceGroup::KEYBOARD_IJKL, "IJKL"},
|
||||||
|
{InputDeviceGroup::KEYBOARD_ARROWS, "Arrow keys"},
|
||||||
|
{InputDeviceGroup::GAMEPAD, "Gamepad"}
|
||||||
|
};
|
||||||
|
const std::map<InputDeviceGroup, std::string> DEVICE_GROUP_CONFIRM = {
|
||||||
|
{InputDeviceGroup::KEYBOARD_WASD, "Q or E"},
|
||||||
|
{InputDeviceGroup::KEYBOARD_IJKL, "U or O"},
|
||||||
|
{InputDeviceGroup::KEYBOARD_ARROWS, "Space"},
|
||||||
|
{InputDeviceGroup::GAMEPAD, "A"}
|
||||||
|
};
|
||||||
|
const std::map<InputDeviceGroup, std::string> DEVICE_GROUP_RUN = {
|
||||||
|
{InputDeviceGroup::KEYBOARD_WASD, "L-Shift or -Ctrl"},
|
||||||
|
{InputDeviceGroup::KEYBOARD_IJKL, "B or R-Alt"},
|
||||||
|
{InputDeviceGroup::KEYBOARD_ARROWS, "R-Shift, -Ctrl or Pad-0"},
|
||||||
|
{InputDeviceGroup::GAMEPAD, "Trigger or Shoulder"}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //HOLESOME_INPUT_CONFIG_H
|
#endif //HOLESOME_INPUT_CONFIG_H
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
std::map<std::string, LevelConfig> const all_levels = {
|
std::map<std::string, LevelConfig> const all_levels = {
|
||||||
{"default", LevelConfig("Default",
|
{"default", LevelConfig("Default",
|
||||||
30,
|
120,
|
||||||
{
|
{
|
||||||
{0, 0},
|
{0, 0},
|
||||||
{18, 18},
|
{18, 18},
|
||||||
|
@ -23,7 +23,7 @@ std::map<std::string, LevelConfig> const all_levels = {
|
||||||
CollectableInLevel("rose", {1, 2}),
|
CollectableInLevel("rose", {1, 2}),
|
||||||
CollectableInLevel("small-tree", {4, 3}),
|
CollectableInLevel("small-tree", {4, 3}),
|
||||||
CollectableInLevel("rose", {8, 3}),
|
CollectableInLevel("rose", {8, 3}),
|
||||||
CollectableInLevel("rose", {6, 7}),
|
CollectableInLevel("lantern", {6, 7}),
|
||||||
CollectableInLevel("rose", {5, 5}),
|
CollectableInLevel("rose", {5, 5}),
|
||||||
CollectableInLevel("tram", {9, 5}),
|
CollectableInLevel("tram", {9, 5}),
|
||||||
CollectableInLevel("rose", {0, 1})
|
CollectableInLevel("rose", {0, 1})
|
||||||
|
|
|
@ -36,11 +36,9 @@ void loadAllFonts()
|
||||||
void runGame()
|
void runGame()
|
||||||
{
|
{
|
||||||
LOG(INFO) << "Starting game ...";
|
LOG(INFO) << "Starting game ...";
|
||||||
auto game = GameFactory::createWindowed("Holesome");
|
auto game = GameFactory::createFullscreen(GAME_NAME);
|
||||||
|
|
||||||
// Load initial level
|
|
||||||
LevelLoader::loadLevel(INITIAL_LEVEL);
|
|
||||||
|
|
||||||
|
game->showJoinScreen();
|
||||||
game->run();
|
game->run();
|
||||||
|
|
||||||
InputMapper::getInstance().reset();
|
InputMapper::getInstance().reset();
|
||||||
|
|
179
src/screens/join_screen.cpp
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
#include "join_screen.hpp"
|
||||||
|
#include "../config.h"
|
||||||
|
#include "../typography/font_manager.hpp"
|
||||||
|
#include "../game/player/player_collection.hpp"
|
||||||
|
#include "../levels.hpp"
|
||||||
|
#include "../game/level/level_loader.hpp"
|
||||||
|
#include "../game/game.h"
|
||||||
|
|
||||||
|
JoinScreen::JoinScreen()
|
||||||
|
{
|
||||||
|
// Title text
|
||||||
|
auto titleText = std::make_shared<sf::Text>();
|
||||||
|
titleText->setString(GAME_NAME);
|
||||||
|
titleText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
titleText->setCharacterSize(150);
|
||||||
|
titleText->setStyle(sf::Text::Bold);
|
||||||
|
titleText->setFillColor(sf::Color::White);
|
||||||
|
titleText->setPosition(REFERENCE_SIZE.x / 2.f - titleText->getGlobalBounds().width / 2.f, 20);
|
||||||
|
add(titleText);
|
||||||
|
|
||||||
|
// Create join-player text
|
||||||
|
auto joinText = std::make_shared<sf::Text>();
|
||||||
|
joinText->setString("Press button to join");
|
||||||
|
joinText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
joinText->setCharacterSize(24);
|
||||||
|
joinText->setFillColor(sf::Color::White);
|
||||||
|
joinText->setPosition(REFERENCE_SIZE.x / 2.f - joinText->getGlobalBounds().width / 2.f, 200);
|
||||||
|
add(joinText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoinScreen::update()
|
||||||
|
{
|
||||||
|
Screen::update();
|
||||||
|
|
||||||
|
// Did any player start game?
|
||||||
|
auto players = PlayerCollection::getInstance()->getPlayers();
|
||||||
|
for (auto &player: players)
|
||||||
|
{
|
||||||
|
auto input = player->getInput();
|
||||||
|
if (input->isPerformingAction(GameAction::CONFIRM))
|
||||||
|
{
|
||||||
|
// Start game
|
||||||
|
Game::getInstance()->generateNewLevel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Update player list items
|
||||||
|
if (players.size() == lastPlayerCount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastPlayerCount = players.size();
|
||||||
|
|
||||||
|
playerList.clear();
|
||||||
|
playerListObjects.clear();
|
||||||
|
int index = 0;
|
||||||
|
for (auto &player: players)
|
||||||
|
{
|
||||||
|
addPlayerAtIndex(player, index);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoinScreen::draw(sf::RenderWindow *window)
|
||||||
|
{
|
||||||
|
Screen::draw(window);
|
||||||
|
|
||||||
|
// Draw player list items
|
||||||
|
for (auto &item: playerList)
|
||||||
|
{
|
||||||
|
window->draw(*item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &item: playerListObjects)
|
||||||
|
{
|
||||||
|
item->draw(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoinScreen::addPlayerAtIndex(std::shared_ptr<Player> &player, int index)
|
||||||
|
{
|
||||||
|
int numberOfMaxPlayers = PlayerCollection::getInstance()->getMaxPlayerCount();
|
||||||
|
int paddingLeftRight = 100;
|
||||||
|
int paddingTop = 400;
|
||||||
|
int relativePaddingPerColumn = 0.1;
|
||||||
|
int columnWidth = (REFERENCE_SIZE.x - 2 * paddingLeftRight) / numberOfMaxPlayers;
|
||||||
|
int paddingPerColumn = columnWidth * relativePaddingPerColumn;
|
||||||
|
int contentWidth = columnWidth - paddingPerColumn;
|
||||||
|
auto topLeft = sf::Vector2f(paddingLeftRight + columnWidth * index,
|
||||||
|
paddingTop);
|
||||||
|
|
||||||
|
// Create player character
|
||||||
|
auto skinSprite = std::make_shared<VersatileSprite>(player->getSkinName(),
|
||||||
|
sf::Vector2f(contentWidth, contentWidth * ISOMETRIC_SKEW) *
|
||||||
|
0.9f);
|
||||||
|
skinSprite->coordinates->setIsometric(IsometricCoordinates(topLeft));
|
||||||
|
playerListObjects.push_back(skinSprite);
|
||||||
|
skinSprite->update();
|
||||||
|
skinSprite->lateUpdate();
|
||||||
|
skinSprite->preRenderUpdate();
|
||||||
|
|
||||||
|
// Show device group
|
||||||
|
auto deviceGroupText = std::make_shared<sf::Text>();
|
||||||
|
InputDeviceGroup deviceGroup = player->getInput()->deviceGroup;
|
||||||
|
deviceGroupText->setString(DEVICE_GROUP_NAMES.at(deviceGroup));
|
||||||
|
deviceGroupText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
deviceGroupText->setCharacterSize(24);
|
||||||
|
deviceGroupText->setFillColor(sf::Color::White);
|
||||||
|
deviceGroupText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - deviceGroupText->getGlobalBounds().width / 2.f,
|
||||||
|
topLeft.y + skinSprite->getSize().y + 20);
|
||||||
|
playerList.push_back(deviceGroupText);
|
||||||
|
|
||||||
|
// Show button to run
|
||||||
|
auto runText = std::make_shared<sf::Text>();
|
||||||
|
runText->setString("Press");
|
||||||
|
runText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
runText->setCharacterSize(15);
|
||||||
|
runText->setFillColor(sf::Color::White);
|
||||||
|
runText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - runText->getGlobalBounds().width / 2.f,
|
||||||
|
topLeft.y + skinSprite->getSize().y + 60);
|
||||||
|
playerList.push_back(runText);
|
||||||
|
|
||||||
|
std::string buttonToRun = DEVICE_GROUP_RUN.at(deviceGroup);
|
||||||
|
auto runButton = std::make_shared<sf::Text>();
|
||||||
|
runButton->setString(buttonToRun);
|
||||||
|
runButton->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
|
||||||
|
runButton->setCharacterSize(15);
|
||||||
|
runButton->setFillColor(sf::Color::White);
|
||||||
|
runButton->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - runButton->getGlobalBounds().width / 2.f,
|
||||||
|
topLeft.y + skinSprite->getSize().y + 80);
|
||||||
|
playerList.push_back(runButton);
|
||||||
|
|
||||||
|
auto confirmText = std::make_shared<sf::Text>();
|
||||||
|
confirmText->setString("to run");
|
||||||
|
confirmText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
confirmText->setCharacterSize(15);
|
||||||
|
confirmText->setFillColor(sf::Color::White);
|
||||||
|
confirmText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - confirmText->getGlobalBounds().width / 2.f,
|
||||||
|
topLeft.y + skinSprite->getSize().y + 100);
|
||||||
|
playerList.push_back(confirmText);
|
||||||
|
|
||||||
|
|
||||||
|
if (lastPlayerCount < MIN_PLAYER_COUNT)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show button to press to start
|
||||||
|
auto pressText = std::make_shared<sf::Text>();
|
||||||
|
pressText->setString("Press");
|
||||||
|
pressText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
pressText->setCharacterSize(24);
|
||||||
|
pressText->setFillColor(sf::Color::Yellow);
|
||||||
|
pressText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - pressText->getGlobalBounds().width / 2.f,
|
||||||
|
topLeft.y + skinSprite->getSize().y + 170);
|
||||||
|
playerList.push_back(pressText);
|
||||||
|
|
||||||
|
std::string buttonToStart = DEVICE_GROUP_CONFIRM.at(deviceGroup);
|
||||||
|
auto buttonText = std::make_shared<sf::Text>();
|
||||||
|
buttonText->setString(buttonToStart);
|
||||||
|
buttonText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
buttonText->setCharacterSize(24);
|
||||||
|
buttonText->setFillColor(sf::Color::Yellow);
|
||||||
|
buttonText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - buttonText->getGlobalBounds().width / 2.f,
|
||||||
|
topLeft.y + skinSprite->getSize().y + 200);
|
||||||
|
playerList.push_back(buttonText);
|
||||||
|
|
||||||
|
auto toStartText = std::make_shared<sf::Text>();
|
||||||
|
toStartText->setString("to start");
|
||||||
|
toStartText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
toStartText->setCharacterSize(24);
|
||||||
|
toStartText->setFillColor(sf::Color::Yellow);
|
||||||
|
toStartText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - toStartText->getGlobalBounds().width / 2.f,
|
||||||
|
topLeft.y + skinSprite->getSize().y + 230);
|
||||||
|
playerList.push_back(toStartText);
|
||||||
|
}
|
27
src/screens/join_screen.hpp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef HOLESOME_JOIN_SCREEN_HPP
|
||||||
|
#define HOLESOME_JOIN_SCREEN_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include "screen.hpp"
|
||||||
|
#include "../game/player/player.hpp"
|
||||||
|
#include <SFML/Graphics/Text.hpp>
|
||||||
|
|
||||||
|
class JoinScreen : public Screen
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JoinScreen();
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void draw(sf::RenderWindow *window) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int lastPlayerCount = -1;
|
||||||
|
std::vector<std::shared_ptr<sf::Drawable>> playerList = {};
|
||||||
|
std::vector<std::shared_ptr<GameObject>> playerListObjects = {};
|
||||||
|
|
||||||
|
void addPlayerAtIndex(std::shared_ptr<Player> &player, int index);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //HOLESOME_JOIN_SCREEN_HPP
|
17
src/screens/loading_screen.cpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include <SFML/Graphics/Text.hpp>
|
||||||
|
#include "loading_screen.hpp"
|
||||||
|
#include "../typography/font_manager.hpp"
|
||||||
|
#include "../config.h"
|
||||||
|
|
||||||
|
LoadingScreen::LoadingScreen(const std::string &message)
|
||||||
|
{
|
||||||
|
auto text = std::make_shared<sf::Text>();
|
||||||
|
text->setString(message);
|
||||||
|
text->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
text->setCharacterSize(24);
|
||||||
|
text->setStyle(sf::Text::Italic);
|
||||||
|
text->setFillColor(sf::Color::White);
|
||||||
|
text->setPosition(REFERENCE_SIZE.x / 2.f - text->getGlobalBounds().width / 2.f,
|
||||||
|
REFERENCE_SIZE.y / 2.f - text->getGlobalBounds().height / 2.f);
|
||||||
|
add(text);
|
||||||
|
}
|
14
src/screens/loading_screen.hpp
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef HOLESOME_LOADING_SCREEN_HPP
|
||||||
|
#define HOLESOME_LOADING_SCREEN_HPP
|
||||||
|
|
||||||
|
|
||||||
|
#include "screen.hpp"
|
||||||
|
|
||||||
|
class LoadingScreen : public Screen
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit LoadingScreen(const std::string &message = "Loading...");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //HOLESOME_LOADING_SCREEN_HPP
|
|
@ -2,9 +2,29 @@
|
||||||
|
|
||||||
#include "../typography/font_manager.hpp"
|
#include "../typography/font_manager.hpp"
|
||||||
#include "../texture_config.h"
|
#include "../texture_config.h"
|
||||||
|
#include "../game/game.h"
|
||||||
|
|
||||||
WinnerScreen::WinnerScreen(const std::shared_ptr<Player> &winner)
|
WinnerScreen::WinnerScreen(std::vector<std::shared_ptr<Player>> players)
|
||||||
: winner(winner)
|
{
|
||||||
|
// Sort players by points descending
|
||||||
|
std::sort(players.begin(), players.end(), [](const std::shared_ptr<Player> &a, const std::shared_ptr<Player> &b)
|
||||||
|
{
|
||||||
|
return a->getPoints() > b->getPoints();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get winner
|
||||||
|
winner = players[0];
|
||||||
|
createWinnerTribute(winner);
|
||||||
|
|
||||||
|
if (players.size() > 1)
|
||||||
|
{
|
||||||
|
// Remove winner from list
|
||||||
|
players.erase(players.begin());
|
||||||
|
listRemainingScores(players);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinnerScreen::createWinnerTribute(const std::shared_ptr<Player> &winner)
|
||||||
{
|
{
|
||||||
// Winner title
|
// Winner title
|
||||||
auto winnerTitle = std::make_shared<sf::Text>();
|
auto winnerTitle = std::make_shared<sf::Text>();
|
||||||
|
@ -17,7 +37,8 @@ WinnerScreen::WinnerScreen(const std::shared_ptr<Player> &winner)
|
||||||
add(winnerTitle);
|
add(winnerTitle);
|
||||||
|
|
||||||
// Add graphic of the winner
|
// Add graphic of the winner
|
||||||
auto winnerGraphic = std::make_shared<VersatileSprite>(PLAYER_SKIN);
|
auto winnerGraphic = std::make_shared<VersatileSprite>(winner->getSkinName(),
|
||||||
|
sf::Vector2f(500, 500 * ISOMETRIC_SKEW));
|
||||||
auto center = REFERENCE_SIZE / 2.f;
|
auto center = REFERENCE_SIZE / 2.f;
|
||||||
auto winnerPosition = center - winnerGraphic->getSize() / 2.f;
|
auto winnerPosition = center - winnerGraphic->getSize() / 2.f;
|
||||||
winnerGraphic->coordinates->setIsometric(IsometricCoordinates(winnerPosition));
|
winnerGraphic->coordinates->setIsometric(IsometricCoordinates(winnerPosition));
|
||||||
|
@ -29,6 +50,73 @@ WinnerScreen::WinnerScreen(const std::shared_ptr<Player> &winner)
|
||||||
points->setString("Points: " + std::to_string(winner->getPoints()));
|
points->setString("Points: " + std::to_string(winner->getPoints()));
|
||||||
points->setCharacterSize(20);
|
points->setCharacterSize(20);
|
||||||
points->setFillColor(sf::Color::White);
|
points->setFillColor(sf::Color::White);
|
||||||
points->setPosition(REFERENCE_SIZE.x / 2 - points->getGlobalBounds().width / 2, REFERENCE_SIZE.y - 300);
|
points->setPosition(REFERENCE_SIZE.x / 2 - points->getGlobalBounds().width / 2, REFERENCE_SIZE.y - 400);
|
||||||
add(points);
|
add(points);
|
||||||
|
|
||||||
|
// Show points below
|
||||||
|
if (winner->getMaxPoints() != winner->getPoints())
|
||||||
|
{
|
||||||
|
auto highestPoints = std::make_shared<sf::Text>();
|
||||||
|
highestPoints->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
highestPoints->setString("Highest Points: " + std::to_string(winner->getMaxPoints()));
|
||||||
|
highestPoints->setCharacterSize(20);
|
||||||
|
highestPoints->setFillColor(sf::Color::Yellow);
|
||||||
|
highestPoints->setPosition(REFERENCE_SIZE.x / 2 - highestPoints->getGlobalBounds().width / 2,
|
||||||
|
REFERENCE_SIZE.y - 360);
|
||||||
|
add(highestPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show press button to continue
|
||||||
|
auto input = winner->getInput();
|
||||||
|
auto confirmButton = DEVICE_GROUP_CONFIRM.at(input->deviceGroup);
|
||||||
|
auto confirmText = std::make_shared<sf::Text>();
|
||||||
|
confirmText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
confirmText->setString("Press " + confirmButton + " to continue");
|
||||||
|
confirmText->setCharacterSize(20);
|
||||||
|
confirmText->setFillColor(sf::Color::White);
|
||||||
|
confirmText->setStyle(sf::Text::Bold);
|
||||||
|
confirmText->setPosition(REFERENCE_SIZE.x / 2 - confirmText->getGlobalBounds().width / 2, REFERENCE_SIZE.y - 200);
|
||||||
|
add(confirmText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinnerScreen::update()
|
||||||
|
{
|
||||||
|
GameObject::update();
|
||||||
|
|
||||||
|
auto input = winner->getInput();
|
||||||
|
if (input->isPerformingAction(GameAction::CONFIRM))
|
||||||
|
{
|
||||||
|
Game::getInstance()->showJoinScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinnerScreen::listRemainingScores(const std::vector<std::shared_ptr<Player>> &remainingPlayers)
|
||||||
|
{
|
||||||
|
int leftPadding = 150;
|
||||||
|
int topPadding = 500;
|
||||||
|
int betweenPadding = 50;
|
||||||
|
int height = 64;
|
||||||
|
int index = 0;
|
||||||
|
for (const auto &player: remainingPlayers)
|
||||||
|
{
|
||||||
|
// Player skin
|
||||||
|
auto playerGraphic = std::make_shared<VersatileSprite>(player->getSkinName(),
|
||||||
|
sf::Vector2f(height / ISOMETRIC_SKEW, height));
|
||||||
|
playerGraphic->coordinates->setIsometric(
|
||||||
|
IsometricCoordinates(leftPadding, topPadding + index * (height + betweenPadding)));
|
||||||
|
add(playerGraphic);
|
||||||
|
|
||||||
|
// Points
|
||||||
|
auto points = std::make_shared<sf::Text>();
|
||||||
|
points->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||||
|
points->setString(std::to_string(player->getMaxPoints()));
|
||||||
|
points->setCharacterSize(20);
|
||||||
|
points->setFillColor(sf::Color::White);
|
||||||
|
points->setPosition(leftPadding + playerGraphic->getSize().x + 10,
|
||||||
|
topPadding + index * (height + betweenPadding) + height / 2.f -
|
||||||
|
points->getGlobalBounds().height / 2.f);
|
||||||
|
add(points);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,16 @@
|
||||||
class WinnerScreen : public Screen
|
class WinnerScreen : public Screen
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WinnerScreen(const std::shared_ptr<Player> &winner);
|
WinnerScreen(std::vector<std::shared_ptr<Player>> players);
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Player> winner;
|
std::shared_ptr<Player> winner;
|
||||||
|
|
||||||
|
void createWinnerTribute(const std::shared_ptr<Player> &winner);
|
||||||
|
|
||||||
|
void listRemainingScores(const std::vector<std::shared_ptr<Player>>& remainingPlayers);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,6 @@ void MaskedSprite::preRenderUpdate()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previousAngle == angle && previousRenderPosition == renderPosition)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (angle == 0)
|
if (angle == 0)
|
||||||
{
|
{
|
||||||
renderPosition = coordinates->isometric().toScreen();
|
renderPosition = coordinates->isometric().toScreen();
|
||||||
|
@ -45,6 +40,11 @@ void MaskedSprite::preRenderUpdate()
|
||||||
sprite->setPosition(renderPosition);
|
sprite->setPosition(renderPosition);
|
||||||
sprite->setRotation(angle);
|
sprite->setRotation(angle);
|
||||||
|
|
||||||
|
if (previousAngle == angle && previousRenderPosition == renderPosition)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
updateFreshMaskedSprite();
|
updateFreshMaskedSprite();
|
||||||
|
|
||||||
previousAngle = angle;
|
previousAngle = angle;
|
||||||
|
@ -78,7 +78,7 @@ void MaskedSprite::updateFreshMaskedSprite()
|
||||||
coordinatesTopLeftCorner.diagonalWorld().vertical);
|
coordinatesTopLeftCorner.diagonalWorld().vertical);
|
||||||
auto xAxis = rotateVectorByAngle(sf::Vector2f(1, 0), angle);
|
auto xAxis = rotateVectorByAngle(sf::Vector2f(1, 0), angle);
|
||||||
auto yAxis = rotateVectorByAngle(sf::Vector2f(0, -1), angle);
|
auto yAxis = rotateVectorByAngle(sf::Vector2f(0, -1), angle);
|
||||||
float xFactorPerPixel = sprite->getScale().x / WORLD_TO_ISO_SCALE;
|
float xFactorPerPixel = sprite->getScale().x / WORLD_TO_ISO_SCALE * sqrt(2);
|
||||||
float yFactorPerPixel = sprite->getScale().y / WORLD_TO_ISO_SCALE;
|
float yFactorPerPixel = sprite->getScale().y / WORLD_TO_ISO_SCALE;
|
||||||
for (int yOffset = 0; yOffset < textureRect.height; yOffset++)
|
for (int yOffset = 0; yOffset < textureRect.height; yOffset++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include "sprites/tiling/tileset_config.hpp"
|
#include "sprites/tiling/tileset_config.hpp"
|
||||||
#include "sprites/configs/masked_sprite_config.hpp"
|
#include "sprites/configs/masked_sprite_config.hpp"
|
||||||
|
|
||||||
#define PLAYER_SKIN "hole"
|
#define PLAYER_SKINS std::vector<std::string> { "player", "player-1", "player-2", "player-3" }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All textures used in the game.
|
* All textures used in the game.
|
||||||
|
@ -18,7 +18,6 @@
|
||||||
std::map<std::string, std::string> const all_textures = {
|
std::map<std::string, std::string> const all_textures = {
|
||||||
{"numbers", "assets/numbers.png"},
|
{"numbers", "assets/numbers.png"},
|
||||||
{"64", "assets/64.png"},
|
{"64", "assets/64.png"},
|
||||||
{"edge", "assets/edge.png"},
|
|
||||||
{"ring", "assets/ring.png"},
|
{"ring", "assets/ring.png"},
|
||||||
{"grasses", "assets/grass_plus.png"},
|
{"grasses", "assets/grass_plus.png"},
|
||||||
{"hole", "assets/hole.png"},
|
{"hole", "assets/hole.png"},
|
||||||
|
@ -28,7 +27,14 @@ std::map<std::string, std::string> const all_textures = {
|
||||||
{"small-tree", "assets/collectables/small-tree.png"},
|
{"small-tree", "assets/collectables/small-tree.png"},
|
||||||
{"stone", "assets/collectables/stone.png"},
|
{"stone", "assets/collectables/stone.png"},
|
||||||
{"tram", "assets/collectables/tram.png"},
|
{"tram", "assets/collectables/tram.png"},
|
||||||
{"iso-tiles", "assets/isometric-tiles.png"}
|
{"lantern", "assets/collectables/lantern.png"},
|
||||||
|
{"player", "assets/player/player.png"},
|
||||||
|
{"player-0", "assets/player/player-0.png"},
|
||||||
|
{"player-1", "assets/player/player-1.png"},
|
||||||
|
{"player-2", "assets/player/player-2.png"},
|
||||||
|
{"player-3", "assets/player/player-3.png"},
|
||||||
|
{"iso-tiles", "assets/isometric-tiles.png"},
|
||||||
|
{"custom-grass-tiles", "assets/grass-tiles.png"}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +44,8 @@ std::map<std::string, std::string> const all_textures = {
|
||||||
std::map<std::string, SheetConfig> const all_sheets = {
|
std::map<std::string, SheetConfig> const all_sheets = {
|
||||||
{"numbers", SheetConfig("numbers", 4, 2)},
|
{"numbers", SheetConfig("numbers", 4, 2)},
|
||||||
{"grasses", SheetConfig("grasses", 25, 14)},
|
{"grasses", SheetConfig("grasses", 25, 14)},
|
||||||
{"iso-tiles", SheetConfig("iso-tiles", 6, 2)}
|
{"iso-tiles", SheetConfig("iso-tiles", 6, 2)},
|
||||||
|
{"custom-grass-tiles", SheetConfig("custom-grass-tiles", 6, 2)}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +64,12 @@ std::map<std::string, SpriteConfig> const all_sprites = {
|
||||||
{"64", SpriteConfig("64")},
|
{"64", SpriteConfig("64")},
|
||||||
{"edge", SpriteConfig("edge")},
|
{"edge", SpriteConfig("edge")},
|
||||||
{"ring", SpriteConfig("ring")},
|
{"ring", SpriteConfig("ring")},
|
||||||
{"hole", SpriteConfig("hole")}
|
{"hole", SpriteConfig("hole")},
|
||||||
|
{"player", SpriteConfig("player")},
|
||||||
|
{"player-0", SpriteConfig("player-0")},
|
||||||
|
{"player-1", SpriteConfig("player-1")},
|
||||||
|
{"player-2", SpriteConfig("player-2")},
|
||||||
|
{"player-3", SpriteConfig("player-3")}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,6 +82,7 @@ std::map<std::string, MaskedSpriteConfig> const all_masked_sprites = {
|
||||||
{"rosebush", MaskedSpriteConfig("rosebush")},
|
{"rosebush", MaskedSpriteConfig("rosebush")},
|
||||||
{"stone", MaskedSpriteConfig("stone")},
|
{"stone", MaskedSpriteConfig("stone")},
|
||||||
{"tram", MaskedSpriteConfig("tram")},
|
{"tram", MaskedSpriteConfig("tram")},
|
||||||
|
{"lantern", MaskedSpriteConfig("lantern")},
|
||||||
{"small-tree", MaskedSpriteConfig("small-tree")}
|
{"small-tree", MaskedSpriteConfig("small-tree")}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,7 +91,7 @@ std::map<std::string, MaskedSpriteConfig> const all_masked_sprites = {
|
||||||
* The key is the name of the tileset, the value is the tileset config.
|
* The key is the name of the tileset, the value is the tileset config.
|
||||||
*/
|
*/
|
||||||
std::map<std::string, TileSetConfig> const all_tilesets = {
|
std::map<std::string, TileSetConfig> const all_tilesets = {
|
||||||
{"iso-tiles", TileSetConfig("iso-tiles", {0, 1, 2, 3, 4, 5})}
|
{"iso-tiles", TileSetConfig("custom-grass-tiles", {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11})}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //HOLESOME_TEXTURE_CONFIG_H
|
#endif //HOLESOME_TEXTURE_CONFIG_H
|
||||||
|
|