import settings.map_lib; import hooks; import biomes; import buildings; import resources; import planet_types; import camps; import anomalies; import pickups; import block_effects; import artifacts; import statuses; from map_systems import IMapHook, MapHook; import generic_hooks; from resources import RARITY_DISTRIBUTION; import cargo; import ship_groups; #section server import systems; import object_creation; import remnant_designs; from objects.Asteroid import createAsteroid; from objects.Anomaly import createAnomaly; from objects.Artifact import createArtifact; from objects.Oddity import createNebula; from empire import Creeps; #section all //MakeStar(, = 100, = (0, 0, 0), Suffix = "") // Generate a star with the specified temperature, radius and position. class MakeStar : MapHook { double AVG_STAR_HEALTH = 20000000000; Document doc("Creates a star in the system."); Argument tempK("Temperature", AT_Range, doc="Star temperature in Kelvin."); Argument rad("Radius", AT_Range, "100.0", doc="Raidus of the star"); Argument position("Position", AT_Position, "(0, 0, 0)", doc="Position relative to the center of the system to create the star."); Argument suffix("Suffix", AT_Locale, "", doc="Suffix to append to the star name."); Argument normalDist("NormalTempRange", AT_Boolean, "False", doc="Whether to use a normal or flat distribution for the star temperature."); Argument moves(AT_Boolean, "False", doc="Whether the star can move and the light should track it."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { double temp = 0.0; if(arguments[0].isRange && arguments[4].boolean) temp = normald(arguments[0].decimal,arguments[1].decimal2); else temp = arguments[0].fromRange(); double radius = arguments[1].fromRange(); vec3d pos = arguments[2].fromPosition(); //Create star ObjectDesc starDesc; starDesc.type = OT_Star; starDesc.flags |= objNoDamage; if(system !is null) { starDesc.name = format(locale::SYSTEM_STAR, system.name); if(arguments[3].str.length > 0) starDesc.name += " "+arguments[3].str; starDesc.position = system.position + pos; } else { starDesc.name = locale::SYSTEM_STAR; } starDesc.radius = radius; starDesc.delayedCreation = true; Star@ star = cast(makeObject(starDesc)); if(system !is null) @star.region = system.object; star.temperature = temp; star.finalizeCreation(); if(system !is null) system.object.enterRegion(star); //Create star node Node@ node = bindNode(star, "StarNode"); node.color = blackBody(temp, max((temp + 15000.0) / 40000.0, 1.0)); if(system !is null) node.hintParentObject(system.object, false); double hp = AVG_STAR_HEALTH * (radius / 75.0); star.Health = hp; star.MaxHealth = hp; //Create light LightDesc lightDesc; lightDesc.att_quadratic = 1.f/(2000.f*2000.f); lightDesc.position = vec3f(star.position); lightDesc.diffuse = node.color * 1.0f; lightDesc.specular = lightDesc.diffuse; lightDesc.radius = star.radius; if(moves.boolean) makeLight(lightDesc, node); else makeLight(lightDesc); if(data !is null) @data.star = star; @current = star; } #section all }; //MakeBlackhole( = 20, = (0, 0, 0)) // Generate a star with the specified temperature, radius and position. class MakeBlackhole : MapHook { double BLACKHOLE_HEALTH = 200000000000; Document doc("Creates a black hole in the system."); Argument rad("Radius", AT_Range, "100.0", doc="Radius of the event horizon."); Argument position("Position", AT_Position, "(0, 0, 0)", doc="Position relative to the center of the system to create the black hole."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { double radius = arguments[0].fromRange(); vec3d pos = arguments[1].fromPosition(); //Create star ObjectDesc starDesc; starDesc.type = OT_Star; starDesc.flags |= objNoDamage; starDesc.name = format(locale::SYSTEM_BLACKHOLE, system.name); starDesc.position = system.position + pos; starDesc.radius = radius; starDesc.delayedCreation = true; Star@ star = cast(makeObject(starDesc)); star.alwaysVisible = true; @star.region = system.object; star.temperature = 0.0; star.finalizeCreation(); system.object.enterRegion(star); star.Health = BLACKHOLE_HEALTH; star.MaxHealth = BLACKHOLE_HEALTH; //Create star node Node@ node = bindNode(star, "BlackholeNode"); node.color = blackBody(16000.0, max((16000.0 + 15000.0) / 40000.0, 1.0)); node.hintParentObject(system.object, false); cast(node).establish(star); //Create light LightDesc lightDesc; lightDesc.att_quadratic = 1.f/(2000.f*2000.f); lightDesc.position = vec3f(star.position); lightDesc.diffuse = node.color * 1.0f; lightDesc.diffuse.a = 0.f; lightDesc.specular = lightDesc.diffuse; lightDesc.radius = star.radius; makeLight(lightDesc); if(data !is null) @data.star = star; @current = star; } #section all }; int parseRarity(const string& v) { if(v.equals_nocase("common")) { return RR_Common; } else if(v.equals_nocase("uncommon")) { return RR_Uncommon; } else if(v.equals_nocase("rare")) { return RR_Rare; } else if(v.equals_nocase("epic")) { return RR_Epic; } else { error(" Error: Invalid rarity spec "+escape(v)); return -1; } } bool parseResourceSpec(array@ resPossib, const string& v) { if(v.findFirst(":") == -1) { if(v == "null") return true; const ResourceType@ type = getResource(v); if(type is null) { error(" Error: Invalid resource "+escape(v)); return false; } resPossib.insertLast(type); return true; } else { array@ args = v.split(":"); if(args.length > 3) { error(" Error: Invalid resource spec "+escape(v)); return false; } int rarity = INT_MAX; if(args[0].equals_nocase("randomtype")) { if(args.length == 3) rarity = parseRarity(args[2]); const ResourceClass@ cls = getResourceClass(args[1]); if(cls is null) { error(" Error: Invalid resource type "+escape(v)); return false; } for(uint i = 0, cnt = cls.types.length; i < cnt; ++i) { if(rarity != INT_MAX && cls.types[i].rarity != rarity) continue; if(cls.types[i].artificial || cls.types[i].distribution <= 0) continue; if(cls.types[i].unique) continue; resPossib.insertLast(cls.types[i]); } return true; } else if(args[0].equals_nocase("randomlevel")) { uint lv = toInt(args[1]); if(args.length == 3) rarity = parseRarity(args[2]); for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) { auto@ res = getResource(i); if(res.level != lv) continue; if(rarity != INT_MAX && res.rarity != rarity) continue; if(res.artificial || res.unique) continue; if(res.limitlessLevel) continue; if(res.frequency <= 0 || res.distribution <= 0) continue; resPossib.insertLast(res); } return true; } else if(args[0].equals_nocase("randomrarity")) { if(args.length == 2) rarity = parseRarity(args[1]); for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) { auto@ res = getResource(i); if(rarity != INT_MAX && res.rarity != rarity) continue; if(res.artificial || res.unique) continue; if(res.frequency <= 0 || res.distribution <= 0) continue; resPossib.insertLast(res); } return true; } else if(args[0].equals_nocase("levelgte")) { uint lv = toInt(args[1]); if(args.length == 3) rarity = parseRarity(args[2]); for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) { auto@ res = getResource(i); if(res.level < lv) continue; if(rarity != INT_MAX && res.rarity != rarity) continue; if(res.artificial || res.unique) continue; if(res.limitlessLevel) continue; if(res.frequency <= 0 || res.distribution <= 0) continue; resPossib.insertLast(res); } return true; } else { for(uint i = 0, cnt = args.length; i < cnt; ++i) { auto@ res = getResource(args[i]); if(res is null) { error(" Error: Invalid resource spec "+escape(v)); return false; } resPossib.insertLast(res); } return true; } } } //MakePlanet( = Destributed, = 6:14, = 125:275, // = (-1, -1)) // Create a new planet with and size . Spaced in the orbit from // the last planet by . class MakePlanet : MapHook { int AVG_PLANET_GRID_WIDTH = 15; int AVG_PLANET_GRID_HEIGHT = 9; double AVG_PLANET_HEALTH = 1500000000; array resPossib; bool distribute = false; Document doc("Create a new planet in the system."); Argument resource(AT_Custom, "distributed", doc="The primary resource on the planet. 'distributed' to randomize."); Argument radius(AT_Range, "6:14", doc="Size of the planet, can be a random range."); Argument orbit_spacing(AT_Range, "125:275", doc="Distance from the previous planet."); Argument grid_size(AT_Position2D, "(-1, -1)", doc="Size of the planet's surface grid. (-1,-1) to randomize based on radius."); Argument conditions(AT_Boolean, "True", doc="Whether to let the planet randomly generate a condition."); Argument distribute_resource(AT_Boolean, "False", doc="Whether or not the selected resources should be frequency distributed."); Argument moons(AT_Boolean, "True", doc="Whether this planet should have a randomized amount of moons generated on it to start with."); Argument rings(AT_Boolean, "True", doc="Whether the planet can have rings generated around it."); Argument physics(AT_Boolean, "True", doc="Whether the planet should be a physical object."); bool instantiate() override { if(arguments[0].str.equals_nocase("distributed")) distribute = true; else if(!parseResourceSpec(resPossib, arguments[0].str)) return false; return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { const ResourceType@ resource; if(resPossib.length > 0) { if(resPossib.length == 1) @resource = resPossib[0]; else if(distribute_resource.boolean) { double roll = randomd(); double freq = 0.0; for(uint i = 0, cnt = resPossib.length; i < cnt; ++i) { freq += resPossib[i].frequency; double chance = resPossib[i].frequency / freq; if(roll < chance) { @resource = resPossib[i]; roll /= chance; } else { roll = (roll - chance) / (1.0 - chance); } } } else @resource = resPossib[randomi(0, resPossib.length-1)]; } if(data is null && distribute) @resource = getDistributedResource(); if(resource !is null) markResourceUsed(resource); double radius = arguments[1].decimal; if(arguments[1].isRange) radius = normald(arguments[1].decimal, arguments[1].decimal2); double spacing = arguments[2].fromRange() * config::SYSTEM_SIZE; system.radius += spacing; double pos = system.radius; if(spacing == 0) pos = randomd(200, system.radius); double angle = randomd(0,twopi); vec3d offset(cos(angle) * pos, 0, sin(angle) * -pos); ObjectDesc planetDesc; planetDesc.flags |= objNoDamage; planetDesc.flags |= objMemorable; planetDesc.type = OT_Planet; planetDesc.delayedCreation = true; @planetDesc.owner = null; planetDesc.radius = radius; planetDesc.position = system.position + offset; if(!physics.boolean) { planetDesc.flags |= objNoPhysics; planetDesc.flags |= objNoCollide; } planetDesc.name = system.name + " "; if(data !is null) appendRoman(data.planets.length + 1, planetDesc.name); else appendRoman(system.object.planetCount + 1, planetDesc.name); Planet@ planet = cast(makeObject(planetDesc)); @planet.region = system.object; if(data !is null) data.planets.insertLast(planet); //Generate biomes const Biome@ biome1; if(resource is null) { @biome1 = getDistributedBiome(); } else { @biome1 = getBiome(resource.nativeBiome); if(biome1 is null) @biome1 = getDistributedBiome(); } const Biome@ biome2 = getDistributedBiome(); const Biome@ biome3 = getDistributedBiome(); //Figure out planet size double sizeFact = clamp(radius / 10.0, 0.1, 5.0); int gridW = round(AVG_PLANET_GRID_WIDTH * sizeFact); int gridH = round(AVG_PLANET_GRID_HEIGHT * sizeFact); vec2d givenGrid = arguments[3].fromPosition2D(); if(givenGrid.x > 0) gridW = ceil(givenGrid.x); if(givenGrid.y > 0) gridH = ceil(givenGrid.y); //Figure out planet type const PlanetType@ planetType = getBestPlanetType(biome1, biome2, biome3); planet.PlanetType = planetType.id; planet.OrbitSize = 100 + radius; //Setup orbit planet.orbitAround(system.position, offset.length); planet.orbitSpin(randomd(35.0, 90.0)); //Create the planet surface; uint resId = uint(-1); if(resource !is null) resId = resource.id; planet.initSurface(gridW, gridH, biome1.id, biome2.id, biome3.id, resId); //Make node PlanetNode@ plNode = cast(bindNode(planet, "PlanetNode")); plNode.establish(planet); plNode.planetType = planet.PlanetType; if(rings.boolean && randomi(0,9) == 0) { uint style = randomi(); plNode.addRing(style); planet.setRing(style); } if(resource !is null) resource.applyGraphics(planet, plNode); else if(distribute && data !is null) data.distributedResources.insertLast(planet); double health = AVG_PLANET_HEALTH * sizeFact; planet.Health = health; planet.MaxHealth = health; planet.finalizeCreation(); //Setup condition if(conditions.boolean && data !is null) { if(randomd() < config::PLANET_CONDITION_CHANCE) data.distributedConditions.insertLast(planet); } //Setup moons if(moons.boolean) { while(randomd() < config::PLANET_MOON_CHANCE) { planet.addMoon(); planet.addStatus(getStatusID("Moon")); } } //Place in region system.object.enterRegion(planet); plNode.hintParentObject(system.object, false); @current = planet; } #section all }; #section server Planet@ spawnPlanetSpec(const vec3d& point, const string& resourceSpec, bool distributeResource = true, double radius = 0.0, bool physics = true) { MakePlanet plHook; plHook.initClass(); plHook.resource.str = resourceSpec; plHook.distribute_resource.boolean = distributeResource; plHook.moons.boolean = false; plHook.rings.boolean = false; plHook.conditions.boolean = false; plHook.physics.boolean = physics; if(radius != 0) plHook.radius.set(radius); plHook.instantiate(); Object@ current; auto@ reg = getRegion(point); auto@ sys = getSystem(reg); plHook.trigger(null, sys, current); auto@ planet = cast(current); planet.orbitAround(point, sys.position); return planet; } #section all class MakeMoon : MapHook { Document doc("Add a moon to the previously generated planet."); Argument size(AT_Decimal, "0", doc="Radius size of the moon."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ obj = current; if(obj.isPlanet) { cast(obj).addMoon(size.decimal); obj.addStatus(getStatusID("Moon")); } } #section all }; class BonusHealth : MapHook { Document doc("Add bonus health to the star or planet."); Argument amount(AT_Decimal, doc="Amount of bonus health to add."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ obj = current; if(obj.isPlanet) { Planet@ pl = cast(obj); pl.Health += amount.decimal; pl.MaxHealth += amount.decimal; } else if(obj.isStar) { Star@ star = cast(obj); star.Health += amount.decimal; star.MaxHealth += amount.decimal; } } #section all }; class MakeNebula : MapHook { Document doc("Turn the system into a nebula."); Argument color(AT_Color, "#f0c870", doc="Color of the nebula."); array colorPossibs; bool instantiate() override { auto@ txt = color.str.split(":"); for(uint i = 0, cnt = txt.length; i < cnt; ++i) colorPossibs.insertLast(toColor(txt[i])); return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Color col = colorPossibs[randomi(0, colorPossibs.length-1)]; createNebula(system.position, system.radius, color=col.rgba, region=system.object); } #section all }; //ExpandSystem( = 1000) // Expand the current system's size by . class ExpandSystem : MapHook { Document doc("Expands the system boundary."); Argument radius("Radius", AT_Range, "1000", doc="Amount to add to the current system radius."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { system.radius += arguments[0].fromRange();; } #section all }; //SetupOrbit( = 100, = (0, 0, 0), Orbit Pct = -1.0) // Setup an orbit on the previous object. class SetupOrbit : MapHook { Document doc("Gives the previously created object an orbit."); Argument radius("Radius", AT_Range, "100.0", doc="Radius at which the object should orbit."); Argument position("Position", AT_Position, "(0, 0, 0)", doc="Position relative to the system to orbit around."); Argument pct("Orbit Pct", AT_Range, "-1.0", doc="Percent through the year to start the orbit (0-1)."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null || !cur.hasOrbit) return; double radius = arguments[0].fromRange(); vec3d pos = arguments[1].fromPosition() + system.position; double pct = arguments[2].fromRange(); vec2d off = random2d(); cur.position = pos + vec3d(off.x, 0, off.y); cur.orbitAround(pos, radius); if(pct >= 0) cur.setOrbitPct(pct); } #section all }; class AddRegionStatus : MapHook { Document doc("Add a status that is given to all objects in the system it can apply to."); Argument status(AT_Status, doc="Type of status to add to objects in the system."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { system.object.addRegionStatus(null, status.integer); } #section all }; class NoRegionVision : MapHook { Document doc("Indicate that this system does not have automatic shared region vision."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { system.donateVision = false; } #section all }; //AddPlanetResource( = Distributed) // Add another resource to the previously generated object. class AddPlanetResource : MapHook { Document doc("Adds a single resource to the most recently created planet."); Argument resID("Resource", AT_Custom, "distributed", doc="Type or types of resource to choose from."); array resPossib; bool distribute = false; bool instantiate() { if(arguments[0].str.equals_nocase("distributed")) distribute = true; else if(!parseResourceSpec(resPossib, arguments[0].str)) return false; return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null || !cur.hasResources) return; const ResourceType@ resource; if(resPossib.length > 0) { if(resPossib.length == 1) @resource = resPossib[0]; else @resource = resPossib[randomi(0, resPossib.length-1)]; } if(data is null && distribute) @resource = getDistributedResource(); if(cur.isAsteroid) { return; } else { if(resource !is null) { markResourceUsed(resource); cur.addResource(resource.id); } else if(distribute && data !is null) data.distributedResources.insertLast(cur); } } #section all }; //AddResource(, ) // Add an income to the previously generated object. class AddResource : MapHook { Document doc("Adds a basic resource income to the most recently created planet."); Argument tileRes("Tile Resource", AT_TileResource, doc="Type of basic resource to produce."); Argument rate("Rate", AT_Integer, "1", doc="Amount of generation to add."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null || !cur.hasSurfaceComponent) return; cur.modResource(arguments[0].integer, +arguments[1].integer); } #section all }; //NoNeedPopulationForLevel() // Set the current planet to not need population to level up. class NoNeedPopulationForLevel : MapHook { #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null || !cur.hasSurfaceComponent) return; cur.setNeedsPopulationForLevel(false); } #section all }; //Rename() // Rename the object. class Rename : MapHook { Document doc("Renames the most recently created object."); Argument name("Name", AT_Locale, doc="Name for the object."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null) return; cur.name = arguments[0].str; } #section all }; //NameSystem() // Rename the system. class NameSystem : MapHook { Document doc("Renames the system."); Argument name("Name", AT_Locale, doc="Name for the system."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { system.object.name = arguments[0].str; system.name = arguments[0].str; } #section all }; //AddQuality( = 100) // Add extra quality to the system. Affects various distributions. class AddQuality : MapHook { Document doc("Adds quality to the system. Quality decides quantity and value of resources, anomalies, etc."); Argument amt("Amount", AT_Range, doc="Quality to add. 100 is a typical improvement."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(data !is null) data.quality += arguments[0].fromRange(); } #section all }; //AddPopulation() // Add population to the previous planet. class AddPopulation : MapHook { Document doc("Adds population to the most recently created planet."); Argument amt("Amount", AT_Range, doc="Population to add."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null || !cur.hasSurfaceComponent) return; cur.addPopulation(arguments[0].fromRange()); } #section all }; //SpawnBuilding(, ) // Spawn a building on the previous planet at . class SpawnBuilding : MapHook { Document doc("Places a building on the most recently created planet."); Argument type("Type", AT_Custom, doc="Building ID to place."); Argument pos("Position", AT_Position2D, doc="Where to place the center of the building on the planet."); Argument develop(AT_Boolean, "False", doc="Whether to mark all tiles its on as developed."); const BuildingType@ bldType; bool instantiate() { @bldType = getBuildingType(arguments[0].str); if(bldType is null) { error(" Error: Could not find building type: '"+escape(arguments[0].str)+"'"); return false; } return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null || !cur.hasSurfaceComponent) return; cur.spawnBuilding(bldType.id, vec2i(arguments[1].fromPosition2D()), develop.boolean); } #section all }; class ForceUsefulSurface : MapHook { Document doc("Force the planet to have a percentage of useful region on its surface."); Argument percent(AT_Decimal, doc="Percent of the surface that needs to be useful."); Argument biome(AT_PlanetBiome, doc="Biome to fill the planet with to ensure surface."); #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ cur = current; if(cur is null || !cur.hasSurfaceComponent) return; auto@ type = getBiome(biome.str); if(type !is null) cur.forceUsefulSurface(percent.decimal, type.id); else print("No such biome: "+biome.str); } #section all }; //MakeAsteroid( = Distributed, = 0.4) // Create a new asteroid with the given resource available. // If is set to Distributed, distribute some random resources // with . class MakeAsteroid : MapHook { Document doc("Creates an asteroid in the system."); Argument cargo(AT_Cargo, EMPTY_DEFAULT, doc="Type of cargo to create on the asteroid."); Argument cargo_amount(AT_Range, "500:10000", doc="Amount of cargo for the asteroid to have."); Argument resource(AT_Custom, EMPTY_DEFAULT, doc="Resource to put on the asteroid."); Argument distribution_chance(AT_Decimal, "0.4", doc="For distributed resources, chance to add additional resource. Repeats until failure."); #section server array resPossib; bool distribute = false; bool noResource = false; bool instantiate() { if(resource.str.length != 0) { if(resource.str.equals_nocase("distributed")) distribute = true; else if(resource.str.equals_nocase("none")) noResource = true; else if(!parseResourceSpec(resPossib, resource.str)) return false; } return MapHook::instantiate(); } void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { vec2d rpos; if(data !is null) rpos = random2d(150.0, system.radius * 1.5 - 250.0); else rpos = random2d(150.0, system.radius * 0.8); vec3d pos = system.position + vec3d(rpos.x, randomd(-50.0, 50.0), rpos.y); Asteroid@ roid = createAsteroid(pos, system.object, delay=true); roid.orbitAround(system.position); roid.orbitSpin(randomd(20.0, 60.0)); @current = roid; if(noResource) { roid.initMesh(); return; } double totChance = config::ASTEROID_OCCURANCE + config::RESOURCE_ASTEROID_OCCURANCE; double resChance = config::RESOURCE_ASTEROID_OCCURANCE; double roll = randomd(0, totChance); int cargoType = cargo.integer; if(cargoType == -1) { auto@ ore = getCargoType("Ore"); if(ore !is null) cargoType = ore.id; } bool cargoSpec = cargo.integer != -1 && config::ASTEROID_OCCURANCE != 0; bool resSpec = (resPossib.length != 0 || distribute) && config::RESOURCE_ASTEROID_OCCURANCE != 0; if((cargoSpec && !resSpec) || ((cargoSpec == resSpec) && roll >= resChance)) { roid.addCargo(cargoType, cargo_amount.fromRange()); } else { if(distribute || resPossib.length == 0) { do { const ResourceType@ type = getDistributedAsteroidResource(); if(roid.getAvailableCostFor(type.id) < 0.0) roid.addAvailable(type.id, type.asteroidCost); } while(randomd() < distribution_chance.decimal); } else { const ResourceType@ resource; if(resPossib.length > 0) { if(resPossib.length == 1) @resource = resPossib[0]; else @resource = resPossib[randomi(0, resPossib.length-1)]; } if(resource !is null) { if(roid.getAvailableCostFor(resource.id) < 0.0) roid.addAvailable(resource.id, resource.asteroidCost); } } } roid.initMesh(); } #section all }; class MakeAsteroidBelt : MapHook { Document doc("Creates an asteroid belt in the system."); Argument count("Count", AT_Integer, "8", doc="Number of asteroids in the belt."); Argument cargo(AT_Cargo, "Ore", doc="Type of cargo to create on the asteroid belt."); Argument cargo_amount(AT_Range, "500:10000", doc="Amount of cargo for the asteroids to have."); Argument distribution_chance(AT_Decimal, "0.4", doc="For distributed resources, chance to add additional resource. Repeats until failure."); bool instantiate() { if(arguments[0].integer <= 0) return false; return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(config::ASTEROID_OCCURANCE == 0 && config::RESOURCE_ASTEROID_OCCURANCE == 0) return; double radius = randomd(0.5,1.5) * system.radius; double angle = randomd(0, twopi); double totChance = config::ASTEROID_OCCURANCE + config::RESOURCE_ASTEROID_OCCURANCE; double resChance = config::RESOURCE_ASTEROID_OCCURANCE; for(uint i = 0, cnt = arguments[0].integer; i < cnt; ++i) { angle += twopi / double(cnt); double ang = angle + randomd(-0.25,0.25) * twopi / double(cnt); vec3d pos = system.position + vec3d(cos(ang) * radius, randomd(-50.0, 50.0), sin(ang) * radius); Asteroid@ roid = createAsteroid(pos, system.object, delay=true); roid.orbitAround(system.position); roid.orbitSpin(randomd(20.0, 60.0)); @current = roid; if(randomd(0, totChance) >= resChance && cargo.integer != -1) { roid.addCargo(cargo.integer, cargo_amount.fromRange()); } else { do { const ResourceType@ type = getDistributedAsteroidResource(); if(roid.getAvailableCostFor(type.id) < 0.0) roid.addAvailable(type.id, type.asteroidCost); } while(randomd() < distribution_chance.decimal); } roid.initMesh(); } } #section all }; //MakeAnomaly( = Distributed) // Generate a anomaly field in this system of type . class MakeAnomaly : MapHook { Document doc("Creates an anomaly in the system."); Argument anomType("Type", AT_Custom, "distributed", doc="Type of anomaly to create."); const AnomalyType@ type; bool instantiate() { if(!arguments[0].str.equals_nocase("distributed")) { @type = getAnomalyType(arguments[0].str); if(type is null) { error(" Error: Could not find anomaly type: "+arguments[0].str); return false; } } return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { const AnomalyType@ gen = type; if(gen is null) @gen = getDistributedAnomalyType(); vec2d rpos = random2d(150.0, system.radius - 250.0); vec3d pos = system.position + vec3d(rpos.x, randomd(-50.0, 50.0), rpos.y); Anomaly@ anomaly = createAnomaly(pos, gen.id); @current = anomaly; } #section all }; class SpawnRandomRemnants : MapHook { Document doc("Spawn a remnant fleet with a random design at a particular size."); Argument size(AT_Integer, doc="Base size of the remnant flagship."); Argument occupation(AT_Range, "0.8", doc="Percentage of support that is filled."); Argument gametime_size(AT_Range, "0", doc="If spawned after game start, increase the flagship size by this much per minute of game time."); Argument offset(AT_Decimal, "0", doc="Minimum offset from the edge of the system to the fleet."); #section server void postTrigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { int sz = size.integer; sz += round(gametime_size.fromRange() * gameTime / 60.0); vec2d campPos = random2d(200.0, (system.radius - offset.decimal) * 0.95); vec3d pos = system.position + vec3d(campPos.x, 0, campPos.y); spawnRemnantFleet(pos, sz, occupation.fromRange()); } #section all } //MakeCreepCamp( = Distributed) // Generate a creep camp in this system of type . class MakeCreepCamp : MapHook { Document doc("Creates a creep camp in the system."); Argument campID("Type", AT_Custom, "distributed", doc="Type of camp to create."); Argument offset(AT_Decimal, "0", doc="Minimum offset from the edge of the system to the camp."); const CampType@ campType; MakeCreepCamp() { argument("Type", AT_Custom, "distributed"); } bool instantiate() { if(!arguments[0].str.equals_nocase("distributed")) { @campType = getCreepCamp(arguments[0].str); if(campType is null) { error(" Error: Could not find creep camp type: '"+escape(arguments[0].str)+"'"); return false; } } return MapHook::instantiate(); } #section server void postTrigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(config::REMNANT_OCCURANCE == 0.0) return; const CampType@ type = campType; if(type is null) @type = getDistributedCreepCamp(); vec2d campPos = random2d(200.0, (system.radius - offset.decimal) * 0.95); vec3d pos = system.position + vec3d(campPos.x, 0, campPos.y); makeCreepCamp(pos, type, system.object); } #section all }; #section server void makeCreepCamp(const vec3d& pos, const CampType@ type, Region@ region = null) { //Create pickup Pickup@ pickup; double roll = randomd(0.0, type.pickupFrequency); for(uint i = 0, cnt = type.pickups.length; i < cnt; ++i) { const PickupType@ ptype = type.pickups[i]; if(roll <= ptype.frequency) { double minRad = 20.0 + ptype.physicalSize * 2.0; double maxRad = 40.0 + ptype.physicalSize * 4.0; vec2d off = random2d(minRad, maxRad); @pickup = createPickup(pos + vec3d(off.x, randomd(5.0, 10.0), off.y), ptype.id, defaultEmpire); pickup.finalizeCreation(); pickup.setCampType(type.id); if(region !is null) { @pickup.region = region; region.enterRegion(pickup); } break; } else { roll -= ptype.frequency; } } //Add statuses to region if(region !is null) { for(uint i = 0, cnt = type.region_statuses.length; i < cnt; ++i) region.addRegionStatus(null, type.region_statuses[i].id); } //Create defenders Object@ lastLeader; for(uint i = 0, cnt = type.ships.length; i < cnt; ++i) { const Design@ dsg = type.ships[i]; uint minCnt = type.shipMins[i]; uint maxCnt = type.shipMaxes[i]; int amt = randomi(minCnt, maxCnt); if(!dsg.hull.hasTag(ST_IsSupport)) { for(; amt > 0; --amt) { vec3d leaderPos = pos; if(lastLeader !is null) leaderPos += random3d(10.0, 40.0); @lastLeader = createShip(leaderPos, dsg, Creeps, free=true, memorable=true); lastLeader.setAutoMode(config::REMNANT_AGGRESSION == 0 ? AM_HoldPosition : AM_RegionBound); lastLeader.sightRange = 0; lastLeader.setRotation(quaterniond_fromAxisAngle(vec3d_up(), randomd(-pi, pi))); for(uint i = 0, cnt = type.statuses.length; i < cnt; ++i) lastLeader.addStatus(type.statuses[i].id); if(type.shipNames[i].length != 0) { lastLeader.name = type.shipNames[i]; lastLeader.named = true; } pickup.addPickupProtector(lastLeader); } } else { for(; amt > 0; --amt) createShip(lastLeader.position, dsg, Creeps, lastLeader, free=true); } } //Create randomized defenders if(type.flagshipSize > 0) { vec3d leaderPos = pos; if(lastLeader !is null) leaderPos += random3d(10.0, 40.0); @lastLeader = spawnRemnantFleet(leaderPos, type.flagshipSize, type.supportOccupation); lastLeader.setAutoMode(config::REMNANT_AGGRESSION == 0 ? AM_HoldPosition : AM_RegionBound); for(uint i = 0, cnt = type.statuses.length; i < cnt; ++i) lastLeader.addStatus(type.statuses[i].id); pickup.addPickupProtector(lastLeader); } if(type.targetStrength > 0) { vec3d leaderPos = pos; if(lastLeader !is null) leaderPos += random3d(10.0, 40.0); @lastLeader = spawnRemnantFleet(leaderPos, composeRemnantFleet(type.targetStrength)); lastLeader.setAutoMode(config::REMNANT_AGGRESSION == 0 ? AM_HoldPosition : AM_RegionBound); for(uint i = 0, cnt = type.statuses.length; i < cnt; ++i) lastLeader.addStatus(type.statuses[i].id); pickup.addPickupProtector(lastLeader); } } #section all class SetStaticSeeableRange : MapHook { Document doc("Set the seeable range of static objects (anomalies, asteroids, etc) in this system."); Argument range(AT_Decimal, doc="Seeable range to set."); #section server void postTrigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { for(uint i = 0, cnt = system.object.objectCount; i < cnt; ++i) { Object@ obj = system.object.objects[i]; if(obj.hasStatuses) continue; obj.seeableRange = range.decimal; } } #section all }; //MakeAdjacentCreepCamp( = Distributed, = False) // Generate a creep camp in an adjacent system of type . class MakeAdjacentCreepCamp : MapHook { Document doc("Creates a creep camp in an adjacent system."); Argument campID("Type", AT_Custom, "distributed", doc="Type of camp to create."); Argument placeFar("Place Far", AT_Boolean, "True", doc="Whether the camp should be pushed to the edge of the system."); const CampType@ campType; bool instantiate() { if(!arguments[0].str.equals_nocase("distributed")) { @campType = getCreepCamp(arguments[0].str); if(campType is null) { error(" Error: Could not find creep camp type: '"+escape(arguments[0].str)+"'"); return false; } } return MapHook::instantiate(); } #section server void postTrigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(data !is null && data.ignoreAdjacencies) return; if(config::REMNANT_OCCURANCE == 0.0) return; const CampType@ type = campType; if(type is null) @type = getDistributedCreepCamp(); if(system.adjacent.length == 0) return; int at = randomi(0, system.adjacent.length-1); SystemDesc@ other = getSystem(system.adjacent[at]); vec2d campPos = random2d(200.0, other.radius); if(arguments[1].boolean) { vec3d off = (other.position - system.position).normalized(other.radius * 0.8); campPos = vec2d(off.x, off.z); } vec3d pos = other.position + vec3d(campPos.x, 0, campPos.y); makeCreepCamp(pos, type); } #section all }; //MakeArtifact( = Distributed) // Generate a anomaly field in this system of type . class MakeArtifact : MapHook { Document doc("Creates an artifact in the system."); Argument artifactType("Type", AT_Custom, "distributed", doc="Type of artifact to create."); const ArtifactType@ type; bool instantiate() { if(!arguments[0].str.equals_nocase("distributed")) { @type = getArtifactType(arguments[0].str); if(type is null) { error(" Error: Could not find artifact type: "+arguments[0].str); return false; } } return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(data !is null) data.artifacts += 1; @current = makeArtifact(system, type is null ? getDistributedArtifactType(system.contestation).id : type.id); } #section all }; #section server Artifact@ makeArtifact(SystemDesc@ system, uint type = uint(-1)) { const ArtifactType@ gen; if(type == uint(-1)) @gen = getDistributedArtifactType(); else @gen = getArtifactType(type); vec2d rpos = random2d(150.0, system.radius - 250.0); vec3d pos = system.position + vec3d(rpos.x, randomd(-50.0, 50.0), rpos.y); Artifact@ obj = createArtifact(pos, gen, system.object); obj.orbitAround(system.position); @obj.region = system.object; system.object.enterRegion(obj); return obj; } #section all //RepeatHomeworlds() // Repeats the block for every homeworld in this system, and sets // the object to the appropriate empire's homeworld. class RepeatHomeworlds : BlockEffect { #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const { if(data.homeworlds is null) return; for(uint n = 0, ncnt = data.homeworlds.length; n < ncnt; ++n) { Empire@ emp = data.homeworlds[n]; for(uint i = 0, cnt = inner.length; i < cnt; ++i) { auto@ cur = cast(inner[i]); if(cur !is null) cur.trigger(data, system, current); } Object@ cur = current; Planet@ pl = cast(cur); if(pl !is null) { @pl.owner = emp; @emp.Homeworld = pl; } } } #section all }; //AddAdjacentAnomalies() // Add anomaly to random adjacent systems. class AddAdjacentAnomalies : MapHook { Document doc("Creates anomalies in adjacent systems."); Argument count("Count", AT_Range, doc="Number to create."); MakeAnomaly maker; #section server void postTrigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(system.adjacent.length == 0) return; if(data !is null && data.ignoreAdjacencies) return; if(config::ANOMALY_OCCURANCE == 0.0) return; uint cnt = arguments[0].fromRange(); for(uint i = 0; i < cnt; ++i) { int at = randomi(0, system.adjacent.length-1); SystemDesc@ other = getSystem(system.adjacent[at]); maker.trigger(null, other, current); } } #section all }; //AddAdjacentArtifacts() // Add anomaly to random adjacent systems. class AddAdjacentArtifacts : MapHook { Document doc("Creates artifacts in adjacent systems."); Argument count("Count", AT_Range, doc="Number to create."); MakeArtifact maker; #section server void postTrigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(system.adjacent.length == 0) return; if(data !is null && data.ignoreAdjacencies) return; uint cnt = arguments[0].fromRange(); for(uint i = 0; i < cnt; ++i) { int at = randomi(0, system.adjacent.length-1); SystemDesc@ other = getSystem(system.adjacent[at]); maker.trigger(null, other, current); } } #section all }; //GuaranteeAdjacentResource(, = 1) // Guarantee an adjacent resource out of a type spec. class GuaranteeAdjacentResource : MapHook { Document doc("Attempts to guarantee that specific reosurces will be available in adjacent systems. Logs on failure."); Argument res("Resource", AT_Custom, doc="Type(s) of resource to guarantee. Only one will be picked."); Argument count("Distance", AT_Integer, "1", doc="System hop distance within which to allow the resource."); array resPossib; bool instantiate() { if(!parseResourceSpec(resPossib, arguments[0].str)) return false; return MapHook::instantiate(); } #section server void add(array& objects, array& sources, SystemData@ data, int distance) { for(uint i = 0, cnt = data.adjacentData.length; i < cnt; ++i) { auto@ other = data.adjacentData[i]; for(uint n = 0, ncnt = other.distributedResources.length; n < ncnt; ++n) { Planet@ pl = cast(other.distributedResources[n]); if(pl !is null) { objects.insertLast(pl); sources.insertLast(other); } } if(distance > 1) add(objects, sources, other, distance-1); } } void postTrigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(data is null) return; if(data.ignoreAdjacencies) return; const ResourceType@ resource; if(resPossib.length > 0) { if(resPossib.length == 1) @resource = resPossib[0]; else @resource = resPossib[randomi(0, resPossib.length-1)]; } if(resource is null) return; array objects; array sources; add(objects, sources, data, arguments[1].integer); if(objects.length != 0) { uint i = randomi(0, objects.length-1); Object@ target = objects[i]; sources[i].distributedResources.remove(target); target.addResource(resource.id); } else { warn("WARNING: Map guarantee '"+arguments[0].str+"' could not find planet around system '"+system.name+"'."); } } #section all }; class OnPlanet : MapHook { GenericEffect@ eff; Document doc("Run the passed hook as a single-time planet effect hook on the object. Because it only runs once, hooks with ticks or data storage will not work!"); Argument hook(AT_Custom, doc="Hook to run."); bool instantiate() override { @eff = cast(parseHook(hook.str, "planet_effects::")); if(eff is null) { error("GenericEffect(): could not find inner hook: "+escape(hook.str)); return false; } return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ c = current; if(c !is null) eff.enable(c, null); } #section all }; class Trigger : MapHook { BonusEffect@ eff; Document doc("Run the specified hook on the current object."); Argument hook(AT_Custom, doc="Hook to run."); bool instantiate() override { @eff = cast(parseHook(hook.str, "bonus_effects::")); if(eff is null) { error("Trigger(): could not find inner hook: "+escape(hook.str)); return false; } return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { Object@ c = current; if(c is null) eff.activate(c, null); } #section all }; class TriggerRegion : MapHook { BonusEffect@ eff; Document doc("Run the specified hook on the current region."); Argument hook(AT_Custom, doc="Hook to run."); bool instantiate() override { @eff = cast(parseHook(hook.str, "bonus_effects::")); if(eff is null) { error("TriggerRegion(): could not find inner hook: "+escape(hook.str)); return false; } return MapHook::instantiate(); } #section server void trigger(SystemData@ data, SystemDesc@ system, Object@& current) const override { if(system !is null) eff.activate(system.object, null); } #section all }; //Copy a region during map generation void mapCopyRegion(SystemDesc@ from, SystemDesc@ to, uint typeMask = ~0) { #section server Region@ reg = from.object; reg.wait(); Object@ current; MakePlanet plHook; plHook.initClass(); plHook.instantiate(); plHook.distribute = false; plHook.resPossib.length = 1; MakeAsteroid roidHook; roidHook.noResource = true; roidHook.initClass(); roidHook.instantiate(); MakeStar starHook; starHook.initClass(); starHook.instantiate(); MakeAnomaly anomalyHook; anomalyHook.initClass(); anomalyHook.instantiate(); MakeArtifact artifactHook; artifactHook.initClass(); artifactHook.instantiate(); //Mirror region properties to.donateVision = from.donateVision; from.object.mirrorRegionStatusTo(to.object); //Copy for(uint i = 0, cnt = reg.objectCount; i < cnt; ++i) { Object@ obj = reg.objects[i]; if(typeMask & (1<(current); pl.orbitAround(destPos, to.position); to.radius = from.radius; if(pl.statusInstanceCount != 0 || obj.statusInstanceCount != 0) { for(uint j = 0, jcnt = pl.statusInstanceCount; j < jcnt; ++j) pl.removeStatus(pl.statusInstanceId[j]); for(uint j = 0, jcnt = obj.statusInstanceCount; j < jcnt; ++j) pl.addStatus(obj.statusInstanceType[j]); } pl.mirrorSurfaceFrom(obj); } else if(obj.isAsteroid) { Asteroid@ base = cast(obj); base.wait(); roidHook.trigger(null, to, current); Asteroid@ roid = cast(current); roid.orbitAround(destPos, to.position); for(uint i = 0, cnt = base.getAvailableCount(); i < cnt; ++i) roid.addAvailable(base.getAvailable(i), base.getAvailableCost(i)); for(uint i = 0, cnt = base.cargoTypes; i < cnt; ++i) roid.addCargo(base.cargoType[i], base.getCargoStored(base.cargoType[i])); } else if(obj.isStar) { Star@ base = cast(obj); starHook.arguments[0].set(base.temperature); starHook.arguments[1].set(base.radius); starHook.arguments[2].set(destPos - to.position); starHook.trigger(null, to, current); } else if(obj.isArtifact) { Artifact@ base = cast(obj); @artifactHook.type = getArtifactType(base.ArtifactType); artifactHook.trigger(null, to, current); Artifact@ artifact = cast(current); artifact.orbitAround(destPos, to.position); } else if(obj.isAnomaly) { Anomaly@ base = cast(obj); @anomalyHook.type = getAnomalyType(base.anomalyType); anomalyHook.trigger(null, to, current); Anomaly@ anomaly = cast(current); anomaly.position = destPos; anomaly.clearOptions(); for(uint i = 0, cnt = base.optionCount; i < cnt; ++i) anomaly.addOption(base.option[i]); } else if(obj.isPickup) { Pickup@ base = cast(obj); Pickup@ pickup = createPickup(destPos, base.PickupType, defaultEmpire); pickup.finalizeCreation(); Ship@ baseProt = cast(base.getProtector()); if(baseProt !is null) { baseProt.wait(); vec3d leadPos = baseProt.position - from.position; leadPos.z = -leadPos.z; leadPos += to.position; Ship@ leader = createShip(leadPos, baseProt.blueprint.design, Creeps, free=true, memorable=true); leader.name = baseProt.name; leader.named = baseProt.named; leader.setAutoMode(config::REMNANT_AGGRESSION == 0 ? AM_HoldPosition : AM_RegionBound); leader.sightRange = 0; pickup.addPickupProtector(leader); for(uint n = 0, ncnt = baseProt.supportCount; n < ncnt; ++n) { Ship@ supp = cast(baseProt.supportShip[n]); if(supp !is null) createShip(leadPos, supp.blueprint.design, Creeps, leader, free=true); } } } else if(obj.isOddity) { Oddity@ odd = cast(obj); if(odd.isGate()) continue; ObjectDesc desc; desc.type = OT_Oddity; desc.radius = odd.radius; desc.position = destPos; desc.flags |= objNoPhysics; desc.flags |= objNoDamage; auto@ mirr = Oddity(desc); mirr.noCollide = true; mirr.makeVisuals(odd.getVisualType(), color=odd.getVisualColor()); } } #section all }