#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 generatedSystems; array generatedGalaxyGas; array 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 GlobalUniqueSystems; //A single map generation script class MapGeneration { GalaxyData gdat; MapSettings@ settings; array uniqueSystems(getSystemTypeCount(), false); array gasses; array systems; array systemData; array homeworlds; array wormholes; array possibleHomeworlds; uint wormholeIndex = 0; double gasSideLen = 1000.0; bool haveLinks = false; bool autoGenerateLinks = true; int galaxyQuality = 0; uint estPlayerCount = 1; uint universePlayerCount = 1; array teamEmpires; array 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 centers; array 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@ 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@ globalHomeworlds) { galaxyQuality = 0; } void calculateQuality(SystemData@ data, array@ globalHomeworlds, array@ 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(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@ getDistributedResources(uint count, int quality, double contestation) { array 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 prev = resources; uint rolls = 0; if(quality != 0) { double bestScore = score; array 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(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(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(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(obj).PlanetType = barrenType.id; data.distributedConditions.remove(cast(obj)); } data.distributedResources.removeAt(amount); } } } //Finalize resource distribution uint resCnt = data.distributedResources.length; bool hasEnergy = false; if(resCnt != 0) { array@ 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<= 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]; } } }; //}}}