From c3c346a0829242983c32997d38b42e5d5174711d Mon Sep 17 00:00:00 2001 From: Marty <85036874+MatusGuy@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:52:20 +0000 Subject: [PATCH] Item Pocket (#3070) Original description by MatusGuy: The Item Pocket allows you to save a powerup for later use. If you collect a flower while having a bonus greater than GROWUP_BONUS, that flower gets equipped and the old flower gets stored in the top left corner of the screen. Now, the player can press the new ITEM control (usually Select/Back or Left Shift) to use the stored flower by throwing it up and catching it. This pull request replaces the powerup stacking feature because this new solution is much more balanced. It also limits the amount of concurrent players to 4. * aeiou * add item pocket input * item pocket workie * item pocket hud * lifesaver texture Co-authored-by: RustyBox@users.noreply.github.com * item pocket bare minimum * god fujjgking damngit * THINGS * pepsi pocket Co-authored-by: RustyBox * this is for the good of the item economy * more bugs fickesed * powerup stacking is gone * editor * fix ci * setting wip * item pocket setting complete * agjhag * tdjyfjgfjgf * bitch * the anticheat mechanism * omfg * wip [ci skip] * wip again [ci skip] * script * augh squirrel thing * Apply suggestions from code review Co-authored-by: Vankata453 <78196474+Vankata453@users.noreply.github.com> * Fix being unable to spawn player [ci skip] * Remove code duplication in `MultiplayerPlayerMenu`, do not remove players from status [ci skip] * Fix scripting documentation consistency with function * wip script item pocket * chagne docs * fix duo player bug weird strange weird * Scripting docs: Full stops [ci skip] * Fix `BonusType` enum descriptions for scripting documentation [ci skip] --------- Co-authored-by: MatusGuy Co-authored-by: RustyBox Co-authored-by: Vankata453 <78196474+Vankata453@users.noreply.github.com> --- data/images/engine/hud/item_pocket.png | Bin 0 -> 31420 bytes src/badguy/badguy.cpp | 8 +- src/badguy/boss.cpp | 21 +- src/badguy/boss.hpp | 1 + src/badguy/snowman.cpp | 2 +- src/badguy/stalactite.cpp | 2 +- src/control/controller.cpp | 1 + src/control/controller.hpp | 1 + src/control/game_controller_manager.cpp | 35 +- src/control/input_manager.cpp | 11 + src/control/input_manager.hpp | 2 +- src/control/joystick_config.cpp | 1 + src/control/joystick_manager.cpp | 34 +- src/control/keyboard_config.cpp | 16 +- src/object/bonus_block.cpp | 14 +- src/object/bullet.cpp | 8 +- src/object/flower.cpp | 8 +- src/object/growup.cpp | 2 +- src/object/moving_sprite.hpp | 1 + src/object/player.cpp | 144 ++++---- src/object/player.hpp | 28 +- src/object/powerup.cpp | 35 +- src/object/powerup.hpp | 38 ++- src/object/trampoline.cpp | 2 +- src/object/weak_block.cpp | 2 +- src/squirrel/supertux_api.cpp | 42 +++ src/supertux/controller_hud.cpp | 6 +- src/supertux/game_session.cpp | 53 ++- src/supertux/game_session.hpp | 4 + src/supertux/level.cpp | 50 ++- src/supertux/level.hpp | 20 ++ src/supertux/level_parser.cpp | 4 + src/supertux/levelintro.cpp | 12 +- src/supertux/menu/cheat_apply_menu.cpp | 32 +- src/supertux/menu/cheat_apply_menu.hpp | 6 +- src/supertux/menu/cheat_menu.cpp | 24 +- src/supertux/menu/editor_level_menu.cpp | 7 + src/supertux/menu/joystick_menu.cpp | 2 + src/supertux/menu/keyboard_menu.cpp | 7 +- src/supertux/menu/multiplayer_player_menu.cpp | 32 +- .../menu/multiplayer_players_menu.cpp | 19 +- .../menu/worldmap_cheat_apply_menu.cpp | 41 +-- .../menu/worldmap_cheat_apply_menu.hpp | 6 +- src/supertux/menu/worldmap_cheat_menu.cpp | 31 +- src/supertux/menu/worldmap_cheat_menu.hpp | 1 - src/supertux/player_status.cpp | 312 ++++++++++++------ src/supertux/player_status.hpp | 55 ++- src/supertux/player_status_hud.cpp | 105 +++--- src/supertux/player_status_hud.hpp | 10 +- src/worldmap/tux.cpp | 12 +- src/worldmap/worldmap.cpp | 6 + src/worldmap/worldmap.hpp | 3 + 52 files changed, 782 insertions(+), 537 deletions(-) create mode 100644 data/images/engine/hud/item_pocket.png diff --git a/data/images/engine/hud/item_pocket.png b/data/images/engine/hud/item_pocket.png new file mode 100644 index 0000000000000000000000000000000000000000..c848dd7a0e56e43ef949344f532baae3b98b679e GIT binary patch literal 31420 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�mY~xSQdha= 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 literal 0 HcmV?d00001 diff --git a/src/badguy/badguy.cpp b/src/badguy/badguy.cpp index 1de57d6aa51..aa698a6987c 100644 --- a/src/badguy/badguy.cpp +++ b/src/badguy/badguy.cpp @@ -617,7 +617,7 @@ HitResponse BadGuy::collision_bullet(Bullet& bullet, const CollisionHit& hit) { if (is_frozen()) { - if (bullet.get_type() == FIRE_BONUS) { + if (bullet.get_type() == BONUS_FIRE) { // Fire bullet thaws frozen badguys. unfreeze(); bullet.remove_me(); @@ -629,7 +629,7 @@ BadGuy::collision_bullet(Bullet& bullet, const CollisionHit& hit) } } else if (is_ignited()) { - if (bullet.get_type() == ICE_BONUS) { + if (bullet.get_type() == BONUS_ICE) { // Ice bullets extinguish ignited badguys. extinguish(); bullet.remove_me(); @@ -640,13 +640,13 @@ BadGuy::collision_bullet(Bullet& bullet, const CollisionHit& hit) return FORCE_MOVE; } } - else if (bullet.get_type() == FIRE_BONUS && is_flammable()) { + else if (bullet.get_type() == BONUS_FIRE && is_flammable()) { // Fire bullets ignite flammable badguys. ignite(); bullet.remove_me(); return ABORT_MOVE; } - else if (bullet.get_type() == ICE_BONUS && is_freezable()) { + else if (bullet.get_type() == BONUS_ICE && is_freezable()) { // Ice bullets freeze freezable badguys. freeze(); bullet.remove_me(); diff --git a/src/badguy/boss.cpp b/src/badguy/boss.cpp index 40e557939fb..0de39d3a91a 100644 --- a/src/badguy/boss.cpp +++ b/src/badguy/boss.cpp @@ -31,6 +31,7 @@ namespace Boss::Boss(const ReaderMapping& reader, const std::string& sprite_name, int layer) : BadGuy(reader, sprite_name, layer), m_lives(), + m_max_lives(), m_pinch_lives(), m_hud_head(), m_hud_icon(), @@ -38,9 +39,11 @@ Boss::Boss(const ReaderMapping& reader, const std::string& sprite_name, int laye m_pinch_activation_script() { reader.get("lives", m_lives, DEFAULT_LIVES); - reader.get("pinch-lives", m_pinch_lives, DEFAULT_PINCH_LIVES); + m_max_lives = m_lives; + m_countMe = true; + reader.get("pinch-lives", m_pinch_lives, DEFAULT_PINCH_LIVES); reader.get("pinch-activation-script", m_pinch_activation_script, ""); } @@ -70,9 +73,13 @@ Boss::draw_hit_points(DrawingContext& context) context.set_translation(Vector(0, 0)); context.transform().scale = 1.f; + float startpos = (context.get_width() - static_cast((m_hud_head->get_width() * m_max_lives))) / 2; for (int i = 0; i < m_lives; ++i) { - context.color().draw_surface(m_hud_head, Vector(BORDER_X + (static_cast(i * m_hud_head->get_width())), BORDER_Y + 1), LAYER_HUD); + context.color().draw_surface(m_hud_head, + Vector(BORDER_X + (startpos + static_cast(i * m_hud_head->get_width())), + BORDER_Y + 1), + LAYER_HUD); } context.pop_transform(); @@ -86,15 +93,15 @@ Boss::get_settings() result.add_text("hud-icon", &m_hud_icon, "hud-icon", "images/creatures/yeti/hudlife.png", OPTION_HIDDEN); result.add_int(_("Lives"), &m_lives, "lives", DEFAULT_LIVES); - - /* l10n: Pinch Mode refers to a particular boss mode that gets - activated once the boss has lost the specified amounts of lives. + + /* l10n: Pinch Mode refers to a particular boss mode that gets + activated once the boss has lost the specified amounts of lives. This setting specifies how many lives need to be spent until pinch mode is activated. */ result.add_int(_("Lives to Pinch Mode"), &m_pinch_lives, "pinch-lives", DEFAULT_PINCH_LIVES); - /* l10n: Pinch Mode refers to a particular boss mode that gets - activated once the boss has lost the specified amounts of lives. + /* l10n: Pinch Mode refers to a particular boss mode that gets + activated once the boss has lost the specified amounts of lives. This setting specifies the squirrel script that gets run to activate boss mode. */ result.add_script(_("Pinch Mode Activation Script"), &m_pinch_activation_script, "pinch-activation-script"); diff --git a/src/badguy/boss.hpp b/src/badguy/boss.hpp index 2a891de106f..b8f306f3bc4 100644 --- a/src/badguy/boss.hpp +++ b/src/badguy/boss.hpp @@ -37,6 +37,7 @@ class Boss : public BadGuy protected: int m_lives; + int m_max_lives; int m_pinch_lives; SurfacePtr m_hud_head; std::string m_hud_icon; diff --git a/src/badguy/snowman.cpp b/src/badguy/snowman.cpp index 196e855bbc4..4542b847f5a 100644 --- a/src/badguy/snowman.cpp +++ b/src/badguy/snowman.cpp @@ -54,7 +54,7 @@ Snowman::loose_head() HitResponse Snowman::collision_bullet(Bullet& bullet, const CollisionHit& hit) { - if (bullet.get_type() == FIRE_BONUS) { + if (bullet.get_type() == BONUS_FIRE) { // Fire bullets destroy snowman's body. Vector snowball_pos = get_pos(); // Hard-coded values from sprites. diff --git a/src/badguy/stalactite.cpp b/src/badguy/stalactite.cpp index 80c94ab98cd..bd6e78bfab8 100644 --- a/src/badguy/stalactite.cpp +++ b/src/badguy/stalactite.cpp @@ -147,7 +147,7 @@ Stalactite::collision_bullet(Bullet& bullet, const CollisionHit& hit) timer.start(SHAKE_TIME); state = STALACTITE_SHAKING; bullet.remove_me(); - if (bullet.get_type() == FIRE_BONUS) + if (bullet.get_type() == BONUS_FIRE) SoundManager::current()->play("sounds/sizzle.ogg", get_pos()); SoundManager::current()->play("sounds/cracking.wav", get_pos()); } diff --git a/src/control/controller.cpp b/src/control/controller.cpp index f96bbfc1524..5b23863eac7 100644 --- a/src/control/controller.cpp +++ b/src/control/controller.cpp @@ -27,6 +27,7 @@ const char* g_control_names[] = { "down", "jump", "action", + "item", "start", "escape", "menu-select", diff --git a/src/control/controller.hpp b/src/control/controller.hpp index bfbe03e2891..37b285e6c41 100644 --- a/src/control/controller.hpp +++ b/src/control/controller.hpp @@ -29,6 +29,7 @@ enum class Control { JUMP, ACTION, + ITEM, START, ESCAPE, diff --git a/src/control/game_controller_manager.cpp b/src/control/game_controller_manager.cpp index a15eb5661de..b3b1f90eecf 100644 --- a/src/control/game_controller_manager.cpp +++ b/src/control/game_controller_manager.cpp @@ -24,7 +24,6 @@ #include "supertux/globals.hpp" #include "supertux/game_session.hpp" #include "supertux/savegame.hpp" -#include "supertux/sector.hpp" #include "util/log.hpp" GameControllerManager::GameControllerManager(InputManager* parent) : @@ -84,7 +83,7 @@ GameControllerManager::process_button_event(const SDL_ControllerButtonEvent& ev) break; case SDL_CONTROLLER_BUTTON_BACK: - set_control(Control::CONSOLE, ev.state); + set_control(Control::ITEM, ev.state); break; case SDL_CONTROLLER_BUTTON_GUIDE: @@ -205,6 +204,9 @@ GameControllerManager::process_axis_event(const SDL_ControllerAxisEvent& ev) void GameControllerManager::on_controller_added(int joystick_index) { + if (!m_parent->can_add_user()) + return; + if (!SDL_IsGameController(joystick_index)) { log_warning << "joystick is not a game controller, ignoring: " << joystick_index << std::endl; @@ -240,16 +242,7 @@ GameControllerManager::on_controller_added(int joystick_index) if (GameSession::current() && !GameSession::current()->get_savegame().is_title_screen() && id != 0) { - auto& sector = GameSession::current()->get_current_sector(); - auto& player_status = GameSession::current()->get_savegame().get_player_status(); - - if (player_status.m_num_players <= id) - player_status.add_player(); - - // ID = 0 is impossible, so no need to write `(id == 0) ? "" : ...` - auto& player = sector.add(player_status, "Tux" + std::to_string(id + 1), id); - - player.multiplayer_prepare_spawn(); + GameSession::current()->on_player_added(id); } } } @@ -272,22 +265,10 @@ GameControllerManager::on_controller_removed(int instance_id) m_game_controllers.erase(it); if (m_parent->m_use_game_controller && g_config->multiplayer_auto_manage_players - && deleted_player_id != 0 && !m_parent->m_uses_keyboard[deleted_player_id]) + && deleted_player_id != 0 && !m_parent->m_uses_keyboard[deleted_player_id] && + GameSession::current()) { - // Sectors in worldmaps have no Player's of that class. - if (Sector::current() && Sector::current()->get_object_count() > 0) - { - auto players = Sector::current()->get_objects_by_type(); - auto it_players = players.begin(); - - while (it_players != players.end()) - { - if (it_players->get_id() == deleted_player_id) - it_players->remove_me(); - - it_players++; - } - } + GameSession::current()->on_player_removed(deleted_player_id); } } else diff --git a/src/control/input_manager.cpp b/src/control/input_manager.cpp index 14c4c9ae603..3825bfe397b 100644 --- a/src/control/input_manager.cpp +++ b/src/control/input_manager.cpp @@ -23,6 +23,8 @@ #include "control/keyboard_manager.hpp" #include "util/log.hpp" +static constexpr int MAX_PLAYERS = 4; + InputManager::InputManager(KeyboardConfig& keyboard_config, JoystickConfig& joystick_config) : m_controllers(), @@ -51,6 +53,12 @@ InputManager::get_controller(int player_id) return *m_controllers[player_id]; } +bool +InputManager::can_add_user() const +{ + return get_num_users() < MAX_PLAYERS; +} + void InputManager::use_game_controller(bool v) { @@ -139,6 +147,9 @@ InputManager::process_event(const SDL_Event& event) void InputManager::push_user() { + if (!can_add_user()) + return; + m_controllers.push_back(std::make_unique()); } diff --git a/src/control/input_manager.hpp b/src/control/input_manager.hpp index 029b37f5e5e..00f986f785e 100644 --- a/src/control/input_manager.hpp +++ b/src/control/input_manager.hpp @@ -59,7 +59,7 @@ class InputManager final : public Currenton Controller& get_controller(int player_id = 0); int get_num_users() const { return static_cast(m_controllers.size()); } - + bool can_add_user() const; void push_user(); void pop_user(); diff --git a/src/control/joystick_config.cpp b/src/control/joystick_config.cpp index 10a360a1180..7f638e09362 100644 --- a/src/control/joystick_config.cpp +++ b/src/control/joystick_config.cpp @@ -38,6 +38,7 @@ JoystickConfig::JoystickConfig() : bind_joybutton(0, 4, Control::PEEK_LEFT); bind_joybutton(0, 5, Control::PEEK_RIGHT); bind_joybutton(0, 6, Control::START); + bind_joybutton(0, 7, Control::ITEM); // Default joystick axis configuration bind_joyaxis(0, -1, Control::LEFT); diff --git a/src/control/joystick_manager.cpp b/src/control/joystick_manager.cpp index 7fd5ed800c0..37b8945331d 100644 --- a/src/control/joystick_manager.cpp +++ b/src/control/joystick_manager.cpp @@ -26,7 +26,6 @@ #include "supertux/globals.hpp" #include "supertux/game_session.hpp" #include "supertux/savegame.hpp" -#include "supertux/sector.hpp" #include "util/log.hpp" JoystickManager::JoystickManager(InputManager* parent_, @@ -55,6 +54,10 @@ void JoystickManager::on_joystick_added(int joystick_index) { log_debug << "on_joystick_added(): " << joystick_index << std::endl; + + if (!parent->can_add_user()) + return; + SDL_Joystick* joystick = SDL_JoystickOpen(joystick_index); if (!joystick) { @@ -96,16 +99,7 @@ JoystickManager::on_joystick_added(int joystick_index) if (GameSession::current() && !GameSession::current()->get_savegame().is_title_screen() && id != 0) { - auto& sector = GameSession::current()->get_current_sector(); - auto& player_status = GameSession::current()->get_savegame().get_player_status(); - - if (player_status.m_num_players <= id) - player_status.add_player(); - - // ID = 0 is impossible, so no need to write `(id == 0) ? "" : ...` - auto& player = sector.add(player_status, "Tux" + std::to_string(id + 1), id); - - player.multiplayer_prepare_spawn(); + GameSession::current()->on_player_added(id); } } } @@ -129,22 +123,10 @@ JoystickManager::on_joystick_removed(int instance_id) joysticks.erase(it); if (!parent->m_use_game_controller && g_config->multiplayer_auto_manage_players - && deleted_player_id != 0 && !parent->m_uses_keyboard[deleted_player_id]) + && deleted_player_id != 0 && !parent->m_uses_keyboard[deleted_player_id] && + GameSession::current()) { - // Sectors in worldmaps have no Player's of that class - if (Sector::current() && Sector::current()->get_object_count() > 0) - { - auto players = Sector::current()->get_objects_by_type(); - auto it_players = players.begin(); - - while (it_players != players.end()) - { - if (it_players->get_id() == deleted_player_id) - it_players->remove_me(); - - it_players++; - } - } + GameSession::current()->on_player_removed(deleted_player_id); } } else diff --git a/src/control/keyboard_config.cpp b/src/control/keyboard_config.cpp index 1ff7f5d762b..da46af73df4 100644 --- a/src/control/keyboard_config.cpp +++ b/src/control/keyboard_config.cpp @@ -34,6 +34,7 @@ KeyboardConfig::KeyboardConfig() : Control::RIGHT, Control::JUMP, Control::ACTION, + Control::ITEM, Control::PEEK_LEFT, Control::PEEK_RIGHT, Control::PEEK_UP, @@ -51,6 +52,7 @@ KeyboardConfig::KeyboardConfig() : m_keymap[SDLK_DOWN] = {0, Control::DOWN}; m_keymap[SDLK_SPACE] = {0, Control::JUMP}; m_keymap[SDLK_LCTRL] = {0, Control::ACTION}; + m_keymap[SDLK_LSHIFT] = {0, Control::ITEM}; m_keymap[SDLK_ESCAPE] = {0, Control::ESCAPE}; m_keymap[SDLK_p] = {0, Control::START}; m_keymap[SDLK_PAUSE] = {0, Control::START}; @@ -64,20 +66,6 @@ KeyboardConfig::KeyboardConfig() : m_keymap[SDLK_F1] = {0, Control::CHEAT_MENU}; m_keymap[SDLK_F2] = {0, Control::DEBUG_MENU}; m_keymap[SDLK_BACKSPACE] = {0, Control::REMOVE}; - - m_keymap[SDLK_a] = {1, Control::LEFT}; - m_keymap[SDLK_d] = {1, Control::RIGHT}; - m_keymap[SDLK_w] = {1, Control::UP}; - m_keymap[SDLK_s] = {1, Control::DOWN}; - m_keymap[SDLK_e] = {1, Control::JUMP}; - m_keymap[SDLK_q] = {1, Control::ACTION}; - - m_keymap[SDLK_j] = {2, Control::LEFT}; - m_keymap[SDLK_l] = {2, Control::RIGHT}; - m_keymap[SDLK_i] = {2, Control::UP}; - m_keymap[SDLK_k] = {2, Control::DOWN}; - m_keymap[SDLK_o] = {2, Control::JUMP}; - m_keymap[SDLK_u] = {2, Control::ACTION}; } void diff --git a/src/object/bonus_block.cpp b/src/object/bonus_block.cpp index 1a9dcf48fb1..6b8834572df 100644 --- a/src/object/bonus_block.cpp +++ b/src/object/bonus_block.cpp @@ -356,31 +356,31 @@ BonusBlock::try_open(Player* player) case Content::FIREGROW: { - raise_growup_bonus(player, FIRE_BONUS, direction); + raise_growup_bonus(player, BONUS_FIRE, direction); break; } case Content::ICEGROW: { - raise_growup_bonus(player, ICE_BONUS, direction); + raise_growup_bonus(player, BONUS_ICE, direction); break; } case Content::AIRGROW: { - raise_growup_bonus(player, AIR_BONUS, direction); + raise_growup_bonus(player, BONUS_AIR, direction); break; } case Content::EARTHGROW: { - raise_growup_bonus(player, EARTH_BONUS, direction); + raise_growup_bonus(player, BONUS_EARTH, direction); break; } case Content::RETROGROW: { - raise_growup_bonus(player, FIRE_BONUS, direction, + raise_growup_bonus(player, BONUS_FIRE, direction, "images/powerups/retro/mints.png", "images/powerups/retro/coffee.png"); break; } @@ -649,7 +649,7 @@ BonusBlock::raise_growup_bonus(Player* player, const BonusType& bonus, const Dir const std::string& growup_sprite, const std::string& flower_sprite) { std::unique_ptr obj; - if (player->get_status().bonus[player->get_id()] == NO_BONUS) + if (player->get_status().bonus[player->get_id()] == BONUS_NONE) { obj = std::make_unique(get_pos(), dir, growup_sprite); } @@ -666,7 +666,7 @@ void BonusBlock::drop_growup_bonus(Player* player, int type, const Direction& dir, bool& countdown, const std::string& growup_sprite) { - if (player->get_status().bonus[player->get_id()] == NO_BONUS) + if (player->get_status().bonus[player->get_id()] == BONUS_NONE) { Sector::get().add(get_pos() + Vector(0, 32), dir, growup_sprite); } diff --git a/src/object/bullet.cpp b/src/object/bullet.cpp index 18c7b65e9db..64d495242ec 100644 --- a/src/object/bullet.cpp +++ b/src/object/bullet.cpp @@ -36,13 +36,13 @@ Bullet::Bullet(const Vector& pos, const Vector& xm, Direction dir, BonusType typ physic.set_velocity(xm); switch (type) { - case FIRE_BONUS: + case BONUS_FIRE: sprite = SpriteManager::current()->create("images/objects/bullets/firebullet.sprite"); lightsprite->set_blend(Blend::ADD); lightsprite->set_color(Color(0.3f, 0.1f, 0.0f)); break; - case ICE_BONUS: + case BONUS_ICE: sprite = SpriteManager::current()->create("images/objects/bullets/icebullet.sprite"); break; @@ -94,7 +94,7 @@ void Bullet::draw(DrawingContext& context) { sprite->draw(context.color(), get_pos(), LAYER_OBJECTS); - if (type == FIRE_BONUS){ + if (type == BONUS_FIRE){ lightsprite->draw(context.light(), m_col.m_bbox.get_middle(), 0); } } @@ -106,7 +106,7 @@ Bullet::collision_solid(const CollisionHit& hit) physic.set_velocity_y(-physic.get_velocity_y()); life_count--; } else if (hit.left || hit.right) { - if (type == ICE_BONUS) { + if (type == BONUS_ICE) { physic.set_velocity_x(-physic.get_velocity_x()); life_count--; } else diff --git a/src/object/flower.cpp b/src/object/flower.cpp index a3f35c4fc0f..c34212766f1 100644 --- a/src/object/flower.cpp +++ b/src/object/flower.cpp @@ -31,22 +31,22 @@ Flower::Flower(BonusType _type, const std::string& custom_sprite) : m_col.m_bbox.set_size(32, 32); lightsprite->set_blend(Blend::ADD); - if (type == FIRE_BONUS) { + if (type == BONUS_FIRE) { sprite = SpriteManager::current()->create(custom_sprite.empty() ? "images/powerups/fireflower/fireflower.sprite" : custom_sprite); SoundManager::current()->preload("sounds/fire-flower.wav"); lightsprite->set_color(Color(0.3f, 0.0f, 0.0f)); } - else if (type == ICE_BONUS) { + else if (type == BONUS_ICE) { sprite = SpriteManager::current()->create(custom_sprite.empty() ? "images/powerups/iceflower/iceflower.sprite" : custom_sprite); SoundManager::current()->preload("sounds/fire-flower.wav"); lightsprite->set_color(Color(0.0f, 0.1f, 0.2f)); } - else if (type == AIR_BONUS) { + else if (type == BONUS_AIR) { sprite = SpriteManager::current()->create(custom_sprite.empty() ? "images/powerups/airflower/airflower.sprite" : custom_sprite); SoundManager::current()->preload("sounds/fire-flower.wav"); lightsprite->set_color(Color(0.15f, 0.0f, 0.15f)); } - else if (type == EARTH_BONUS) { + else if (type == BONUS_EARTH) { sprite = SpriteManager::current()->create(custom_sprite.empty() ? "images/powerups/earthflower/earthflower.sprite" : custom_sprite); SoundManager::current()->preload("sounds/fire-flower.wav"); lightsprite->set_color(Color(0.0f, 0.3f, 0.0f)); diff --git a/src/object/growup.cpp b/src/object/growup.cpp index 1fda040df81..297089dfaf3 100644 --- a/src/object/growup.cpp +++ b/src/object/growup.cpp @@ -84,7 +84,7 @@ GrowUp::collision(GameObject& other, const CollisionHit& hit ) { auto player = dynamic_cast(&other); if (player != nullptr) { - if (!player->add_bonus(GROWUP_BONUS, true)) { + if (!player->add_bonus(BONUS_GROWUP, true)) { // Tux can't grow right now. collision_solid( hit ); return ABORT_MOVE; diff --git a/src/object/moving_sprite.hpp b/src/object/moving_sprite.hpp index e6e045953e6..87050496008 100644 --- a/src/object/moving_sprite.hpp +++ b/src/object/moving_sprite.hpp @@ -64,6 +64,7 @@ class MovingSprite : public MovingObject virtual void on_type_change(int old_type) override; int get_layer() const override { return m_layer; } + void set_layer(int layer) { m_layer = layer; } bool has_found_sprite() const { return m_sprite_found; } const std::string& get_sprite_name() const { return m_sprite_name; } diff --git a/src/object/player.cpp b/src/object/player.cpp index 1203d5fbec5..132f359050d 100644 --- a/src/object/player.cpp +++ b/src/object/player.cpp @@ -151,6 +151,9 @@ const std::array BUBBLE_ACTIONS = { "normal", "small" }; const float BUTTJUMP_WAIT_TIME = 0.2f; // the length of time that the buttjump action is being played const float BUTTJUMP_SPEED = 800.f; +const int MAX_FIRE_BULLETS = 2; +const int MAX_ICE_BULLETS = 2; + } // namespace Player::Player(PlayerStatus& player_status, const std::string& name_, int player_id) : @@ -628,7 +631,7 @@ Player::update(float dt_sec) if (m_dying && m_dying_timer.check()) { - set_bonus(NO_BONUS, true); + set_bonus(BONUS_NONE, true); m_dead = true; if (!Sector::get().get_object_count([](const Player& p) { return p.is_alive(); })) @@ -1265,9 +1268,9 @@ Player::handle_horizontal_input() ax = dirsign * RUN_ACCELERATION_X; } // limit speed - if (vx >= MAX_RUN_XM + BONUS_RUN_XM *((get_bonus() == AIR_BONUS) ? 1 : 0)) { + if (vx >= MAX_RUN_XM + BONUS_RUN_XM *((get_bonus() == BONUS_AIR) ? 1 : 0)) { ax = std::min(ax, -OVERSPEED_DECELERATION); - } else if (vx <= -MAX_RUN_XM - BONUS_RUN_XM * ((get_bonus() == AIR_BONUS) ? 1 : 0)) { + } else if (vx <= -MAX_RUN_XM - BONUS_RUN_XM * ((get_bonus() == BONUS_AIR) ? 1 : 0)) { ax = std::max(ax, OVERSPEED_DECELERATION); } } @@ -1412,7 +1415,7 @@ Player::do_backflip() { m_backflip_direction = (m_dir == Direction::LEFT)?(+1):(-1); m_backflipping = true; - do_jump((get_bonus() == AIR_BONUS) ? -720.0f : -580.0f); + do_jump((get_bonus() == BONUS_AIR) ? -720.0f : -580.0f); SoundManager::current()->play("sounds/flip.wav", get_pos()); m_backflip_timer.start(TUX_BACKFLIP_TIME); } @@ -1490,7 +1493,7 @@ Player::handle_vertical_input() } else { // airflower allows for higher jumps- // jump a bit higher if we are running; else do a normal jump - if (get_bonus() == AIR_BONUS) + if (get_bonus() == BONUS_AIR) do_jump((fabsf(m_physic.get_velocity_x()) > MAX_WALK_XM) ? -620.0f : -580.0f); else do_jump((fabsf(m_physic.get_velocity_x()) > MAX_WALK_XM) ? -580.0f : -520.0f); @@ -1499,7 +1502,7 @@ Player::handle_vertical_input() m_coyote_timer.stop(); // airflower glide only when holding jump key } - else if (m_controller->hold(Control::JUMP) && get_bonus() == AIR_BONUS && m_physic.get_velocity_y() > MAX_GLIDE_YM) { + else if (m_controller->hold(Control::JUMP) && get_bonus() == BONUS_AIR && m_physic.get_velocity_y() > MAX_GLIDE_YM) { // glide stops if buttjump is initiated if (!m_controller->hold(Control::DOWN)) { @@ -1555,7 +1558,7 @@ Player::handle_vertical_input() if (m_controller->pressed(Control::JUMP) && m_can_walljump && !m_backflipping) { SoundManager::current()->play((is_big()) ? "sounds/bigjump.wav" : "sounds/jump.wav", get_pos()); - m_physic.set_velocity_x(get_bonus() == AIR_BONUS ? + m_physic.set_velocity_x(get_bonus() == BONUS_AIR ? m_on_left_wall ? 480.f : -480.f : m_on_left_wall ? 380.f : -380.f); do_jump(-520.f); } @@ -1647,11 +1650,9 @@ Player::handle_input() /* Shoot! */ auto active_bullets = Sector::get().get_object_count([this](const Bullet& b){ return &b.get_player() == this; }); - if (m_controller->pressed(Control::ACTION) && (get_bonus() == FIRE_BONUS || get_bonus() == ICE_BONUS) && !just_grabbed) { - if ((get_bonus() == FIRE_BONUS && - active_bullets < m_player_status.max_fire_bullets[get_id()]) || - (get_bonus() == ICE_BONUS && - active_bullets < m_player_status.max_ice_bullets[get_id()])) + if (m_controller->pressed(Control::ACTION) && (get_bonus() == BONUS_FIRE || get_bonus() == BONUS_ICE) && !just_grabbed) { + if ((get_bonus() == BONUS_FIRE && active_bullets < MAX_FIRE_BULLETS) || + (get_bonus() == BONUS_ICE && active_bullets < MAX_ICE_BULLETS)) { Vector pos = get_pos() + Vector(m_col.m_bbox.get_width() / 2.f, m_col.m_bbox.get_height() / 2.f); Direction swim_dir; @@ -1670,7 +1671,7 @@ Player::handle_input() } /* Turn to Stone */ - if (m_controller->hold(Control::DOWN) && !m_does_buttjump && m_coyote_timer.started() && !m_swimming && (std::abs(m_physic.get_velocity_x()) > 150.f) && get_bonus() == EARTH_BONUS && !m_growing) { + if (m_controller->hold(Control::DOWN) && !m_does_buttjump && m_coyote_timer.started() && !m_swimming && (std::abs(m_physic.get_velocity_x()) > 150.f) && get_bonus() == BONUS_EARTH && !m_growing) { m_physic.set_gravity_modifier(1.0f); // Undo jump_early_apex adjust_height(TUX_WIDTH); m_stone = true; @@ -1779,7 +1780,7 @@ Player::handle_input() else if (!m_sliding && (m_coyote_timer.started()) && !m_skidding_timer.started() && (m_floor_normal.y != 0 || (m_controller->hold(Control::LEFT) || m_controller->hold(Control::RIGHT))) && m_controller->pressed(Control::DOWN) && std::abs(m_physic.get_velocity_x()) > 1.f && - get_bonus()!= EARTH_BONUS) + get_bonus()!= BONUS_EARTH) { sideways_push(m_dir == Direction::LEFT ? -100.f : 100.f); adjust_height(DUCKED_TUX_HEIGHT); @@ -1908,19 +1909,20 @@ Player::get_coins() const } BonusType -Player::string_to_bonus(const std::string& bonus) const { +Player::string_to_bonus(const std::string& bonus) const +{ if (bonus == "grow") { - return GROWUP_BONUS; + return BONUS_GROWUP; } else if (bonus == "fireflower") { - return FIRE_BONUS; + return BONUS_FIRE; } else if (bonus == "iceflower") { - return ICE_BONUS; + return BONUS_ICE; } else if (bonus == "airflower") { - return AIR_BONUS; + return BONUS_AIR; } else if (bonus == "earthflower") { - return EARTH_BONUS; + return BONUS_EARTH; } else if (bonus == "none") { - return NO_BONUS; + return BONUS_NONE; } else { std::ostringstream msg; msg << "Unknown bonus type " << bonus; @@ -1933,15 +1935,15 @@ Player::bonus_to_string() const { switch(get_bonus()) { - case GROWUP_BONUS: + case BONUS_GROWUP: return "grow"; - case FIRE_BONUS: + case BONUS_FIRE: return "fireflower"; - case ICE_BONUS: + case BONUS_ICE: return "iceflower"; - case AIR_BONUS: + case BONUS_AIR: return "airflower"; - case EARTH_BONUS: + case BONUS_EARTH: return "earthflower"; default: return "none"; @@ -1964,13 +1966,13 @@ bool Player::add_bonus(BonusType type, bool animate) { // always ignore NO_BONUS - if (type == NO_BONUS) { + if (type == BONUS_NONE) { return true; } // ignore GROWUP_BONUS if we're already big - if (type == GROWUP_BONUS) { - if (get_bonus() != NO_BONUS) + if (type == BONUS_GROWUP) { + if (get_bonus() != BONUS_NONE) return true; } @@ -1978,13 +1980,13 @@ Player::add_bonus(BonusType type, bool animate) } bool -Player::set_bonus(BonusType type, bool animate, bool increment_powerup_counter) +Player::set_bonus(BonusType type, bool animate) { if (m_dying) { return false; } - if ((get_bonus() == NO_BONUS) && (type != NO_BONUS || m_stone)) { + if ((get_bonus() == BONUS_NONE) && (type != BONUS_NONE || m_stone)) { if (!m_swimming && !m_sliding) { if (!adjust_height(BIG_TUX_HEIGHT)) @@ -2006,7 +2008,7 @@ Player::set_bonus(BonusType type, bool animate, bool increment_powerup_counter) } } - if (type == NO_BONUS) { + if (type == BONUS_NONE) { if (!adjust_height(SMALL_TUX_HEIGHT)) { log_debug << "Can't adjust Tux height" << std::endl; return false; @@ -2014,25 +2016,14 @@ Player::set_bonus(BonusType type, bool animate, bool increment_powerup_counter) if (m_does_buttjump) m_does_buttjump = false; } - if ((type == NO_BONUS) || (type == GROWUP_BONUS)) { - m_player_status.max_fire_bullets[get_id()] = 0; - m_player_status.max_ice_bullets[get_id()] = 0; - m_player_status.max_air_time[get_id()] = 0; - m_player_status.max_earth_time[get_id()] = 0; - } - - if (increment_powerup_counter) + if (type > BONUS_GROWUP) { - if (type == FIRE_BONUS) m_player_status.max_fire_bullets[get_id()]++; - if (type == ICE_BONUS) m_player_status.max_ice_bullets[get_id()]++; - if (type == AIR_BONUS) m_player_status.max_air_time[get_id()]++; - if (type == EARTH_BONUS) m_player_status.max_earth_time[get_id()]++; - } + m_player_status.add_item_to_pocket(get_bonus(), this); - if (!m_second_growup_sound_timer.started() && - type > GROWUP_BONUS && type != get_bonus()) - { - m_second_growup_sound_timer.start(0.5); + if (!m_second_growup_sound_timer.started() && type != get_bonus()) + { + m_second_growup_sound_timer.start(0.5); + } } m_player_status.bonus[get_id()] = type; @@ -2109,15 +2100,15 @@ Player::draw(DrawingContext& context) std::string sa_prefix = ""; std::string sa_postfix = ""; - if (get_bonus() == GROWUP_BONUS) + if (get_bonus() == BONUS_GROWUP) sa_prefix = "big"; - else if (get_bonus() == FIRE_BONUS) + else if (get_bonus() == BONUS_FIRE) sa_prefix = "fire"; - else if (get_bonus() == ICE_BONUS) + else if (get_bonus() == BONUS_ICE) sa_prefix = "ice"; - else if (get_bonus() == AIR_BONUS) + else if (get_bonus() == BONUS_AIR) sa_prefix = "air"; - else if (get_bonus() == EARTH_BONUS) + else if (get_bonus() == BONUS_EARTH) sa_prefix = "earth"; else sa_prefix = "small"; @@ -2324,10 +2315,10 @@ Player::draw(DrawingContext& context) m_sprite->draw(context.color(), draw_pos, LAYER_OBJECTS + 1); //TODO: Replace recoloring with proper costumes - Color power_color = (get_bonus() == FIRE_BONUS ? Color(1.f, 0.7f, 0.5f) : - get_bonus() == ICE_BONUS ? Color(0.7f, 1.f, 1.f) : - get_bonus() == AIR_BONUS ? Color(0.7f, 1.f, 0.5f) : - get_bonus() == EARTH_BONUS ? Color(1.f, 0.9f, 0.6f) : + Color power_color = (get_bonus() == BONUS_FIRE ? Color(1.f, 0.7f, 0.5f) : + get_bonus() == BONUS_ICE ? Color(0.7f, 1.f, 1.f) : + get_bonus() == BONUS_AIR ? Color(0.7f, 1.f, 0.5f) : + get_bonus() == BONUS_EARTH ? Color(1.f, 0.9f, 0.6f) : Color(1.f, 1.f, 1.f)); for (auto& bubble_sprite : m_active_bubbles) @@ -2515,20 +2506,20 @@ Player::kill(bool completely) if (!completely && is_big()) { SoundManager::current()->play("sounds/hurt.wav", get_pos()); - if (get_bonus() > GROWUP_BONUS) + if (get_bonus() > BONUS_GROWUP) { m_safe_timer.start(TUX_SAFE_TIME); m_is_intentionally_safe = false; - set_bonus(GROWUP_BONUS, true); + set_bonus(BONUS_GROWUP, true); } - else if (get_bonus() == GROWUP_BONUS) + else if (get_bonus() == BONUS_GROWUP) { m_safe_timer.start(TUX_SAFE_TIME /* + GROWING_TIME */); m_is_intentionally_safe = false; m_duck = false; m_crawl = false; stop_backflipping(); - set_bonus(NO_BONUS, true); + set_bonus(BONUS_NONE, true); } } else { SoundManager::current()->play("sounds/kill.wav", get_pos()); @@ -2547,7 +2538,7 @@ Player::kill(bool completely) m_invincible_timer.stop(); m_physic.set_acceleration(0, 0); m_physic.set_velocity(0, -700); - set_bonus(NO_BONUS, true); + set_bonus(BONUS_NONE, true); m_dying = true; m_dying_timer.start(3.0); set_group(COLGROUP_DISABLED); @@ -2689,7 +2680,7 @@ Player::set_velocity(float x, float y) void Player::bounce(BadGuy& ) { - if (!(get_bonus() == AIR_BONUS)) + if (!(get_bonus() == BONUS_AIR)) m_physic.set_velocity_y(m_controller->hold(Control::JUMP) ? -520.0f : -300.0f); else { m_physic.set_velocity_y(m_controller->hold(Control::JUMP) ? -580.0f : -340.0f); @@ -2811,7 +2802,7 @@ Player::stop_climbing(Climbable& /*climbable*/) if (m_controller->hold(Control::JUMP) && !m_controller->hold(Control::DOWN)) { m_on_ground_flag = true; m_jump_early_apex = false; - do_jump(get_bonus() == AIR_BONUS ? -540.0f : -480.0f); + do_jump(get_bonus() == BONUS_AIR ? -540.0f : -480.0f); } else if (m_controller->hold(Control::UP)) { m_on_ground_flag = true; @@ -2866,7 +2857,7 @@ Player::handle_input_rolling() if (!m_controller->hold(Control::DOWN)) { stop_rolling(false); } - else if (get_bonus() != EARTH_BONUS) { + else if (get_bonus() != BONUS_EARTH) { stop_rolling(); } } @@ -2951,6 +2942,24 @@ Player::stop_backflipping() //m_santahatsprite->set_angle(0.0f); } +void +Player::eject_item_pocket() +{ + m_player_status.give_item_from_pocket(this); +} + +int +Player::get_item_pocket() const +{ + return m_player_status.get_item_pocket(this); +} + +void +Player::set_item_pocket(int bonus) +{ + m_player_status.add_item_to_pocket(static_cast(bonus), this); +} + bool Player::has_grabbed(const std::string& object_name) const { @@ -3187,6 +3196,9 @@ Player::register_class(ssq::VM& vm) cls.addFunc("get_input_pressed", &Player::get_input_pressed); cls.addFunc("get_input_held", &Player::get_input_held); cls.addFunc("get_input_released", &Player::get_input_released); + cls.addFunc("eject_item_pocket", &Player::eject_item_pocket); + cls.addFunc("get_item_pocket", &Player::get_item_pocket); + cls.addFunc("set_item_pocket", &Player::set_item_pocket); cls.addVar("visible", &Player::m_visible); } diff --git a/src/object/player.hpp b/src/object/player.hpp index 0c834ee2367..0b791c30ce0 100644 --- a/src/object/player.hpp +++ b/src/object/player.hpp @@ -139,11 +139,11 @@ class Player final : public MovingObject bool is_dying() const { return m_dying; } /** - * Returns true if the player is currently alive + * Returns true if the player is currently alive * (not dying or dead) */ bool is_alive() const { return !is_dying() && !is_dead(); } - + /** * Returns true if the player can be controlled. * (alive and not currently in a win sequence) @@ -204,7 +204,7 @@ class Player final : public MovingObject bool add_bonus(BonusType type, bool animate = false); /** like add_bonus, but can also downgrade the bonus items carried */ - bool set_bonus(BonusType type, bool animate = false, bool increment_powerup_counter = true); + bool set_bonus(BonusType type, bool animate = false); BonusType get_bonus() const; std::string bonus_to_string() const; @@ -290,7 +290,7 @@ class Player final : public MovingObject void override_velocity() { m_velocity_override = true; } bool is_dead() const { return m_dead; } - bool is_big() const { return get_bonus() != NO_BONUS; } + bool is_big() const { return get_bonus() != BONUS_NONE; } bool is_stone() const { return m_stone; } bool is_sliding() const { return m_sliding; } bool is_swimming() const { return m_swimming; } @@ -380,7 +380,7 @@ class Player final : public MovingObject /** * @scripting * @description Gets whether the current input on the keyboard/controller/touchpad has been pressed. - * @param string $input Can be “left”, “right”, “up”, “down”, “jump”, “action”, “start”, “escape”, + * @param string $input Can be “left”, “right”, “up”, “down”, “jump”, “action”, "item", “start”, “escape”, “menu-select”, “menu-select-space”, “menu-back”, “remove”, “cheat-menu”, “debug-menu”, “console”, “peek-left”, “peek-right”, “peek-up” or “peek-down”. */ @@ -412,6 +412,24 @@ class Player final : public MovingObject void set_dir(bool right); void stop_backflipping(); + /** + * @scripting + * @description Ejects the item in the player's Item Pocket. + */ + void eject_item_pocket(); + + /** + * @scripting + * @description Returns the item currently in the player's Item Pocket as a ""BONUS"" enum value. + */ + int get_item_pocket() const; + + /** + * @scripting + * @description Ejects the item in the player's Item Pocket. + */ + void set_item_pocket(int bonus); + void position_grabbed_object(bool teleport = false); bool try_grab(); diff --git a/src/object/powerup.cpp b/src/object/powerup.cpp index 0211972bdf6..9eea696961d 100644 --- a/src/object/powerup.cpp +++ b/src/object/powerup.cpp @@ -205,28 +205,28 @@ PowerUp::collision(GameObject& other, const CollisionHit&) { case EGG: case MINTS: - if (!player->add_bonus(GROWUP_BONUS, true)) + if (!player->add_bonus(BONUS_GROWUP, true)) return FORCE_MOVE; SoundManager::current()->play("sounds/grow.ogg", get_pos()); break; case FIRE: case COFFEE: - if (!player->add_bonus(FIRE_BONUS, true)) + if (!player->add_bonus(BONUS_FIRE, true)) return FORCE_MOVE; SoundManager::current()->play("sounds/fire-flower.wav", get_pos()); break; case ICE: - if (!player->add_bonus(ICE_BONUS, true)) + if (!player->add_bonus(BONUS_ICE, true)) return FORCE_MOVE; SoundManager::current()->play("sounds/fire-flower.wav", get_pos()); break; case AIR: - if (!player->add_bonus(AIR_BONUS, true)) + if (!player->add_bonus(BONUS_AIR, true)) return FORCE_MOVE; SoundManager::current()->play("sounds/fire-flower.wav", get_pos()); break; case EARTH: - if (!player->add_bonus(EARTH_BONUS, true)) + if (!player->add_bonus(BONUS_EARTH, true)) return FORCE_MOVE; SoundManager::current()->play("sounds/fire-flower.wav", get_pos()); break; @@ -247,6 +247,31 @@ PowerUp::collision(GameObject& other, const CollisionHit&) return ABORT_MOVE; } +PowerUp::Type +PowerUp::get_type_from_bonustype(int type) +{ + switch (type) + { + case BONUS_GROWUP: + return EGG; + + case BONUS_FIRE: + return FIRE; + + case BONUS_ICE: + return ICE; + + case BONUS_AIR: + return AIR; + + case BONUS_EARTH: + return EARTH; + + default: + return FIRE; + } +} + void PowerUp::update(float dt_sec) { diff --git a/src/object/powerup.hpp b/src/object/powerup.hpp index b9ad6d2fd58..e236d85586b 100644 --- a/src/object/powerup.hpp +++ b/src/object/powerup.hpp @@ -20,8 +20,23 @@ #include "object/moving_sprite.hpp" #include "supertux/physic.hpp" -class PowerUp final : public MovingSprite +class PowerUp : public MovingSprite { +public: + enum Type { + EGG, + FIRE, + ICE, + AIR, + EARTH, + STAR, + ONEUP, + FLIP, + MINTS, + COFFEE, + HERRING + }; + public: PowerUp(const ReaderMapping& mapping); PowerUp(const Vector& pos, int type); @@ -41,31 +56,18 @@ class PowerUp final : public MovingSprite virtual std::string get_display_name() const override { return display_name(); } virtual GameObjectClasses get_class_types() const override { return MovingSprite::get_class_types().add(typeid(PowerUp)); } + static Type get_type_from_bonustype(int type); + std::vector get_patches() const override; virtual ObjectSettings get_settings() override; virtual void after_editor_set() override; -private: +protected: /** Initialize power up sprites and other defaults */ void initialize(); void setup_lightsprite(); -public: - enum Type { - EGG, - FIRE, - ICE, - AIR, - EARTH, - STAR, - ONEUP, - FLIP, - MINTS, - COFFEE, - HERRING - }; - -private: +protected: Physic physic; std::string script; bool no_physics; diff --git a/src/object/trampoline.cpp b/src/object/trampoline.cpp index 8485918414f..048f0358df9 100644 --- a/src/object/trampoline.cpp +++ b/src/object/trampoline.cpp @@ -108,7 +108,7 @@ Trampoline::collision(GameObject& other, const CollisionHit& hit) //player is falling down on trampoline if (hit.top && vy >= 0) { - if (!(player->get_status().bonus[player->get_id()] == AIR_BONUS)) + if (!(player->get_status().bonus[player->get_id()] == BONUS_AIR)) { if (player->get_controller().hold(Control::JUMP)) vy = VY_MIN; diff --git a/src/object/weak_block.cpp b/src/object/weak_block.cpp index 266ce21b550..00132f612e2 100644 --- a/src/object/weak_block.cpp +++ b/src/object/weak_block.cpp @@ -118,7 +118,7 @@ WeakBlock::collision_bullet(Bullet& bullet, const CollisionHit& hit) case STATE_NORMAL: //Ensure only fire destroys weakblock - if (bullet.get_type() == FIRE_BONUS) { + if (bullet.get_type() == BONUS_FIRE) { startBurning(); bullet.remove_me(); } diff --git a/src/squirrel/supertux_api.cpp b/src/squirrel/supertux_api.cpp index 13379b92d17..f1e266ae90e 100644 --- a/src/squirrel/supertux_api.cpp +++ b/src/squirrel/supertux_api.cpp @@ -787,6 +787,38 @@ static void resume_target_timer() GameSession::current()->set_target_timer_paused(false); } +/** + * @scripting + * @description Override the Item Pocket setting in the Level. + * @param string $allow Can be "on", "off", or "inherit" (use the setting in the Level). + */ +static void override_item_pocket(const std::string& allow) +{ + if (!GameSession::current()) return; + GameSession::current()->get_savegame().get_player_status().m_override_item_pocket = ::Level::get_setting_from_name(allow); +} + +/** + * @scripting + * @description Get the override value for the Item Pocket. + * Can be "on", "off", or "inherit" (use the setting in the Level). + */ +static std::string is_item_pocket_overridden() +{ + if (!GameSession::current()) return "off"; + return ::Level::get_setting_name(GameSession::current()->get_savegame().get_player_status().m_override_item_pocket); +} + +/** + * @scripting + * @description Check if the Item Pocket is allowed in this level. + */ +static bool is_item_pocket_allowed() +{ + if (!GameSession::current()) return false; + return GameSession::current()->get_savegame().get_player_status().is_item_pocket_allowed(); +} + } // namespace Level } // namespace scripting @@ -813,6 +845,13 @@ void register_supertux_scripting_api(ssq::VM& vm) vm.setConst("ANCHOR_BOTTOM", AnchorPoint::ANCHOR_BOTTOM); vm.setConst("ANCHOR_BOTTOM_RIGHT", AnchorPoint::ANCHOR_BOTTOM_RIGHT); + vm.setConst("BONUS_NONE", BonusType::BONUS_NONE); + vm.setConst("BONUS_GROWUP", BonusType::BONUS_GROWUP); + vm.setConst("BONUS_FIRE", BonusType::BONUS_FIRE); + vm.setConst("BONUS_AIR", BonusType::BONUS_AIR); + vm.setConst("BONUS_EARTH", BonusType::BONUS_EARTH); + vm.setConst("BONUS_ICE", BonusType::BONUS_ICE); + /* Global functions */ vm.addFunc("display", &scripting::Globals::display); vm.addFunc("print_stacktrace", &scripting::Globals::print_stacktrace); @@ -872,6 +911,9 @@ void register_supertux_scripting_api(ssq::VM& vm) level.addFunc("toggle_pause", &scripting::Level::toggle_pause); level.addFunc("pause_target_timer", &scripting::Level::pause_target_timer); level.addFunc("resume_target_timer", &scripting::Level::resume_target_timer); + level.addFunc("override_item_pocket", &scripting::Level::override_item_pocket); + level.addFunc("is_item_pocket_overridden", &scripting::Level::is_item_pocket_overridden); + level.addFunc("is_item_pocket_allowed", &scripting::Level::is_item_pocket_allowed); } /* EOF */ diff --git a/src/supertux/controller_hud.cpp b/src/supertux/controller_hud.cpp index 40c78e97464..edaceffab5f 100644 --- a/src/supertux/controller_hud.cpp +++ b/src/supertux/controller_hud.cpp @@ -42,8 +42,10 @@ ControllerHUD::ControllerHUD() : m_controls[Control::ACTION] = Rectf::from_center(btn_pos + Vector(-20.0f, 0.0f), btn_size); m_controls[Control::JUMP] = Rectf::from_center(btn_pos + Vector(0.0f, 20.0f), btn_size); - m_controls[Control::START] = Rectf::from_center(pos + Vector(16.0f, 24.0f), Sizef(16, 8)); - m_controls[Control::ESCAPE] = Rectf::from_center(pos + Vector(-16.0f, 24.0f), Sizef(16, 8)); + m_controls[Control::ITEM] = Rectf::from_center(pos + Vector(-24.f, 24.0f), Sizef(16, 8)); + m_controls[Control::ESCAPE] = Rectf::from_center(pos + Vector(0.0f, 24.0f), Sizef(16, 8)); + m_controls[Control::START] = Rectf::from_center(pos + Vector(24.0f, 24.0f), Sizef(16, 8)); + } void diff --git a/src/supertux/game_session.cpp b/src/supertux/game_session.cpp index bd79212dcdc..242d474c4cf 100644 --- a/src/supertux/game_session.cpp +++ b/src/supertux/game_session.cpp @@ -91,9 +91,7 @@ GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Stat { set_start_point(DEFAULT_SECTOR_NAME, DEFAULT_SPAWNPOINT_NAME); - m_boni_at_start.resize(InputManager::current()->get_num_users(), NO_BONUS); - m_max_fire_bullets_at_start.resize(InputManager::current()->get_num_users(), 0); - m_max_ice_bullets_at_start.resize(InputManager::current()->get_num_users(), 0); + m_boni_at_start.resize(InputManager::current()->get_num_users(), BONUS_NONE); m_data_table.clear(); @@ -118,8 +116,6 @@ GameSession::reset_level() PlayerStatus& currentStatus = m_savegame.get_player_status(); currentStatus.coins = m_coins_at_start; currentStatus.bonus = m_boni_at_start; - currentStatus.max_fire_bullets = m_max_fire_bullets_at_start; - currentStatus.max_ice_bullets = m_max_ice_bullets_at_start; clear_respawn_points(); m_activated_checkpoint = nullptr; @@ -129,13 +125,41 @@ GameSession::reset_level() m_data_table.clear(); } +void +GameSession::on_player_added(int id) +{ + if (m_savegame.get_player_status().m_num_players <= id) + m_savegame.get_player_status().add_player(); + + // ID = 0 is impossible, so no need to write `(id == 0) ? "" : ...` + auto& player = m_currentsector->add(m_savegame.get_player_status(), "Tux" + std::to_string(id + 1), id); + + player.multiplayer_prepare_spawn(); +} + +bool +GameSession::on_player_removed(int id) +{ + // Sectors in worldmaps have no Player's of that class + if (!m_currentsector) + return false; + + for (Player* player : m_currentsector->get_players()) + { + if (player->get_id() == id) + { + player->remove_me(); + return true; + } + } + return false; +} + int GameSession::restart_level(bool after_death, bool preserve_music) { const PlayerStatus& currentStatus = m_savegame.get_player_status(); m_coins_at_start = currentStatus.coins; - m_max_fire_bullets_at_start = currentStatus.max_fire_bullets; - m_max_ice_bullets_at_start = currentStatus.max_ice_bullets; m_boni_at_start = currentStatus.bonus; // Needed for the title screen apparently. @@ -145,7 +169,7 @@ GameSession::restart_level(bool after_death, bool preserve_music) { for (const auto& p : m_currentsector->get_players()) { - p->set_bonus(m_boni_at_start.at(p->get_id()), false, false); + p->set_bonus(m_boni_at_start.at(p->get_id()), false); m_boni_at_start[p->get_id()] = currentStatus.bonus[p->get_id()]; } } @@ -390,8 +414,6 @@ GameSession::abort_level() PlayerStatus& currentStatus = m_savegame.get_player_status(); currentStatus.coins = m_coins_at_start; - currentStatus.max_fire_bullets = m_max_fire_bullets_at_start; - currentStatus.max_ice_bullets = m_max_ice_bullets_at_start; SoundManager::current()->stop_sounds(); } @@ -476,7 +498,6 @@ GameSession::setup() ScreenManager::current()->set_screen_fade(std::make_unique(shrinkpos, TELEPORT_FADE_TIME, SHRINKFADE_LAYER, ShrinkFade::FADEIN)); } - m_end_seq_started = false; } @@ -604,7 +625,17 @@ GameSession::update(float dt_sec, const Controller& controller) m_play_time += dt_sec; m_level->m_stats.finish(m_play_time); } + + for (Player* player : m_currentsector->get_players()) + { + if (player->get_controller().pressed(Control::ITEM) && m_savegame.get_player_status().m_item_pockets.size() > 0) + { + player->get_status().give_item_from_pocket(player); + } + } + m_currentsector->update(dt_sec); + } else { bool are_all_stopped = true; diff --git a/src/supertux/game_session.hpp b/src/supertux/game_session.hpp index f8d60e42a6d..6068b9b1580 100644 --- a/src/supertux/game_session.hpp +++ b/src/supertux/game_session.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -96,6 +97,9 @@ class GameSession final : public Screen, const bool make_invincible = false); void reset_level(); + void on_player_added(int id); + bool on_player_removed(int id); + void set_start_point(const std::string& sectorname, const std::string& spawnpointname); void set_start_pos(const std::string& sectorname, const Vector& pos); diff --git a/src/supertux/level.cpp b/src/supertux/level.cpp index 038339f0158..01334e04d2f 100644 --- a/src/supertux/level.cpp +++ b/src/supertux/level.cpp @@ -37,7 +37,6 @@ #include "util/writer.hpp" static PlayerStatus s_dummy_player_status(1); - Level* Level::s_current = nullptr; Level::Level(bool worldmap) : @@ -52,6 +51,7 @@ Level::Level(bool worldmap) : m_stats(), m_target_time(), m_tileset("images/tiles.strf"), + m_allow_item_pocket(ON), m_suppress_pause_menu(), m_is_in_cutscene(false), m_skip_cutscene(false), @@ -60,6 +60,11 @@ Level::Level(bool worldmap) : m_wmselect_bkg() { s_current = this; + + if (!is_worldmap()) + { + m_allow_item_pocket = INHERIT; + } } Level::~Level() @@ -75,11 +80,11 @@ Level::initialize(const Statistics::Preferences& stat_preferences) m_stats.init(*this, stat_preferences); - Savegame* savegame = (GameSession::current() && !Editor::current() ? + Savegame* savegame = (GameSession::current() && !Editor::is_active() ? &GameSession::current()->get_savegame() : nullptr); PlayerStatus& player_status = savegame ? savegame->get_player_status() : s_dummy_player_status; - if (savegame && !m_suppress_pause_menu && !savegame->is_title_screen()) + if (Editor::current() || (savegame && !savegame->is_title_screen() && !m_suppress_pause_menu)) { for (auto& sector : m_sectors) sector->add(player_status); @@ -189,6 +194,8 @@ Level::save(Writer& writer) writer.write("suppress-pause-menu", m_suppress_pause_menu); } + writer.write("allow-item-pocket", get_setting_name(static_cast(m_allow_item_pocket))); + writer.write("icon", m_icon); writer.write("icon-locked", m_icon_locked); @@ -213,6 +220,43 @@ Level::save(Writer& writer) writer.end_list("supertux-level"); } +std::string +Level::get_setting_name(Setting setting) +{ + switch (setting) + { + case ON: + return "on"; + + case OFF: + return "off"; + + case INHERIT: + return "inherit"; + } + + return "on"; +} + +Level::Setting +Level::get_setting_from_name(std::string setting) +{ + if (setting == "on") + { + return ON; + } + else if (setting == "off") + { + return OFF; + } + else if (setting == "inherit") + { + return INHERIT; + } + + return ON; +} + void Level::add_sector(std::unique_ptr sector) { diff --git a/src/supertux/level.hpp b/src/supertux/level.hpp index 358e069aa02..36d78a308e6 100644 --- a/src/supertux/level.hpp +++ b/src/supertux/level.hpp @@ -20,6 +20,7 @@ #include "supertux/statistics.hpp" class Player; +class PlayerStatus; class ReaderMapping; class Sector; class Writer; @@ -76,17 +77,36 @@ class Level final void load_old_format(const ReaderMapping& reader); public: + enum Setting + { + OFF, + ON, + + /// Inherit setting from worldmap. + /// If level is in a levelset, this defaults to ON. + INHERIT + }; + static std::string get_setting_name(Setting setting); + static Setting get_setting_from_name(std::string setting); + bool m_is_worldmap; + std::string m_name; std::string m_author; std::string m_contact; std::string m_license; std::string m_filename; std::string m_note; + std::vector > m_sectors; + Statistics m_stats; float m_target_time; + std::string m_tileset; + + int m_allow_item_pocket; ///< This is actually a Level::Setting. It's an int because casting is wack. + bool m_suppress_pause_menu; bool m_is_in_cutscene; bool m_skip_cutscene; diff --git a/src/supertux/level_parser.cpp b/src/supertux/level_parser.cpp index 63584112e5f..c0ac8abb9c1 100644 --- a/src/supertux/level_parser.cpp +++ b/src/supertux/level_parser.cpp @@ -177,6 +177,10 @@ LevelParser::load(const ReaderDocument& doc) level.get("icon-locked", m_level.m_icon_locked); level.get("bkg", m_level.m_wmselect_bkg); + std::string name = m_level.m_is_worldmap ? "on" : "inherit"; + level.get("allow-item-pocket", name); + m_level.m_allow_item_pocket = Level::get_setting_from_name(name); + std::optional level_stat_preferences; if (level.get("statistics", level_stat_preferences)) stat_preferences.parse(*level_stat_preferences); diff --git a/src/supertux/levelintro.cpp b/src/supertux/levelintro.cpp index 0ee7d377adb..a674122c3e9 100644 --- a/src/supertux/levelintro.cpp +++ b/src/supertux/levelintro.cpp @@ -84,7 +84,7 @@ LevelIntro::update(float dt_sec, const Controller& controller) continue; auto bonus_prefix = m_player_status.get_bonus_prefix(i); - if (m_player_status.bonus[i] == FIRE_BONUS && g_config->christmas_mode) + if (m_player_status.bonus[i] == BONUS_FIRE && g_config->christmas_mode) { bonus_prefix = "big"; } @@ -152,10 +152,10 @@ LevelIntro::draw(Compositor& compositor) m_player_sprite[i]->draw(context.color(), Vector((context.get_width() - m_player_sprite[i]->get_current_hitbox_width()) / 2 - offset, static_cast(py) + m_player_sprite_py[i] - m_player_sprite[i]->get_current_hitbox_height()), LAYER_FOREGROUND1); - Color power_color = (m_player_status.bonus[i] == FIRE_BONUS ? Color(1.f, 0.7f, 0.5f) : - m_player_status.bonus[i] == ICE_BONUS ? Color(0.7f, 1.f, 1.f) : - m_player_status.bonus[i] == AIR_BONUS ? Color(0.7f, 1.f, 0.5f) : - m_player_status.bonus[i] == EARTH_BONUS ? Color(1.f, 0.9f, 0.6f) : + Color power_color = (m_player_status.bonus[i] == BONUS_FIRE ? Color(1.f, 0.7f, 0.5f) : + m_player_status.bonus[i] == BONUS_ICE ? Color(0.7f, 1.f, 1.f) : + m_player_status.bonus[i] == BONUS_AIR ? Color(0.7f, 1.f, 0.5f) : + m_player_status.bonus[i] == BONUS_EARTH ? Color(1.f, 0.9f, 0.6f) : Color(1.f, 1.f, 1.f)); m_player_sprite[i]->set_color(power_color); @@ -241,7 +241,7 @@ LevelIntro::push_player() m_player_sprite_jump_timer.push_back(std::make_unique()); //Show appropriate tux animation for player status. - if (m_player_status.bonus[i] == FIRE_BONUS && g_config->christmas_mode) + if (m_player_status.bonus[i] == BONUS_FIRE && g_config->christmas_mode) { m_player_sprite[i]->set_action("big-walk-right"); m_santa_sprite[i]->set_action("default"); diff --git a/src/supertux/menu/cheat_apply_menu.cpp b/src/supertux/menu/cheat_apply_menu.cpp index 242612ab071..f07e6e1afd0 100644 --- a/src/supertux/menu/cheat_apply_menu.cpp +++ b/src/supertux/menu/cheat_apply_menu.cpp @@ -25,9 +25,7 @@ #include "supertux/sector.hpp" CheatApplyMenu::CheatApplyMenu(std::function callback) : - m_callback_1(callback), - m_callback_2(nullptr), - m_stack_count(-1) + m_callback(callback) { add_label(_("Apply cheat to player")); add_hl(); @@ -42,26 +40,6 @@ CheatApplyMenu::CheatApplyMenu(std::function callback) : add_back(_("Back")); } -CheatApplyMenu::CheatApplyMenu(std::function callback) : - m_callback_1(nullptr), - m_callback_2(callback), - m_stack_count(1) -{ - add_label(_("Apply cheat to player")); - add_hl(); - - add_intfield(_("Count"), &m_stack_count, -2); - add_hl(); - add_entry(-1, _("All Players")); - for (const auto player : Sector::get().get_players()) - { - add_entry(player->get_id(), fmt::format(_("Player {}"), player->get_id() + 1)); - } - - add_hl(); - add_back(_("Back")); -} - void CheatApplyMenu::menu_action(MenuItem& item) { @@ -72,13 +50,9 @@ CheatApplyMenu::menu_action(MenuItem& item) for (const auto& player : Sector::get().get_players()) { - if (id == -1 || id == player->get_id()) + if ((id == -1 || id == player->get_id()) && m_callback) { - if (m_callback_2) - m_callback_2(*player, m_stack_count); - - if (m_callback_1) - m_callback_1(*player); + m_callback(*player); } } diff --git a/src/supertux/menu/cheat_apply_menu.hpp b/src/supertux/menu/cheat_apply_menu.hpp index 560eb3217ef..86e6078b971 100644 --- a/src/supertux/menu/cheat_apply_menu.hpp +++ b/src/supertux/menu/cheat_apply_menu.hpp @@ -25,15 +25,11 @@ class CheatApplyMenu final : public Menu { public: CheatApplyMenu(std::function callback); - /** Use this for cheats that need a stack count, e. g. giving fire flowers */ - CheatApplyMenu(std::function callback); void menu_action(MenuItem& item) override; private: - std::function m_callback_1; - std::function m_callback_2; - int m_stack_count; + std::function m_callback; private: CheatApplyMenu(const CheatApplyMenu&) = delete; diff --git a/src/supertux/menu/cheat_menu.cpp b/src/supertux/menu/cheat_menu.cpp index 871efec94e4..7feebe9e0a2 100644 --- a/src/supertux/menu/cheat_menu.cpp +++ b/src/supertux/menu/cheat_menu.cpp @@ -70,42 +70,38 @@ CheatMenu::menu_action(MenuItem& item) case MNID_GROW: if (single_player) { - single_player->set_bonus(GROWUP_BONUS); + single_player->set_bonus(BONUS_GROWUP); MenuManager::instance().clear_menu_stack(); } else { MenuManager::instance().push_menu(std::make_unique([](Player& player){ - player.set_bonus(GROWUP_BONUS); + player.set_bonus(BONUS_GROWUP); })); } break; case MNID_FIRE: - MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - player.set_bonus(FIRE_BONUS); - player.get_status().max_fire_bullets[player.get_id()] = count; + MenuManager::instance().push_menu(std::make_unique([](Player& player){ + player.set_bonus(BONUS_FIRE); })); break; case MNID_ICE: - MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - player.set_bonus(ICE_BONUS); - player.get_status().max_ice_bullets[player.get_id()] = count; + MenuManager::instance().push_menu(std::make_unique([](Player& player){ + player.set_bonus(BONUS_ICE); })); break; case MNID_AIR: - MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - player.set_bonus(AIR_BONUS); - player.get_status().max_air_time[player.get_id()] = count; + MenuManager::instance().push_menu(std::make_unique([](Player& player){ + player.set_bonus(BONUS_AIR); })); break; case MNID_EARTH: - MenuManager::instance().push_menu(std::make_unique([](Player& player, int count){ - player.set_bonus(EARTH_BONUS); - player.get_status().max_earth_time[player.get_id()] = count; + MenuManager::instance().push_menu(std::make_unique([](Player& player){ + player.set_bonus(BONUS_EARTH); })); break; diff --git a/src/supertux/menu/editor_level_menu.cpp b/src/supertux/menu/editor_level_menu.cpp index 48e12400739..b4728d29b46 100644 --- a/src/supertux/menu/editor_level_menu.cpp +++ b/src/supertux/menu/editor_level_menu.cpp @@ -37,6 +37,13 @@ EditorLevelMenu::EditorLevelMenu() : add_textfield(_("Level Note"), &(level->m_note)); add_file(_("Tileset"), &(level->m_tileset), std::vector(1, ".strf"), {}, true); + std::vector choices = {_("No"), _("Yes")}; + if (!is_worldmap) + { + choices.push_back(_("Inherit")); + } + add_string_select(-1, _("Allow Item Pocket"), &(level->m_allow_item_pocket), choices); + if (!is_worldmap) { add_floatfield(_("Target Time"), &(level->m_target_time)); diff --git a/src/supertux/menu/joystick_menu.cpp b/src/supertux/menu/joystick_menu.cpp index a3cb64d8e4b..c0ce30628ca 100644 --- a/src/supertux/menu/joystick_menu.cpp +++ b/src/supertux/menu/joystick_menu.cpp @@ -74,6 +74,7 @@ JoystickMenu::recreate_menu() add_controlfield(static_cast(Control::RIGHT), _("Right")); add_controlfield(static_cast(Control::JUMP), _("Jump")); add_controlfield(static_cast(Control::ACTION), _("Action")); + add_controlfield(static_cast(Control::ITEM), _("Item Pocket")); add_controlfield(static_cast(Control::START), _("Pause/Menu")); add_controlfield(static_cast(Control::PEEK_LEFT), _("Peek Left")); add_controlfield(static_cast(Control::PEEK_RIGHT), _("Peek Right")); @@ -228,6 +229,7 @@ JoystickMenu::refresh() refresh_menu_item(Control::JUMP); refresh_menu_item(Control::ACTION); + refresh_menu_item(Control::ITEM); refresh_menu_item(Control::START); refresh_menu_item(Control::PEEK_LEFT); refresh_menu_item(Control::PEEK_RIGHT); diff --git a/src/supertux/menu/keyboard_menu.cpp b/src/supertux/menu/keyboard_menu.cpp index 892e16e7a41..ac7212ff947 100644 --- a/src/supertux/menu/keyboard_menu.cpp +++ b/src/supertux/menu/keyboard_menu.cpp @@ -39,6 +39,7 @@ KeyboardMenu::KeyboardMenu(InputManager& input_manager, int player_id) : add_controlfield(static_cast(Control::RIGHT), _("Right")); add_controlfield(static_cast(Control::JUMP), _("Jump")); add_controlfield(static_cast(Control::ACTION), _("Action")); + add_controlfield(static_cast(Control::ITEM), _("Item Pocket")); add_controlfield(static_cast(Control::PEEK_LEFT), _("Peek Left")); add_controlfield(static_cast(Control::PEEK_RIGHT), _("Peek Right")); @@ -127,9 +128,9 @@ KeyboardMenu::menu_action(MenuItem& item) void KeyboardMenu::refresh() { - const auto& controls = { Control::UP, Control::DOWN, Control::LEFT, Control::RIGHT, - Control::JUMP, Control::ACTION, - Control::PEEK_LEFT, Control::PEEK_RIGHT, + const auto& controls = { Control::UP, Control::DOWN, Control::LEFT, Control::RIGHT, + Control::JUMP, Control::ACTION, Control::ITEM, + Control::PEEK_LEFT, Control::PEEK_RIGHT, Control::PEEK_UP, Control::PEEK_DOWN }; const auto& developer_controls = { Control::CHEAT_MENU, Control::DEBUG_MENU, Control::CONSOLE }; diff --git a/src/supertux/menu/multiplayer_player_menu.cpp b/src/supertux/menu/multiplayer_player_menu.cpp index e4fb62d4bd3..cd3b6035457 100644 --- a/src/supertux/menu/multiplayer_player_menu.cpp +++ b/src/supertux/menu/multiplayer_player_menu.cpp @@ -23,12 +23,12 @@ #include "control/input_manager.hpp" #include "control/joystick_manager.hpp" #include "gui/dialog.hpp" -#include "supertux/gameconfig.hpp" +#include "object/player.hpp" #include "supertux/game_session.hpp" +#include "supertux/gameconfig.hpp" #include "supertux/globals.hpp" #include "supertux/savegame.hpp" #include "supertux/sector.hpp" -#include "object/player.hpp" #include "util/gettext.hpp" #include "util/log.hpp" @@ -65,18 +65,12 @@ MultiplayerPlayerMenu::MultiplayerPlayerMenu(int player_id) return; } - for (auto* player : GameSession::current()->get_current_sector().get_players()) + if (!GameSession::current()->on_player_removed(player_id)) { - if (player->get_id() == player_id) - { - player->remove_me(); - return; - } + log_warning << "Could not find player with ID " << player_id + << " (number " << (player_id + 1) << "in sector" + << std::endl; } - - log_warning << "Could not find player with ID " << player_id - << " (number " << (player_id + 1) << "in sector" - << std::endl; }); add_entry(_("Respawn Player"), [player_id] { @@ -115,19 +109,7 @@ MultiplayerPlayerMenu::MultiplayerPlayerMenu(int player_id) return; } - auto& sector = GameSession::current()->get_current_sector(); - auto& player_status = GameSession::current()->get_savegame().get_player_status(); - - // TODO: This is probably needed because adding/removing users manually - // or automatically by plugging in controllers might not always fix the - // player_status object; check if that statement is correct - if (player_status.m_num_players <= player_id) - player_status.add_player(); - - // ID = 0 is impossible, so no need to write `(id == 0) ? "" : ...` - auto& player = sector.add(player_status, "Tux" + std::to_string(player_id + 1), player_id); - - player.multiplayer_prepare_spawn(); + GameSession::current()->on_player_added(player_id); }); } } diff --git a/src/supertux/menu/multiplayer_players_menu.cpp b/src/supertux/menu/multiplayer_players_menu.cpp index fd7ffe50d50..787a52b78af 100644 --- a/src/supertux/menu/multiplayer_players_menu.cpp +++ b/src/supertux/menu/multiplayer_players_menu.cpp @@ -43,16 +43,19 @@ MultiplayerPlayersMenu::MultiplayerPlayersMenu() add_hl(); - add_entry(_("Add Player"), [] { - InputManager::current()->push_user(); + if (InputManager::current()->can_add_user()) + { + add_entry(_("Add Player"), [] { + InputManager::current()->push_user(); - if (GameSession::current() && GameSession::current()->get_savegame().get_player_status().m_num_players < InputManager::current()->get_num_users()) - { - GameSession::current()->get_savegame().get_player_status().add_player(); - } + if (GameSession::current() && GameSession::current()->get_savegame().get_player_status().m_num_players < InputManager::current()->get_num_users()) + { + GameSession::current()->get_savegame().get_player_status().add_player(); + } - MenuManager::instance().set_menu(std::make_unique()); - }); + MenuManager::instance().set_menu(std::make_unique()); + }); + } if (InputManager::current()->get_num_users() > 1) { diff --git a/src/supertux/menu/worldmap_cheat_apply_menu.cpp b/src/supertux/menu/worldmap_cheat_apply_menu.cpp index 4a5c80040cb..01da6d274b1 100644 --- a/src/supertux/menu/worldmap_cheat_apply_menu.cpp +++ b/src/supertux/menu/worldmap_cheat_apply_menu.cpp @@ -25,11 +25,9 @@ #include "supertux/sector.hpp" WorldmapCheatApplyMenu::WorldmapCheatApplyMenu(int num_players, - std::function callback) : + std::function callback) : m_num_players(num_players), - m_callback_1(callback), - m_callback_2(nullptr), - m_stack_count(-1) + m_callback(callback) { add_label(_("Apply cheat to player")); add_hl(); @@ -41,27 +39,6 @@ WorldmapCheatApplyMenu::WorldmapCheatApplyMenu(int num_players, add_back(_("Back")); } -WorldmapCheatApplyMenu::WorldmapCheatApplyMenu(int num_players, - std::function callback) : - m_num_players(num_players), - m_callback_1(nullptr), - m_callback_2(callback), - m_stack_count(1) -{ - add_label(_("Apply cheat to player")); - add_hl(); - - add_intfield("Count", &m_stack_count, -2); - add_hl(); - add_entry(-1, _("All Players")); - - for (int i = 0; i < m_num_players; i++) - add_entry(i, fmt::format(_("Player {}"), i + 1)); - - add_hl(); - add_back(_("Back")); -} - void WorldmapCheatApplyMenu::menu_action(MenuItem& item) { @@ -74,20 +51,14 @@ WorldmapCheatApplyMenu::menu_action(MenuItem& item) { for (int i = 0; i < m_num_players; i++) { - if (m_callback_2) - m_callback_2(i, m_stack_count); - - if (m_callback_1) - m_callback_1(i); + if (m_callback) + m_callback(i); } } else { - if (m_callback_2) - m_callback_2(id, m_stack_count); - - if (m_callback_1) - m_callback_1(id); + if (m_callback) + m_callback(id); } MenuManager::instance().clear_menu_stack(); diff --git a/src/supertux/menu/worldmap_cheat_apply_menu.hpp b/src/supertux/menu/worldmap_cheat_apply_menu.hpp index 1fdb724125a..bee28a24830 100644 --- a/src/supertux/menu/worldmap_cheat_apply_menu.hpp +++ b/src/supertux/menu/worldmap_cheat_apply_menu.hpp @@ -25,16 +25,12 @@ class WorldmapCheatApplyMenu final : public Menu { public: WorldmapCheatApplyMenu(int num_players, std::function callback); - /** Use this for cheats that need a stack count, e. g. giving fire flowers */ - WorldmapCheatApplyMenu(int num_players, std::function callback); void menu_action(MenuItem& item) override; private: int m_num_players; - std::function m_callback_1; - std::function m_callback_2; - int m_stack_count; + std::function m_callback; private: WorldmapCheatApplyMenu(const WorldmapCheatApplyMenu&) = delete; diff --git a/src/supertux/menu/worldmap_cheat_menu.cpp b/src/supertux/menu/worldmap_cheat_menu.cpp index 7a061373873..14747efe3d4 100644 --- a/src/supertux/menu/worldmap_cheat_menu.cpp +++ b/src/supertux/menu/worldmap_cheat_menu.cpp @@ -71,41 +71,37 @@ WorldmapCheatMenu::menu_action(MenuItem& item) { case MNID_GROW: do_cheat(status, [&status](int player) { - status.bonus[player] = GROWUP_BONUS; + status.bonus[player] = BONUS_GROWUP; }); break; case MNID_FIRE: - do_cheat(status, [&status](int player, int count) { - status.bonus[player] = FIRE_BONUS; - status.max_fire_bullets[player] = count; + do_cheat(status, [&status](int player) { + status.bonus[player] = BONUS_FIRE; }); break; case MNID_ICE: - do_cheat(status, [&status](int player, int count) { - status.bonus[player] = ICE_BONUS; - status.max_ice_bullets[player] = count; + do_cheat(status, [&status](int player) { + status.bonus[player] = BONUS_ICE; }); break; case MNID_AIR: - do_cheat(status, [&status](int player, int count) { - status.bonus[player] = AIR_BONUS; - status.max_air_time[player] = count; + do_cheat(status, [&status](int player) { + status.bonus[player] = BONUS_AIR; }); break; case MNID_EARTH: - do_cheat(status, [&status](int player, int count) { - status.bonus[player] = EARTH_BONUS; - status.max_earth_time[player] = count; + do_cheat(status, [&status](int player) { + status.bonus[player] = BONUS_EARTH; }); break; case MNID_SHRINK: do_cheat(status, [&status](int player) { - status.bonus[player] = NO_BONUS; + status.bonus[player] = BONUS_NONE; }); break; @@ -174,13 +170,6 @@ WorldmapCheatMenu::do_cheat(PlayerStatus& status, } } -void -WorldmapCheatMenu::do_cheat(PlayerStatus& status, - std::function callback) -{ - MenuManager::instance().push_menu(std::make_unique(status.m_num_players, callback)); -} - WorldmapLevelSelectMenu::WorldmapLevelSelectMenu() { auto worldmap_sector = worldmap::WorldMapSector::current(); diff --git a/src/supertux/menu/worldmap_cheat_menu.hpp b/src/supertux/menu/worldmap_cheat_menu.hpp index 2f32e2ade98..5c82bfa2341 100644 --- a/src/supertux/menu/worldmap_cheat_menu.hpp +++ b/src/supertux/menu/worldmap_cheat_menu.hpp @@ -49,7 +49,6 @@ class WorldmapCheatMenu final : public Menu private: void do_cheat(PlayerStatus& status, std::function callback); - void do_cheat(PlayerStatus& status, std::function callback); private: WorldmapCheatMenu(const WorldmapCheatMenu&) = delete; diff --git a/src/supertux/player_status.cpp b/src/supertux/player_status.cpp index 5b44f9b4345..2cc9c95b854 100644 --- a/src/supertux/player_status.cpp +++ b/src/supertux/player_status.cpp @@ -20,23 +20,25 @@ #include #include "audio/sound_manager.hpp" +#include "object/player.hpp" #include "supertux/globals.hpp" #include "supertux/game_session.hpp" +#include "supertux/sector.hpp" #include "util/log.hpp" #include "util/reader_mapping.hpp" #include "util/writer.hpp" +#include "worldmap/level_tile.hpp" +#include "worldmap/worldmap.hpp" static const int START_COINS = 100; static const int MAX_COINS = 9999; PlayerStatus::PlayerStatus(int num_players) : m_num_players(num_players), + m_item_pockets(num_players), + m_override_item_pocket(Level::INHERIT), coins(START_COINS), bonus(num_players), - max_fire_bullets(num_players), - max_ice_bullets(num_players), - max_air_time(num_players), - max_earth_time(num_players), worldmap_sprite("images/worldmap/common/tux.sprite"), last_worldmap(), title_level() @@ -60,21 +62,16 @@ PlayerStatus::take_checkpoint_coins() coins = 0; } -void PlayerStatus::reset(int num_players) +void +PlayerStatus::reset(int num_players) { coins = START_COINS; // Keep in sync with a section in read() bonus.clear(); - bonus.resize(num_players, NO_BONUS); - max_fire_bullets.clear(); - max_fire_bullets.resize(num_players, 0); - max_ice_bullets.clear(); - max_ice_bullets.resize(num_players, 0); - max_air_time.clear(); - max_air_time.resize(num_players, 0); - max_earth_time.clear(); - max_earth_time.resize(num_players, 0); + bonus.resize(num_players, BONUS_NONE); + m_item_pockets.clear(); + m_item_pockets.resize(num_players, BONUS_NONE); m_num_players = num_players; } @@ -98,6 +95,68 @@ PlayerStatus::respawns_at_checkpoint() const GameSession::current()->reset_checkpoint_button; } +std::string +PlayerStatus::get_bonus_name(BonusType bonustype) +{ + switch (bonustype) { + case BONUS_FIRE: + return "fireflower"; + case BONUS_ICE: + return "iceflower"; + case BONUS_AIR: + return "airflower"; + case BONUS_EARTH: + return "earthflower"; + case BONUS_GROWUP: + return "egg"; + case BONUS_NONE: + return "none"; + default: + log_warning << "Unknown bonus " << static_cast(bonustype) << std::endl; + return "none"; + } +} + +BonusType +PlayerStatus::get_bonus_from_name(const std::string& name) +{ + if (name == "none") { + return BONUS_NONE; + } else if (name == "growup") { + return BONUS_GROWUP; + } else if (name == "fireflower") { + return BONUS_FIRE; + } else if (name == "iceflower") { + return BONUS_ICE; + } else if (name == "airflower") { + return BONUS_AIR; + } else if (name == "earthflower") { + return BONUS_EARTH; + } else { + log_warning << "Unknown bonus '" << name << "' in savefile"<< std::endl; + return BONUS_NONE; + } +} + +std::string +PlayerStatus::get_bonus_sprite(BonusType bonustype) +{ + switch (bonustype) { + case BONUS_FIRE: + return "images/powerups/fireflower/fireflower.sprite"; + case BONUS_ICE: + return "images/powerups/iceflower/iceflower.sprite"; + case BONUS_AIR: + return "images/powerups/airflower/airflower.sprite"; + case BONUS_EARTH: + return "images/powerups/earthflower/earthflower.sprite"; + case BONUS_GROWUP: + return "images/powerups/egg/egg.sprite"; + default: + return ""; + } +} + void PlayerStatus::add_coins(int count, bool play_sound) { @@ -127,34 +186,8 @@ PlayerStatus::write(Writer& writer) writer.start_list("tux" + std::to_string(i + 1)); } - switch (bonus[i]) { - case NO_BONUS: - writer.write("bonus", "none"); - break; - case GROWUP_BONUS: - writer.write("bonus", "growup"); - break; - case FIRE_BONUS: - writer.write("bonus", "fireflower"); - break; - case ICE_BONUS: - writer.write("bonus", "iceflower"); - break; - case AIR_BONUS: - writer.write("bonus", "airflower"); - break; - case EARTH_BONUS: - writer.write("bonus", "earthflower"); - break; - default: - log_warning << "Unknown bonus type." << std::endl; - writer.write("bonus", "none"); - } - - writer.write("fireflowers", max_fire_bullets[i]); - writer.write("iceflowers", max_ice_bullets[i]); - writer.write("airflowers", max_air_time[i]); - writer.write("earthflowers", max_earth_time[i]); + writer.write("bonus", get_bonus_name(bonus[i])); + writer.write("item-pocket", get_bonus_name(m_item_pockets[i])); if (i != 0) { @@ -193,19 +226,10 @@ PlayerStatus::read(const ReaderMapping& mapping) // Keep this in sync with reset() if (bonus.size() < static_cast(id)) - bonus.resize(id, NO_BONUS); - - if (max_fire_bullets.size() < static_cast(id)) - max_fire_bullets.resize(id, 0); - - if (max_ice_bullets.size() < static_cast(id)) - max_ice_bullets.resize(id, 0); + bonus.resize(id, BONUS_NONE); - if (max_air_time.size() < static_cast(id)) - max_air_time.resize(id, 0); - - if (max_earth_time.size() < static_cast(id)) - max_earth_time.resize(id, 0); + if (m_item_pockets.size() < static_cast(id)) + m_item_pockets.resize(id, BONUS_NONE); } else if (id == 0) { @@ -233,32 +257,96 @@ PlayerStatus::read(const ReaderMapping& mapping) mapping.get("title-level", title_level); } +void +PlayerStatus::give_item_from_pocket(Player* player) +{ + if (!is_item_pocket_allowed()) + return; + + BonusType bonustype = m_item_pockets[player->get_id()]; + if (bonustype == BONUS_NONE) + return; + + m_item_pockets[player->get_id()] = BONUS_NONE; + + auto& powerup = Sector::get().add(bonustype, Vector(0,0)); + powerup.set_pos(player->get_pos() - Vector(0.f, powerup.get_bbox().get_height() + 15)); +} + +void +PlayerStatus::add_item_to_pocket(BonusType bonustype, Player* player) +{ + if (!is_item_pocket_allowed()) + return; + + if (bonustype <= BONUS_GROWUP) + return; + + m_item_pockets[player->get_id()] = bonustype; +} + +BonusType +PlayerStatus::get_item_pocket(const Player* player) const +{ + return m_item_pockets[player->get_id()]; +} + +bool +PlayerStatus::is_item_pocket_allowed() const +{ + if (m_override_item_pocket != Level::INHERIT) + { + return m_override_item_pocket == Level::ON; + } + + GameSession* session = GameSession::current(); + if (!session) + { + worldmap::WorldMap* worldmap = worldmap::WorldMap::current(); + if (worldmap) + { + return worldmap->is_item_pocket_allowed(); + } + else + { + // Not in a level nor in a worldmap. Return true, I guess. + return true; + } + } + + Level& level = session->get_current_level(); + int allowed = static_cast(level.m_allow_item_pocket); + + if (allowed != Level::INHERIT) + { + return allowed == Level::ON; + } + else + { + worldmap::WorldMap* worldmap = worldmap::WorldMap::current(); + if (worldmap) + { + return worldmap->is_item_pocket_allowed(); + } + else + { + // This level is probably in a levelset, pick ON. + return true; + } + } +} + void PlayerStatus::parse_bonus_mapping(const ReaderMapping& map, int id) { std::string bonusname; if (map.get("bonus", bonusname)) { - if (bonusname == "none") { - bonus[id] = NO_BONUS; - } else if (bonusname == "growup") { - bonus[id] = GROWUP_BONUS; - } else if (bonusname == "fireflower") { - bonus[id] = FIRE_BONUS; - } else if (bonusname == "iceflower") { - bonus[id] = ICE_BONUS; - } else if (bonusname == "airflower") { - bonus[id] = AIR_BONUS; - } else if (bonusname == "earthflower") { - bonus[id] = EARTH_BONUS; - } else { - log_warning << "Unknown bonus '" << bonusname << "' in savefile for player " << (id + 1) << std::endl; - bonus[id] = NO_BONUS; - } + bonus[id] = get_bonus_from_name(bonusname); + } + + if (map.get("item-pocket", bonusname)) { + m_item_pockets[id] = get_bonus_from_name(bonusname); } - map.get("fireflowers", max_fire_bullets[id]); - map.get("iceflowers", max_ice_bullets[id]); - map.get("airflowers", max_air_time[id]); - map.get("earthflowers", max_earth_time[id]); } std::string @@ -266,17 +354,17 @@ PlayerStatus::get_bonus_prefix(int player_id) const { switch (bonus[player_id]) { default: - case NO_BONUS: + case BONUS_NONE: return "small"; - case GROWUP_BONUS: + case BONUS_GROWUP: return "big"; - case FIRE_BONUS: + case BONUS_FIRE: return "fire"; - case ICE_BONUS: + case BONUS_ICE: return "ice"; - case AIR_BONUS: + case BONUS_AIR: return "air"; - case EARTH_BONUS: + case BONUS_EARTH: return "earth"; } } @@ -286,11 +374,8 @@ PlayerStatus::add_player() { m_num_players++; - bonus.resize(m_num_players, NO_BONUS); - max_fire_bullets.resize(m_num_players, 0); - max_ice_bullets.resize(m_num_players, 0); - max_air_time.resize(m_num_players, 0); - max_earth_time.resize(m_num_players, 0); + bonus.resize(m_num_players, BONUS_NONE); + m_item_pockets.resize(m_num_players, BONUS_NONE); } void @@ -301,17 +386,56 @@ PlayerStatus::remove_player(int player_id) for (int i = player_id; i < m_num_players; i++) { bonus[i] = bonus[i + 1]; - max_fire_bullets[i] = max_fire_bullets[i + 1]; - max_ice_bullets[i] = max_ice_bullets[i + 1]; - max_air_time[i] = max_air_time[i + 1]; - max_earth_time[i] = max_earth_time[i + 1]; + m_item_pockets[i] = m_item_pockets[i + 1]; + } + + bonus.resize(m_num_players, BONUS_NONE); + m_item_pockets.resize(m_num_players, BONUS_NONE); +} + + +PlayerStatus::PocketPowerUp::PocketPowerUp(BonusType bonustype, Vector pos): + PowerUp(pos, PowerUp::get_type_from_bonustype(bonustype)), + m_cooldown_timer(), + m_blink_timer(), + m_visible(true) +{ + physic.set_velocity_y(-325.f); + physic.set_gravity_modifier(0.4f); + set_layer(LAYER_FOREGROUND1); + m_col.m_group = COLGROUP_DISABLED; +} + +void +PlayerStatus::PocketPowerUp::update(float dt_sec) +{ + PowerUp::update(dt_sec); + + bool check = m_cooldown_timer.check(); + if (!m_cooldown_timer.started() && !check && m_col.m_group != COLGROUP_TOUCHABLE) + { + m_cooldown_timer.start(1.3f); + m_blink_timer.start(.15f, true); + } + + if (check) + { + m_visible = true; + m_blink_timer.stop(); + m_col.m_group = COLGROUP_TOUCHABLE; } - bonus.resize(m_num_players, NO_BONUS); - max_fire_bullets.resize(m_num_players, 0); - max_ice_bullets.resize(m_num_players, 0); - max_air_time.resize(m_num_players, 0); - max_earth_time.resize(m_num_players, 0); + if (m_blink_timer.check()) + m_visible = !m_visible; +} + +void +PlayerStatus::PocketPowerUp::draw(DrawingContext& context) +{ + if (!m_visible) + return; + + PowerUp::draw(context); } /* EOF */ diff --git a/src/supertux/player_status.hpp b/src/supertux/player_status.hpp index 088c1fcfa5e..2880720bd54 100644 --- a/src/supertux/player_status.hpp +++ b/src/supertux/player_status.hpp @@ -23,15 +23,28 @@ #include #include +#include "object/powerup.hpp" +#include "supertux/timer.hpp" +#include "supertux/level.hpp" + class DrawingContext; +class Player; class ReaderMapping; class Writer; static const float BORDER_X = 10; static const float BORDER_Y = 10; +/** + * @scripting + */ enum BonusType { - NO_BONUS = 0, GROWUP_BONUS, FIRE_BONUS, ICE_BONUS, AIR_BONUS, EARTH_BONUS + BONUS_NONE = 0, /*!< @description No bonus. */ + BONUS_GROWUP, /*!< @description Growup (a.k.a. egg) bonus. */ + BONUS_FIRE, /*!< @description Fire bonus. */ + BONUS_ICE, /*!< @description Ice bonus. */ + BONUS_AIR, /*!< @description Air bonus. */ + BONUS_EARTH /*!< @description Earth bonus. */ }; /** This class keeps player status between different game sessions (for @@ -47,11 +60,22 @@ class PlayerStatus final void write(Writer& writer); void read(const ReaderMapping& mapping); + void give_item_from_pocket(Player* player); + void add_item_to_pocket(BonusType bonustype, Player* player); + BonusType get_item_pocket(const Player* player) const; + + bool is_item_pocket_allowed() const; + int get_max_coins() const; bool can_reach_checkpoint() const; bool respawns_at_checkpoint() const; + bool has_hat_sprite(int player_id) const { return bonus[player_id] > BONUS_GROWUP; } + + static std::string get_bonus_name(BonusType bonustype); + static BonusType get_bonus_from_name(const std::string& name); + + static std::string get_bonus_sprite(BonusType bonustype); std::string get_bonus_prefix(int player_id) const;/**Returns the prefix of the animations that should be displayed*/ - bool has_hat_sprite(int player_id) const { return bonus[player_id] > GROWUP_BONUS; } void add_player(); void remove_player(int player_id); @@ -59,15 +83,34 @@ class PlayerStatus final private: void parse_bonus_mapping(const ReaderMapping& map, int id); +private: + /// PowerUp that flings itself upwards + /// can't be collected right away. + class PocketPowerUp : public PowerUp + { + public: + PocketPowerUp(BonusType bonustype, Vector pos); + virtual void update(float dt_sec) override; + virtual void draw(DrawingContext& context) override; + + public: + Timer m_cooldown_timer; + Timer m_blink_timer; + bool m_visible; + + private: + PocketPowerUp(const PocketPowerUp&) = delete; + PocketPowerUp& operator=(const PocketPowerUp&) = delete; + }; + public: int m_num_players; + std::vector m_item_pockets; + Level::Setting m_override_item_pocket; + int coins; std::vector bonus; - std::vector max_fire_bullets; /**< maximum number of fire bullets in play */ - std::vector max_ice_bullets; /**< maximum number of ice bullets in play */ - std::vector max_air_time; /** max_earth_time; /**< determines maximum number of seconds player can turn to stone */ std::string worldmap_sprite; /**< the sprite of Tux that should be used in worldmap */ std::string last_worldmap; /**< the last played worldmap */ diff --git a/src/supertux/player_status_hud.cpp b/src/supertux/player_status_hud.cpp index e9f7e4c2018..2f23a3851a5 100644 --- a/src/supertux/player_status_hud.cpp +++ b/src/supertux/player_status_hud.cpp @@ -16,12 +16,19 @@ #include "supertux/player_status_hud.hpp" +#include + +#include "sprite/sprite_manager.hpp" #include "supertux/game_object.hpp" +#include "supertux/level.hpp" #include "supertux/player_status.hpp" #include "supertux/resources.hpp" +#include "supertux/title_screen.hpp" +#include "supertux/screen_manager.hpp" #include "video/drawing_context.hpp" #include "video/surface.hpp" #include "editor/editor.hpp" +#include "worldmap/worldmap_sector.hpp" static const int DISPLAYED_COINS_UNSET = -1; @@ -30,9 +37,13 @@ PlayerStatusHUD::PlayerStatusHUD(PlayerStatus& player_status) : displayed_coins(DISPLAYED_COINS_UNSET), displayed_coins_frame(0), coin_surface(Surface::from_file("images/engine/hud/coins-0.png")), - fire_surface(Surface::from_file("images/engine/hud/fire-0.png")), - ice_surface(Surface::from_file("images/engine/hud/ice-0.png")) + m_bonus_sprites(), + m_item_pocket_border(Surface::from_file("images/engine/hud/item_pocket.png")) { + m_bonus_sprites[BONUS_FIRE] = SpriteManager::current()->create("images/powerups/fireflower/fireflower.sprite"); + m_bonus_sprites[BONUS_ICE] = SpriteManager::current()->create("images/powerups/iceflower/iceflower.sprite"); + m_bonus_sprites[BONUS_AIR] = SpriteManager::current()->create("images/powerups/airflower/airflower.sprite"); + m_bonus_sprites[BONUS_EARTH] = SpriteManager::current()->create("images/powerups/earthflower/earthflower.sprite"); } void @@ -70,78 +81,38 @@ PlayerStatusHUD::draw(DrawingContext& context) context.push_transform(); context.set_translation(Vector(0, 0)); context.transform().scale = 1.f; - if (!Editor::is_active()) + if (coin_surface) { - if (coin_surface) - { - context.color().draw_surface(coin_surface, - Vector(context.get_width() - BORDER_X - static_cast(coin_surface->get_width()) - Resources::fixed_font->get_text_width(coins_text), - hudpos), - LAYER_HUD); - } - - context.color().draw_text(Resources::fixed_font, - coins_text, - Vector(static_cast(context.get_width()) - BORDER_X - Resources::fixed_font->get_text_width(coins_text), - hudpos + 13.f), - ALIGN_LEFT, - LAYER_HUD, - PlayerStatusHUD::text_color); + context.color().draw_surface(coin_surface, + Vector(context.get_width() - BORDER_X - static_cast(coin_surface->get_width()) - Resources::fixed_font->get_text_width(coins_text), + hudpos), + LAYER_HUD); } - hudpos += 8.f; - for (int target = 0; target < InputManager::current()->get_num_users(); target++) - { - SurfacePtr surface; - std::string ammo_text; + context.color().draw_text(Resources::fixed_font, + coins_text, + Vector(static_cast(context.get_width()) - BORDER_X - Resources::fixed_font->get_text_width(coins_text), + hudpos + 13.f), + ALIGN_LEFT, + LAYER_HUD, + PlayerStatusHUD::text_color); - if (m_player_status.bonus[target] == FIRE_BONUS) - { - surface = fire_surface; - ammo_text = std::to_string(m_player_status.max_fire_bullets[target]); - } - else if (m_player_status.bonus[target] == ICE_BONUS) - { - surface = ice_surface; - ammo_text = std::to_string(m_player_status.max_ice_bullets[target]); - } - else - { - continue; - } - - hudpos += static_cast(surface->get_height()); - const float ammo_text_width = Resources::fixed_font->get_text_width(ammo_text); - - if (InputManager::current()->get_num_users() > 1) + if (m_player_status.is_item_pocket_allowed()) + { + for (int i = 0; i < InputManager::current()->get_num_users(); i++) { - const std::string player_text = std::to_string(target + 1) + ":"; - - context.color().draw_text(Resources::fixed_font, - player_text, - Vector(context.get_width() - BORDER_X - ammo_text_width - - static_cast(surface->get_width()) - - Resources::fixed_font->get_text_width(player_text) - 3.f, - hudpos + 13.f), - ALIGN_LEFT, - LAYER_HUD, - PlayerStatusHUD::text_color); + float ypos = static_cast(m_item_pocket_border->get_height() * i); + Vector pos(BORDER_X, BORDER_Y + ypos); + context.color().draw_surface(m_item_pocket_border, pos, LAYER_HUD); + + if (m_bonus_sprites.find(m_player_status.m_item_pockets[i]) != m_bonus_sprites.end()) + { + pos += 20; + Sprite* sprite = m_bonus_sprites[m_player_status.m_item_pockets.front()].get(); + sprite->draw(context.color(), pos, LAYER_HUD); + } } - - context.color().draw_surface(surface, - Vector(context.get_width() - BORDER_X - ammo_text_width - - static_cast(surface->get_width()), - hudpos), - LAYER_HUD); - - context.color().draw_text(Resources::fixed_font, - ammo_text, - Vector(context.get_width() - BORDER_X - ammo_text_width, - hudpos + 13.f), - ALIGN_LEFT, - LAYER_HUD, - PlayerStatusHUD::text_color); } context.pop_transform(); diff --git a/src/supertux/player_status_hud.hpp b/src/supertux/player_status_hud.hpp index 3bcc649eef0..38038c7438d 100644 --- a/src/supertux/player_status_hud.hpp +++ b/src/supertux/player_status_hud.hpp @@ -21,11 +21,14 @@ #include "supertux/game_object.hpp" +#include + +#include "sprite/sprite.hpp" +#include "supertux/player_status.hpp" #include "video/color.hpp" #include "video/surface_ptr.hpp" class DrawingContext; -class PlayerStatus; class PlayerStatusHUD : public GameObject { @@ -49,8 +52,9 @@ class PlayerStatusHUD : public GameObject int displayed_coins; int displayed_coins_frame; SurfacePtr coin_surface; - SurfacePtr fire_surface; - SurfacePtr ice_surface; + + std::unordered_map m_bonus_sprites; + SurfacePtr m_item_pocket_border; private: PlayerStatusHUD(const PlayerStatusHUD&) = delete; diff --git a/src/worldmap/tux.cpp b/src/worldmap/tux.cpp index 383c5bd3297..e0236c37429 100644 --- a/src/worldmap/tux.cpp +++ b/src/worldmap/tux.cpp @@ -95,17 +95,17 @@ Tux::draw(DrawingContext& context) std::string Tux::get_action_prefix_for_bonus(const BonusType& bonus) const { - if (bonus == GROWUP_BONUS) + if (bonus == BONUS_GROWUP) return "large"; - if (bonus == FIRE_BONUS) + if (bonus == BONUS_FIRE) return "fire"; - if (bonus == ICE_BONUS) + if (bonus == BONUS_ICE) return "ice"; - if (bonus == AIR_BONUS) + if (bonus == BONUS_AIR) return "air"; - if (bonus == EARTH_BONUS) + if (bonus == BONUS_EARTH) return "earth"; - if (bonus == NO_BONUS) + if (bonus == BONUS_NONE) return "small"; return ""; diff --git a/src/worldmap/worldmap.cpp b/src/worldmap/worldmap.cpp index 89d1b3bc829..bd82a613242 100644 --- a/src/worldmap/worldmap.cpp +++ b/src/worldmap/worldmap.cpp @@ -25,6 +25,7 @@ #include "supertux/fadetoblack.hpp" #include "supertux/game_manager.hpp" #include "supertux/gameconfig.hpp" +#include "supertux/level.hpp" #include "supertux/menu/menu_storage.hpp" #include "supertux/player_status.hpp" #include "supertux/screen_manager.hpp" @@ -59,6 +60,7 @@ WorldMap::WorldMap(const std::string& filename, Savegame& savegame, m_next_worldmap(), m_passive_message(), m_passive_message_timer(), + m_allow_item_pocket(true), m_enter_level(false), m_in_level(false), m_in_world_select(false) @@ -81,6 +83,10 @@ WorldMap::WorldMap(const std::string& filename, Savegame& savegame, mapping.get("tileset", tileset_name, "images/ice_world.strf"); m_tileset = TileManager::current()->get_tileset(tileset_name); + std::string name = "on"; + mapping.get("allow-item-pocket", name); + m_allow_item_pocket = (Level::get_setting_from_name(name) == Level::ON); + auto iter = mapping.get_iter(); while (iter.next()) { diff --git a/src/worldmap/worldmap.hpp b/src/worldmap/worldmap.hpp index abfcc4e3c24..37e345f76e1 100644 --- a/src/worldmap/worldmap.hpp +++ b/src/worldmap/worldmap.hpp @@ -88,6 +88,8 @@ class WorldMap final : public Currenton const std::string& get_filename() const; + bool is_item_pocket_allowed() const { return m_allow_item_pocket; } + private: void on_escape_press(); @@ -111,6 +113,7 @@ class WorldMap final : public Currenton std::string m_passive_message; Timer m_passive_message_timer; + bool m_allow_item_pocket; bool m_enter_level; bool m_in_level; bool m_in_world_select;