Bob/src/Gameplay.cpp

657 lines
No EOL
20 KiB
C++

#include "Gameplay.hpp"
Player *Player::current_player = nullptr;
SDL_Point operator+(SDL_Point left, SDL_Point right)
{
return {left.x + right.x, left.y + right.y};
}
SDL_Point operator-(SDL_Point left, SDL_Point right)
{
return {left.x - right.x, left.y - right.y};
}
SDL_Point operator*(SDL_Point left, SDL_Point right)
{
return {left.x * right.x, left.y * right.y};
}
SDL_Point operator/(SDL_Point left, SDL_Point right)
{
return {(left.x / right.x), (left.x / right.y)};
}
SDL_Point operator*(double left, SDL_Point right)
{
int x = (int) (left * right.x);
int y = (int) (left * right.y);
return {x, y};
}
SDL_Point operator*(SDL_Point left, double right)
{
return right * left;
}
SDL_Point operator/(SDL_Point left, double right)
{
return (1 / right) * left;
}
double operator!(SDL_Point left)
{
double length = std::sqrt(left.x * left.x + left.y * left.y);
return length;
}
Field Field::cubic_round(double x, double y, double z)
{
double round_x = std::round(x);
double round_y = std::round(y);
double round_z = std::round(z);
double x_err = std::abs(round_x - x);
double y_err = std::abs(round_y - y);
double z_err = std::abs(round_z - z);
if (x_err > y_err && x_err > z_err)
{
round_x = -round_y - round_z;
} else if (y_err > z_err)
{
round_y = -round_x - round_z;
} else
{
round_z = -round_x - round_y;
}
Sint16 x_out = (int) round_x;
Sint16 y_out = (int) round_y;
Sint16 z_out = (int) round_z;
return Field(x_out, y_out, z_out);
}
Field Field::hex_direction(Uint8 direction)
{
assert (0 <= direction && direction <= 5);
return hex_directions[direction];
}
Field Field::get_neighbor(Uint8 direction) const
{
return hex_direction(direction) + *this;
}
Point Field::field_to_point(const Layout *layout) const
{
const Orientation m = layout->orientation;
double x = (m.f0 * this->x + m.f1 * this->y) * layout->size;
double y = (m.f2 * this->x + m.f3 * this->y) * layout->size;
return {x + layout->origin.x, y + layout->origin.y};
}
Field Point::point_to_field(const Layout *layout) const
{
const Orientation m = layout->orientation;
double rel_x = (this->x - layout->origin.x) / layout->size;
double rel_y = (this->y - layout->origin.y) / layout->size;
double x = m.b0 * rel_x + m.b1 * rel_y;
double y = m.b2 * rel_x + m.b3 * rel_y;
return Field::cubic_round(x, y, -x - y);
}
Point field_corner_offset(Uint8 corner, const Layout *layout)
{
double angle = 2.0 * M_PI * (corner + layout->orientation.start_angle) / 6;
double x = (layout->size * cos(angle));
double y = (layout->size * sin(angle));
return {x, y};
}
std::vector<Point> Field::field_to_polygon(const Layout *layout) const
{
std::vector<Point> corners = this->field_to_polygon_normalized(layout);
Point center = this->field_to_point(layout);
for (Point &p : corners)
{
p = p + center;
}
return corners;
}
std::vector<SDL_Point> Field::field_to_polygon_sdl(const Layout *layout) const
{
std::vector<SDL_Point> corners;
for (uint8_t i = 0; i < 6; i++)
{
Point center = this->field_to_point(layout);
Point offset = field_corner_offset(i, layout);
SDL_Point p;
p.x = (int) offset.x + center.x;
p.y = (int) offset.y + center.y;
corners.push_back(p);
}
Point center = this->field_to_point(layout);
Point offset = field_corner_offset(0, layout);
SDL_Point p;
p.x = (int) offset.x + center.x;
p.y = (int) offset.y + center.y;
corners.push_back(p);
return corners;
}
std::vector<Point> Field::field_to_polygon_normalized(const Layout *layout) const
{
std::vector<Point> corners;
for (uint8_t i = 0; i < 6; i++)
{
Point offset = field_corner_offset(i, layout);
corners.push_back(offset);
}
return corners;
}
void FieldMeta::regenerate_resources()
{
this->resources = resources_base;
if (this->upgrades[Regeneration_1])
this->resources *= 2;
if (this->upgrades[Regeneration_2])
this->resources *= 2;
if (this->upgrades[Regeneration_3])
this->resources *= 2;
trigger_event(BOB_FIELDUPDATEEVENT, 0, (void *) this, nullptr);
this->changed = true;
}
Resource HexagonGrid::get_resources_of_cluster(Cluster *cluster)
{
Resource res = {0, 0, 0};
for (FieldMeta *elem : *cluster)
{
Resource r_plus = elem->get_resources();
res += r_plus;
}
return res;
}
bool FieldMeta::upgrade(Upgrade upgrade)
{
// check available resources for cluster and consume resources
if (this->upgrades[upgrade])
return this->upgrades[upgrade];
Cluster cluster = this->grid->get_cluster(this);
Resource cluster_resources = this->grid->get_resources_of_cluster(&cluster);
auto pair = UPGRADE_COSTS.find(upgrade);
if (pair != UPGRADE_COSTS.end())
{
Resource costs = pair->second;
if (costs > cluster_resources) // too expensive for you
return this->upgrades[upgrade];
Resource remaining_costs = this->grid->consume_resources_of_cluster(&cluster, costs);
static const Resource neutral = {0, 0, 0};
if (remaining_costs == neutral)
{
this->upgrades[upgrade] = true;
}
}
trigger_event(BOB_FIELDUPGRADEVENT, 0, (void *) this, nullptr);
return this->upgrades[upgrade];
}
FieldMeta *FieldMeta::get_neighbor(Uint8 direction)
{
return this->grid->get_neighbor(this, direction);
}
FieldMeta *HexagonGrid::get_neighbor(FieldMeta *meta, Uint8 direction)
{
Field neighbor_field = meta->get_field().get_neighbor(direction);
FieldMeta *neighbor = nullptr;
auto pair = this->fields.find(neighbor_field);
if (pair != this->fields.end())
neighbor = pair->second;
return neighbor;
}
Cluster HexagonGrid::get_cluster(FieldMeta *field)
{
Cluster visited = Cluster();
FieldMeta *current = nullptr;
std::set<FieldMeta *> seen = {field};
while (!seen.empty()) // still unvisited fields, that can be reached
{
current = *(seen.begin());
seen.erase(current);
for (Uint8 i = 0; i < 6; i++)
{
FieldMeta *neighbor = this->get_neighbor(current, i);
// neighbor is unvisited, unseen and inside same cluster
if (neighbor != nullptr && *(neighbor->get_owner()) == *(current->get_owner())
&& visited.find(neighbor) == visited.end() && seen.find(neighbor) == seen.end())
{
seen.insert(neighbor); // discovered an unseen neighbor, will visit later
}
}
visited.insert(current); // have seen all of the neighbors for this field
}
return visited;
}
void FieldMeta::consume_resources(Resource costs)
{
this->resources -= costs;
}
Resource HexagonGrid::consume_resources_of_cluster(Cluster *cluster, Resource costs)
{
for (FieldMeta *meta : *cluster)
{
// mind the "special" definition of -=, only byte of what you can chew or leave nothing behind
Resource tmp = costs;
costs -= meta->get_resources();
meta->consume_resources(tmp);
}
trigger_event(BOB_FIELDUPDATEEVENT, 0, (void *) this, nullptr);
return costs; // > {0, 0, 0} means there were not enough resources
}
bool Player::fight(FieldMeta *field)
{
if (*this == *(field->get_owner())) // friendly fire
{
return false;
}
// defending player's defense against attacking player's offense
int power_level = field->get_defense(); // it's over 9000
for (Uint8 i = 0; i < 6; i++)
{
FieldMeta *neighbor = field->get_neighbor(i);
if (neighbor == nullptr) // there is no neighbor in this direction
{
continue;
}
if (*(neighbor->get_owner()) == *this) // comparison by UUID
power_level -= neighbor->get_offense();
else if (*(neighbor->get_owner()) == *(field->get_owner()))
power_level += neighbor->get_defense();
// else ignore, field / player not part of the fight (e.g. default player)
}
if (power_level < 0) // attacking player has won
{
field->set_owner(this);
this->fought = true;
return true;
}
this->fought = true;
return false;
}
void FieldMeta::handle_event(const SDL_Event *event)
{
if (event->type == BOB_NEXTROUNDEVENT)
{
this->regenerate_resources();
this->changed = true;
}
}
void FieldMeta::load(SDL_Renderer *renderer, Layout *layout)
{
Point precise_location = this->field.field_to_point(layout);
SDL_Point location;
location.x = (int) precise_location.x;
location.y = (int) precise_location.y;
std::vector<Point> polygon = this->field.field_to_polygon(layout);
SDL_Color color = this->owner->get_color();
Sint16 vx[6];
Sint16 vy[6];
for (int i = 0; i < 6; i++)
{
vx[i] = (Sint16) polygon[i].x;
vy[i] = (Sint16) polygon[i].y;
}
if (this->owner->get_id().is_nil())
color = {0x77, 0x77, 0x77, 0xff};
if (this->fighting)
color = {0x0, 0x77, 0x77, 0xff};
filledPolygonRGBA(renderer, vx, vy, 6, color.r, color.g, color.b, 0x77);
SDL_Color inverse;
inverse.r = (Uint8) (color.r + 0x77);
inverse.g = (Uint8) (color.g + 0x77);
inverse.b = (Uint8) (color.b + 0x77);
inverse.a = 0xff;
double resource_size = layout->size / 4;
if (this->resources_base.triangle > 0)
{
static const SDL_Point trigon[] = {{0, -1},
{-1, 1},
{1, 1}};
for (int i = 0; i < 3; i++)
{
vx[i] = (Sint16) (location.x + (trigon[i].x * resource_size));
vy[i] = (Sint16) (location.y + (trigon[i].y * resource_size));
}
trigonRGBA(renderer, vx[0], vy[0], vx[1], vy[1], vx[2], vy[2], inverse.r, inverse.g, inverse.b, inverse.a);
}
if (this->resources_base.circle > 0)
{
circleRGBA(renderer, (Sint16) (location.x), Sint16(location.y), (Sint16) resource_size, inverse.r, inverse.g,
inverse.b, inverse.a);
}
if (this->resources_base.square > 0)
{
static const SDL_Point square[] = {{-1, -1},
{-1, 1},
{1, 1},
{1, -1}};
for (int i = 0; i < 4; i++)
{
vx[i] = (Sint16) (location.x + square[i].x * resource_size);
vy[i] = (Sint16) (location.y + square[i].y * resource_size);
}
polygonRGBA(renderer, vx, vy, 4, inverse.r, inverse.g, inverse.b, inverse.a);
}
}
void HexagonGrid::load()
{
if (this->texture == nullptr)
{
SDL_Rect db;
SDL_GetDisplayBounds(0, &db);
this->texture = SDL_CreateTexture(this->renderer->get_renderer(), SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, db.w, db.h);
}
this->renderer->set_target(this->texture);
renderer->set_draw_color({0x00, 0x00, 0x00, 0x00});
this->renderer->clear();
SDL_Rect bounds = this->layout->box;
bounds.x -= 4 * this->layout->size;
bounds.y -= 4 * this->layout->size;
bounds.w += 8 * this->layout->size;
bounds.h += 8 * this->layout->size;
renderer->set_draw_color({0xff, 0xff, 0xff, 0xff});
renderer->set_blend_mode(SDL_BLENDMODE_BLEND);
Field some_field = {0, 0, 0};
std::vector<Point> norm_polygon = some_field.field_to_polygon_normalized(this->layout);
for (std::pair<Field, FieldMeta *> const &elem : this->fields)
{
Field field = elem.first;
Point center = field.field_to_point(this->layout);
SDL_Point i_c;
i_c.x = (int) center.x;
i_c.y = (int) center.y;
if (inside_target(&bounds, &i_c))
{
elem.second->load(this->renderer->get_renderer(), this->layout);
//std::vector<SDL_Point> polygon = field.field_to_polygon_sdl(this->layout);
Sint16 vx[6];
Sint16 vy[6];
for (int i = 0; i < 6; i++)
{
vx[i] = (Sint16) (center.x + norm_polygon[i].x);
vy[i] = (Sint16) (center.y + norm_polygon[i].y);
}
/*if (SDL_RenderDrawLines(renderer->get_renderer(), polygon.data(), 7) < 0)
{
throw SDL_RendererException();
}*/
polygonRGBA(renderer->get_renderer(), vx, vy, 6, 0xff, 0xff, 0xff, 0xff);
if (elem.first == this->marker->get_field())
{
filledPolygonRGBA(this->renderer->get_renderer(), vx, vy, 6, 0x77, 0x77, 0x77, 0x77);
}
}
}
this->renderer->set_target(nullptr);
}
void HexagonGrid::render(Renderer *renderer)
{
if (this->changed)
{
this->load();
this->changed = false;
}
renderer->copy(this->texture, &(this->layout->box), &(this->layout->box));
}
void HexagonGrid::handle_event(SDL_Event *event)
{
Player *attacking;
Player *owner;
SDL_Point mouse = {0, 0};
SDL_GetMouseState(&mouse.x, &mouse.y);
int scroll = this->layout->size / 10 * event->wheel.y;
double old_size = this->layout->size;
SDL_Point old_origin = this->layout->origin;
switch (event->type)
{
case SDL_MOUSEWHEEL:
if (old_size + scroll < 20)
{
this->layout->size = 20;
}
else if (old_size + scroll > 100)
{
this->layout->size = 100;
}
else
{
this->layout->size += scroll;
}
this->move(((1.0 - (double) this->layout->size / old_size) * (mouse - old_origin)));
if (!on_rectangle(&layout->box))
{
this->layout->size -= scroll;
}
else
{
this->update_marker();
this->changed = true;
}
break;
case SDL_MOUSEMOTION:
if (this->panning)
{
Point marker_pos = this->marker->get_field().field_to_point(this->layout);
SDL_Point p;
p.x = (int) marker_pos.x;
p.y = (int) marker_pos.y;
this->move(mouse - p);
}
this->update_marker();
break;
case SDL_MOUSEBUTTONDOWN:
switch (event->button.button)
{
case SDL_BUTTON_MIDDLE:
this->panning = !(this->panning);
break;
case SDL_BUTTON_RIGHT:
trigger_event(BOB_FIELDSELECTEDEVENT, 0, (void *) this->marker, nullptr);
this->changed = true;
break;
case SDL_BUTTON_LEFT:
if (this->selecting)
{
if (this->place(this->selecting_player, this->marker))
{
trigger_event(BOB_PLAYERADDED, 0, (void *) this->selecting_player, nullptr);
}
else
{
trigger_event(BOB_PLAYERADDED, 1, (void *) this->selecting_player, nullptr);
}
this->selecting = false;
}
else
{
owner = this->marker->get_owner();
if (*owner == *(Player::current_player))
{
if (this->first_attack != nullptr)
{
this->first_attack->set_fighting(false);
}
this->first_attack = this->marker;
this->first_attack->set_fighting(true);
}
else if (this->first_attack != nullptr)
{
attacking = this->first_attack->get_owner();
attacking->fight(this->marker);
this->first_attack->set_fighting(false);
this->first_attack = nullptr;
}
}
changed = true;
break;
default:
break;
}
break;
default:
if (event->type == BOB_NEXTROUNDEVENT)
{
for (std::pair<Field, FieldMeta *> field : this->fields)
{
field.second->regenerate_resources();
}
}
break;
}
for (auto elem : this->fields)
{
elem.second->handle_event(event);
}
}
void HexagonGrid::move(SDL_Point m)
{
this->layout->origin = this->layout->origin + m;
// check if some part is inside layout->box
if (!on_rectangle(&layout->box))
this->layout->origin = this->layout->origin - m;
this->update_marker();
this->changed = true;
}
void HexagonGrid::update_marker()
{
SDL_Point m = {0, 0};
SDL_GetMouseState(&(m.x), &(m.y));
Point p = {0.0, 0.0};
p.x = m.x;
p.y = m.y;
FieldMeta *n_marker = this->point_to_field(p);
if (n_marker != nullptr)
{
this->marker = n_marker;
trigger_event(BOB_MARKERUPDATE, 0, (void *) n_marker, nullptr);
}
else
{
trigger_event(BOB_MARKERUPDATE, 1, (void *) marker, nullptr);
}
this->changed = true;
}
FieldMeta *HexagonGrid::point_to_field(const Point p)
{
Field field = p.point_to_field(this->layout);
FieldMeta *meta = nullptr;
auto pair = this->fields.find(field);
if (pair != this->fields.end())
meta = pair->second;
return meta;
}
FieldMeta *HexagonGrid::get_field(Field field)
{
FieldMeta *meta = nullptr;
auto pair = this->fields.find(field);
if (pair != this->fields.end())
meta = pair->second;
return meta;
}
bool HexagonGrid::on_rectangle(SDL_Rect *rect)
{
// check if center inside rect for ANY field
for (const auto &pair : this->fields)
{
Point precise_p = pair.first.field_to_point(layout);
SDL_Point p;
p.x = (int) precise_p.x;
p.y = (int) precise_p.y;
if (p.x > rect->x && p.y > rect->y && p.x < (rect->x + rect->w) && p.y < (rect->y + rect->h))
{
return true;
}
}
return false;
}
void HexagonGrid::update_dimensions(SDL_Point dimensions)
{
this->layout->origin = {dimensions.x / 2, dimensions.y / 2};
this->layout->box.w = dimensions.x;
this->layout->box.h = dimensions.y;
this->changed = true;
}
Point HexagonGrid::field_to_point(FieldMeta *field)
{
return field->get_field().field_to_point(this->layout);
}
bool inside_target(const SDL_Rect *target, const SDL_Point *position)
{
return target->x < position->x && target->x + target->w > position->x && target->y < position->y &&
target->y + target->h > position->y;
}
bool HexagonGrid::place(Player *player, FieldMeta *center)
{
std::vector<FieldMeta *> selected;
selected.push_back(center);
for (Uint8 i = 0; i < 6; i++)
{
FieldMeta *neighbor = center->get_neighbor(i);
if (neighbor->get_owner()->get_id() == boost::uuids::nil_uuid())
{
selected.push_back(neighbor);
}
else
{
return false;
}
}
static const Resource lower = {0, 0, 0};
Resource resources = {0, 0, 0};
for (auto r : selected)
{
resources += r->get_resources();
}
if (resources > lower)
{
for (auto i : selected)
{
i->set_owner(player);
i->set_offense(1);
}
return true;
}
return false;
}
void Player::handle_event(SDL_Event *event)
{
if (event->type == BOB_NEXTROUNDEVENT)
{
this->fought = false;
}
}