from util.design_designer import DesignType, autoSupportNames, autoFlagNames; import util.design_export; import designs; export DesignType; export Designer; export autoSupportNames, autoFlagNames; import int getTraitID(const string&) from "traits"; const array DIRWEIGHT = {1.0, 0.2, 1.0, 1.0, 0.2, 1.0}; tidy class SubsystemData { int id = 0; int defId = -1; array hexes; int rotation = -1; vec2u core; int fillerCount = 0; void set_def(const SubsystemDef@ def) { defId = def.index; } const SubsystemDef@ get_def() { return getSubsystemDef(defId); } }; tidy class HexData { bool marked = false; bool cleared = false; int freeAdj = 6; int markedAdj = 0; //SubsystemData@ subsys; int subsysId = -1; const ModuleDef@ module; //Clear new data in clear() below SubsystemData@ subsys(Designer& dsg) { if(subsysId == -1) return null; return dsg.subsystems[subsysId]; } }; enum HexMask { HM_DownLeft = 0x1, HM_Down = 0x2, HM_DownRight = 0x4, HM_UpRight = 0x8, HM_Up = 0x10, HM_UpLeft = 0x20, HM_ALL = HM_DownLeft | HM_Down | HM_DownRight | HM_UpRight | HM_Up | HM_UpLeft, }; tidy class Designer { DesignType type; Empire@ owner; int size; int hexLimit; string className; bool randomHull = false; const Design@ baseOffDesign; const SubsystemDef@ baseOffSubsystem; vec2u gridSize; vec2u center; array hexes; array subsystems; array markedHexes; array applied; array composition; double totalFrequency = 0; Tag@ primaryArmor; Designer(uint type, int size, Empire@ emp, const string& className = "Combat", bool compose = true) { @this.owner = emp; this.type = DesignType(type); this.size = max(size, 1); this.className = className; @primaryArmor = tag("PrimaryArmor"); gridSize = vec2u(getDesignGridSize(hulltag, size)); center = vec2u(gridSize.x / 2, gridSize.y / 2 - 1); switch(type) { case DT_Support: hexLimit = 60; if(compose) composeSupport(); break; case DT_Satellite: hexLimit = 70; if(compose) composeSatellite(); break; case DT_Flagship: hexLimit = 128; if(compose) composeFlagship(); break; case DT_Station: hexLimit = 160; if(compose) composeStation(); break; } } void composeSupport() { composition.length = 0; composition.insertLast(Internal(tag("ControlCore"), 0.04, 0.06)); composition.insertLast(Weapon(tag("Weapon"), 0.40, 0.50)); composition.insertLast(Exhaust(tag("Engine"), 0.35, 0.40)); composition.insertLast(Chance(0.25, Exhaust(tag("SecondaryThrust"), 0.05, 0.20))); composition.insertLast(ArmorLayer(tag("PrimaryArmor"), HM_DownLeft | HM_UpLeft | HM_Down | HM_Up, 1, 2)); } void composeSatellite() { composition.length = 0; composition.insertLast(Internal(tag("ControlCore"), 0.04, 0.06)); composition.insertLast(Internal(tag("ControlCore"), 0.04, 0.06)); composition.insertLast(Weapon(tag("Weapon"), 0.40, 0.50, allDirections = true)); composition.insertLast(Weapon(tag("Weapon"), 0.40, 0.50, allDirections = true)); composition.insertLast(ArmorLayer(tag("PrimaryArmor"), HM_ALL, 1, 2)); } void composeFlagship(bool haveSupport = true, bool tryFTL = true, bool supply = true, bool weapons = true, bool power = true, bool clear = true) { if(clear) composition.length = 0; //Weapons for flagships if(weapons) { composition.insertLast(Weapon(tag("Weapon") & tag("MainDPS"), 0.15, 0.25)); composition.insertLast(Weapon(tag("Weapon") & tag("MainDPS"), 0.15, 0.25)); composition.insertLast(Chance(0.25, Weapon(tag("Weapon") & tag("SecondaryDPS"), 0.075, 0.15))); } //Engine pair composition.insertLast(Exhaust(tag("Engine") & tag("GivesThrust"), 0.25, 0.35)); //Control core if(owner.hasTrait(getTraitID("Ancient"))) { composition.insertLast(Internal(subsystem("AncientCore"), 0.025, 0.05)); composition.insertLast(Chance(0.5, Internal(subsystem("AncientCore"), 0.025, 0.05))); if(power) composition.insertLast(Chance(0.2, Internal(tag("IsReactor"), 0.01, 0.03))); } else { composition.insertLast(Internal(tag("ControlCore"), 0.025, 0.05)); composition.insertLast(Chance(0.5, Internal(tag("ControlCore"), 0.025, 0.05))); if(power) composition.insertLast(Internal(tag("IsReactor"), 0.04, 0.06)); } //Shrine if needed if(owner.hasTrait(getTraitID("Devout"))) composition.insertLast(Internal(tag("Prayer"), 0.05, 0.10)); //Secondary modules composition.insertLast(Chance(0.5, Internal(tag("SecondaryDefense"), 0.075, 0.15))); if(tryFTL) composition.insertLast(Internal(tag("Hyperengine"), 0.10, 0.18)); if(supply) composition.insertLast(Filler(subsystem("SupplyModule"), 0.09, 0.13)); if(haveSupport) composition.insertLast(Filler(subsystem("SupportCapModule"), 0.04, 0.14)); //Armor composition.insertLast(ArmorLayer(tag("PrimaryArmor"), HM_DownLeft | HM_UpLeft | HM_Down | HM_Up, 1, 1)); for(int i = 0; i < 1 + size / 400 && i < 4; ++i) composition.insertLast(Chance(0.33, ArmorLayer(tag("PrimaryArmor"), HM_DownLeft | HM_UpLeft, 1, 1))); } void composeStation(bool clear = true) { if(clear) composition.length = 0; composition.insertLast(Weapon(tag("Weapon") & tag("MainDPS"), 0.15, 0.25, allDirections = true)); composition.insertLast(Weapon(tag("Weapon") & tag("MainDPS"), 0.15, 0.25, allDirections = true)); composition.insertLast(Weapon(tag("Weapon") & tag("MainDPS"), 0.15, 0.25, allDirections = true)); composition.insertLast(Chance(0.33, Weapon(tag("Weapon") & tag("SecondaryDPS"), 0.075, 0.15))); //Control core if(owner.hasTrait(getTraitID("Ancient"))) { composition.insertLast(Internal(subsystem("AncientCore"), 0.025, 0.05)); composition.insertLast(Chance(0.5, Internal(subsystem("AncientCore"), 0.025, 0.05))); composition.insertLast(Chance(0.2, Internal(tag("IsReactor"), 0.01, 0.03))); } else { composition.insertLast(Internal(tag("ControlCore"), 0.025, 0.05)); composition.insertLast(Chance(0.5, Internal(tag("ControlCore"), 0.025, 0.05))); composition.insertLast(Internal(tag("IsReactor"), 0.04, 0.06)); } //Shrine if needed composition.insertLast(Internal(tag("Prayer"), 0.15, 0.20)); composition.insertLast(Chance(0.5, Internal(tag("SecondaryDefense"), 0.075, 0.15))); composition.insertLast(Filler(subsystem("SupplyModule"), 0.09, 0.13)); composition.insertLast(ArmorLayer(tag("PrimaryArmor"), HM_ALL, 1, 1)); for(int i = 0; i < 1 + size / 400 && i < 4; ++i) composition.insertLast(Chance(0.33, ArmorLayer(tag("PrimaryArmor"), HM_ALL, 1, 1))); } void composeGate() { composition.length = 0; composition.insertLast(HorizSpan(tag("Gate"), 1.0, 1.0)); composition.insertLast(Internal(tag("ControlCore"), 0.02, 0.03)); composeStation(clear=false); } void composeSlipstream() { composition.length = 0; composition.insertLast(Exhaust(tag("Engine") & tag("GivesThrust"), 0.50, 0.50)); composition.insertLast(Internal(tag("ControlCore"), 0.05, 0.05)); composition.insertLast(Internal(tag("ControlCore"), 0.05, 0.05)); composition.insertLast(Internal(tag("Slipstream"), 1.00, 1.00)); //Shrine if needed if(owner.hasTrait(getTraitID("Devout"))) composition.insertLast(Internal(tag("Prayer"), 0.15, 0.20)); composition.insertLast(ArmorLayer(tag("PrimaryArmor"), HM_DownLeft | HM_UpLeft | HM_Down | HM_Up, 1, 1)); } void composeMothership() { composition.length = 0; composition.insertLast(Internal(tag("ControlCore"), 0.2, 0.5)); composition.insertLast(Internal(tag("ControlCore"), 0.2, 0.5)); composition.insertLast(Applied(tag("Mothership"))); composeFlagship(supply=false, power=false, clear=false); hexLimit = 225; } void composeScout() { composition.length = 0; composition.insertLast(Exhaust(tag("Engine") & tag("GivesThrust"), 0.80, 0.80)); composition.insertLast(Internal(tag("ControlCore"), 0.05, 0.05)); composition.insertLast(Internal(tag("Hyperengine"), 0.50, 0.50)); //Shrine if needed if(owner.hasTrait(getTraitID("Devout"))) composition.insertLast(Internal(tag("Prayer"), 0.15, 0.20)); composition.insertLast(ArmorLayer(tag("PrimaryArmor"), HM_DownLeft | HM_UpLeft | HM_Down | HM_Up, 1, 1)); } Tag@ tag(const string& value) { return Tag(this, value); } SubsystemFilter@ subsystem(const string& value) { return SubsystemFilter(getSubsystemDef(value)); } vec2u flip(const vec2u& p) { if(p.x % 2 == center.x % 2) return vec2u(p.x, center.y * 2 - p.y); else if(center.x % 2 == 0) return vec2u(p.x, center.y * 2 - p.y - 1); else return vec2u(p.x, center.y * 2 - p.y + 1); } HexGridAdjacency flip(uint d) { switch(d) { case HEX_Up: return HEX_Down; case HEX_UpRight: return HEX_DownRight; case HEX_DownRight: return HEX_UpRight; case HEX_Down: return HEX_Up; case HEX_DownLeft: return HEX_UpLeft; case HEX_UpLeft: return HEX_DownLeft; } return HEX_Up; } HexGridAdjacency reverse(uint d) { switch(d) { case HEX_Up: return HEX_Down; case HEX_UpRight: return HEX_DownLeft; case HEX_DownRight: return HEX_UpLeft; case HEX_Down: return HEX_Up; case HEX_DownLeft: return HEX_UpRight; case HEX_UpLeft: return HEX_DownRight; } return HEX_Up; } uint mask(uint d) { return 1<= 0 && hull.minSize > desc.size) continue; if(hull.maxSize >= 0 && hull.maxSize < desc.size) continue; hullCount += 1; if(randomd() < 1.0 / double(hullCount)) @desc.hull = hull; } if(desc.hull is null) @desc.hull = getBestHull(desc, hulltag, owner); } else { @desc.hull = getBestHull(desc, hulltag, owner); } return makeDesign(desc); } string get_hulltag() { string hullTag = "Flagship"; switch(type) { case DT_Support: hullTag = "Support"; break; case DT_Satellite: hullTag = "Satellite"; break; case DT_Flagship: hullTag = "Flagship"; break; case DT_Station: hullTag = "Station"; break; } return hullTag; } vec2u markedRandom() { if(markedHexes.length == 0) return vec2u(uint(-1), uint(-1)); return markedHexes[randomi(0, markedHexes.length-1)]; } vec2u markedInternal() { double totalWeight = 0; vec2u chosen; for(uint i = 0, cnt = markedHexes.length; i < cnt; ++i) { vec2u pos = markedHexes[i]; auto@ dat = hex[pos]; double w = 1.0 + double(6-dat.freeAdj); double dist = abs(pos.x - center.x) + abs(pos.y - center.y); w /= (max(dist, 1.0) / 100.0); totalWeight += w; if(randomd() < w / totalWeight) chosen = pos; } return chosen; } vec2u markedFromDirection(uint directions) { vec2u found(uint(-1), uint(-1)); double checked = 0.0; if(directions & HM_DownLeft != 0) { uint x = gridSize.x-1; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); do { auto@ data = hex[pos]; if(data.subsysId != -1 && !data.subsys(this).def.passExterior) { break; } if(data.marked) { checked += 1.0; if(randomd() < 1.0 / checked) found = pos; break; } } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_DownLeft))); } } if(directions & HM_UpLeft != 0) { uint x = gridSize.x-1; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); do { auto@ data = hex[pos]; if(data.subsysId != -1 && !data.subsys(this).def.passExterior) break; if(data.marked) { checked += 1.0; if(randomd() < 1.0 / checked) found = pos; break; } } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_UpLeft))); } } if(directions & HM_DownRight != 0) { uint x = 0; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); do { auto@ data = hex[pos]; if(data.subsysId != -1 && !data.subsys(this).def.passExterior) { break; } if(data.marked) { checked += 1.0; if(randomd() < 1.0 / checked) found = pos; break; } } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_DownRight))); } } if(directions & HM_UpRight != 0) { uint x = 0; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); do { auto@ data = hex[pos]; if(data.subsysId != -1 && !data.subsys(this).def.passExterior) break; if(data.marked) { checked += 1.0; if(randomd() < 1.0 / checked) found = pos; break; } } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_UpRight))); } } if(directions & HM_Up != 0) { uint y = gridSize.y-1; for(uint x = 0; x < gridSize.x; ++x) { vec2u pos(x, y); do { auto@ data = hex[pos]; if(data.subsysId != -1 && !data.subsys(this).def.passExterior) break; if(data.marked) { checked += 1.0; if(randomd() < 1.0 / checked) found = pos; break; } } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_Up))); } } if(directions & HM_Down != 0) { uint y = 0; for(uint x = 0; x < gridSize.x; ++x) { vec2u pos(x, y); do { auto@ data = hex[pos]; if(data.subsysId != -1 && !data.subsys(this).def.passExterior) break; if(data.marked) { checked += 1.0; if(randomd() < 1.0 / checked) found = pos; break; } } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_Down))); } } return found; } void clearDir(const vec2u& pos, uint dir) { vec2u clearPos = pos; bool shouldClear = true; while(advanceHexPosition(clearPos, gridSize, HexGridAdjacency(dir))) { auto@ dat = hex[clearPos]; if(dat.marked || (dat.subsysId != -1 && !dat.subsys(this).def.passExterior)) { shouldClear = false; break; } } if(shouldClear) { clearPos = pos; while(advanceHexPosition(clearPos, gridSize, HexGridAdjacency(dir))) { auto@ dat = hex[clearPos]; dat.cleared = true; } } } bool isExterior(const vec2u& fromPosition, uint directions) { vec2u offPos = fromPosition; for(uint d = 0; d < 6; ++d) { vec2u pos = offPos; if((1< coated; if(directions & HM_DownLeft != 0) { uint x = gridSize.x-1; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); vec2u prevPos = pos; bool found = false; do { auto@ data = hex[pos]; if(data.subsysId == subsys.id && coated.find(pos) != -1) break; if(data.marked) { found = true; break; } if(data.subsysId != -1) { if(!data.subsys(this).def.passExterior) found = true; break; } prevPos = pos; } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_DownLeft))); if(found && !hex[prevPos].cleared) { addHex(subsys, prevPos); coated.insertLast(prevPos); } } } if(directions & HM_UpLeft != 0) { uint x = gridSize.x-1; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); vec2u prevPos = pos; bool found = false; do { auto@ data = hex[pos]; if(data.subsysId == subsys.id && coated.find(pos) != -1) break; if(data.marked) { found = true; break; } if(data.subsysId != -1) { if(!data.subsys(this).def.passExterior) found = true; break; } prevPos = pos; } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_UpLeft))); if(found && !hex[prevPos].cleared) { addHex(subsys, prevPos); coated.insertLast(prevPos); } } } if(directions & HM_DownRight != 0) { uint x = 0; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); vec2u prevPos = pos; bool found = false; do { auto@ data = hex[pos]; if(data.subsysId == subsys.id && coated.find(pos) != -1) break; if(data.marked) { found = true; break; } if(data.subsysId != -1) { if(!data.subsys(this).def.passExterior) found = true; break; } prevPos = pos; } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_DownRight))); if(found && !hex[prevPos].cleared) { addHex(subsys, prevPos); coated.insertLast(prevPos); } } } if(directions & HM_UpRight != 0) { uint x = 0; for(uint y = 0; y < gridSize.y; ++y) { vec2u pos(x, y); vec2u prevPos = pos; bool found = false; do { auto@ data = hex[pos]; if(data.subsysId == subsys.id && coated.find(pos) != -1) break; if(data.marked) { found = true; break; } if(data.subsysId != -1) { if(!data.subsys(this).def.passExterior) found = true; break; } prevPos = pos; } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_UpRight))); if(found && !hex[prevPos].cleared) { addHex(subsys, prevPos); coated.insertLast(prevPos); } } } if(directions & HM_Down != 0) { uint y = 0; for(uint x = 0; x < gridSize.x; ++x) { vec2u pos(x, y); vec2u prevPos = pos; bool found = false; do { auto@ data = hex[pos]; if(data.subsysId == subsys.id && coated.find(pos) != -1) break; if(data.marked) { found = true; break; } if(data.subsysId != -1) { if(!data.subsys(this).def.passExterior) found = true; break; } prevPos = pos; } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_Down))); if(found && !hex[prevPos].cleared) { addHex(subsys, prevPos); coated.insertLast(prevPos); } } } if(directions & HM_Up != 0) { uint y = gridSize.y-1; for(uint x = 0; x < gridSize.x; ++x) { vec2u pos(x, y); vec2u prevPos = pos; bool found = false; do { auto@ data = hex[pos]; if(data.subsysId == subsys.id && coated.find(pos) != -1) break; if(data.marked) { found = true; break; } if(data.subsysId != -1) { if(!data.subsys(this).def.passExterior) found = true; break; } prevPos = pos; } while(advanceHexPosition(pos, gridSize, HexGridAdjacency(HEX_Up))); if(found && !hex[prevPos].cleared) { addHex(subsys, prevPos); coated.insertLast(prevPos); } } } } int snake(SubsystemData@ subsys, uint directions, const vec2u& fromPosition) { int snaked = 0; vec2u offPos = fromPosition; while(true) { bool found = false; for(uint d = 0; d < 6; ++d) { vec2u pos = offPos; if(!advanceHexPosition(pos, gridSize, HexGridAdjacency(d))) continue; if((1< types; bool computed = false; string hullTag; Empire@ owner; Tag(Designer& dsg, const string& tag) { this.tag = getSubsystemTag(tag); hullTag = dsg.hulltag; @owner = dsg.owner; } const SubsystemDef@ choose() { compute(); if(types.length == 0) return null; return types[randomi(0, types.length-1)]; } void compute() { if(computed) return; computed = true; for(uint i = 0, cnt = getSubsystemDefCount(); i < cnt; ++i) { auto@ def = getSubsystemDef(i); if(!def.hasTag(tag)) continue; if(!def.hasHullTag(hullTag)) continue; if(def.hasTag(ST_SpecialCost)) continue; if(def.hasTag(ST_Disabled)) continue; if(!owner.isUnlocked(def)) continue; if(def.hasTag(ST_RaceSpecial) && !owner.major) continue; types.insertLast(def); } } bool filter(const SubsystemDef& def) const { if(computed) return types.find(def) != -1; return def.hasTag(tag); } Tag& opAnd(Filter& other) { compute(); for(uint i = 0, cnt = types.length; i < cnt; ++i) { if(!other.filter(types[i])) { types.removeAt(i); --i; --cnt; } } other.clear(); return this; } }; tidy class SubsystemFilter : Filter { const SubsystemDef@ type; SubsystemFilter(const SubsystemDef@ def) { @this.type = def; } bool filter(const SubsystemDef& def) const { return def is type; } const SubsystemDef@ choose() { return type; } }; tidy class Distributor { double frequency = 1.0; const SubsystemDef@ type; void prime() { } void distribute(Designer& dsg) { } }; tidy class Chance : Distributor { double chance; Distributor@ inner; Chance(double chance, Distributor@ inner) { this.chance = chance; @this.inner = inner; } void prime() { if(randomd() < chance) { inner.prime(); @type = inner.type; frequency = inner.frequency; } else { @type = null; frequency = 0.0; } } void distribute(Designer& dsg) { if(frequency == 0) return; inner.distribute(dsg); } }; tidy class Weapon : Distributor { Filter@ filter; const SubsystemDef@ def; bool mirror; bool allDirections; double minFreq = 0; double maxFreq = 0; Weapon(Filter@ filter, double minFreq, double maxFreq, bool mirror = true, bool allDirections = false) { @this.filter = filter; this.mirror = mirror; this.minFreq = minFreq; this.maxFreq = maxFreq; this.allDirections = allDirections; } void prime() override { if(def !is null) @type = def; else @type = filter.choose(); frequency = randomd(minFreq, maxFreq); if(type is null) frequency = 0.0; } void distribute(Designer& dsg) override { if(type is null) return; double pct = frequency / dsg.totalFrequency; if(mirror) pct *= 0.5; int hexes = pct * double(dsg.hexLimit); bool limitArc = type.hasTag(ST_HexLimitArc); vec2u core; if(allDirections) core = dsg.markedFromDirection(HM_ALL); else if(!limitArc || randomd() < 0.3) core = dsg.markedFromDirection(HM_DownLeft | HM_UpLeft | HM_Down | HM_Up); else core = dsg.markedFromDirection(HM_DownLeft | HM_UpLeft); if(!dsg.valid(core)) return; auto@ subsys = dsg.addSubsystem(type); subsys.core = core; if(!dsg.valid(subsys.core)) return; subsys.rotation = dsg.getFreeRotation(subsys.core, prioritize=!allDirections); if(limitArc) dsg.clearDirections(subsys.core, HM_ALL); else dsg.clearDirections(subsys.core, dsg.mask(subsys.rotation)); //Spread the subsystem dsg.addHex(subsys, subsys.core, subsys.def.coreModule); for(int i = 0; i < hexes; ++i) { if(!dsg.spread(subsys, freeWeight=2.0)) break; } if(mirror) { auto@ dupe = dsg.dupeMirror(subsys); if(dupe !is null) { if(limitArc) dsg.clearDirections(dupe.core, HM_ALL); else dsg.clearDirections(dupe.core, dsg.mask(dupe.rotation)); } } } }; tidy class Exhaust : Distributor { Filter@ filter; const SubsystemDef@ def; bool mirror; double minFreq = 0; double maxFreq = 0; Exhaust(Filter@ filter, double minFreq, double maxFreq, bool mirror = true) { @this.filter = filter; this.mirror = mirror; this.minFreq = minFreq; this.maxFreq = maxFreq; } void prime() override { if(def !is null) @type = def; else @type = filter.choose(); frequency = randomd(minFreq, maxFreq); if(type is null) frequency = 0.0; } void distribute(Designer& dsg) override { if(type is null) return; double pct = frequency / dsg.totalFrequency; if(mirror) pct *= 0.5; int hexes = pct * double(dsg.hexLimit); vec2u core = dsg.markedFromDirection(HM_DownRight);// | HM_UpRight); if(!dsg.valid(core)) return; auto@ subsys = dsg.addSubsystem(type); subsys.core = core; dsg.clearDirections(subsys.core, HM_DownLeft | HM_UpLeft); //Spread the subsystem dsg.addHex(subsys, subsys.core, subsys.def.coreModule); for(int i = 0; i < hexes; ++i) { if(!dsg.spread(subsys, freeWeight=2.0)) break; } if(mirror) { auto@ dupe = dsg.dupeMirror(subsys); if(dupe !is null) dsg.clearDirections(dupe.core, HM_DownLeft | HM_UpLeft); } } }; tidy class Internal : Distributor { Filter@ filter; const SubsystemDef@ def; double minFreq = 0; double maxFreq = 0; Internal(Filter@ filter, double minFreq, double maxFreq) { @this.filter = filter; this.minFreq = minFreq; this.maxFreq = maxFreq; } void prime() override { if(def !is null) @type = def; else @type = filter.choose(); frequency = randomd(minFreq, maxFreq); if(type is null) frequency = 0.0; } void distribute(Designer& dsg) override { if(type is null) return; double pct = frequency / dsg.totalFrequency; int hexes = pct * double(dsg.hexLimit); auto@ subsys = dsg.addSubsystem(type); subsys.core = dsg.markedInternal(); //Spread the subsystem dsg.addHex(subsys, subsys.core, subsys.def.coreModule); for(int i = 0; i < hexes; ++i) { if(!dsg.spread(subsys, freeWeight=-0.1)) break; } } }; tidy class Applied : Distributor { Filter@ filter; const SubsystemDef@ def; Applied(Filter@ filter) { @this.filter = filter; } void prime() override { if(def !is null) @type = def; else @type = filter.choose(); } void distribute(Designer& dsg) override { if(type is null) return; dsg.addApplied(type); } }; tidy class HorizSpan : Distributor { Filter@ filter; const SubsystemDef@ def; double minFreq = 0; double maxFreq = 0; HorizSpan(Filter@ filter, double minFreq, double maxFreq) { @this.filter = filter; this.minFreq = minFreq; this.maxFreq = maxFreq; } void prime() override { if(def !is null) @type = def; else @type = filter.choose(); frequency = randomd(minFreq, maxFreq); if(type is null) frequency = 0.0; } void distribute(Designer& dsg) override { if(type is null) return; double pct = frequency / dsg.totalFrequency; auto@ subsys = dsg.addSubsystem(type); subsys.core = dsg.markedInternal(); //Spread the subsystem dsg.addHex(subsys, subsys.core, subsys.def.coreModule); int snaked = 0; snaked += dsg.snake(subsys, HM_UpLeft|HM_DownLeft, subsys.core); snaked += dsg.snake(subsys, HM_UpRight|HM_DownRight, subsys.core); int hexes = pct * double(dsg.hexLimit) - snaked; for(int i = 0; i < hexes; ++i) { if(!dsg.spread(subsys, freeWeight=-0.1)) break; } //Clear outside of all hexes for(uint i = 0, cnt = subsys.hexes.length; i < cnt; ++i) { if(dsg.isExterior(subsys.hexes[i], HM_DownLeft)) { dsg.clearDirections(subsys.hexes[i], HM_DownLeft); break; } if(dsg.isExterior(subsys.hexes[i], HM_UpLeft)) { dsg.clearDirections(subsys.hexes[i], HM_UpLeft); break; } } for(uint i = 0, cnt = subsys.hexes.length; i < cnt; ++i) { if(dsg.isExterior(subsys.hexes[i], HM_DownRight)) { dsg.clearDirections(subsys.hexes[i], HM_DownRight); break; } if(dsg.isExterior(subsys.hexes[i], HM_UpRight)) { dsg.clearDirections(subsys.hexes[i], HM_UpRight); break; } } } }; tidy class Filler : Distributor { Filter@ filter; const SubsystemDef@ def; double minFreq = 0; double maxFreq = 0; Filler(Filter@ filter, double minFreq, double maxFreq) { @this.filter = filter; this.minFreq = minFreq; this.maxFreq = maxFreq; } void prime() override { if(def !is null) @type = def; else @type = filter.choose(); frequency = randomd(minFreq, maxFreq); if(type is null) frequency = 0.0; } void distribute(Designer& dsg) override { double pct = frequency / dsg.totalFrequency; int hexes = pct * double(dsg.hexLimit); auto@ subsys = dsg.addSubsystem(type); for(int i = 0; i < hexes; ++i) { vec2u pos = dsg.markedRandom(); if(!dsg.valid(pos)) break; dsg.addHex(subsys, pos); } } }; tidy class ArmorLayer : Distributor { Filter@ filter; const SubsystemDef@ def; uint directions = 0; int minLayers = 0; int maxLayers = 0; int layers = 0; ArmorLayer(Filter@ filter, uint directions, int minLayers, int maxLayers) { @this.filter = filter; this.directions = directions; this.minLayers = minLayers; this.maxLayers = maxLayers; } void prime() override { if(def !is null) @type = def; else @type = filter.choose(); layers = randomi(minLayers, maxLayers); } void distribute(Designer& dsg) override { if(type is null) return; auto@ subsys = dsg.addSubsystem(type); for(int i = 0; i < layers; ++i) dsg.addCoating(subsys, directions); } }; const Design@ createRandomDesign(uint type, int size, Empire@ emp) { Designer dsg(type, size, emp); return dsg.design(); }