#pragma once #include "engine/prefabs/includes.h" #include "ghhb_game.h" #include #include class InstrumentSelectScreen : public Scene { public: Font font = {0}; static constexpr int SLOT_TOP = 0; static constexpr int SLOT_BOTTOM = 1; static constexpr int SLOT_LEFT = 2; static constexpr int SLOT_RIGHT = 3; static constexpr int BUTTON_SLOT[4] = { SLOT_TOP, // GAMEPAD_BUTTON_LEFT_FACE_UP SLOT_BOTTOM, // GAMEPAD_BUTTON_LEFT_FACE_DOWN SLOT_LEFT, // GAMEPAD_BUTTON_LEFT_FACE_LEFT SLOT_RIGHT, // GAMEPAD_BUTTON_LEFT_FACE_RIGHT }; static constexpr int FACE_BUTTONS[4] = { GAMEPAD_BUTTON_LEFT_FACE_UP, GAMEPAD_BUTTON_LEFT_FACE_DOWN, GAMEPAD_BUTTON_LEFT_FACE_LEFT, GAMEPAD_BUTTON_LEFT_FACE_RIGHT, }; static const Color BORDER_COLOR; static constexpr const char* INSTRUMENT_IMAGE_PATHS[MAX_INSTRUMENT_TYPES] = { "assets/instruments/guitar.png", "assets/instruments/sax.png", "assets/instruments/violin.png", "assets/instruments/piano.png", }; std::array instrument_owner = {-1, -1, -1, -1}; std::vector participant_order; void init_services() override { add_service(); } void init() override { auto font_manager = game->get_manager(); font = font_manager->get_font("Roboto"); instrument_owner.fill(-1); participant_order.clear(); } int get_or_assign_gamepad_index(int physical_gamepad_id) { for (size_t i = 0; i < participant_order.size(); i++) { if (participant_order[i] == physical_gamepad_id) return static_cast(i); } if (participant_order.size() < static_cast(MAX_INSTRUMENT_TYPES)) { participant_order.push_back(physical_gamepad_id); return static_cast(participant_order.size() - 1); } return -1; } void update(float delta_time) override { for (int gp = 0; gp < MAX_GAMEPADS; gp++) { if (!IsGamepadAvailable(gp)) continue; for (int b = 0; b < 4; b++) { if (IsGamepadButtonPressed(gp, FACE_BUTTONS[b])) { int slot = BUTTON_SLOT[b]; int gi = get_or_assign_gamepad_index(gp); if (gi >= 0) { if (instrument_owner[slot] == gi) instrument_owner[slot] = -1; else instrument_owner[slot] = gi; } } } } bool at_least_one_selected = false; for (int s = 0; s < MAX_INSTRUMENT_TYPES; s++) { if (instrument_owner[s] >= 0) { at_least_one_selected = true; break; } } if (at_least_one_selected) { if (IsKeyPressed(KEY_ENTER)) { for (int i = 0; i < MAX_INSTRUMENT_TYPES; i++) { if (instrument_owner[i] >= 0) { INSTRUMENT_GAMEPAD_INDEX[i] = instrument_owner[i]; INSTRUMENT_PHYSICAL_GAMEPAD[i] = participant_order[instrument_owner[i]]; } else { INSTRUMENT_GAMEPAD_INDEX[i] = -1; INSTRUMENT_PHYSICAL_GAMEPAD[i] = -1; } } game->go_to_scene("ghhb"); return; } for (int gp = 0; gp < MAX_GAMEPADS; gp++) { if (IsGamepadAvailable(gp) && IsGamepadButtonPressed(gp, GAMEPAD_BUTTON_MIDDLE_RIGHT)) { for (int i = 0; i < MAX_INSTRUMENT_TYPES; i++) { if (instrument_owner[i] >= 0) { INSTRUMENT_GAMEPAD_INDEX[i] = instrument_owner[i]; INSTRUMENT_PHYSICAL_GAMEPAD[i] = participant_order[instrument_owner[i]]; } else { INSTRUMENT_GAMEPAD_INDEX[i] = -1; INSTRUMENT_PHYSICAL_GAMEPAD[i] = -1; } } game->go_to_scene("ghhb"); return; } } } } void draw() override { float w = static_cast(GetScreenWidth()); float h = static_cast(GetScreenHeight()); float cx = w * 0.5f; float cy = h * 0.5f; /* Vertices in counter-clockwise order so Raylib fills the triangle. */ Vector2 top_tri[3] = {{cx, cy}, {w, 0}, {0, 0}}; Vector2 bottom_tri[3] = {{0, h}, {w, h}, {cx, cy}}; Vector2 left_tri[3] = {{0, 0}, {0, h}, {cx, cy}}; Vector2 right_tri[3] = {{cx, cy}, {w, h}, {w, 0}}; auto draw_wedge = [this](const Vector2* v, int slot) { DrawTriangle(v[0], v[1], v[2], INSTRUMENT_COLORS[slot]); }; draw_wedge(top_tri, SLOT_TOP); draw_wedge(bottom_tri, SLOT_BOTTOM); draw_wedge(left_tri, SLOT_LEFT); draw_wedge(right_tri, SLOT_RIGHT); DrawLineEx(Vector2{0, 0}, Vector2{w, h}, 3.0f, BORDER_COLOR); DrawLineEx(Vector2{w, 0}, Vector2{0, h}, 3.0f, BORDER_COLOR); auto* tex_svc = get_service(); auto draw_center_icon = [this, tex_svc](float ix, float iy, int slot) { const char* path = INSTRUMENT_IMAGE_PATHS[slot]; if (FileExists(path)) { Texture2D& tex = tex_svc->get_texture(path); float tw = static_cast(tex.width); float th = static_cast(tex.height); float scale = 1.0f; float max_dim = 120.0f; if (tw > max_dim || th > max_dim) scale = max_dim / (tw > th ? tw : th); float dw = tw * scale; float dh = th * scale; DrawTextureEx(tex, Vector2{ix - dw * 0.5f, iy - dh * 0.5f}, 0.0f, scale, WHITE); } else { DrawRectangle(static_cast(ix - 40), static_cast(iy - 40), 80, 80, Color{80, 80, 100, 255}); } }; const float icon_y_top = h / 6.0f; const float icon_y_bottom = h * 5.0f / 6.0f; const float icon_x_left = w / 6.0f; const float icon_x_right = w * 5.0f / 6.0f; draw_center_icon(cx, icon_y_top, SLOT_TOP); draw_center_icon(cx, icon_y_bottom, SLOT_BOTTOM); draw_center_icon(icon_x_left, cy, SLOT_LEFT); draw_center_icon(icon_x_right, cy, SLOT_RIGHT); const char* player_labels[4] = {"P1", "P2", "P3", "P4"}; const float label_font_size = 60.0f; const float label_offset = 50.0f; const float label_offset_bottom = 110.0f; auto draw_slot_label = [this, &player_labels, label_font_size, label_offset, label_offset_bottom](float ix, float iy, int slot) { if (instrument_owner[slot] >= 0) { const char* label = player_labels[instrument_owner[slot]]; float lw = MeasureTextEx(font, label, label_font_size, 1.0f).x; float ly = (slot == SLOT_BOTTOM) ? iy - label_offset_bottom : iy + label_offset; DrawTextEx(font, label, Vector2{ix - lw * 0.5f, ly}, label_font_size, 1.0f, WHITE); } else { const char* label = "CPU"; float lw = MeasureTextEx(font, label, label_font_size, 1.0f).x; float ly = (slot == SLOT_BOTTOM) ? iy - label_offset_bottom : iy + label_offset; DrawTextEx(font, label, Vector2{ix - lw * 0.5f, ly}, label_font_size, 1.0f, Color{180, 180, 180, 255}); } }; draw_slot_label(cx, icon_y_top, SLOT_TOP); draw_slot_label(cx, icon_y_bottom, SLOT_BOTTOM); draw_slot_label(icon_x_left, cy, SLOT_LEFT); draw_slot_label(icon_x_right, cy, SLOT_RIGHT); bool at_least_one_selected = false; for (int s = 0; s < MAX_INSTRUMENT_TYPES; s++) { if (instrument_owner[s] >= 0) { at_least_one_selected = true; break; } } if (at_least_one_selected) { const char* prompt = "Press Start to begin"; float font_size = 24.0f; float pw = MeasureTextEx(font, prompt, font_size, 1.0f).x; DrawTextEx(font, prompt, Vector2{cx - pw * 0.5f, h - 40.0f}, font_size, 1.0f, WHITE); } else { const char* prompt = "Select at least one instrument"; float font_size = 24.0f; float pw = MeasureTextEx(font, prompt, font_size, 1.0f).x; DrawTextEx(font, prompt, Vector2{cx - pw * 0.5f, h - 40.0f}, font_size, 1.0f, WHITE); } } }; inline const Color InstrumentSelectScreen::BORDER_COLOR = {45, 55, 72, 255};