1261 lines
31 KiB
ActionScript
1261 lines
31 KiB
ActionScript
#priority init 1500
|
|
import object_creation;
|
|
import map_systems;
|
|
from map_systems import IMapHook;
|
|
from regions.regions import addRegion;
|
|
import settings.map_lib;
|
|
import settings.game_settings;
|
|
import statuses;
|
|
import planet_types;
|
|
from empire import Creeps, majorEmpireCount;
|
|
import Artifact@ makeArtifact(SystemDesc@ system, uint type = uint(-1)) from "map_effects";
|
|
import void createWormhole(SystemDesc@ from, SystemDesc@ to) from "objects.Oddity";
|
|
import void mapCopyRegion(SystemDesc@ from, SystemDesc@ to, uint typeMask = ~0) from "map_effects";
|
|
import util.map_tools;
|
|
|
|
//Global data to be access after generation
|
|
array<SystemDesc@> generatedSystems;
|
|
array<GasData@> generatedGalaxyGas;
|
|
array<GalaxyData@> generatedGalaxies;
|
|
NameGenerator sysNames;
|
|
|
|
Node@ getCullingNode(const vec3d& pos) {
|
|
/*uint cnt = generatedGalaxies.length;
|
|
if(cnt > 1) {
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
auto@ glx = generatedGalaxies[i];
|
|
if(glx.origin.distanceToSQ(pos) < glx.radius * glx.radius)
|
|
return glx.cullingNode;
|
|
}
|
|
}*/
|
|
|
|
return null;
|
|
}
|
|
|
|
void init() {
|
|
sysNames.preventDuplicates = true;
|
|
sysNames.read("data/system_names.txt");
|
|
|
|
GlobalUniqueSystems.length = getSystemTypeCount();
|
|
for(uint i = 0, cnt = GlobalUniqueSystems.length; i < cnt; ++i)
|
|
GlobalUniqueSystems[i] = false;
|
|
}
|
|
|
|
//Default constants
|
|
const uint HOMEWORLD_TARGET_LINKS = 4;
|
|
array<bool> GlobalUniqueSystems;
|
|
|
|
//A single map generation script
|
|
class MapGeneration {
|
|
GalaxyData gdat;
|
|
MapSettings@ settings;
|
|
|
|
array<bool> uniqueSystems(getSystemTypeCount(), false);
|
|
array<GasData@> gasses;
|
|
array<SystemDesc@> systems;
|
|
array<SystemData@> systemData;
|
|
array<SystemData@> homeworlds;
|
|
array<SystemData@> wormholes;
|
|
array<SystemData@> possibleHomeworlds;
|
|
uint wormholeIndex = 0;
|
|
double gasSideLen = 1000.0;
|
|
bool haveLinks = false;
|
|
bool autoGenerateLinks = true;
|
|
int galaxyQuality = 0;
|
|
|
|
uint estPlayerCount = 1;
|
|
uint universePlayerCount = 1;
|
|
|
|
array<Empire@> teamEmpires;
|
|
array<SystemData@> teamPositions;
|
|
|
|
Node@ cullingNode;
|
|
|
|
vec3d origin;
|
|
vec3d leftExtent;
|
|
vec3d rightExtent;
|
|
double radius;
|
|
|
|
Region@ region;
|
|
Star@ star;
|
|
|
|
ObjectDesc sysDesc;
|
|
ObjectDesc starDesc;
|
|
ObjectDesc planetDesc;
|
|
LightDesc lightDesc;
|
|
SystemDesc@ system;
|
|
|
|
MapGeneration() {
|
|
sysDesc.type = OT_Region;
|
|
sysDesc.flags |= objNoPhysics;
|
|
sysDesc.delayedCreation = true;
|
|
|
|
starDesc.type = OT_Star;
|
|
starDesc.radius = 100.0;
|
|
|
|
planetDesc.flags |= objMemorable;
|
|
planetDesc.type = OT_Planet;
|
|
planetDesc.delayedCreation = true;
|
|
|
|
lightDesc.diffuse = Colorf(2.7f, 2.0f, 1.0f);
|
|
lightDesc.specular = lightDesc.diffuse;
|
|
lightDesc.att_quadratic = 1.f/(2000.f*2000.f);
|
|
}
|
|
|
|
void modSettings(GameSettings& settings) {
|
|
}
|
|
|
|
double getSetting(uint index, double def = 0.0) {
|
|
return settings.getSetting(index, def);
|
|
}
|
|
|
|
void placeSystems() {
|
|
}
|
|
|
|
void placeLinks() {
|
|
}
|
|
|
|
void prepareSystem(SystemData@ data, SystemDesc@ desc) {
|
|
if(data.homeworlds !is null && data.mirrorSystem !is null)
|
|
data.ignoreAdjacencies = true;
|
|
desc.radius = 250.0;
|
|
if(data.homeworlds !is null) {
|
|
const SystemType@ hwType = getSystemType("HomeSystem");
|
|
if(hwType !is null)
|
|
data.systemType = hwType.id;
|
|
@data.systemCode = null;
|
|
}
|
|
}
|
|
|
|
void generateSystems() {
|
|
bool systemCulling = systemData.length > 1;
|
|
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
prepareSystem(systemData[i], systems[i]);
|
|
generateSystem(systemData[i], systems[i], systemCulling);
|
|
}
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i)
|
|
postGenerateSystem(systemData[i], systems[i]);
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i)
|
|
finalizeSystem(systemData[i], systems[i]);
|
|
}
|
|
|
|
SystemData@ addSystem(vec3d point, int quality = 0, bool canHaveHomeworld = true, int sysType = -1, const SystemCode@ code = null, SystemData@ mirrorSystem = null) {
|
|
SystemData dat;
|
|
dat.index = systemData.length;
|
|
dat.position = point;
|
|
dat.quality = quality;
|
|
dat.canHaveHomeworld = canHaveHomeworld;
|
|
dat.systemType = sysType;
|
|
@dat.mirrorSystem = mirrorSystem;
|
|
if(mirrorSystem !is null && code is null)
|
|
@code = SystemCode();
|
|
@dat.systemCode = code;
|
|
|
|
systemData.insertLast(dat);
|
|
return dat;
|
|
}
|
|
|
|
void preGenerate() {
|
|
//Place systems from the map
|
|
placeSystems();
|
|
|
|
//Place links from the map
|
|
placeLinks();
|
|
|
|
//If the map has no links, automatically generate them
|
|
if(autoGenerateLinks) {
|
|
generateAutomatedLinks();
|
|
while(!ensureConnectedLinks());
|
|
}
|
|
|
|
//Update the full extent of the map
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
vec3d pos = systemData[i].position;
|
|
|
|
if(pos.x < leftExtent.x)
|
|
leftExtent.x = pos.x;
|
|
if(pos.y < leftExtent.y)
|
|
leftExtent.y = pos.y;
|
|
if(pos.z < leftExtent.z)
|
|
leftExtent.z = pos.z;
|
|
|
|
if(pos.x > rightExtent.x)
|
|
rightExtent.x = pos.x;
|
|
if(pos.y > rightExtent.y)
|
|
rightExtent.y = pos.y;
|
|
if(pos.z > rightExtent.z)
|
|
rightExtent.z = pos.z;
|
|
}
|
|
|
|
radius = max((rightExtent - leftExtent).length / 2.0, 6000.0);
|
|
}
|
|
|
|
void setOrigin(vec3d Origin) {
|
|
origin = Origin;
|
|
move(origin);
|
|
}
|
|
|
|
vec3d getAveragePosition() {
|
|
vec3d avg;
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i)
|
|
avg += systemData[i].position;
|
|
return avg / double(systemData.length);
|
|
}
|
|
|
|
void move(const vec3d& move) {
|
|
leftExtent += origin;
|
|
rightExtent += origin;
|
|
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i)
|
|
systemData[i].position += origin;
|
|
}
|
|
|
|
void generate() {
|
|
//Create the culling node
|
|
//@cullingNode = createCullingNode(origin, radius * 1.5);
|
|
|
|
//Generate everything that was placed
|
|
generateSystems();
|
|
|
|
//Generate the galaxy gas
|
|
prepGalaxyGas(max(rightExtent.x - leftExtent.x, rightExtent.y - leftExtent.y));
|
|
generateGas();
|
|
|
|
//Add local data to generated lists
|
|
for(uint i = 0, cnt = gasses.length; i < cnt; ++i)
|
|
generatedGalaxyGas.insertLast(gasses[i]);
|
|
|
|
//Create placed wormholes
|
|
generateWormholes();
|
|
|
|
//Create galaxy data
|
|
gdat.index = generatedGalaxies.length;
|
|
gdat.origin = origin;
|
|
gdat.radius = radius;
|
|
gdat.systems = systems;
|
|
@gdat.cullingNode = cullingNode;
|
|
|
|
//Create galaxy plane node
|
|
@gdat.plane = GalaxyPlaneNode();
|
|
gdat.plane.establish(origin, radius);
|
|
|
|
generatedGalaxies.insertLast(gdat);
|
|
}
|
|
|
|
//** {{{ Homeworlds
|
|
bool canHaveHomeworld(SystemData@ data, Empire@ emp) {
|
|
return data.canHaveHomeworld;
|
|
}
|
|
|
|
bool canHaveHomeworld(Empire@ emp) {
|
|
return true;
|
|
}
|
|
|
|
SystemData@ addPossibleHomeworld(SystemData@ data) {
|
|
possibleHomeworlds.insertLast(data);
|
|
return data;
|
|
}
|
|
|
|
SystemData@ findHomeworld(Empire@ emp, vec3d goal) {
|
|
if(possibleHomeworlds.length != 0) {
|
|
SystemData@ sys;
|
|
if(emp.team != -1 && teamPositions.length != 0 && config::TEAMS_START_CLOSE != 0) {
|
|
double bestDist = INFINITY;
|
|
for(uint i = 0, cnt = possibleHomeworlds.length; i < cnt; ++i) {
|
|
auto@ check = possibleHomeworlds[i];
|
|
double d = 0;
|
|
for(uint n = 0, ncnt = teamEmpires.length; n < ncnt; ++n) {
|
|
if(teamEmpires[n].team == emp.team)
|
|
d += teamPositions[n].position.distanceTo(check.position);
|
|
}
|
|
if(d < bestDist) {
|
|
@sys = check;
|
|
bestDist = d;
|
|
}
|
|
}
|
|
if(sys !is null)
|
|
possibleHomeworlds.remove(sys);
|
|
}
|
|
if(sys is null) {
|
|
uint index = randomi(0, possibleHomeworlds.length - 1);
|
|
@sys = possibleHomeworlds[index];
|
|
possibleHomeworlds.removeAt(index);
|
|
}
|
|
if(emp.team != -1) {
|
|
teamEmpires.insertLast(emp);
|
|
teamPositions.insertLast(sys);
|
|
}
|
|
return sys;
|
|
}
|
|
double best = 0;
|
|
uint sysCount = systemData.length;
|
|
SystemData@ result;
|
|
for(uint i = 0; i < sysCount; ++i) {
|
|
SystemData@ dat = systemData[i];
|
|
if(!canHaveHomeworld(dat, emp))
|
|
continue;
|
|
|
|
double w = 1.0;
|
|
|
|
uint linkCnt = dat.adjacent.length;
|
|
w /= max(1, sqr(int(linkCnt) - int(HOMEWORLD_TARGET_LINKS)));
|
|
|
|
w /= goal.distanceToSQ(dat.position);
|
|
|
|
if(w > best) {
|
|
best = w;
|
|
@result = dat;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void markHomeworld(SystemData@ dat) {
|
|
//List this as a homeworld
|
|
homeworlds.insertLast(dat);
|
|
}
|
|
// }}}
|
|
|
|
//** {{{ Gas Generation
|
|
void prepGalaxyGas(double sideLen) {
|
|
gasSideLen = sideLen;
|
|
|
|
gasses.length = 64;
|
|
for(uint i = 0; i < 64; ++i) {
|
|
int x = int(i) % 8;
|
|
int y = int(i) / 8;
|
|
|
|
GasData data;
|
|
@data.gdat = gdat;
|
|
data.position = vec3d((double(x) - 3.5) * sideLen / 8.0, 0.0, (double(y) - 3.5) * sideLen / 8.0) + origin;
|
|
data.scale = sideLen / 16.0;
|
|
data.generate(cullingNode);
|
|
@gasses[i] = data;
|
|
}
|
|
}
|
|
|
|
void generateGas() {
|
|
Color innerBright, outerBright;
|
|
|
|
switch(randomi(0,2)) {
|
|
case 0:
|
|
innerBright = Color(0xc08060ff);
|
|
outerBright = Color(0x0040c0ff);
|
|
break;
|
|
case 1:
|
|
innerBright = Color(0xc08060ff);
|
|
outerBright = Color(0x006040ff);
|
|
break;
|
|
case 2:
|
|
innerBright = Color(0xc08060ff);
|
|
outerBright = Color(0x600060ff);
|
|
break;
|
|
}
|
|
|
|
for(uint i = 0, cnt = systems.length; i < cnt; ++i) {
|
|
vec3d sysPos = systems[i].position;
|
|
double edgePct = sysPos.distanceTo(origin) / (radius * 0.6);
|
|
|
|
int brightCount = 10 + int(4.0 * edgePct);
|
|
for(int k = 0; k < brightCount; ++k) {
|
|
Color col = innerBright.interpolate(outerBright, edgePct);
|
|
col.a = randomi(0x14,0x1c);
|
|
vec3d pos = sysPos + vec3d(randomd(-10000.0, 10000.0), randomd(-8000.0,8000.0) * (1.0 - edgePct * 0.75), randomd(-10000.0, 10000.0));
|
|
|
|
createGalaxyGas(pos, 7500.0 - 2000.0 * edgePct, col, k == 0);
|
|
}
|
|
|
|
int darkCount = 1 + int(3.0 * edgePct);
|
|
for(int k = 0; k < darkCount; ++k) {
|
|
//Color col = Color(0x200c1815).interpolate(Color(0x08060340), edgePct);
|
|
Colorf fcol;
|
|
fcol.fromHSV(randomd(0,360.0), randomd(0.0,0.2), randomd(0.0,0.2));
|
|
fcol.a = randomd(0.1,0.2);
|
|
|
|
vec3d pos = sysPos + vec3d(randomd(-10000.0, 10000.0), randomd(-2000.0,2000.0), randomd(-10000.0, 10000.0));
|
|
createGalaxyGas(pos, 4200.0, Color(fcol), true);
|
|
}
|
|
}
|
|
}
|
|
|
|
GasData@ gasNodeForPoint(const vec3d& pos) {
|
|
int x = clamp(int(((pos.x - origin.x) * (8.0 / gasSideLen)) + 4.0), 0, 7);
|
|
int y = clamp(int(((pos.z - origin.z) * (8.0 / gasSideLen)) + 4.0), 0, 7);
|
|
return gasses[x + (y*8)];
|
|
}
|
|
|
|
void createGalaxyGas(const vec3d& position, double radius, const Color& col, bool structured) {
|
|
GasData@ gas = gasNodeForPoint(position);
|
|
gas.addSprite(position, radius, col.rgba, structured);
|
|
}
|
|
// }}}
|
|
|
|
//** {{{ Link Generation
|
|
void addLink(uint from, uint to) {
|
|
addLink(systemData[from], systemData[to]);
|
|
}
|
|
|
|
void addLink(SystemData@ from, SystemData@ to) {
|
|
if(from.adjacent.find(to.index) == -1) {
|
|
from.adjacent.insertLast(to.index);
|
|
from.adjacentData.insertLast(to);
|
|
}
|
|
if(to.adjacent.find(from.index) == -1) {
|
|
to.adjacent.insertLast(from.index);
|
|
to.adjacentData.insertLast(from);
|
|
}
|
|
haveLinks = true;
|
|
}
|
|
|
|
void generateAutomatedLinks(uint targLinks = 3) {
|
|
AngularItem[] items(16);
|
|
uint cnt = systemData.length;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
SystemData@ desc = systemData[i];
|
|
if(!desc.autoGenerateLinks)
|
|
continue;
|
|
|
|
//Clear angular list
|
|
for(uint p = 0; p < 16; ++p)
|
|
items[p].clear();
|
|
|
|
//Add angular items to list
|
|
for(uint j = 0; j < cnt; ++j) {
|
|
SystemData@ other = systemData[j];
|
|
if(other is desc)
|
|
continue;
|
|
|
|
vec2d offset(other.position.x - desc.position.x, other.position.z - desc.position.z);
|
|
|
|
double angle = offset.radians() + twopi;
|
|
double dist = offset.length;
|
|
if(dist > 31000.0)
|
|
continue;
|
|
|
|
//double sz = atan(other.radius / dist);
|
|
double sz = atan(1200.0 / dist); //TODO: Base this on something?
|
|
|
|
int closest = int(angle / twopi * 16.0);
|
|
int firstBox = int((angle - sz) / twopi * 16.0);
|
|
int lastBox = int((angle + sz) / twopi * 16.0);
|
|
|
|
for(int p = firstBox; p <= lastBox; ++p) {
|
|
AngularItem@ item = items[(p+16) % 16];
|
|
if(item.desc is null || item.dist > dist) {
|
|
@item.desc = other;
|
|
item.dist = dist;
|
|
item.blocked = p != closest;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Turn items into links
|
|
uint linksMade = desc.adjacent.length;
|
|
double distReq = 13000.0;
|
|
do {
|
|
for(uint p = 0, n = randomi(0,15); p < 16; ++p) {
|
|
AngularItem@ item = items[n];
|
|
if(!item.blocked && item.desc !is null && item.desc.autoGenerateLinks && item.dist <= distReq) {
|
|
if(desc.adjacent.find(item.desc.index) == -1) {
|
|
desc.adjacent.insertLast(item.desc.index);
|
|
desc.adjacentData.insertLast(item.desc);
|
|
}
|
|
|
|
if(item.desc.adjacent.find(desc.index) == -1) {
|
|
item.desc.adjacent.insertLast(desc.index);
|
|
item.desc.adjacentData.insertLast(desc);
|
|
}
|
|
|
|
++linksMade;
|
|
if(distReq > 13000.0 && linksMade >= targLinks)
|
|
break;
|
|
@item.desc = null;
|
|
}
|
|
|
|
n = (n+1) % 16;
|
|
}
|
|
|
|
//Slowly relax distance requirement until we have at least
|
|
//the target amount of links to work with.
|
|
distReq += 3000.0;
|
|
} while(linksMade < targLinks && distReq <= 31000.0);
|
|
}
|
|
}
|
|
|
|
void mark(SystemData@ desc, int markWith, vec3d& point, uint& count) {
|
|
count += 1;
|
|
point += desc.position;
|
|
desc.marked = markWith;
|
|
|
|
for(uint i = 0, cnt = desc.adjacent.length; i < cnt; ++i) {
|
|
SystemData@ other = systemData[desc.adjacent[i]];
|
|
if(other.marked != markWith)
|
|
mark(other, markWith, point, count);
|
|
}
|
|
}
|
|
|
|
bool ensureConnectedLinks() {
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i)
|
|
systemData[i].marked = -1;
|
|
|
|
int ind = 0;
|
|
array<vec3d> centers;
|
|
array<uint> counts;
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
SystemData@ desc = systemData[i];
|
|
if(desc.marked == -1) {
|
|
vec3d point;
|
|
uint count = 0;
|
|
|
|
mark(desc, ind, point, count);
|
|
|
|
centers.insertLast(point / double(count));
|
|
counts.insertLast(count);
|
|
++ind;
|
|
}
|
|
}
|
|
|
|
if(ind > 1) {
|
|
//Find the smallest subgraph.
|
|
int smallest = 0;
|
|
uint smallnum = counts[0];
|
|
for(int i = 1; i < ind; ++i) {
|
|
if(counts[i] < smallnum) {
|
|
smallnum = counts[i];
|
|
smallest = i;
|
|
}
|
|
}
|
|
|
|
//Find the best way to connect it to any other subgraph
|
|
SystemData@ bestSmall;
|
|
SystemData@ bestLarge;
|
|
double bestDist = INFINITY;
|
|
for(int n = 0; n < ind; ++n) {
|
|
if(n == smallest)
|
|
continue;
|
|
|
|
vec3d center = centers[n];
|
|
SystemData@ closestSmall;
|
|
double closest = INFINITY;
|
|
|
|
//Find a system from the smaller set closest to the larger set's center
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
SystemData@ desc = systemData[i];
|
|
if(desc.marked == smallest) {
|
|
double d = desc.position.distanceToSQ(center);
|
|
if(d < closest) {
|
|
@closestSmall = desc;
|
|
closest = d;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Find the system from the larger set that is closest to the smaller system
|
|
SystemData@ closestLarge;
|
|
closest = INFINITY;
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
SystemData@ desc = systemData[i];
|
|
if(desc.marked == n) {
|
|
double d = desc.position.distanceToSQ(closestSmall.position);
|
|
if(d < closest) {
|
|
@closestLarge = desc;
|
|
closest = d;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(closest < bestDist) {
|
|
@bestSmall = closestSmall;
|
|
@bestLarge = closestLarge;
|
|
bestDist = closest;
|
|
}
|
|
}
|
|
|
|
//Make the link
|
|
addLink(bestSmall, bestLarge);
|
|
return false;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
//}}}
|
|
|
|
//** {{{ System Calculation
|
|
double getContestation(SystemData@ data, array<double>@ local) {
|
|
double nearest = INFINITY, secondary = INFINITY;
|
|
|
|
uint hwCnt = homeworlds.length;
|
|
if(local.length < hwCnt)
|
|
local.length = hwCnt;
|
|
for(uint i = 0; i < hwCnt; ++i) {
|
|
double dist = data.hwDistances[i];
|
|
if(dist < nearest) {
|
|
secondary = nearest;
|
|
nearest = dist;
|
|
}
|
|
else if(dist < secondary) {
|
|
secondary = dist;
|
|
}
|
|
local[i] = dist;
|
|
}
|
|
|
|
double pts = 0.0;
|
|
|
|
//The nearest 2 players make up most of the points
|
|
pts += (nearest / secondary) * 200.0 - 50.0;
|
|
|
|
//Additional players can give bonus points
|
|
for(uint i = 0; i < hwCnt; ++i) {
|
|
double d = local[i];
|
|
if(d <= secondary)
|
|
continue;
|
|
double pct = (nearest / d);
|
|
if(pct > 0.5)
|
|
pts += (pct - 0.5) * 100;
|
|
}
|
|
|
|
return pts;
|
|
}
|
|
|
|
void calculateGalaxyQuality(array<SystemData@>@ globalHomeworlds) {
|
|
galaxyQuality = 0;
|
|
}
|
|
|
|
void calculateQuality(SystemData@ data, array<SystemData@>@ globalHomeworlds, array<double>@ local) {
|
|
if(data.homeworlds !is null)
|
|
return;
|
|
if(homeworlds.length < 2) {
|
|
data.quality += galaxyQuality;
|
|
data.contestation = homeworlds.length == 0 ? INFINITY : 0;
|
|
}
|
|
else {
|
|
data.contestation = getContestation(data, local);
|
|
data.quality += galaxyQuality + max(int(data.contestation), 0);
|
|
}
|
|
}
|
|
|
|
void calculateHomeworldDistances() {
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i)
|
|
@systemData[i].hwDistances = array<double>(homeworlds.length, INFINITY);
|
|
|
|
for(uint hwInd = 0, hwCnt = homeworlds.length; hwInd < hwCnt; ++hwInd) {
|
|
priority_queue pqueue;
|
|
set_int visited;
|
|
|
|
pqueue.push(int(homeworlds[hwInd].index), INFINITY);
|
|
systemData[homeworlds[hwInd].index].hwDistances[hwInd] = 0.0;
|
|
|
|
while(!pqueue.empty()) {
|
|
int index = pqueue.top();
|
|
pqueue.pop();
|
|
|
|
if(visited.contains(index))
|
|
continue;
|
|
visited.insert(index);
|
|
|
|
auto@ dat = systemData[index];
|
|
double curDist = dat.hwDistances[hwInd];
|
|
|
|
for(uint i = 0, cnt = dat.adjacentData.length; i < cnt; ++i) {
|
|
auto@ adj = dat.adjacentData[i];
|
|
if(visited.contains(adj.index))
|
|
continue;
|
|
|
|
double dDist = curDist + 1000.0 + adj.position.distanceTo(dat.position);
|
|
if(dDist < adj.hwDistances[hwInd]) {
|
|
adj.hwDistances[hwInd] = dDist;
|
|
pqueue.push(adj.index, -int(dDist/1000.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
array<const ResourceType@>@ getDistributedResources(uint count, int quality, double contestation) {
|
|
array<const ResourceType@> resources(count);
|
|
if(count == 0)
|
|
return resources;
|
|
|
|
double score = 1.0;
|
|
for(uint i = 0; i < count; ++i) {
|
|
const ResourceType@ type = getDistributedResourceContest(contestation);
|
|
@resources[i] = type;
|
|
score /= type.rarityScore;
|
|
}
|
|
|
|
array<const ResourceType@> prev = resources;
|
|
uint rolls = 0;
|
|
|
|
if(quality != 0) {
|
|
double bestScore = score;
|
|
|
|
array<const ResourceType@> reroll(count);
|
|
while(quality != 0) {
|
|
bool getBetter = false;
|
|
if(quality > 0) {
|
|
getBetter = true;
|
|
if(quality < 100) {
|
|
if(randomi(0,99) > quality)
|
|
break;
|
|
quality = 0;
|
|
}
|
|
else {
|
|
quality -= 100;
|
|
}
|
|
}
|
|
else if(quality > -100) {
|
|
if(randomi(0,99) > -quality)
|
|
break;
|
|
quality = 0;
|
|
}
|
|
else {
|
|
quality += 100;
|
|
}
|
|
|
|
double rrScore = 1.0;
|
|
for(uint i = 0; i < count; ++i) {
|
|
const ResourceType@ type = getDistributedResourceContest(contestation);
|
|
@reroll[i] = type;
|
|
rrScore /= type.rarityScore;
|
|
}
|
|
|
|
rolls += 1;
|
|
if(getBetter) {
|
|
if(rrScore > bestScore) {
|
|
bestScore = rrScore;
|
|
resources = reroll;
|
|
}
|
|
}
|
|
else {
|
|
if(rrScore < bestScore) {
|
|
bestScore = rrScore;
|
|
resources = reroll;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return resources;
|
|
}
|
|
//}}}
|
|
|
|
//** {{{ System Generation
|
|
void generateLinks(SystemData@ from, SystemDesc@ into) {
|
|
for(uint i = 0, cnt = from.adjacent.length; i < cnt; ++i) {
|
|
if(from.adjacent[i] > from.index)
|
|
continue;
|
|
SystemDesc@ other = systems[from.adjacent[i]];
|
|
double dist = from.position.distanceTo(other.position);
|
|
|
|
into.adjacent.insertLast(other.index);
|
|
into.adjacentDist.insertLast(dist);
|
|
|
|
other.adjacent.insertLast(into.index);
|
|
other.adjacentDist.insertLast(dist);
|
|
}
|
|
}
|
|
|
|
void generateRegions() {
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
SystemData@ data = systemData[i];
|
|
|
|
//Create the region
|
|
string name = sysNames.generate();
|
|
sysDesc.name = name;
|
|
sysDesc.position = data.position;
|
|
|
|
//Hint the creation of the region, so we can reference its hint data in saves
|
|
LockHint hint();
|
|
Region@ region = cast<Region>(makeObject(sysDesc));
|
|
region.alwaysVisible = true;
|
|
|
|
//Remember the system position
|
|
region.InnerRadius = 1000;
|
|
region.OuterRadius = 1500;
|
|
region.radius = region.OuterRadius;
|
|
|
|
//Create the system descriptor
|
|
SystemDesc desc;
|
|
desc.index = generatedSystems.length;
|
|
region.SystemId = desc.index;
|
|
desc.name = name;
|
|
desc.position = data.position;
|
|
desc.radius = region.OuterRadius;
|
|
desc.assignGroup = data.assignGroup;
|
|
@desc.object = region;
|
|
data.sysIndex = desc.index;
|
|
|
|
systems.insertLast(desc);
|
|
generatedSystems.insertLast(desc);
|
|
}
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
auto@ data = systemData[i];
|
|
auto@ desc = systems[i];
|
|
|
|
generateLinks(data, desc);
|
|
addRegion(desc.object);
|
|
}
|
|
}
|
|
|
|
void generateSystem(SystemData@ data, SystemDesc@ desc, bool systemCulling = true) {
|
|
//Hint that objects in one iteration should
|
|
//start in the same lock
|
|
@region = desc.object;
|
|
@system = desc;
|
|
desc.contestation = data.contestation;
|
|
|
|
LockHint hint(region);
|
|
|
|
//Create the system node
|
|
Node@ snode;
|
|
if(systemCulling) {
|
|
@snode = bindCullingNode(region, desc.position, 1000.0);
|
|
//snode.reparent(cullingNode);
|
|
}
|
|
|
|
//Find a valid system type
|
|
if(data.systemCode !is null) {
|
|
Object@ current;
|
|
for(uint i = 0, cnt = data.systemCode.hooks.length; i < cnt; ++i) {
|
|
auto@ hook = cast<IMapHook@>(data.systemCode.hooks[i]);
|
|
if(hook !is null)
|
|
hook.trigger(data, system, current);
|
|
}
|
|
}
|
|
else {
|
|
const SystemType@ type = getSystemType(data.systemType);
|
|
if(type is null) {
|
|
@type = getDistributedSystemType();
|
|
data.systemType = type.id;
|
|
while(type.unique != SU_NonUnique) {
|
|
if(type.unique == SU_Galaxy) {
|
|
if(uniqueSystems[type.id]) {
|
|
@type = getDistributedSystemType();
|
|
continue;
|
|
}
|
|
else {
|
|
uniqueSystems[type.id] = true;
|
|
break;
|
|
}
|
|
}
|
|
else if(type.unique == SU_Global) {
|
|
if(GlobalUniqueSystems[type.id]) {
|
|
@type = getDistributedSystemType();
|
|
continue;
|
|
}
|
|
else {
|
|
GlobalUniqueSystems[type.id] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
type.generate(data, desc);
|
|
}
|
|
|
|
if(data.mirrorSystem !is null) {
|
|
SystemDesc@ otherDesc = systems[data.mirrorSystem.index];
|
|
desc.radius = otherDesc.radius / 1.5;
|
|
}
|
|
|
|
//Set region radius
|
|
region.InnerRadius = desc.radius;
|
|
region.OuterRadius = desc.radius * 1.5;
|
|
region.radius = region.OuterRadius;
|
|
desc.radius = region.OuterRadius;
|
|
|
|
//Update the culling node boundary based on the system data
|
|
if(snode !is null) {
|
|
snode.scale = region.radius + 128.0;
|
|
snode.rebuildTransform();
|
|
}
|
|
|
|
//Clear unnecessary references
|
|
@region = null;
|
|
@star = null;
|
|
}
|
|
|
|
void postGenerateSystem(SystemData@ data, SystemDesc@ desc) {
|
|
//Do post generation effects
|
|
if(data.systemCode !is null) {
|
|
Object@ current;
|
|
for(uint i = 0, cnt = data.systemCode.hooks.length; i < cnt; ++i) {
|
|
auto@ hook = cast<IMapHook@>(data.systemCode.hooks[i]);
|
|
if(hook !is null)
|
|
hook.postTrigger(data, system, current);
|
|
}
|
|
}
|
|
else {
|
|
const SystemType@ type = getSystemType(data.systemType);
|
|
if(type !is null)
|
|
type.postGenerate(data, desc);
|
|
}
|
|
}
|
|
|
|
void finalizeSystem(SystemData@ data, SystemDesc@ desc) {
|
|
//Do mirror copying
|
|
if(data.mirrorSystem !is null && data.homeworlds is null)
|
|
mapCopyRegion(systems[data.mirrorSystem.index], desc);
|
|
|
|
//Deal with resource scarcity
|
|
if(config::RESOURCE_SCARCITY != 0 && data.distributedResources.length != 0) {
|
|
uint sysCnt = generatedSystems.length;
|
|
double targSys = 20.0 * double(majorEmpireCount);
|
|
double factor = 1.0;
|
|
if(sysCnt > uint(targSys))
|
|
factor = pow(double(sysCnt) - targSys, 0.1);
|
|
double pct = (targSys * factor) / double(sysCnt) / config::RESOURCE_SCARCITY;
|
|
if(pct < 1.0) {
|
|
double step = 1.0 / double(data.distributedResources.length);
|
|
uint amount = floor(pct / step);
|
|
if(randomd() < (pct - double(amount)*step) / step)
|
|
amount += 1;
|
|
amount = max(amount, data.quality / 200);
|
|
|
|
for(uint i = amount, cnt = data.distributedResources.length; i < cnt; ++i) {
|
|
auto@ obj = data.distributedResources[amount];
|
|
if(obj.isPlanet) {
|
|
auto@ barren = getStatusType("Barren");
|
|
if(barren !is null)
|
|
obj.addStatus(barren.id);
|
|
auto@ barrenType = getPlanetType("Barren");
|
|
if(barrenType !is null)
|
|
cast<Planet>(obj).PlanetType = barrenType.id;
|
|
data.distributedConditions.remove(cast<Planet>(obj));
|
|
}
|
|
data.distributedResources.removeAt(amount);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Finalize resource distribution
|
|
uint resCnt = data.distributedResources.length;
|
|
bool hasEnergy = false;
|
|
if(resCnt != 0) {
|
|
array<const ResourceType@>@ resources = getDistributedResources(resCnt, data.quality, data.contestation);
|
|
for(uint i = 0; i < resCnt; ++i) {
|
|
if(resources[i].tilePressure[TR_Energy] > 0)
|
|
hasEnergy = true;
|
|
data.distributedResources[i].addResource(resources[i].id);
|
|
|
|
auto@ biome = getBiome(resources[i].nativeBiome);
|
|
if(biome !is null)
|
|
data.distributedResources[i].replaceFirstBiomeWith(biome.id);
|
|
markResourceUsed(resources[i]);
|
|
}
|
|
data.distributedResources.length = 0;
|
|
}
|
|
|
|
//Finalize conditions
|
|
for(uint i = 0, cnt = data.distributedConditions.length; i < cnt; ++i)
|
|
data.distributedConditions[i].addRandomCondition();
|
|
|
|
//Do handicaps
|
|
if(data.homeworlds !is null && data.homeworlds.length == 1) {
|
|
Empire@ emp = data.homeworlds[0];
|
|
while(emp.handicap >= 10) {
|
|
bool found = false;
|
|
uint plCnt = region.planetCount;
|
|
for(uint i = 0, index = randomi(0, plCnt-1); i < plCnt; ++i) {
|
|
Planet@ pl = region.planets[index];
|
|
if((pl.owner is null || !pl.owner.valid) && pl.valid && !pl.destroying) {
|
|
pl.destroy();
|
|
emp.handicap -= 10;
|
|
found = true;
|
|
break;
|
|
}
|
|
index = (index + 1) % plCnt;
|
|
}
|
|
|
|
if(!found)
|
|
break;
|
|
}
|
|
}
|
|
if(data.homeworlds !is null) {
|
|
for(uint i = 0, cnt = data.homeworlds.length; i < cnt; ++i)
|
|
@data.homeworlds[i].HomeSystem = desc.object;
|
|
}
|
|
|
|
//Ensure artifacts in systems with energy
|
|
if(hasEnergy && data.artifacts == 0)
|
|
makeArtifact(desc);
|
|
|
|
//Do home system mirroring
|
|
if(data.homeworlds !is null && data.mirrorSystem !is null) {
|
|
if(data.mirrorSystem.homeworlds !is null) {
|
|
SystemData@ other = data.mirrorSystem;
|
|
SystemDesc@ otherDesc = systems[other.index];
|
|
|
|
for(uint i = 0, cnt = desc.object.objectCount; i < cnt; ++i) {
|
|
Object@ obj = desc.object.objects[i];
|
|
if(obj.isAsteroid)
|
|
obj.destroy();
|
|
}
|
|
|
|
uint types = 1<<uint(OT_Anomaly);
|
|
types |= 1<<uint(OT_Artifact);
|
|
types |= 1<<uint(OT_Asteroid);
|
|
mapCopyRegion(otherDesc, desc, types);
|
|
|
|
for(uint i = 0, cnt = otherDesc.object.planetCount; i < cnt; ++i) {
|
|
Planet@ pl = otherDesc.object.planets[i];
|
|
vec3d destPos = pl.position - other.position;
|
|
destPos.z = -destPos.z;
|
|
destPos += data.position;
|
|
|
|
Planet@ mirr = desc.object.planets[i];
|
|
mirr.orbitAround(destPos, data.position);
|
|
if(pl !is null && mirr !is null) {
|
|
for(uint n = 0, ncnt = mirr.nativeResourceCount; n < ncnt; ++n) {
|
|
mirr.removeResource(mirr.nativeResourceId[0]);
|
|
mirr.wait();
|
|
}
|
|
for(uint n = 0, ncnt = pl.nativeResourceCount; n < ncnt; ++n)
|
|
mirr.addResource(pl.nativeResourceType[n]);
|
|
mirr.wait();
|
|
}
|
|
mirr.mirrorSurfaceFrom(pl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//}}}
|
|
|
|
//** {{{ Wormhole Generation
|
|
void placeWormholes(uint amount) {
|
|
uint sysCnt = systemData.length;
|
|
for(uint i = 0; i < amount; ++i) {
|
|
uint index = randomi(0, sysCnt - 1);
|
|
SystemData@ data;
|
|
|
|
for(uint n = 0; n < sysCnt; ++n) {
|
|
@data = systemData[index];
|
|
index = (index+1) % sysCnt;
|
|
|
|
if(data.homeworlds !is null)
|
|
continue;
|
|
bool adjHW = false;
|
|
for(uint j = 0, jcnt = data.adjacent.length; j < jcnt; ++j) {
|
|
if(systemData[data.adjacent[j]].homeworlds !is null) {
|
|
adjHW = true;
|
|
break;
|
|
}
|
|
}
|
|
if(adjHW)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
wormholes.insertLast(data);
|
|
}
|
|
}
|
|
|
|
SystemDesc@ getWormhole() {
|
|
if(wormholeIndex >= wormholes.length)
|
|
return null;
|
|
auto@ sys = generatedSystems[wormholes[wormholeIndex].sysIndex];
|
|
wormholeIndex += 1;
|
|
return sys;
|
|
}
|
|
|
|
void createWormhole(SystemData@ from, SystemData@ to) {
|
|
from.wormholes.insertLast(to.index);
|
|
}
|
|
|
|
void generateWormholes() {
|
|
for(uint i = 0, cnt = systemData.length; i < cnt; ++i) {
|
|
auto@ data = systemData[i];
|
|
for(uint n = 0, ncnt = data.wormholes.length; n < ncnt; ++n) {
|
|
auto@ other = systemData[data.wormholes[n]];
|
|
auto@ desc = systems[i];
|
|
auto@ otherDesc = systems[other.index];
|
|
|
|
::createWormhole(desc, otherDesc);
|
|
addWormhole(desc, otherDesc);
|
|
addWormhole(otherDesc, desc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void addWormhole(SystemDesc@ dat, SystemDesc@ other) {
|
|
dat.wormholes.insertLast(other.index);
|
|
}
|
|
//}}}
|
|
|
|
//** {{{ Utilities
|
|
double middleAngle(double from, double to) {
|
|
double diff = to - from;
|
|
if(diff < 0)
|
|
diff += twopi;
|
|
double newAngle = from + (diff * 0.5);
|
|
if(newAngle >= twopi)
|
|
newAngle -= twopi;
|
|
return newAngle;
|
|
}
|
|
|
|
double angleDiff(double a, double b) {
|
|
double diff = a - b;
|
|
if(diff < -pi)
|
|
diff += twopi;
|
|
else if(diff >= pi)
|
|
diff -= twopi;
|
|
return abs(diff);
|
|
}
|
|
//}}}
|
|
|
|
//** {{{ Game code
|
|
void initDefs() {
|
|
}
|
|
|
|
void preInit() {
|
|
}
|
|
|
|
void init() {
|
|
}
|
|
|
|
void tick(double time) {
|
|
}
|
|
|
|
void save(SaveFile& file) {
|
|
}
|
|
|
|
void load(SaveFile& file) {
|
|
}
|
|
//}}}
|
|
};
|
|
|
|
// {{{ Automation data structures
|
|
final class AngularItem {
|
|
double dist = 0.0;
|
|
SystemData@ desc;
|
|
bool blocked = false;
|
|
|
|
void clear() {
|
|
@desc = null;
|
|
blocked = false;
|
|
}
|
|
};
|
|
|
|
//}}}
|
|
|
|
// {{{ Gas data structures
|
|
final class GasSprite {
|
|
vec3d pos;
|
|
double scale;
|
|
uint color;
|
|
bool structured;
|
|
};
|
|
|
|
final class GasData {
|
|
GalaxyData@ gdat;
|
|
GalaxyGas@ node;
|
|
GasSprite[] sprites;
|
|
vec3d position;
|
|
double scale;
|
|
|
|
void generate(Node@ parent = null) {
|
|
@node = GalaxyGas();
|
|
node.position = position;
|
|
node.scale = scale;
|
|
if(parent !is null)
|
|
node.reparent(parent);
|
|
node.rebuildTransform();
|
|
}
|
|
|
|
void addSprite(vec3d pos, double scale, uint color, bool structured) {
|
|
GasSprite sprt;
|
|
sprt.pos = pos;
|
|
sprt.scale = scale;
|
|
sprt.color = color;
|
|
sprt.structured = structured;
|
|
|
|
sprites.insertLast(sprt);
|
|
node.addSprite(pos, scale, color, structured);
|
|
}
|
|
|
|
void save(SaveFile& msg) {
|
|
msg << position;
|
|
msg << scale;
|
|
msg << gdat.index;
|
|
|
|
uint cnt = sprites.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
GasSprite@ sprt = sprites[i];
|
|
msg << sprt.pos;
|
|
msg << sprt.scale;
|
|
msg << sprt.color;
|
|
msg << sprt.structured;
|
|
}
|
|
}
|
|
|
|
void load(SaveFile& msg) {
|
|
msg >> position;
|
|
msg >> scale;
|
|
|
|
uint gindex = 0;
|
|
msg >> gindex;
|
|
@gdat = generatedGalaxies[gindex];
|
|
generate(gdat.cullingNode);
|
|
|
|
uint cnt = 0;
|
|
msg >> cnt;
|
|
sprites.length = cnt;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
GasSprite@ sprt = sprites[i];
|
|
msg >> sprt.pos;
|
|
msg >> sprt.scale;
|
|
msg >> sprt.color;
|
|
bool structured = true;
|
|
if(msg >= SV_0041)
|
|
msg >> structured;
|
|
|
|
node.addSprite(sprt.pos, sprt.scale, sprt.color, structured);
|
|
}
|
|
}
|
|
};
|
|
//}}}
|
|
|
|
// {{{ Galaxy data structures
|
|
final class GalaxyData {
|
|
uint index = 0;
|
|
vec3d origin;
|
|
double radius;
|
|
SystemDesc@[] systems;
|
|
Node@ cullingNode;
|
|
GalaxyPlaneNode@ plane;
|
|
|
|
void save(SaveFile& msg) {
|
|
msg << origin;
|
|
msg << radius;
|
|
|
|
uint cnt = systems.length;
|
|
msg << cnt;
|
|
for(uint i = 0; i < cnt; ++i)
|
|
msg << systems[i].index;
|
|
}
|
|
|
|
void load(SaveFile& msg) {
|
|
msg >> origin;
|
|
msg >> radius;
|
|
@cullingNode = createCullingNode(origin, radius * 1.5);
|
|
|
|
@plane = GalaxyPlaneNode();
|
|
plane.establish(origin, radius);
|
|
|
|
uint cnt = 0;
|
|
msg >> cnt;
|
|
systems.length = cnt;
|
|
|
|
uint ind = 0;
|
|
for(uint i = 0; i < cnt; ++i) {
|
|
msg >> ind;
|
|
@systems[i] = generatedSystems[ind];
|
|
}
|
|
}
|
|
};
|
|
//}}}
|