About weather: generating clouds
-
Requisites
Map editing tools, such as:
- Ozonex FaF Editor (https://github.com/ozonexo3/FAForeverMapEditor/releases)
or - GPG Editor (https://wiki.faforever.com/index.php?title=Map_Editing_Tools)
At the moment of writing the Ozonex editor doesn't support weather markers. You'll need the GPG editor to get this all to work.
Weather
Weather generators are rare, if not at all, used by maps in the vault. A lot of the original maps were supposed to use them but the code to generate the weather is all commented out. In turn, maps generally have no weather at all.
Weather on the map Rainmakers SurvivalGenerating weather is fairly easy but you need to know the exact steps. The GPG editor allows the creation of the proper markers. Creating and customizing the weather involves three steps: placing the proper markers in the GPG editor, editing the script file of the map and then editing those markers inside the save file through a text editor.
GPG editor
Open up your map and open up the marker tools (F6). From there on navigate completely to the right and at the end you'll find the markers:
- Weather Definition
- Weather Generator
Place one of both of them in the center of the map. Then save the map and close the GPG editor - this prevents you from accidentally overriding the next step. You can have multiple generators, but for the sake of the article, I'll assume that you only have one.
You can view and edit the properties of a marker inside the editor. Select a marker and extent the 'Marker Editor' window vertically. This will reveal all the properties of, for example, a Weather Definition marker.
The weather can not be rendered and inspected inside the editor. You'll need to open up the map in Forged Alliance in order to inspect it. Constantly copying your map from the editor folder to the game folder is a bit of a pickle - therefore we use a text editor to edit the properties. This allows you to iterate more quickly when you restart your map to inspect the results.
Script file
Open up the _script.lua file from your map in your favorite text editor. A typical script file from a non-adaptive map looks like this:
local ScenarioUtils = import('/lua/sim/ScenarioUtilities.lua') local ScenarioFramework = import('/lua/ScenarioFramework.lua') function OnPopulate() ScenarioUtils.InitializeArmies() -- optional, only if applicable ScenarioFramework.SetPlayableArea('AREA_1' , false) end function OnStart(scenario) end
We want to add in the weather thread so that clouds are slowly generated over time. Change it to:
local ScenarioUtils = import('/lua/sim/ScenarioUtilities.lua') local ScenarioFramework = import('/lua/ScenarioFramework.lua') -- for weather generation local Weather = import('/lua/weather.lua') function OnPopulate() ScenarioUtils.InitializeArmies() -- optional, only if applicable ScenarioFramework.SetPlayableArea('AREA_1' , false) end function OnStart(scenario) -- for weather generation Weather.CreateWeather() end
With the current weather definition and generator clouds should already be generating. Start the game, disable fog of war and make sure that it is. Then it is time to start tweaking!
If you have no vision over a weather generator marker then you will not see the clouds that are being generated.
Save file
Open up the _save.lua file from your map in your favorite text editor. Any editor will suffice, as long as it has a search function.
Weather definition
Search the file (CTRL + F) for 'Weather Definition'. You'll find in the markers chain a marker that looks like the following:
['Weather Definition 00'] = { ['WeatherType03Chance'] = FLOAT( 0.300000 ), ['color'] = STRING( 'FF808000' ), ['WeatherType01'] = STRING( 'SnowClouds' ), ['WeatherType02'] = STRING( 'WhiteThickClouds' ), ['WeatherType04'] = STRING( 'None' ), ['WeatherDriftDirection'] = VECTOR3( 1, 0, 0 ), ['WeatherType01Chance'] = FLOAT( 0.300000 ), ['WeatherType03'] = STRING( 'WhitePatchyClouds' ), ['WeatherType04Chance'] = FLOAT( 0.100000 ), ['MapStyle'] = STRING( 'Tundra' ), ['WeatherType02Chance'] = FLOAT( 0.300000 ), ['hint'] = BOOLEAN( true ), ['type'] = STRING( 'Weather Definition' ), ['prop'] = STRING( '/env/common/props/markers/M_Defensive_prop.bp' ), ['orientation'] = VECTOR3( 0, -0, 0 ), ['position'] = VECTOR3( 522.5, 68.8418, 541.5 ), },
These values are scrambled - they can appear in any order. The important bits are:
- WeatherType01 / 02 / 03 / 04: The type of weather that will be generated by all weather generators.
- WeatherChance01 / 02 / 03 / 04: The chance that the given type will be generated.
- MapStyle: Determines in what table the weather types will be searched for, such as 'Desert' or 'Evergreen'.
- Weather Drift Direction: This value is not referenced in the code and is therefore not used.
The direction that the clouds slowly move towards. You typically want this to match the direction of your water. With the standard camera the positive x-axis points to the right of a map, the positive y-axis points to the sky and the positive z-axis points the bottom of a map.
All values are case sensitive. As an example, it is 'Dsert' and not 'desert'.
Values that are not used by the code but by the editor:
- prop, orientation, color, hint
Each theme has its own list of weather that can share names but look different. Therefore choosing the right map style is important. We can find all the available weather as comments in one of the lua files of the base game:
Map Style Types: Desert Evergreen Geothermal Lava RedRock Tropical Tundra Style Weather Types: Desert LightStratus - Evergreen CumulusClouds - StormClouds - RainClouds - WARNING, only use these a ForceType on a weather generator, max 2 per map Geothermal Lava RedRock LightStratus - Tropical LightStratus - Tundra WhitePatchyClouds - SnowClouds - WARNING, only use these a ForceType on a weather generator, max 2 per map All Styles: Notes: ( Cirrus style cloud emitters, should be used on a ForceType Weather Generator, placed ) ( in the center of a map. Take note that the these are sized specific for map scale ) CirrusSparse256 - CirrusMedium256 - CirrusHeavy256 - CirrusSparse512 - CirrusMedium512 - CirrusHeavy512 - CirrusSparse1024 - CirrusMedium1024 - CirrusHeavy1024 - CirrusSparse4096 - CirrusMedium4096 - CirrusHeavy4096 -
This defines all the weather that is available. The RainClouds and SnowClouds generate rain and snow respectively - these should be used sparsely. An unlisted entry is 'None', which indicates that the no weather may be generated at all.
Weather generator
Search the file (CTRL + F) for 'Weather Generator'. You'll find in the markers chain a marker that looks like the following:
['Weather Generator 00'] = { ['cloudCountRange'] = FLOAT( 0.000000 ), ['color'] = STRING( 'FF808000' ), ['ForceType'] = STRING( 'None' ), ['cloudHeightRange'] = FLOAT( 15.000000 ), ['cloudSpread'] = FLOAT( 150.000000 ), ['spawnChance'] = FLOAT( 1.000000 ), ['cloudEmitterScaleRange'] = FLOAT( 0.000000 ), ['cloudEmitterScale'] = FLOAT( 1.000000 ), ['cloudCount'] = FLOAT( 10.000000 ), ['cloudHeight'] = FLOAT( 180.000000 ), ['hint'] = BOOLEAN( true ), ['type'] = STRING( 'Weather Generator' ), ['prop'] = STRING( '/env/common/props/markers/M_Defensive_prop.bp' ), ['orientation'] = VECTOR3( 0, -0, 0 ), ['position'] = VECTOR3( 518.5, 68.6699, 542.5 ), },
These values are scrambled - they can appear in any order. The important bits are:
- ForceType: Forces the weather on this generator to the given type. It can only be forced into emitters that are part of the map style chosen in the weather definition marker. Is 'None' by default: this allows the weather definition marker to determine its type. As an example, given that the MapStyle is Evergreen we can set it to CumulusClouds, StormClouds or RainClouds.
- cloudSpread: How spread out the clouds are.
- spawnChance: Determines whether or not it can have phases where there are no clouds generated at all.
- cloudEmitterScale: The absolute scale of all clouds.
- cloudEmitterScaleRange: A range value that randomizes the absolute scale.
- cloudCount: The absolute number of clouds available at once.
- cloudCountRange: A range value that randomizes the absolute number.
- cloudHeight: The absolute height at which the clouds can spawn, starting from the position of the marker.
- cloudHeightRange: A range value that randomizes the absolute height.
As an example on the range variables: if the absolute value is 90 and the range is 15 then the final value is randomly chosen from the range [75,105].
All values are case sensitive. As an example, it is 'None' and not 'none'.
Values that are not used by the code but by the editor:
- prop, orientation, color, hint
Under the hood
It is always good to dive into the code and discover how values are used in computations. This will bring in more perspective as to what is possible and leaves out speculation on what you feel it is doing.
Let's start off with reading out the markers - turning the concrete data into a more abstract form that allows us to construct the weather.
function GetWeatherMarkerData(MapScale) local markers = ScenarioUtils.GetMarkers() local WeatherDefinition = {} local ClusterDataList = {} local defaultcloudclusterSpread = math.floor(((MapScale[1] + MapScale[2]) * 0.5) * 0.15) // Make a list of all the markers in the scenario that are of the markerType if markers then for k, v in markers do // Read in weather cluster positions and data if v.type == 'Weather Generator' then table.insert( ClusterDataList, { clusterSpread = v.cloudSpread or defaultcloudclusterSpread, cloudCount = v.cloudCount or 10, cloudCountRange = v.cloudCountRange or 0, cloudHeight = v.cloudHeight or 180, cloudHeightRange = v.cloudHeightRange or 10, position = v.position, emitterScale = v.cloudEmitterScale or 1, emitterScaleRange = v.cloudEmitterScaleRange or 0, forceType = v.ForceType or "None", spawnChance = v.spawnChance or 1, } ) // Read in weather definition elseif v.type == 'Weather Definition' then if table.getn( WeatherDefinition ) > 0 then LOG('WARNING: Weather, multiple weather definitions found. Last read Weather definition will override any previous ones.') end WeatherDefinition = { MapStyle = v.MapStyle or "None", WeatherTypes = { { Type = v.WeatherType01 or "None", Chance = v.WeatherType01Chance or 0.25, }, { Type = v.WeatherType02 or "None", Chance = v.WeatherType02Chance or 0.25, }, { Type = v.WeatherType03 or "None", Chance = v.WeatherType03Chance or 0.25, }, { Type = v.WeatherType04 or "None", Chance = v.WeatherType04Chance or 0.25, }, }, Direction = v.WeatherDriftDirection or {0,0,0}, } end end end return WeatherDefinition,ClusterDataList end
This shows us what data is relevant and transformed into another format. We can also see that there are a lot of sane defaults provided. And last but certainly not least: we can see what the new names for the data is that is used throughout the code.
function CreateWeatherThread() local MapScale = ScenarioInfo.size // x,z map scaling local WeatherDefinition, ClusterData = GetWeatherMarkerData(MapScale) local MapStyle = WeatherDefinition.MapStyle local WeatherEffectsType = GetRandomWeatherEffectType( WeatherDefinition ) //WeatherEffectsType = 'StormClouds' if WeatherEffectsType == 'None' then return end local numClusters = table.getn( ClusterData ) if not WeatherDefinition.WeatherTypes and numClusters then LOG(' WARNING: Weather, no [Weather Definition] marker placed, with [Weather Generator] markers placed in map, aborting weather generation') return end // If we have any clusters, then generate cluster list if numClusters != 0 then local notfoundMapStyle = true for k, v in MapStyleList do if MapStyle == v then SpawnWeatherAtClusterList( ClusterData, MapStyle, WeatherEffectsType ) notfoundMapStyle = false end end if notfoundMapStyle and (MapStyle != 'None') then LOG(' WARNING: Weather Map style [' .. MapStyle .. '] not defined. Define this as one of the Map Style Definitions. ' .. repr(MapStyleList)) end end end
For each cluster, which is a generator, the data that has been transformed before is being used to call the function to start generating clouds on top of those generators.
The cloud types are directly determined from the given map style that is defined in the weather definition marker - only the cloud types form that style are available because that is where the code is searching for to find a matching type.
function SpawnWeatherAtClusterList( ClusterData, MapStyle, EffectType ) local numClusters = table.getn( ClusterData ) local WeatherEffects = MapWeatherList[MapStyle][EffectType] // Exit out early, if for some reason, we have no effects defined for this if (WeatherEffects == nil) or (WeatherEffects != nil and (table.getn(WeatherEffects) == 0)) then return end // Parse through cluster position and datal for i = 1, numClusters do // Determine whether current cluster should spawn or not if ClusterData[i].spawnChance < 1 then local pick if util.GetRandomFloat( 0, 1 ) > ClusterData[i].spawnChance then LOG( 'Cluster ' .. i .. ' No clouds generated ' ) continue end end local clusterSpreadHalfSize = ClusterData[i].clusterSpread * 0.5 local numCloudsPerCluster = nil if ClusterData[i].cloudCountRange != 0 then numCloudsPerCluster = util.GetRandomInt(ClusterData[i].cloudCount - ClusterData[i].cloudCountRange / 2,ClusterData[i].cloudCount + ClusterData[i].cloudCountRange / 2) else numCloudsPerCluster = ClusterData[i].cloudCount end local clusterEffectMaxScale = ClusterData[i].emitterScale + ClusterData[i].emitterScaleRange local clusterEffectMinScale = ClusterData[i].emitterScale - ClusterData[i].emitterScaleRange // Calculate weather cluster entity positional range local LeftX = ClusterData[i].position[1] - clusterSpreadHalfSize local TopZ = ClusterData[i].position[3] - clusterSpreadHalfSize local RightX = ClusterData[i].position[1] + clusterSpreadHalfSize local BottomZ = ClusterData[i].position[3] + clusterSpreadHalfSize // Get base height and height range local BaseHeight = ClusterData[i].position[2] + ClusterData[i].cloudHeight local HeightOffset = ClusterData[i].cloudHeightRange // Choose weather cluster effects local clusterWeatherEffects = WeatherEffects local numEffects = table.getn(WeatherEffects) if ClusterData[i].forceType != "None" then clusterWeatherEffects = MapWeatherList[MapStyle][ClusterData[i].forceType] LOG( 'Force Effect Type: ', ClusterData[i].forceType ) numEffects = table.getn(clusterWeatherEffects) end // Generate Clouds for our cluster for j = 0, numCloudsPerCluster do local cloud = Entity() local x = util.GetRandomInt( LeftX, RightX ) local y = BaseHeight + util.GetRandomInt(-HeightOffset,HeightOffset) local z = util.GetRandomInt( TopZ, BottomZ ) Warp( cloud, Vector(x,y,z) ) local EmitterGroupSeed = util.GetRandomInt(1,numEffects) local numEmitters = table.getn(clusterWeatherEffects[EmitterGroupSeed]) local effects = clusterWeatherEffects[EmitterGroupSeed] for k, v in clusterWeatherEffects[EmitterGroupSeed] do CreateEmitterAtBone(cloud,-2,-1,v):ScaleEmitter(util.GetRandomFloat( clusterEffectMaxScale, clusterEffectMinScale )) end end end end
This is where the generators are turned into emitters. All the data is transformed (again) into the proper format that dictates how large the emitter can be, how much it should spawn and at what height it should spawn.
The type of a specific generator can be overridden when you force its type - this is determined when the type of emitter is chosen. Even for the forced type it only searches through the weather available in the selected map style for the forced type.
The emitters are attached to a (dummy) entity. If a player has no vision over this entity then the attached emitters do not spawn.
Frequently asked Questions (FAQ)
I don't see any clouds
Make sure that:
- Your script file has been updated properly. You can check the logs (F9) - if there is a warning then you did something wrong. If all is good then you should see something similar somewhere in your log to:
INFO: Weather Definition { INFO: Direction={ 15, -30, 0, type="VECTOR3" }, INFO: MapStyle="Tundra", INFO: WeatherTypes={ INFO: { Chance=0.30000001192093, Type="WhitePatchyClouds" }, INFO: { Chance=0.30000001192093, Type="WhitePatchyClouds" }, INFO: { Chance=0.30000001192093, Type="None" }, INFO: { Chance=0.10000000149012, Type="None" } INFO: } INFO: } INFO: Weather Effect Type: \000WhitePatchyClouds
- You need to have at least one generate that has its 'ForceType' value to 'None'. Make sure that the word starts with a capital letter.
- You need vision over the weather generator marker in order for it to generate. You can test this best by having no fog of war on your map.
About you
If you have interesting sources, approaches, opinions or ideas that aren't listed yet but may be valuable to the article: feel free to leave a message down below or contact me on Discord. The idea is to create a bunch of resources to share our knowledge surrounding various fields of development in Supreme Commander.
If you've used this resource for one of your maps feel free to make a post below: I'd love to know about it!
- Ozonex FaF Editor (https://github.com/ozonexo3/FAForeverMapEditor/releases)
-
This is cool. How resource - intensive is this feature? let's assume a default/usual cloud situation.
As SupCom gets sluggish in prolonged gameplay with tons of units, how smart is it to use this, how much will it impact SIM speed? -
Clouds are annoying and 10x as annoying when you are watching a replay at +10.
-
Hmm, I wouldn't want clouds ever. Nothing that obscures my view of the map would be a good thing?
-
If it is subtle then it is fine in my opinion.
-
It's an artistic tool that could be used poorly or well. And it could be used to make other effects. Idk, placing a green or black one under water to give it a subtly different texture. Having it in some mountains meant to be impassable anyways?
-
Weather is cool and not much of an issue as you can see the icons through the cloud cover anyways. What are some good maps with nice looking weather?
-
Clouds can definitely be an issue, funeral plains has clouds which can be annoying.
-
A lot of people turn off "screen shake" even though it adds to immersion. I don't think people want clouds interfering with their ability to see things. Is there a way to toggle clouds on/off? And if not, can that be added?
-
also, I assume they are static, unable to move? Or can they?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Hi everyone, I had seen the weather markers in FAF Map Editor. Do they work? Or has anyone tested this? So for my part I'm a fan if the clouds are placed where the main thing is not affected.
-
yes they work
-
thx
-
@saver I've recently used clouds in Project Tabula. Placed them so that they don't interfere with gameplay, though: only when you zoom all the way out do you have some light clouds, and there are some clouds below the plateau.
-
And clouds do not show in the editor
-
Thank you for the answers