2[
WorkbenchPluginAttribute(name:
"Generate projectile wind data", description:
"", wbModules: {
"WorldEditor" }, awesomeFontCode: 0xf72e)]
3class SCR_ProjectileWindageDataGeneratorPlugin : WorldEditorPlugin
8 [
Attribute(uiwidget:
UIWidgets.ResourceNamePicker,
desc:
"Override config files to which generated data will be saved.\nIf left empty, or number of entries is less than number of entries in [Projectiles] list, then system will try to fetch the config from the projectile prefab data.",
params:
"conf class=SCR_ProjectileWindTable")]
14 [
Attribute(defvalue:
"1",
desc:
"Init speed coefs for which data should be generated.\nWhen this array is left empty, then system will use init speed coef = 1, and in case of mortar shells, it will fetch their values from SCR_MortarShellGadgetComponent.ChargeRingConfig",
params:
"0.01 inf")]
18 protected float m_fMaxSimulatedHorizontalDistance;
20 [
Attribute(defvalue:
"60",
desc:
"Maximum simulated time for the projectile’s flight. When simulated time will reach this value, then simulation will be stopped even when projectile has not reached its end of travel",
params:
"1 inf 0.01")]
21 protected float m_fMaxSimulationTime;
24 protected float m_fPeakSearchPrecision;
29 if (Workbench.ScriptDialog(
"Configuration",
"Select prefabs and their coresponding configs.",
this) == 0)
32 if (!m_aProjectiles || m_aProjectiles.IsEmpty())
50 entitySource = res.GetResource().ToEntitySource();
55 return GetGame().SpawnEntityPrefab(res, world);
62 protected float GetInitSpeed(notnull
IEntitySource entitySource)
65 for (
int i, componentsCount = entitySource.GetComponentCount(); i < componentsCount; i++)
67 componentSrc = entitySource.GetComponent(i);
75 componentSrc.Get(
"InitSpeed", initSpeed);
89 SCR_MortarShellGadgetComponent mortarShellComp = SCR_MortarShellGadgetComponent.Cast(projectile.FindComponent(SCR_MortarShellGadgetComponent));
92 for (
int j, count = mortarShellComp.GetNumberOfChargeRingConfigurations(); j < count; j++)
94 initSpeedCoefs.Insert(mortarShellComp.GetChargeRingConfig(j)[1]);
99 initSpeedCoefs.Insert(1);
102 return initSpeedCoefs;
106 protected void GenerateData()
108 WorldEditor worldEditor = Workbench.GetModule(WorldEditor);
117 if (windSpeeds.IsEmpty())
121 bool initCoefsOverride = !initSpeedCoefs.IsEmpty();
124 configs.Copy(m_aConfigsOverride);
140 failedEntries.Set(i,
"Failed to spawn projectile");
147 failedEntries.Set(i,
"Failed to find some form of ProjectileMoveComponent");
151 initSpeed = GetInitSpeed(entitySource);
154 failedEntries.Set(i,
"Failed to find init speed");
158 if (!initCoefsOverride)
159 initSpeedCoefs = FetchInitSpeedCoefs(projectile);
161 if (configs.IsIndexValid(i))
162 configFile = configs[i];
164 configFile =
string.Empty;
166 if (configFile.IsEmpty())
169 configFile =
data.GetProjectileWindTable();
170 if (configFile.IsEmpty())
172 failedEntries.Set(i,
"Failed to find config file");
177 configs.Insert(configFile);
181 config = GenerateWindData(initSpeedCoefs, initSpeed, windSpeeds, moveComp);
183 if (res.IsValid() && !
BaseContainerTools.SaveContainer(res.GetResource().ToBaseContainer(), configFile))
184 failedEntries.Set(i,
"Failed save generated data");
191 foreach (
int id,
string reason : failedEntries)
193 failedResult +=
string.Format(
"\nID=%1 - %2 - Reason=%3",
id.
ToString(),
FilePath.StripPath(m_aProjectiles[
id]),
reason);
196 string detailedTextAddition =
".";
197 if (!failedEntries.IsEmpty())
199 failedResult =
string.Format(
"%1%2%3",
"Generation failed for given entries:", failedResult,
"\n\n\nYou can use Ctrl+C to copy the content of this dialog.");
200 detailedTextAddition =
" with errors! For more inforamtion check details below."
203 ResourceManager resourceMgr = SCR_WorldEditorToolHelper.GetResourceManager();
204 if (resourceMgr && !configs.IsEmpty())
206 array<string> configPaths = {};
213 resourceMgr.RebuildResourceFiles(configPaths,
"PC");
216 Workbench.Dialog(
"Finished",
"Generation of windage data has been finished" + detailedTextAddition, failedResult);
228 const float minElevAngle = 1;
229 const float maxElevAngle = 90;
230 vector poiNoWind, poiCrosswind, poiTailwind, poiHeadwind;
231 vector tailwind, headwind, crosswind;
235 array<ref SCR_ProjectileWindData>
data = {};
239 float initSpeedWithCoef;
240 foreach (
float wind : windSpeeds)
242 headwind = -
vector.Forward * wind;
243 tailwind =
vector.Forward * wind;
244 crosswind =
vector.Right * wind;
245 foreach (
float coef : initSpeedCoefs)
248 firingSolutions = {};
249 initSpeedWithCoef = initSpeed * coef;
250 for (
int angleDeg = minElevAngle; angleDeg <= maxElevAngle; angleDeg++)
252 poiNoWind = projectileMoveComp.GetProjectileSimulationResult(
vector.Zero, initSpeedWithCoef, angleDeg, maxSimulationTime: m_fMaxSimulationTime, maxHorizontalDistance: m_fMaxSimulatedHorizontalDistance);
253 poiCrosswind = projectileMoveComp.GetProjectileSimulationResult(
vector.Zero, initSpeedWithCoef, angleDeg, windSpeed: crosswind, maxSimulationTime: m_fMaxSimulationTime, maxHorizontalDistance: m_fMaxSimulatedHorizontalDistance);
254 poiHeadwind = projectileMoveComp.GetProjectileSimulationResult(
vector.Zero, initSpeedWithCoef, angleDeg, windSpeed: headwind, maxSimulationTime: m_fMaxSimulationTime, maxHorizontalDistance: m_fMaxSimulatedHorizontalDistance);
255 poiTailwind = projectileMoveComp.GetProjectileSimulationResult(
vector.Zero, initSpeedWithCoef, angleDeg, windSpeed: tailwind, maxSimulationTime: m_fMaxSimulationTime, maxHorizontalDistance: m_fMaxSimulatedHorizontalDistance);
258 if (poiNoWind[2] > 0)
260 peakPoint = FindPeakHeight(projectileMoveComp, initSpeedWithCoef, angleDeg, poiNoWind[2], m_fPeakSearchPrecision, m_fMaxSimulationTime);
261 preImpactPosition = projectileMoveComp.GetProjectileSimulationResult(
vector.Zero, initSpeedWithCoef, angleDeg, maxHorizontalDistance: poiNoWind[2] - 1, maxSimulationTime: m_fMaxSimulationTime);
262 angleOfImpact =
vector.Direction(poiNoWind, preImpactPosition).Normalized().VectorToAngles()[1];
267 preImpactPosition =
vector.Zero;
278 (
Math.AbsFloat(poiHeadwind[2] - poiNoWind[2]) +
Math.AbsFloat(poiTailwind[2] - poiNoWind[2])) * 0.5,
282 firingSolutions.Insert(fireSolValues);
283 values.Insert(value);
302 protected vector FindPeakHeight(notnull
ProjectileMoveComponent projectileMoveComp,
float initSpeedWithCoef,
float angleDeg,
float maxDist,
float precision = 10,
float maxSimulationTime = 60)
312 while (minDist < maxDist)
314 midDist = (minDist + maxDist) * 0.5;
316 first = projectileMoveComp.GetProjectileSimulationResult(
vector.Zero, initSpeedWithCoef, angleDeg, maxHorizontalDistance: midDist -
precision, maxSimulationTime: maxSimulationTime);
317 second = projectileMoveComp.GetProjectileSimulationResult(
vector.Zero, initSpeedWithCoef, angleDeg, maxHorizontalDistance: midDist, maxSimulationTime: maxSimulationTime);
319 if (first[1] > second[1])
321 else if (first[1] <= second[1])
327 if (first[1] > second[1])
335 protected int ButtonRun()
342 protected int ButtonClose()
ArmaReforgerScripted GetGame()
ResourceName resourceName
IEntity SpawnEntity(ResourceName entityResourceName, notnull IEntity slotOwner)
Get all prefabs that have the spawner data
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto external Managed FindComponent(typename typeName)
Object holding reference to resource. In destructor release the resource.
static float ConvertFromRadians(float radianAngleFrom, SCR_EOpticsAngleUnits toUnitType)
static float ConvertToRadians(float angleFrom, SCR_EOpticsAngleUnits fromUnitType)
SCR_FieldOfViewSettings Attribute
array< float > TFloatArray
proto external string ToString()
Plain C++ pointer, no weak pointers, no memory management.
array< vector > TVectorArray
array< ResourceName > TResourceNameArray