1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
|
#include "UniverseGenerator.h"
#include "../util/Random.h"
#include "../util/i18n.h"
#include "../util/MultiplayerCommon.h"
#include "../util/GameRules.h"
#include "../util/AppInterface.h"
#include "../Empire/Empire.h"
#include "../Empire/EmpireManager.h"
#include "../universe/Planet.h"
#include "../universe/System.h"
#include "../universe/Species.h"
#include "../util/ScopedTimer.h"
#include <boost/container/flat_map.hpp>
#include <limits>
namespace {
DeclareThreadSafeLogger(effects);
}
//////////////////////////////////////////
// Universe Setup Functions //
//////////////////////////////////////////
namespace Delauney {
/** simple 2D point. would have used array of systems, but System
* class has limits on the range of positions that would interfere
* with the triangulation algorithm (need a single large covering
* triangle that overlaps all actual points being triangulated) */
class DTPoint {
public:
DTPoint() = default;
DTPoint(double x_, double y_) :
x(x_),
y(y_)
{}
double x = 0.0;
double y = 0.0;
};
/* simple class for an integer that has an associated "sorting value",
* so the integer can be stored in a list sorted by something other than
* the value of the integer */
class SortValInt {
public:
SortValInt(int num_, double sort_val_) :
num(num_),
sortVal(sort_val_)
{}
int num;
double sortVal;
};
/** list of three interger array indices, and some additional info about
* the triangle that the corresponding points make up, such as the
* circumcentre and radius, and a function to find if another point is in
* the circumcircle */
class DTTriangle {
public:
DTTriangle() = default;
DTTriangle(int vert1, int vert2, int vert3,
const std::vector<Delauney::DTPoint>& points);
///< determines whether a specified point is within the circumcircle of the triangle
bool PointInCircumCircle(const Delauney::DTPoint& p);
const std::array<int, 3>& Verts() {return verts;}
private:
std::array<int, 3> verts{0, 0, 0}; ///< indices of vertices of triangle
Delauney::DTPoint centre{0.0, 0.0}; ///< location of circumcentre of triangle
double radius2{0.0}; ///< radius of circumcircle squared
};
DTTriangle::DTTriangle(int vert1, int vert2, int vert3,
const std::vector<Delauney::DTPoint>& points)
{
if (vert1 == vert2 || vert1 == vert3 || vert2 == vert3)
throw std::runtime_error("Attempted to create Triangle with two of the same vertex indices.");
// record indices of vertices of triangle
verts = {vert1, vert2, vert3};
// extract position info for vertices
const double& x1 = points[vert1].x;
const double& x2 = points[vert2].x;
const double& x3 = points[vert3].x;
const double& y1 = points[vert1].y;
const double& y2 = points[vert2].y;
const double& y3 = points[vert3].y;
// calculate circumcircle and circumcentre of triangle
double a, Sx, Sy, b;
a = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
Sx = 0.5 * ((x1 * x1 + y1 * y1) * (y2 - y3) +
(x2 * x2 + y2 * y2) * (y3 - y1) +
(x3 * x3 + y3 * y3) * (y1 - y2));
Sy = -0.5* ((x1 * x1 + y1 * y1) * (x2 - x3) +
(x2 * x2 + y2 * y2) * (x3 - x1) +
(x3 * x3 + y3 * y3) * (x1 - x2));
b = ((x1 * x1 + y1 * y1) * (x2 * y3 - x3 * y2) +
(x2 * x2 + y2 * y2) * (x3 * y1 - x1 * y3) +
(x3 * x3 + y3 * y3) * (x1 * y2 - x2 * y1));
// make sure nothing funky's going on...
if (std::abs(a) < 0.01)
throw std::runtime_error("Attempted to find circumcircle for a triangle with vertices in a line.");
// finish!
centre.x = Sx / a;
centre.y = Sy / a;
radius2 = (Sx*Sx + Sy*Sy)/(a*a) + b/a;
};
bool DTTriangle::PointInCircumCircle(const Delauney::DTPoint& p) {
double vectX = p.x - centre.x;
double vectY = p.y - centre.y;
return (vectX*vectX + vectY*vectY < radius2);
};
/** Runs a Delauney Triangulation on a set of 2D points corresponding
* to the locations of the systems in \a systems_vec */
std::list<Delauney::DTTriangle> DelauneyTriangulate(
const std::vector<std::shared_ptr<const System>>& systems_vec, double universe_width)
{
// ensure a useful list of systems was passed...
if (systems_vec.empty()) {
ErrorLogger() << "Attempted to run Delauney Triangulation on empty array of systems";
return std::list<Delauney::DTTriangle>();
}
// extract systems positions from system objects.
std::vector<Delauney::DTPoint> points_vec;
points_vec.reserve(systems_vec.size() + 3);
for (auto& system : systems_vec)
points_vec.push_back({system->X(), system->Y()});
// entries in points_vec correspond to entries in \a systems_vec
// so that the index of an item in systems_vec will have a
// corresponding points at that index in points_vec
// add points for covering triangle. the point positions should be big
// enough to form a triangle that encloses all the points in points_vec
// (or at least one whose circumcircle covers all points)
points_vec.push_back({-1.0, -1.0});
points_vec.push_back({2.0 * (universe_width + 1.0), -1.0});
points_vec.push_back({-1.0, 2.0 * (universe_width + 1.0)});
// initialize triangle_list.
// add last three points into the first triangle, the "covering triangle"
std::list<Delauney::DTTriangle> triangle_list;
int num_points_in_vec = points_vec.size();
triangle_list.push_front({num_points_in_vec - 1, num_points_in_vec - 2,
num_points_in_vec - 3, points_vec});
if (points_vec.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
ErrorLogger() << "Attempted to run Delauney Triangulation on " << points_vec.size()
<< " points. The limit is " << std::numeric_limits<int>::max();
return std::list<Delauney::DTTriangle>();
}
// loop through points generated from systems, excluding the final
// 3 points added for the covering triangle
for (std::size_t n = 0; n < points_vec.size() - 3; n++) {
// list of indices in vector of points extracted from removed
// triangles that need to be retriangulated
std::list<Delauney::SortValInt> point_idx_list;
const auto& cur_point = points_vec.at(n);
// check each triangle to see if the current point lies in its
// circumcircle. if so, delete the triangle and add its vertices to a list
auto cur_tri_it = triangle_list.begin();
while (cur_tri_it != triangle_list.end()) {
// get current triangle
Delauney::DTTriangle& tri = *cur_tri_it;
// check if point to be added to triangulation is within the
// circumcircle for the current triangle
if (!tri.PointInCircumCircle(cur_point)) {
// point not in circumcircle for this triangle.
// go next triangle in list
++cur_tri_it;
continue;
}
// point is in circumcircle for this triangle.
// insert the triangle's vertices' indices into the
// list. add in sorted position based on angle of direction
// to current point n being inserted. don't add if doing so
// would duplicate an index already in the list
for (int tri_vert_idx : tri.Verts()) {
// get sorting value to order points clockwise
// circumferentially around point n
// vector from point n to current point
double vx = points_vec[tri_vert_idx].x - points_vec[n].x;
double vy = points_vec[tri_vert_idx].y - points_vec[n].y;
double mag = std::sqrt(vx*vx + vy*vy);
// normalize
vx /= mag;
vy /= mag;
// dot product with (0, 1) is vy, magnitude of cross
// product is vx this gives a range of "sort value"
// from -2 to 2, around the circle
double sort_value = (vx >= 0) ? (vy + 1) : (-vy - 1);
// iterate through list, finding insert spot and
// verifying uniqueness (or add if list is empty)
auto idx_list_it = point_idx_list.begin();
if (idx_list_it == point_idx_list.end()) {
// list is empty
point_idx_list.push_back({tri_vert_idx, sort_value});
} else {
while (idx_list_it != point_idx_list.end()) {
if (idx_list_it->num == tri_vert_idx)
break;
if (idx_list_it->sortVal > sort_value) {
point_idx_list.insert(idx_list_it, {tri_vert_idx, sort_value});
break;
}
++idx_list_it;
}
if (idx_list_it == point_idx_list.end()) {
// point wasn't added, so should go at end
point_idx_list.push_back({tri_vert_idx, sort_value});
}
}
} // end for c
// remove current triangle from list of triangles
cur_tri_it = triangle_list.erase(cur_tri_it);
} // end while
// add triangle for last and first points and n
triangle_list.push_front(
{static_cast<int>(n), (point_idx_list.front()).num, (point_idx_list.back()).num, points_vec});
// go through list of points, making new triangles out of them
auto idx_list_it = point_idx_list.begin();
int num = idx_list_it->num;
++idx_list_it;
while (idx_list_it != point_idx_list.end()) {
int num2 = num;
num = idx_list_it->num;
triangle_list.push_front({static_cast<int>(n), num2, num, points_vec});
++idx_list_it;
} // end while
} // end for
DebugLogger() << "DelauneyTriangulate generated list of "
<< triangle_list.size() << " triangles";
return triangle_list;
}
}
namespace {
int IntSetMapSizeCount(const std::map<int, std::set<int>>& in) {
int retval{0};
for (const auto& entry : in)
retval += entry.second.size();
return retval;
}
/** Used by GenerateStarlanes. Determines if two systems are connected by
* maxLaneJumps or less edges on graph. */
bool ConnectedWithin(int system1, int system2, int maxLaneJumps,
const std::map<int, std::set<int>>& system_lanes)
{
// check for simple cases for quick termination
if (system1 == system2) return true; // system is always connected to itself
if (0 == maxLaneJumps) return false; // no system is connected to any other system by less than 1 jump
if (!system_lanes.contains(system1)) return false; // start system not in lanes map
if (!system_lanes.contains(system2)) return false; // destination system not in lanes map
if (system_lanes.at(system1).empty()) return false; // no lanes out of start system
if (system_lanes.at(system2).empty()) return false; // no lanes out of destination system
// list of indices of systems that are accessible from previously visited systems.
// when a new system is found to be accessible, it is added to the back of the
// list. the list is iterated through from front to back to find systems
// to examine
std::vector<int> accessibleSystemsList;
accessibleSystemsList.reserve(system_lanes.size());
// Map from system_id to the number of starlane jumps away from system1
// that sytsem is. This indicates the depth of the search, which allows
// the serch to terminate after searching to the depth of maxLaneJumps
// without finding system2
boost::container::flat_map<int, int> accessibleSystemsMap;
accessibleSystemsMap.reserve(system_lanes.size());
for (const auto sys : system_lanes | range_keys)
accessibleSystemsMap.emplace_hint(accessibleSystemsMap.end(), sys, -1);
static constexpr int mapped_type_max = std::numeric_limits<decltype(accessibleSystemsMap)::mapped_type>::max();
maxLaneJumps = std::min(maxLaneJumps, mapped_type_max);
// add starting system to list and set of accessible systems
accessibleSystemsList.push_back(system1);
accessibleSystemsMap[system1] = 0;
// loop through visited systems
for (std::size_t idx = 0; idx < accessibleSystemsList.size(); ++idx) {
auto cur_sys_id = accessibleSystemsList[idx];
// check that iteration hasn't reached maxLaneJumps levels deep, which would
// mean that system2 isn't within maxLaneJumps starlane jumps of system1
auto curDepth = accessibleSystemsMap[cur_sys_id];
if (curDepth >= maxLaneJumps) return false;
// get set of starlanes for this system
for (auto curLaneDest : system_lanes.at(cur_sys_id)) {
// check for goal
if (curLaneDest == system2) return true;
auto& cur_lane_dest_depth = accessibleSystemsMap[curLaneDest];
if (cur_lane_dest_depth < 0) {
// this lane was not yet considered, so update its depth in map and add to list ot consider
accessibleSystemsList.push_back(curLaneDest);
cur_lane_dest_depth = curDepth + 1;
}
}
}
return false; // default
}
/** Removes lanes from passed graph that are angularly too close to
* each other. */
void CullAngularlyTooCloseLanes(double max_lane_uvect_dot_product,
std::map<int, std::set<int>>& system_lanes,
const std::map<int, std::shared_ptr<const System>>& systems)
{
// 2 component vector and vect + magnitude typedefs
typedef std::pair<std::pair<double, double>, double> VectAndMagTypeQQ;
std::set<std::pair<int, int>> lanesToRemoveSet; // start and end stars of lanes to be removed in final step...
// make sure data is consistent
if (system_lanes.size() != systems.size()) {
ErrorLogger() << "CullAngularlyTooCloseLanes got different size vectors of lane sets and systems. Doing nothing.";
return;
}
if (systems.size() < 3) return; // nothing worth doing for less than three systems
//DebugLogger() << "Culling Too Close Angularly Lanes";
// loop through systems
for (const auto& entry : systems) {
// can't have pairs of lanes departing from a system if that system
// has less than two lanes
if (system_lanes.at(entry.first).size() < 2)
continue;
// get position of current system (for use in calculated vectors)
auto startX = entry.second->X();
auto startY = entry.second->Y();
auto cur_sys_id = entry.first;
/** componenets of vectors of lanes of current system, indexed by destination system number */
std::map<int, VectAndMagTypeQQ> laneVectsMap;
// get unit vectors for all lanes of this system
auto laneSetIter1 = system_lanes[cur_sys_id].begin();
while (laneSetIter1 != system_lanes[cur_sys_id].end()) {
// get destination for this lane
auto dest1 = *laneSetIter1;
// get vector to this lane destination
auto vectX1 = systems.at(dest1)->X() - startX;
auto vectY1 = systems.at(dest1)->Y() - startY;
// normalize
auto mag1 = std::sqrt(vectX1 * vectX1 + vectY1 * vectY1);
vectX1 /= mag1;
vectY1 /= mag1;
// store lane in map of lane vectors
laneVectsMap.emplace(dest1, VectAndMagTypeQQ{{vectX1, vectY1}, mag1});
++laneSetIter1;
}
// iterate through lanes of cur_sys_id
laneSetIter1 = system_lanes[cur_sys_id].begin();
++laneSetIter1; // start at second, since iterators are used in pairs, and starting both at the first wouldn't be a valid pair
while (laneSetIter1 != system_lanes[cur_sys_id].end()) {
// get destination of current starlane
const auto& dest1 = *laneSetIter1;
auto lane1{(cur_sys_id < dest1) ?
std::pair{cur_sys_id, dest1} : std::pair{dest1, cur_sys_id}};
// check if this lane has already been added to the set of lanes to remove
if (lanesToRemoveSet.contains(lane1)) {
++laneSetIter1;
continue;
}
// extract data on starlane vector...
auto laneVectsMapIter = laneVectsMap.find(dest1);
assert(laneVectsMapIter != laneVectsMap.end());
const auto& [temp_vec, mag1] = laneVectsMapIter->second;
const auto& [vectX1, vectY1] = temp_vec;
// iterate through other lanes of cur_sys_id, in order
// to get all possible pairs of lanes
auto laneSetIter2 = system_lanes[cur_sys_id].begin();
while (laneSetIter2 != laneSetIter1) {
auto dest2 = *laneSetIter2;
std::pair<int, int> lane2;
if (cur_sys_id < dest2)
lane2 = {cur_sys_id, dest2};
else
lane2 = {dest2, cur_sys_id};
// check if this lane has already been added to the
// set of lanes to remove
if (lanesToRemoveSet.contains(lane2)) {
++laneSetIter2;
continue;
}
// extract data on starlane vector...
laneVectsMapIter = laneVectsMap.find(dest2);
assert(laneVectsMapIter != laneVectsMap.end());
const auto& [temp_vect2, mag2] = laneVectsMapIter->second;
const auto& [vectX2, vectY2] = temp_vect2;
// find dot product
auto dotProd = vectX1 * vectX2 + vectY1 * vectY2;
// if dotProd is big enough, then lanes are too close angularly
// thus one needs to be removed.
if (dotProd > max_lane_uvect_dot_product) {
// preferentially remove the longer lane
if (mag1 > mag2) {
lanesToRemoveSet.insert(lane1);
break; // don't need to check any more lanes against lane1, since lane1 has been removed
} else {
lanesToRemoveSet.insert(lane2);
}
}
++laneSetIter2;
}
++laneSetIter1;
}
}
// iterate through set of lanes to remove, and remove them in turn...
auto lanes_to_remove_it = lanesToRemoveSet.begin();
auto lanes_to_remove_end = lanesToRemoveSet.end();
while (lanes_to_remove_it != lanes_to_remove_end) {
const auto& [l_start, l_end] = *lanes_to_remove_it;
system_lanes[l_start].erase(l_end);
system_lanes[l_end].erase(l_start);
// check that removing lane hasn't disconnected systems
if (!ConnectedWithin(l_start, l_end, systems.size(), system_lanes)) {
// they aren't connected... reconnect them
system_lanes[l_start].insert(l_end);
system_lanes[l_end].insert(l_start);
}
++lanes_to_remove_it;
}
}
/** Removes lanes from passed graph that are too long. */
void CullTooLongLanes(double max_lane_length,
std::map<int, std::set<int>>& system_lanes,
const std::map<int, std::shared_ptr<const System>>& systems)
{
DebugLogger() << "CullTooLongLanes max lane length: " << max_lane_length
<< " potential lanes: " << IntSetMapSizeCount(system_lanes)
<< " systems: " << systems.size();
ScopedTimer timer("CullTooLongLanes", std::chrono::milliseconds{1});
// map, indexed by lane length, of start and end stars of lanes to be removed
std::multimap<double, std::pair<int, int>, std::greater<double>> lanes_to_remove;
// make sure data is consistent
if (system_lanes.size() != systems.size())
return;
// nothing worth doing for less than two systems (no lanes!)
if (systems.size() < 2)
return;
// get squared max lane lenth, so as to eliminate the need to take square roots of lane lenths...
const double max_lane_length2 = max_lane_length*max_lane_length;
std::size_t total_lanes_count = 0;
// loop through systems and lanes to find long lanes
for (const auto& [cur_sys_id, cur_system] : systems) {
// get position of current system (for use in calculating vector)
double startX = cur_system->X();
double startY = cur_system->Y();
// iterate through all lanes in system, checking lengths and
// marking to be removed if necessary
for (const auto dest_sys_id : system_lanes[cur_sys_id]) {
total_lanes_count++;
// convert start and end into ordered pair to represent lane
std::pair<int, int> lane;
if (cur_sys_id < dest_sys_id)
lane = {cur_sys_id, dest_sys_id};
else
lane = {dest_sys_id, cur_sys_id};
// get vector to this lane destination
const auto& dest_system = systems.at(dest_sys_id);
auto vectX = dest_system->X() - startX;
auto vectY = dest_system->Y() - startY;
// compare magnitude of vector to max allowed
double lane_length2 = vectX*vectX + vectY*vectY;
if (lane_length2 > max_lane_length2) {
// lane is too long! mark it to be removed
TraceLogger() << "CullTooLongLanes wants to remove lane of length "
<< std::sqrt(lane_length2)
<< " between systems with ids: "
<< cur_sys_id << " and " << dest_sys_id;
lanes_to_remove.emplace(lane_length2, lane);
}
}
}
DebugLogger() << "CullTooLongLanes identified " << lanes_to_remove.size()
<< " long lanes to possibly remove of " << total_lanes_count << " lanes total";
std::size_t total_checked_lanes = 0;
std::size_t removable_lanes = 0;
// TODO: Consider re-implementing this as a minimal spanning tree
// and immediately remove any lane not in that tree, and
// thus avoid having to check connectivity for each
// potential lane removal
// Iterate through set of lanes to remove, and remove them in turn.
// Lanes are sorted by length, so are processed longest-first
for (const auto& [dist, lane] : lanes_to_remove) {
if (!system_lanes[lane.first].contains(lane.second) ||
!system_lanes[lane.second].contains(lane.first))
{ continue; }
// remove lane
system_lanes[lane.first].erase(lane.second);
system_lanes[lane.second].erase(lane.first);
total_checked_lanes++;
// if removing lane has disconnected systems, reconnect them
if (!ConnectedWithin(lane.first, lane.second, systems.size(), system_lanes)) {
system_lanes[lane.first].insert(lane.second);
system_lanes[lane.second].insert(lane.first);
TraceLogger() << "CullTooLongLanes can't remove lane between systems with ids: "
<< lane.first << " and " << lane.second
<< " because they would then be disconnected (more than "
<< systems.size() << " jumps apart)";
} else {
removable_lanes++;
TraceLogger() << "CullTooLongLanes removing lane between systems with ids: "
<< lane.first << " and " << lane.second;
}
}
DebugLogger() << "CullTooLongLanes left with " << IntSetMapSizeCount(system_lanes)
<< " lanes. " << total_checked_lanes << " were checked and "
<< removable_lanes << " were removable and "
<< total_checked_lanes - removable_lanes << " were not removable";
}
}
void GenerateStarlanes(int max_jumps_between_systems, int max_starlane_length,
Universe& universe, const EmpireManager& empires)
{
DebugLogger() << "GenerateStarlanes max jumps b/w sys: " << max_jumps_between_systems
<< " max lane length: " << max_starlane_length;
std::vector<int> triangle_vertices; // indices of stars that form vertices of a triangle
// array of set to store final, included starlanes for each star
std::map<int, std::set<int>> system_lanes;
// array of set to store possible starlanes for each star,
// as extracted form triangulation
std::map<int, std::set<int>> potential_system_lanes;
// get systems
auto sys_rng = std::as_const(universe.Objects()).all<System>();
std::vector<std::shared_ptr<const System>> sys_vec{sys_rng.begin(), sys_rng.end()};
std::map<int, std::shared_ptr<const System>> sys_map;
std::transform(sys_rng.begin(), sys_rng.end(), std::inserter(sys_map, sys_map.end()),
[](const std::shared_ptr<const System>& p) { return std::pair{p->ID(), p}; });
// generate lanes
if (GetGameRules().Get<bool>("RULE_STARLANES_EVERYWHERE")) {
// if the lanes everywhere rules is true, add starlanes to every star
// to every other star...
for (const auto& sys1 : sys_vec) {
for (const auto& sys2 : sys_vec) {
if (sys1->ID() == sys2->ID())
continue;
potential_system_lanes[sys1->ID()].insert(sys2->ID());
}
}
DebugLogger() << "Generated " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes between all system pairs";
CullTooLongLanes(max_starlane_length, potential_system_lanes, sys_map);
DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after length culling";
system_lanes = potential_system_lanes;
} else {
// pass systems to Delauney Triangulation routine, getting array of triangles back
auto triangle_list = Delauney::DelauneyTriangulate(sys_vec, universe.UniverseWidth());
if (triangle_list.empty()) {
ErrorLogger() << "Got blank list of triangles from Triangulation.";
return;
}
// extract triangles from list, add edges to sets of potential starlanes
// for each star (in array)
while (!triangle_list.empty()) {
// extract indices for the corners of the triangles, which should
// correspond to indices in sys_vec, except that there can also be
// indices up to sys_vec.size() + 2, which correspond to extra points
// used by the algorithm
const auto& [s1, s2, s3] = triangle_list.front().Verts();
if (s1 < 0 || s2 < 0 || s3 < 0) {
ErrorLogger() << "Got negative vector indices from DelauneyTriangulate!";
triangle_list.pop_front();
continue;
}
// get system ids for ends of lanes from the sys_vec indices
int sys1_id = INVALID_OBJECT_ID;
if (static_cast<std::size_t>(s1) < sys_vec.size())
sys1_id = sys_vec.at(s1)->ID();
int sys2_id = INVALID_OBJECT_ID;
if (static_cast<std::size_t>(s2) < sys_vec.size())
sys2_id = sys_vec.at(s2)->ID();
int sys3_id = INVALID_OBJECT_ID;
if (static_cast<std::size_t>(s3) < sys_vec.size())
sys3_id = sys_vec.at(s3)->ID();
// add starlanes to list of potential starlanes for each star,
// making sure each pair involves only valid indices into sys_vec
if (sys1_id != INVALID_OBJECT_ID && sys2_id != INVALID_OBJECT_ID) {
potential_system_lanes[sys1_id].insert(sys2_id);
potential_system_lanes[sys2_id].insert(sys1_id);
}
if (sys2_id != INVALID_OBJECT_ID && sys3_id != INVALID_OBJECT_ID) {
potential_system_lanes[sys2_id].insert(sys3_id);
potential_system_lanes[sys3_id].insert(sys2_id);
}
if (sys3_id != INVALID_OBJECT_ID && sys1_id != INVALID_OBJECT_ID) {
potential_system_lanes[sys3_id].insert(sys1_id);
potential_system_lanes[sys1_id].insert(sys3_id);
}
triangle_list.pop_front();
}
DebugLogger() << "Extracted " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes from triangulation";
CullTooLongLanes(max_starlane_length, potential_system_lanes, sys_map);
DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after length culling";
CullAngularlyTooCloseLanes(0.98, potential_system_lanes, sys_map);
DebugLogger() << "Left with " << IntSetMapSizeCount(potential_system_lanes) << " potential starlanes after angular culling";
system_lanes = potential_system_lanes;
// attempt removing lanes, but don't do so if it would make the systems
// the lane connects too far apart
for (const auto& [sys1_id, sys1_lanes] : potential_system_lanes) {
for (auto& sys2_id : sys1_lanes) {
if (sys2_id <= sys1_id)
continue; // skip lanes that should already have been checked since lanes exist in both directions
// try removing lane
system_lanes[sys1_id].erase(sys2_id);
system_lanes[sys2_id].erase(sys1_id);
if (!ConnectedWithin(sys1_id, sys2_id, max_jumps_between_systems, system_lanes)) {
// lane removal was a bad idea. restore it
system_lanes[sys1_id].insert(sys2_id);
system_lanes[sys2_id].insert(sys1_id);
}
}
}
}
// add the starlane to the stars
for (auto& sys : universe.Objects().all<System>()) {
const auto& sys_lanes = system_lanes[sys->ID()];
for (auto& lane_end_id : sys_lanes)
sys->AddStarlane(lane_end_id);
}
DebugLogger() << "Initializing System Graph";
universe.InitializeSystemGraph(empires);
}
void SetActiveMetersToTargetMaxCurrentValues(ObjectMap& object_map) {
TraceLogger(effects) << "SetActiveMetersToTargetMaxCurrentValues";
// check for each pair of meter types. if both exist, set active
// meter current value equal to target meter current value.
for (const auto& object : object_map.all()) {
for (auto& entry : AssociatedMeterTypes()) {
if (Meter* meter = object->GetMeter(entry.first)) {
if (Meter* targetmax_meter = object->GetMeter(entry.second))
meter->SetCurrent(targetmax_meter->Current());
}
}
}
}
void SetNativePopulationValues(ObjectMap& object_map) {
for (const auto& object : object_map.all()) {
Meter* meter = object->GetMeter(MeterType::METER_POPULATION);
Meter* targetmax_meter = object->GetMeter(MeterType::METER_TARGET_POPULATION);
// only applies to unowned planets
if (meter && targetmax_meter && object->Unowned()) {
double r = RandZeroToOne();
double factor = (0.1 < r) ? r : 0.1;
meter->SetCurrent(targetmax_meter->Current() * factor);
}
}
}
bool SetEmpireHomeworld(Empire* empire, int planet_id, std::string species_name,
ScriptingContext& context)
{
// get home planet and system, check if they exist
auto home_planet = context.ContextObjects().get<Planet>(planet_id);
if (!home_planet)
return false;
auto home_system = context.ContextObjects().get<System>(home_planet->SystemID());
if (!home_system)
return false;
DebugLogger() << "SetEmpireHomeworld: setting system " << home_system->ID()
<< " (planet " << home_planet->ID() << ") to be home system for empire " << empire->EmpireID();
// get species, check if it exists
auto species = context.species.GetSpecies(species_name);
if (!species) {
ErrorLogger() << "SetEmpireHomeworld: couldn't get species \""
<< species_name << "\" to set with homeworld id " << home_planet->ID();
return false;
}
// set homeword's planet type to the preferred type for this species
const auto& spte = species->PlanetEnvironments();
if (!spte.empty()) {
// invert map from planet type to environments to map from
// environments to type, sorted by environment
std::map<PlanetEnvironment, PlanetType> sept;
for (const auto& entry : spte)
sept[entry.second] = entry.first;
// assuming enum values are ordered in increasing goodness...
PlanetType preferred_planet_type = sept.rbegin()->second;
// both the current as well as the original type need to be set to the preferred type
home_planet->SetType(preferred_planet_type);
home_planet->SetOriginalType(preferred_planet_type);
// set planet size according to planet type
if (preferred_planet_type == PlanetType::PT_ASTEROIDS)
home_planet->SetSize(PlanetSize::SZ_ASTEROIDS);
else if (preferred_planet_type == PlanetType::PT_GASGIANT)
home_planet->SetSize(PlanetSize::SZ_GASGIANT);
else
home_planet->SetSize(PlanetSize::SZ_MEDIUM);
}
home_planet->Colonize(empire->EmpireID(), species_name, Meter::LARGE_VALUE, context);
context.species.AddSpeciesHomeworld(std::move(species_name), home_planet->ID());
empire->SetCapitalID(home_planet->ID(), context.ContextObjects());
context.Empires().RefreshCapitalIDs();
empire->AddExploredSystem(home_planet->SystemID(), BEFORE_FIRST_TURN, context.ContextObjects());
return true;
}
void InitEmpires(const std::map<int, PlayerSetupData>& player_setup_data, EmpireManager& empires) {
DebugLogger() << "Initializing " << player_setup_data.size() << " empires";
// copy empire colour table, so that individual colours can be removed after they're used
auto colors{EmpireColors()};
// create empire objects and do some basic initilization for each player
for (auto& [empire_id, psd] : player_setup_data) {
// use map key for empire ID so that the calling code can get the
// correct empire for each player in player_setup_data
if (empire_id == ALL_EMPIRES)
ErrorLogger() << "InitEmpires empire id (" << empire_id << ") is invalid";
const auto& player_name = psd.player_name;
auto empire_colour = psd.empire_color;
bool authenticated = psd.authenticated;
// validate or generate empire colour
// ensure no other empire gets auto-assigned this colour automatically
auto color_it = std::find(colors.begin(), colors.end(), empire_colour);
if (color_it != colors.end())
colors.erase(color_it);
static constexpr EmpireColor CLR_ZERO{{0, 0, 0, 0}};
// if no colour already set, do so automatically
if (empire_colour == CLR_ZERO) {
if (!colors.empty()) {
// take next colour from list
empire_colour = colors[0];
colors.erase(colors.begin());
} else {
// as a last resort, make up a colour
empire_colour = {{static_cast<uint8_t>(RandInt(0, 255)),
static_cast<uint8_t>(RandInt(0, 255)),
static_cast<uint8_t>(RandInt(0, 255)),
255}};
}
}
// set generic default empire name
std::string empire_name = UserString("EMPIRE") + std::to_string(empire_id);
DebugLogger() << "Universe::InitEmpires creating new empire" << " with ID: " << empire_id
<< " for player: " << player_name << " in team: " << psd.starting_team;
// create new Empire object through empire manager
empires.CreateEmpire(empire_id, std::move(empire_name), player_name,
empire_colour, authenticated);
}
empires.ResetDiplomacy();
for (auto& [player_id1, psd1] : player_setup_data) {
if (psd1.starting_team < 0)
continue;
for (auto& [player_id2, psd2] : player_setup_data) {
if (player_id1 == player_id2)
continue;
if (psd1.starting_team != psd2.starting_team)
continue;
empires.SetDiplomaticStatus(player_id1, player_id2, DiplomaticStatus::DIPLO_ALLIED);
}
}
}
|