wip 3
parent
f313527b20
commit
80bd8b0a52
|
|
@ -53,6 +53,11 @@ enum class MatchMode : std::uint8_t {
|
|||
Client = 2,
|
||||
};
|
||||
|
||||
enum class StartupField : std::uint8_t {
|
||||
HostIp = 0,
|
||||
Port = 1,
|
||||
};
|
||||
|
||||
struct UnitState {
|
||||
std::uint32_t unit_id = 0;
|
||||
PlayerId owner = PlayerId::None;
|
||||
|
|
@ -133,9 +138,14 @@ struct LocalViewState {
|
|||
std::string host_ip = "127.0.0.1";
|
||||
std::string port = "9000";
|
||||
std::string connection_message = "Not connected";
|
||||
StartupField startup_field = StartupField::HostIp;
|
||||
|
||||
int selected_area = -1;
|
||||
std::optional<std::uint32_t> selected_unit;
|
||||
std::optional<std::uint32_t> selected_combat_unit;
|
||||
bool selecting_move_destination = false;
|
||||
int move_from_area = -1;
|
||||
int move_target_area = -1;
|
||||
std::optional<std::uint32_t> selected_card;
|
||||
int pending_combat_spend = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -79,15 +79,21 @@ void sync_registry_from_game_state(AppContext &ctx) {
|
|||
static_cast<float>(board_y + row * cell_size));
|
||||
|
||||
auto &render = ctx.registry.add_component<RenderComponent>(area_entity);
|
||||
render.draw = [controller = area.controller](raylib::Vector2 screen_pos, bool,
|
||||
render.draw = [controller = area.controller](raylib::Vector2 screen_pos, bool selected,
|
||||
bool hl) {
|
||||
DrawRectangle(static_cast<int>(screen_pos.x), static_cast<int>(screen_pos.y),
|
||||
cell_size - 2, cell_size - 2, area_fill_color(controller));
|
||||
if (selected) {
|
||||
DrawRectangleLinesEx(Rectangle{screen_pos.x, screen_pos.y,
|
||||
static_cast<float>(cell_size - 2),
|
||||
static_cast<float>(cell_size - 2)},
|
||||
2.0f, Color{32, 37, 47, 255});
|
||||
}
|
||||
if (hl) {
|
||||
DrawRectangleLinesEx(Rectangle{screen_pos.x, screen_pos.y,
|
||||
static_cast<float>(cell_size - 2),
|
||||
static_cast<float>(cell_size - 2)},
|
||||
3.0f, Color{32, 37, 47, 255});
|
||||
5.0f, Color{234, 157, 52, 255});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -154,6 +160,7 @@ int main() {
|
|||
raylib::Window window(700, 460, "Island Dominion");
|
||||
window.SetState(FLAG_WINDOW_RESIZABLE);
|
||||
window.SetTargetFPS(60);
|
||||
SetExitKey(KEY_NULL);
|
||||
|
||||
AppContext ctx;
|
||||
initialize_game_state(ctx.game_state);
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@
|
|||
|
||||
#include "raylib.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
bool point_in_rect(float px, float py, float x, float y, float w, float h) {
|
||||
|
|
@ -44,6 +47,198 @@ std::uint16_t parse_port_or_default(const std::string &text) {
|
|||
}
|
||||
return static_cast<std::uint16_t>(value);
|
||||
}
|
||||
|
||||
void select_area_and_local_unit(AppContext &ctx, int area_index) {
|
||||
if (area_index < 0 || area_index >= area_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.local.selected_area = area_index;
|
||||
ctx.local.selected_unit.reset();
|
||||
ctx.local.selected_combat_unit.reset();
|
||||
|
||||
const AreaState &area = ctx.game_state.areas[area_index];
|
||||
for (const UnitState &unit : area.units) {
|
||||
if (unit.owner == ctx.local.local_player) {
|
||||
ctx.local.selected_unit = unit.unit_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.local.selecting_move_destination = false;
|
||||
ctx.local.move_from_area = -1;
|
||||
ctx.local.move_target_area = -1;
|
||||
}
|
||||
|
||||
void move_selected_area(AppContext &ctx, int d_row, int d_col) {
|
||||
int current =
|
||||
ctx.local.selecting_move_destination ? ctx.local.move_target_area : ctx.local.selected_area;
|
||||
if (current < 0 || current >= area_count) {
|
||||
current = 0;
|
||||
}
|
||||
|
||||
int row = current / board_size;
|
||||
int col = current % board_size;
|
||||
|
||||
row = std::clamp(row + d_row, 0, board_size - 1);
|
||||
col = std::clamp(col + d_col, 0, board_size - 1);
|
||||
|
||||
const int next = area_index_from_row_col(row, col);
|
||||
if (ctx.local.selecting_move_destination) {
|
||||
ctx.local.move_target_area = next;
|
||||
} else {
|
||||
select_area_and_local_unit(ctx, next);
|
||||
}
|
||||
}
|
||||
|
||||
void cycle_selected_local_unit(AppContext &ctx) {
|
||||
std::vector<std::pair<int, std::uint32_t>> owned_units;
|
||||
owned_units.reserve(16);
|
||||
|
||||
for (int area_index = 0; area_index < area_count; ++area_index) {
|
||||
const AreaState &area = ctx.game_state.areas[area_index];
|
||||
for (const UnitState &unit : area.units) {
|
||||
if (unit.owner == ctx.local.local_player) {
|
||||
owned_units.emplace_back(area_index, unit.unit_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (owned_units.empty()) {
|
||||
ctx.local.selected_unit.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t index = 0;
|
||||
if (ctx.local.selected_unit.has_value()) {
|
||||
for (std::size_t i = 0; i < owned_units.size(); ++i) {
|
||||
if (owned_units[i].second == *ctx.local.selected_unit) {
|
||||
index = (i + 1) % owned_units.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.local.selected_area = owned_units[index].first;
|
||||
ctx.local.selected_unit = owned_units[index].second;
|
||||
ctx.local.selected_combat_unit.reset();
|
||||
ctx.local.selecting_move_destination = false;
|
||||
ctx.local.move_from_area = -1;
|
||||
ctx.local.move_target_area = -1;
|
||||
}
|
||||
|
||||
void begin_move_destination_selection(AppContext &ctx) {
|
||||
if (ctx.local.selected_area < 0 || !ctx.local.selected_unit.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.local.selecting_move_destination = true;
|
||||
ctx.local.move_from_area = ctx.local.selected_area;
|
||||
ctx.local.move_target_area = ctx.local.selected_area;
|
||||
}
|
||||
|
||||
void cancel_move_destination_selection(AppContext &ctx) {
|
||||
ctx.local.selecting_move_destination = false;
|
||||
ctx.local.move_from_area = -1;
|
||||
ctx.local.move_target_area = -1;
|
||||
}
|
||||
|
||||
void confirm_move_destination_selection(AppContext &ctx) {
|
||||
if (!ctx.local.selecting_move_destination || !ctx.local.selected_unit.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (ctx.local.move_from_area < 0 || ctx.local.move_target_area < 0) {
|
||||
return;
|
||||
}
|
||||
if (!are_adjacent(ctx.local.move_from_area, ctx.local.move_target_area)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerAction action;
|
||||
action.type = ActionType::MoveUnit;
|
||||
action.unit_id = *ctx.local.selected_unit;
|
||||
action.from_area = ctx.local.move_from_area;
|
||||
action.to_area = ctx.local.move_target_area;
|
||||
ctx.pending_local_actions.push(action);
|
||||
if (ctx.local.mode == MatchMode::Client) {
|
||||
ctx.network.send(MessageType::ActionRequest, serialize_action_request(action));
|
||||
}
|
||||
|
||||
ctx.local.selected_area = ctx.local.move_target_area;
|
||||
cancel_move_destination_selection(ctx);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> local_units_in_combat_area(const AppContext &ctx) {
|
||||
std::vector<std::uint32_t> ids;
|
||||
const int area_index = ctx.game_state.combat.area_index;
|
||||
if (area_index < 0 || area_index >= area_count) {
|
||||
return ids;
|
||||
}
|
||||
|
||||
const AreaState &area = ctx.game_state.areas[area_index];
|
||||
for (const UnitState &unit : area.units) {
|
||||
if (unit.owner == ctx.local.local_player) {
|
||||
ids.push_back(unit.unit_id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void cycle_combat_unit(AppContext &ctx) {
|
||||
std::vector<std::uint32_t> ids = local_units_in_combat_area(ctx);
|
||||
if (ids.empty()) {
|
||||
ctx.local.selected_combat_unit.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.local.selected_combat_unit.has_value()) {
|
||||
ctx.local.selected_combat_unit = ids.front();
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < ids.size(); ++i) {
|
||||
if (ids[i] == *ctx.local.selected_combat_unit) {
|
||||
ctx.local.selected_combat_unit = ids[(i + 1) % ids.size()];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.local.selected_combat_unit = ids.front();
|
||||
}
|
||||
|
||||
void update_card_selection_for_combat(AppContext &ctx) {
|
||||
const PlayerState &player = player_for(ctx.game_state, ctx.local.local_player);
|
||||
if (player.hand.empty()) {
|
||||
ctx.local.selected_card.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.local.selected_card.has_value()) {
|
||||
ctx.local.selected_card = player.hand.front().card_id;
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t selected_index = player.hand.size();
|
||||
for (std::size_t i = 0; i < player.hand.size(); ++i) {
|
||||
if (player.hand[i].card_id == *ctx.local.selected_card) {
|
||||
selected_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KEY_C)) {
|
||||
if (selected_index == player.hand.size()) {
|
||||
ctx.local.selected_card = player.hand.front().card_id;
|
||||
} else {
|
||||
ctx.local.selected_card =
|
||||
player.hand[(selected_index + 1) % player.hand.size()].card_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KEY_X)) {
|
||||
ctx.local.selected_card.reset();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void input_system(AppContext &ctx) {
|
||||
|
|
@ -117,12 +312,19 @@ void input_system(AppContext &ctx) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (ctx.local.local_player != ctx.game_state.active_player &&
|
||||
if (!ctx.game_state.combat.active && ctx.local.local_player != ctx.game_state.active_player &&
|
||||
ctx.local.mode == MatchMode::Client) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.game_state.combat.active) {
|
||||
std::vector<std::uint32_t> combat_units = local_units_in_combat_area(ctx);
|
||||
if (!combat_units.empty() && !ctx.local.selected_combat_unit.has_value()) {
|
||||
ctx.local.selected_combat_unit = combat_units.front();
|
||||
}
|
||||
|
||||
update_card_selection_for_combat(ctx);
|
||||
|
||||
if (IsKeyPressed(KEY_ONE)) {
|
||||
ctx.local.pending_combat_spend = 1;
|
||||
}
|
||||
|
|
@ -135,27 +337,30 @@ void input_system(AppContext &ctx) {
|
|||
if (IsKeyPressed(KEY_ZERO)) {
|
||||
ctx.local.pending_combat_spend = 0;
|
||||
}
|
||||
if (IsKeyPressed(KEY_TAB)) {
|
||||
cycle_combat_unit(ctx);
|
||||
}
|
||||
|
||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) {
|
||||
int area_index = ctx.game_state.combat.area_index;
|
||||
if (area_index >= 0) {
|
||||
const AreaState &area = ctx.game_state.areas[area_index];
|
||||
for (const UnitState &unit : area.units) {
|
||||
if (unit.owner == ctx.local.local_player) {
|
||||
CombatChoice choice;
|
||||
choice.unit_id = unit.unit_id;
|
||||
choice.resources_spent = ctx.local.pending_combat_spend;
|
||||
if (ctx.local.selected_card.has_value()) {
|
||||
choice.card_id = ctx.local.selected_card;
|
||||
}
|
||||
choice.submitted = true;
|
||||
if (!ctx.local.selected_combat_unit.has_value() && !combat_units.empty()) {
|
||||
ctx.local.selected_combat_unit = combat_units.front();
|
||||
}
|
||||
|
||||
ctx.pending_local_combat_choice = choice;
|
||||
if (ctx.local.mode == MatchMode::Client) {
|
||||
ctx.network.send(MessageType::CombatChoice,
|
||||
serialize_combat_choice(choice));
|
||||
}
|
||||
break;
|
||||
if (ctx.local.selected_combat_unit.has_value()) {
|
||||
CombatChoice choice;
|
||||
choice.unit_id = *ctx.local.selected_combat_unit;
|
||||
choice.resources_spent = ctx.local.pending_combat_spend;
|
||||
if (ctx.local.selected_card.has_value()) {
|
||||
choice.card_id = ctx.local.selected_card;
|
||||
}
|
||||
choice.submitted = true;
|
||||
|
||||
ctx.pending_local_combat_choice = choice;
|
||||
if (ctx.local.mode == MatchMode::Client) {
|
||||
ctx.network.send(MessageType::CombatChoice,
|
||||
serialize_combat_choice(choice));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,6 +368,24 @@ void input_system(AppContext &ctx) {
|
|||
return;
|
||||
}
|
||||
|
||||
ctx.local.selected_combat_unit.reset();
|
||||
|
||||
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_L)) {
|
||||
move_selected_area(ctx, 0, 1);
|
||||
}
|
||||
if (IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_H)) {
|
||||
move_selected_area(ctx, 0, -1);
|
||||
}
|
||||
if (IsKeyPressed(KEY_DOWN) || IsKeyPressed(KEY_J)) {
|
||||
move_selected_area(ctx, 1, 0);
|
||||
}
|
||||
if (IsKeyPressed(KEY_UP) || IsKeyPressed(KEY_K)) {
|
||||
move_selected_area(ctx, -1, 0);
|
||||
}
|
||||
if (IsKeyPressed(KEY_TAB)) {
|
||||
cycle_selected_local_unit(ctx);
|
||||
}
|
||||
|
||||
int cell_size = 100;
|
||||
int board_px = board_size * cell_size;
|
||||
int board_x = (GetScreenWidth() - board_px) / 2;
|
||||
|
|
@ -171,35 +394,27 @@ void input_system(AppContext &ctx) {
|
|||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
int area_idx = grid_cell_at_mouse(GetMouseX(), GetMouseY(), board_x, board_y, cell_size);
|
||||
if (area_idx >= 0) {
|
||||
ctx.local.selected_area = area_idx;
|
||||
|
||||
const AreaState &area = ctx.game_state.areas[area_idx];
|
||||
ctx.local.selected_unit.reset();
|
||||
for (const UnitState &unit : area.units) {
|
||||
if (unit.owner == ctx.local.local_player) {
|
||||
ctx.local.selected_unit = unit.unit_id;
|
||||
break;
|
||||
}
|
||||
if (ctx.local.selecting_move_destination) {
|
||||
ctx.local.move_target_area = area_idx;
|
||||
} else {
|
||||
select_area_and_local_unit(ctx, area_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.local.selected_area >= 0 && ctx.local.selected_unit.has_value()) {
|
||||
if (IsKeyPressed(KEY_M)) {
|
||||
int from = ctx.local.selected_area;
|
||||
int to = from + 1;
|
||||
if (to < area_count && are_adjacent(from, to)) {
|
||||
PlayerAction action;
|
||||
action.type = ActionType::MoveUnit;
|
||||
action.unit_id = *ctx.local.selected_unit;
|
||||
action.from_area = from;
|
||||
action.to_area = to;
|
||||
ctx.pending_local_actions.push(action);
|
||||
if (ctx.local.mode == MatchMode::Client) {
|
||||
ctx.network.send(MessageType::ActionRequest, serialize_action_request(action));
|
||||
}
|
||||
}
|
||||
if (ctx.local.selected_area >= 0 && ctx.local.selected_unit.has_value() &&
|
||||
IsKeyPressed(KEY_M)) {
|
||||
begin_move_destination_selection(ctx);
|
||||
}
|
||||
|
||||
if (ctx.local.selecting_move_destination) {
|
||||
if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) {
|
||||
confirm_move_destination_selection(ctx);
|
||||
}
|
||||
if (IsKeyPressed(KEY_ESCAPE)) {
|
||||
cancel_move_destination_selection(ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.local.selected_area >= 0 && IsKeyPressed(KEY_B)) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ void render_system(AppContext &ctx) {
|
|||
if (ctx.registry.has_component<AreaComponent>(e)) {
|
||||
const auto *area = ctx.registry.get_component<AreaComponent>(e);
|
||||
selected = area && area->area_index == ctx.local.selected_area;
|
||||
highlighted = selected;
|
||||
highlighted = area && ctx.local.selecting_move_destination &&
|
||||
area->area_index == ctx.local.move_target_area;
|
||||
}
|
||||
|
||||
if (ctx.registry.has_component<UnitComponent>(e)) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,16 @@ const char *player_label(PlayerId p) {
|
|||
}
|
||||
return "None";
|
||||
}
|
||||
|
||||
const char *tier_label(UnitTier tier) {
|
||||
if (tier == UnitTier::Light) {
|
||||
return "Light";
|
||||
}
|
||||
if (tier == UnitTier::Normal) {
|
||||
return "Normal";
|
||||
}
|
||||
return "Heavy";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ui_system(AppContext &ctx) {
|
||||
|
|
@ -73,20 +83,71 @@ void ui_system(AppContext &ctx) {
|
|||
ctx.game_state.actions_remaining),
|
||||
GetScreenWidth() - 350, 14, 20, DARKBLUE);
|
||||
|
||||
DrawText("Keys: click area, B build unit, F build fortress, M move right, P pass", 20,
|
||||
DrawText("Keys: arrows/hjkl move, Tab cycle units, M start move, Enter confirm, B/F/P", 20,
|
||||
GetScreenHeight() - 30, 20, DARKGRAY);
|
||||
|
||||
if (ctx.local.selecting_move_destination) {
|
||||
DrawText(TextFormat("Move preview: %d -> %d (Enter confirm, Esc cancel)",
|
||||
ctx.local.move_from_area, ctx.local.move_target_area),
|
||||
20, GetScreenHeight() - 56, 20, DARKBLUE);
|
||||
}
|
||||
|
||||
if (ctx.game_state.combat.active) {
|
||||
DrawRectangle(140, 140, GetScreenWidth() - 280, GetScreenHeight() - 280,
|
||||
Color{25, 28, 34, 230});
|
||||
DrawText("Combat", 170, 170, 32, RAYWHITE);
|
||||
DrawText("Choose spend with 0-3, click to submit first valid unit.", 170, 212, 20,
|
||||
DrawText("Tab: unit, C: next card, X: no card, 0-3: spend, Enter: submit", 170, 212, 20,
|
||||
RAYWHITE);
|
||||
DrawText(TextFormat("Pending spend: %d", ctx.local.pending_combat_spend), 170, 242, 22,
|
||||
GOLD);
|
||||
|
||||
const int area_index = ctx.game_state.combat.area_index;
|
||||
int y = 276;
|
||||
if (area_index >= 0 && area_index < area_count) {
|
||||
const AreaState &area = ctx.game_state.areas[area_index];
|
||||
DrawText("Your units in combat area:", 170, y, 20, SKYBLUE);
|
||||
y += 26;
|
||||
bool any_local = false;
|
||||
for (const UnitState &unit : area.units) {
|
||||
if (unit.owner != ctx.local.local_player) {
|
||||
continue;
|
||||
}
|
||||
any_local = true;
|
||||
const bool selected = ctx.local.selected_combat_unit.has_value() &&
|
||||
*ctx.local.selected_combat_unit == unit.unit_id;
|
||||
DrawText(TextFormat("%s Unit #%u%s", tier_label(unit.tier), unit.unit_id,
|
||||
selected ? " <selected>" : ""),
|
||||
190, y, 20, selected ? YELLOW : RAYWHITE);
|
||||
y += 22;
|
||||
}
|
||||
if (!any_local) {
|
||||
DrawText("(no local units available)", 190, y, 20, LIGHTGRAY);
|
||||
y += 22;
|
||||
}
|
||||
}
|
||||
|
||||
const PlayerState &self = player_for(ctx.game_state, ctx.local.local_player);
|
||||
y += 10;
|
||||
DrawText("Card selection:", 170, y, 20, SKYBLUE);
|
||||
y += 24;
|
||||
if (self.hand.empty()) {
|
||||
DrawText("(no cards in hand)", 190, y, 20, LIGHTGRAY);
|
||||
} else if (!ctx.local.selected_card.has_value()) {
|
||||
DrawText("None selected", 190, y, 20, RAYWHITE);
|
||||
} else {
|
||||
int chosen_bonus = 0;
|
||||
for (const CardState &card : self.hand) {
|
||||
if (card.card_id == *ctx.local.selected_card) {
|
||||
chosen_bonus = card.bonus;
|
||||
break;
|
||||
}
|
||||
}
|
||||
DrawText(TextFormat("Card #%u (+%d)", *ctx.local.selected_card, chosen_bonus), 190, y,
|
||||
20, RAYWHITE);
|
||||
}
|
||||
|
||||
if (!ctx.game_state.combat.last_result.empty()) {
|
||||
DrawText(ctx.game_state.combat.last_result.c_str(), 170, 276, 22, SKYBLUE);
|
||||
DrawText(ctx.game_state.combat.last_result.c_str(), 170, y + 30, 22, SKYBLUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue