Lua解読分析集

Last-modified: 2024-05-03 (金) 21:41:09

Standalone ScenarioおよびCampaignで使用されているシナリオ作者以外誰にも使いこなせなさそうなLuaを解読してみるページ
Lua Script実用例集/LuaInitに改名する?

ここでは"LuaInit"(シナリオロード時に行われる各関数の読み込み)を見ていく

目次

各パラメーターの設定・管理関連

  • scenarioScriptFilePath = '/BrassDrum/'
    シナリオ用Luaフォルダの場所
  • IsBetaVersion(true)
    IsBetaVersion関数の実行に影響
  • scenarioZuluOffset = 0
    シナリオが行われる場所の時差
  • inDevelopment = true
    2陣営シナリオ関連の関数実行に影響

RunScript(fileName)

Luaファイルを呼び出し実行する,シナリオLuaフォルダの場所名と拡張子.luaを書く必要のない工夫がされている

function RunScript(fileName)
	local scriptFilePath = scenarioScriptFilePath..fileName..'.lua'
	print ('Attempting to execute '..scriptFilePath)
	if ScenEdit_RunScript(scriptFilePath) then
		print ('Success!')
	end
end

LuaReset()

Keyvalueをすべて削除+LuaInit.luaを再読み込みする

function LuaReset()
	print ('Resetting...')
	ScenEdit_ClearKeyValue("")
	print ('KeyValues cleared.')
	RunScript('LuaInit')
end

ConfirmSpecialAction(actionDescription)

特別アクションの実行確認らしいが使用例未確認

function ConfirmSpecialAction(actionDescription)
	local choice = ScenEdit_MsgBox('Are you sure you want to execute the special action: \n \n'..actionDescription, 4)
	if choice == 'Yes' then
		return true
	else
		ScenEdit_MsgBox('Event Cancelled')
		return false
	end
end

ConvertStringToBoolean(stringName)

keyvalueにしたbooleanを再変換する
(keyvalueがbooleanの場合そのまま読み込まれるのでわざわざ再変換しなくていいんだけど)

function ConvertStringToBoolean(stringName)
	local stringName = string.upper(stringName)
	local result = false
	if stringName == 'TRUE' then
		result = true
	end
	return result
end

IsBetaVersion(booleanValue)

trueだと一部関数でで管理メッセージが出る

function IsBetaVersion(booleanValue)
	if booleanValue == true then
		ScenEdit_SetKeyValue('betaVersion','true')
		return true
	elseif booleanValue == false then
		ScenEdit_SetKeyValue('betaVersion','false')
		return false
	else
		local betaStatus = ScenEdit_GetKeyValue('betaVersion')
		local result = ConvertStringToBoolean(betaStatus)
		return result
	end
end

DebugModeIsOn(booleanValue)

trueだと一部関数実行時に確認メッセージが表示される

function DebugModeIsOn(booleanValue)
	if booleanValue == true then
		ScenEdit_SetKeyValue('debugMode','true')
		return true
	elseif booleanValue == false then
		ScenEdit_SetKeyValue('debugMode','false')
		return false
	else
		local debugStatus = ScenEdit_GetKeyValue('debugMode')
		local result = ConvertStringToBoolean(debugStatus)
		return result
	end
end

BugMessage(eventName,description)

function BugMessage(eventName,description)
	if IsBetaVersion() then
		ScenEdit_MsgBox('An issue occured with '..eventName..';\n'..description..'\n\nThis will not affect system stability but may affect scenario balance. \n\nPlease report this in the Beta Testing Forum. \n\nThanks!', 0)
	else
		ScenEdit_MsgBox('An issue occured with '..eventName..';\n'..description..'\n\nThis will not affect system stability but may affect scenario balance. \n\nPlease report this in the Tech Support subforum on the Matrix Games forum, including a screenshot of this message. \n\nhttp://www.matrixgames.com/forums/tt.asp?forumid=1279', 0)
	end
end

Round(num, numDecimalPlaces) 

四捨五入する(四捨五入する値,表示桁数)

function Round(num, numDecimalPlaces)
  local mult = 10^(numDecimalPlaces or 0)
  return math.floor(num * mult + 0.5) / mult
end

ReturnTimeStringAsNumberOfMinutes

Unitラッパーのairbornetimeは航空ユニットの飛行時間を"2 hr 15 min"のように表記するが,この" X hr X min"文字列を分単位の数値に変換する(ここに使用例がある)
airbornetimeが1時間以上かつ1日未満でないと機能しない(飛行時間を秒として返すairbornetime_vを使ったほうが楽だと思われるが…?)

  • targetStringが「25」など長さが3未満である(=すでに分の数値)場合は,文字列を数値としてそのまま返す,長さが3以上だと" ** hr ~~ min"形式であるみなされる(もっとも長さが3以上でも「100」のような数値だけだと関数は結果を返さない)
  1. targetString内から" hr"の文字列がある個所を探し,targetStringの1文字目から" hr"がある個所の1文字前まで,すなわち時間をhourとして取得する
  2. targetString内から" min"の文字列がある個所を探し,targetStringの" hr"がある個所より("hr"の2文字を飛ばした)3文字目から" min"がある個所の1文字前まで,すなわち分をminとして取得する
  3. (hour*60)+minuteの計算をして飛行時間を分に変換
function ReturnTimeStringAsNumberOfMinutes(targetString)
	if string.len(targetString) < 3 then --Is this already a number of minutes?
		result = tonumber(targetString)
	else --if not, parse the string to get the values
		local hourDelimiter = string.find(targetString,' hr')
		if hourDelimiter ~= nil then
            hour = string.sub(targetString,1,hourDelimiter-1)
        end
		local minuteDelimiter = string.find(targetString,' min')
		if minuteDelimiter ~= nil then
            minute = string.sub(targetString,hourDelimiter+3,minuteDelimiter-1)
        end
		result = (hour*60)+minute
	end
	return result
end

ShuffleTable( t )

テーブルの入れ替え

function ShuffleTable( t )
    local rand = math.random
    assert( t, "ShuffleTable() expected a table, got nil" )
    local iterations = #t
    local j

    for i = iterations, 2, -1 do
        j = rand(i)
        t[i], t[j] = t[j], t[i]
    end
end

ChangeScore(side,amt,reason)

陣営現在の得点に加点・減点

function ChangeScore(side,amt,reason)
	local newScore = ScenEdit_GetScore(side) + amt
	ScenEdit_SetScore(side,newScore,reason)
	print (side..' score changed to '..newScore)
	return newScore
end

マップ関係

ConvertDecimalPositionToDegrees(latitude,longitude)

10進法座標から60進法座標への変換(緯度,経度)

10進数法座標=60進法度+(60進法分/60)+(60進法秒/60/60)であることを理解する(参考→軽~く軌道計算! 換算.すなわち

  • 10進法1=60進法1度
  • 10進法1/60=0.0167=60進法1分
  • 10進法1/3600=0.000278=60進法1秒
    であり,さらに
  1. 10進数法座標の整数部分が60進法度
  2. 1. の小数部分に60を乗算したもの「(10進数法座標-60進法度)*60」の整数部分が60進法分
  3. 2. の小数部分に60を乗算したもの「( (10進数法座標-60進法度)*60)-60進法分)*60」の整数部分が60進法秒
    となる.
function ConvertDecimalPositionToDegrees(latitude,longitude)
	local latitidePrefix, longitudePrefix
	if latitude > 0 then
		latitidePrefix = "N"
	else
		latitidePrefix = "S"
		latitude = latitude*-1
	end

	local latitudeDegrees = math.floor(latitude)
	local latitudeMinutes = math.floor((latitude - latitudeDegrees)*60)
	local latitudeSeconds = (((latitude-latitudeDegrees)*60) - latitudeMinutes)*60
	latitudeSeconds = Round(latitudeSeconds, 0)

	if longitude > 0 then
		longitudePrefix = "E"
	else
		longitudePrefix = "W"
		longitude = longitude*-1
	end

	local longitudeDegrees = math.floor(longitude)
	local longitudeMinutes = math.floor((longitude - longitudeDegrees)*60)
	local longitudeSeconds = (((longitude-longitudeDegrees)*60) - longitudeMinutes)*60
	longitudeSeconds = Round(longitudeSeconds, 0)

	local result = (latitidePrefix..latitudeDegrees .. "°" .. latitudeMinutes .. "'" .. latitudeSeconds .. '", '..
		longitudePrefix..longitudeDegrees .. "°" .. longitudeMinutes .. "'" .. longitudeSeconds .. '"')
	return result
end

RandomPosition(latitudeMin,latitudeMax,longitudeMin,longitudeMax)

ランダムで地図上の座標を指定する(最小緯度,最大緯度,最小経度,最大経度)

  1. 最小緯経度,最大緯経度からランダムに度を決める
  2. 分と秒を決める
    Commandにおいて10進数座標は13桁の数値によって表される(もっとも60進法1秒=0.000278なので7桁あれば十分)のだが,math.random(1,(10^13)/(10^13)の計算によって10^-13 (0.0000000000001)~1の小数点値が出る.これが座標の分と秒となる.
  • 度と分・秒の値を合計して10進法座標完成
function RandomPosition(latitudeMin,latitudeMax,longitudeMin,longitudeMax)
	local lat_var = math.random(1,(10^13)) --random number between 1 and 10^13
	local lon_var = math.random(1,(10^13)) --random number between 1 and 10^13
	local pos_lat = math.random(latitudeMin,latitudeMax) + (lat_var/(10^13)) --latitude;
	local pos_lon = math.random(longitudeMin,longitudeMax) + (lon_var/(10^13)) --longitude;
	return {latitude=pos_lat,longitude=pos_lon}
end

CircularRandomPosition(x_latitude, x_longitude, max_radius)

ランダム位置の取得

  1. 指定座標中心に72個の点からなる円を生成する(半径は0.1-max_radiusからランダム)
  2. 72ある点のうち1点をランダムに選択する
function CircularRandomPosition(x_latitude, x_longitude, max_radius)
	local randomisationCircle = World_GetCircleFromPoint({
		latitude=x_latitude,
		longitude=x_longitude,
		radius=math.random(0.1,max_radius),
		numpoints = 72})
	local randomisedPoint = randomisationCircle[math.random(1,#randomisationCircle)]
    return randomisedPoint
end

OverWater (latitude, longitude)

指定位置が海上か判定=高度0m未満

function OverWater (latitude, longitude)
	local pointElevation = World_GetElevation({
		latitude = latitude,
		longitude = longitude})
	if pointElevation < 0 then
		return true
	else
		return false
	end
end

JitterPosition(guid,radius,overWater)

  1. ユニットの位置を起点にCircularRandomPosition()でランダム位置を取得する
  2. ランダム位置をOverWater ()によって判定する(true=海上,false=陸上)
  3. overWaterの値と同じならユニットをランダム位置に移動させる
function JitterPosition(guid,radius,overWater)

	if overWater == nil then overWater = false end
	local unit = ScenEdit_GetUnit({guid=guid})
	local newPos = CircularRandomPosition(unit.latitude,unit.longitude,radius)
	if OverWater(newPos.latitude,newPos.longitude) == overWater then
		ScenEdit_SetUnit({
			guid=unit.guid,
			latitude=newPos.latitude,
			longitude=newPos.longitude
		})
	end
end

CleanUpIdleCivilianShips()

Lua解読分析集/Game_Setup#水上ユニット生成(方法1)においてウェイポイントつきの水上ユニットが生成されるが,最終ウェイポイントに到着した=ウェイポイント未設定か速力0の水上ユニットを削除する

function CleanUpIdleCivilianShips()
	local unitList = VP_GetSide({side='Civilian'}).units
	local numberOfCivilianUnits = #unitList
	local numberOfUnitsDeleted = 0
	for k,v in ipairs (unitList) do
		local unit = ScenEdit_GetUnit({guid=v.guid})
		if (unit.course[1] == nil or unit.speed == 0) and
			unit.type == 'Ship' then
			ScenEdit_DeleteUnit({guid=v.guid})
			numberOfUnitsDeleted = numberOfUnitsDeleted + 1
		end
	end

	if DebugModeIsOn() and numberOfUnitsDeleted > 0 then
		ScenEdit_SpecialMessage('playerside','Civ_Cleanup fired. <BR>Initial civilian Units: '..numberOfCivilianUnits..' <BR>Civilian units cleaned up: '..numberOfUnitsDeleted)
	end
end

メッセージ関連

RadioSoundEffect()

Effectフォルダにある無線音声ファイル"radioChirp1.mp3"から"radioChirp8.mp3"のいずれかを再生する

function RadioSoundEffect()
	local fileName = 'radioChirp'..math.random(1,8)..'.mp3'
	ScenEdit_PlaySound(fileName)
end

DTG(TimeVar)

メッセージの時間テンプレート

DTGSoviet(),DTGNonAligned()なども文面がやや異なるだけで機能同じ

function DTG(TimeVar)
	if TimeVar == nil then
		TimeVar = ScenEdit_CurrentTime()
	end
	local msgtime = os.date("!%d%H%M" .. "Z" .. " " .. "%b %y", TimeVar)
	local msgtime = string.upper(msgtime)
	return msgtime
end

DTGPreEpoch(timeVar)

シナリオ日時がUNIXエポックである1970年より前の場合,そのままではos.date()関数が使えないのでこちらを使う
参考:https://www.matrixgames.com/forums/viewtopic.php?p=4097200#p4097200

  • 現在のUNIX時間に4070908800(2099/1/1 00:00:00,1970年の129年後)を加算する
  • 年が129年進んでいる以外は月・日・時間・分・秒が一致する(ただし閏年前後で日付ずれ発生,以下参照)
  • タイムスタンプテーブルを取得しそこから日・時間・分・月を取得(string.format()をつかって二桁表記に直す)
  • 最後に年-129の値を取得する(もっともここでは1966年なら「66」のように二桁表記にしたいので1900+129=2029を減算している
function DTGPreEpoch(timeVar)
	if timeVar == nil then timeVar = ScenEdit_CurrentTime() end
	local timeVar = timeVar + 4070908800 -- add seconds to bring year up to 2099 (+129 years)
	local timeStampTable = os.date("!*t",timeVar) -- build timestamp table
	local months = {'JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'}
	local messageTime = string.format('%02d',timeStampTable.day)..
        string.format('%02d',timeStampTable.hour) ..
        string.format('%02d',timeStampTable.min) ..
        'Z ' ..
        months[timeStampTable.month] ..
        ' ' ..
        (timeStampTable.year-2029)
	return messageTime
end

ただしこのスクリプトコードではもとの年と129年後の年とで閏年の来るタイミングが一致していないため,閏日が来たとき1年間にわたり日付がずれる欠陥がある.

UNIX時間+4070908800
閏年でない閏年
-895968003981312000
1967/3/1 00:00:002096/2/29 00:00:00

一年後

UNIX時間+4070908800
閏年閏年でない
-580608004012848000
1968/2/29 00:00:002097/2/28 00:00:00

次の日はどちらも3/1のため,次の閏日が来るまで日付が再一致する

1970年以降このコードを使うと68169600秒=閏年である1972/2/29 00:00:00を3/1と認識してしまい,しかも以降の日付で1日ずれた状態が続く.これは2100年が100で割り切れる数値でうるう年ではない(参考)のでUNIX時間+4070908800が4107542400 (2100/3/1 00:00:00)に到達したとき閏日が挿入されないため.

修正案→Lua Script実用例集#icab611e

GenerateRadioMessageBody(theMessage,callsign)

(無線通信風メッセージの本文指定)

function GenerateRadioMessageBody(theMessage,callsign)
	local result
	if callsign == nil then callsign = 'unknown station' end
    result = '<P>'..string.upper(callsign)..'<BR> <I>"'..theMessage..'"</I></P> '
	return result
end

RadioMessage(band,frequency,theMessage,location)

無線通信メッセージのテンプレート
RadioMessageSoviet()とかRadioMessageNATO()とかも文言が若干違うだけでやってることは同じ

function RadioMessage(band,frequency,theMessage,location)
	assert(theMessage,'RadioMessage(): No message passed!')
	local DTG = DTG()
	local theMessage = "<P>"..DTG..' <BR>'..band..' <BR>'..frequency..' </P>'..theMessage
	if location ~= nil then
		ScenEdit_SpecialMessage('playerside',theMessage,{latitude=location.latitude,longitude=location.longitude})
	else
		ScenEdit_SpecialMessage('playerside',theMessage)
	end
	RegisterMessage(theMessage)
	RadioSoundEffect()
end

ACP126(rec_station,snd_station,precedence,from,to,classification,body)

テレックスメッセージのテンプレート
https://en.wikipedia.org/wiki/16-line_message_format

  • rec_station --4 letter code (e.g. YDCX) 受信先のステーション名
  • snd_station --4 letter code +/- NR 3 number (e.g. YBDN NR 270)  送信元のステーション名
  • precedence --Flash (Z), Immediate (O), Priority (P), Routine (R), Flash Override (Y) 優先度
  • dtg = DTG() 受信時刻
  • from --e.g. MET FLT OPS 送信元
  • to --e.g. SSN 21 SEAWOLF 受信先
  • classification --Unclass +/- SBU / FOUO / NOFORN (Restricted), Confidential, Secret, Top Secret 機密度
  • body 本文
function ACP126(rec_station,snd_station,precedence,from,to,classification,body)
	_,gr = body:gsub("%S+","")
	local sig_string = string.upper(
'<P><FONT face=Consolas>'..rec_station..' <BR>'..
	'DE '..snd_station..' <BR>'..
	precedence..' '..dtg..' <BR>'..
	'fm '..from..' <BR>'..
	'to '..to..' <BR>'..
	'wd gr'..gr..' <BR>'..
	'bt <BR>'..
	classification..' <BR>'..
	body..' <BR>'..
	'bt <BR>'..
	'nnnn </P>'
)
	return sig_string
end

TelexMessageToPlayer(rec_station, snd_station, precedence, from, to, classification, body, location)

プレイヤー陣営にテレックスメッセージを表示する

function TelexMessageToPlayer(rec_station, snd_station, precedence, from, to, classification, body, location)
	local theMessage = ACP126(rec_station, snd_station, precedence, from, to, classification, body)
	if location ~= nil then
		ScenEdit_SpecialMessage('playerside',theMessage,{latitude=location.latitude,longitude=location.longitude})
	else
		ScenEdit_SpecialMessage('playerside',theMessage)
	end
	ScenEdit_PlaySound('telex.mp3')
	RegisterMessage(theMessage)
end

RegisterMessage(messageString)

メッセージの登録

function RegisterMessage(messageString)
	local counter = 0
	for int1 = 1, 10 do
		local storedMessage = ScenEdit_GetKeyValue('storedMessage_'..int1)
		if storedMessage ~= nil then
			counter = counter + 1
		end
	end

	if counter == 10 then
		for int2 = 1, 10 do
			local shiftedMessage = ScenEdit_GetKeyValue('storedMessage_'..int2)
			local shiftedMessageSlot = int2 - 1
			ScenEdit_SetKeyValue('storedMessage_'..shiftedMessageSlot,shiftedMessage)
		end
		messageNumber = 10
	elseif counter <= 9 then
		messageNumber = counter + 1
	elseif counter == 0 then
		messageNumber = 1
	end
	ScenEdit_SetKeyValue('storedMessage_'..messageNumber,messageString)
end

ReplayMessages()

最新のものからメッセージ表示

function ReplayMessages()
	for i = 10,1,-1 do
		local message = ScenEdit_GetKeyValue('storedMessage_'..i)
		if message ~= nil and message ~= '' then
			ScenEdit_SpecialMessage('playerside',message)
		end
	end
end

天候関連

Timeis(timeVar)

タイムテーブルから日付・時・分を抽出する

function TimeIs(timeVar)
	if timeVar == nil then
timeVar = ScenEdit_CurrentTime()
end
	local timeStampTable = os.date("!*t",timeVar)
	local timeTable = {day = timeStampTable.day, 	hour = timeStampTable.hour, minute = timeStampTable.min}
    return timeTable
end

WeatherReportIsDue()

現地時間が天候判定を行う0,6,12,18時かの判定

scenarioZuluOffset,TimeIs()と合わせて使う 現地時間を6で割った余りが0のとき=0,6,12,18時にtrueを返す

function WeatherReportIsDue()
	local result = false
	local hourZulu = TimeIs().hour
	local hourLocal = hourZulu + scenarioZuluOffset
	if hourLocal %6 == 0 then
			result = true
	end
	return result
end

WeatherDrift(天候を変える)

function WeatherDrift()
	local weatherBaseline = { seastate = 4, temp = 13, undercloud = 0.4, rainfall = 0 }
	local seastateVariability = math.random(0,2)
	local undercloudVariability = math.random(0,4)/10
	local tempVariability = math.random(-1,4)
	local rainfallVariability = math.random(0,10)

	local newTemp =  weatherBaseline.temp + tempVariability
	local newRainfall = weatherBaseline.rainfall + rainfallVariability
	local newUndercloud = weatherBaseline.undercloud + undercloudVariability
	local newSeastate = weatherBaseline.seastate + seastateVariability

	ScenEdit_SetWeather(
		newTemp, --temp
		newRainfall, --rainfall
		newUndercloud, --undercloud
		newSeastate --seastate
	)
end

ConvertTempCtoF(C温度をF温度に変換)

function ConvertTempCtoF(temp)
	local result = Round((temp*1.8) + 32,0)
	return result
end

GenerateRainDescriptor(降雨の説明値)

function GenerateRainDescriptor(rain)
	local result
	if rain == 0 then  result = 'nil'
		elseif rain < 5 then  result = 'very light'
		elseif rain < 11 then  result = 'light'
		elseif rain < 20 then  result = 'moderate'
		elseif rain < 30 then  result = 'heavy'
		elseif rain < 40 then  result = 'very heavy'
		else  result = 'extreme'
	end
	return result
end

GenerateCloudDescriptor(雲の説明値)

function GenerateCloudDescriptor(cloud)
	local result
	if cloud == 0 then  result = 'clear skies'
		elseif cloud < 0.2 then result = 'light low clouds'
		elseif cloud < 0.3 then result = 'light middle clouds'
		elseif cloud < 0.4 then result = 'light high clouds'
		elseif cloud < 0.5 then result = 'moderate low clouds'
		elseif cloud < 0.6 then result = 'moderate middle clouds'
		elseif cloud < 0.7 then result = 'moderate high clouds'
		elseif cloud < 0.8 then result = 'moderate middle clouds & light high clouds'
		elseif cloud < 0.9 then result = 'solid middle clouds & moderate high clouds'
		elseif cloud < 1.0 then result = 'thin fog & solid cloud cover'
		else result = 'thick fog & solid cloud cover'
	end
	return result
end

WeatherReport(現在の天候をメッセージ表示)

function WeatherReport(outlook)
	if outlook == nil then outlook = 'next forecast at '..DTG(ScenEdit_CurrentTime()+21600) end

	local weather, DTG = ScenEdit_GetWeather(), DTG()
	local tempC, tempF, cloud, rain, sea = weather.temp, ConvertTempCtoF(weather.temp), GenerateCloudDescriptor(weather.undercloud), GenerateRainDescriptor(weather.rainfall), weather.seastate

	local theMessage = ACP126(
		'ALL',
		'HYDR',
		'r',
		'HYDROGRAPHIC OFFICE TAUNTON',
		'ALL STATIONS',
		'unclass',
		'WX REPORT '..DTG..' - ENGLISH CHANNEL <BR>AVERAGE TEMP '..tempC..'°C / '..tempF..'°F <BR> SEA STATE '..sea..' <BR>'..rain..' PRECIPITATION <BR>'..cloud..' <BR>'..outlook
		)

	ScenEdit_SpecialMessage('playerside',theMessage)

	if not ScenIsInDevelopment then
		RegisterMessage(theMessage)
	end
end

ユニット関連

RandomiseUnitTableProficiency(unitTable)

ユニットについて練度をランダム設定する グループでは所属ユニットの練度が一律設定されてしまうので除外する

0=Novice (5%)
1=Cadet (20%)
2=Regular (50%)
3="Veteran (20%)
4=Ace (5%)

function RandomiseUnitTableProficiency(unitTable)
	for k,v in ipairs (unitTable) do
		local unit = ScenEdit_GetUnit({guid=v.guid})
		if unit.type ~= 'Group' then
			local chance = math.random(1,100)
			if chance <=50 then proficiency = 2
			elseif chance <=70 then proficiency = 3
			elseif chance <=90 then proficiency = 1
			elseif chance <=95 then proficiency = 4
			elseif chance <=100 then proficiency = 0
			end
			ScenEdit_SetUnit({guid=v.guid,proficiency=proficiency})
		end
	end
end

RandomiseSideUnitProficiency(sideName)

RandomiseUnitTableProficiency()と合わせて指定陣営の全ユニットについて練度をランダム設定する

function RandomiseSideUnitProficiency(sideName)
	local sideUnits = VP_GetSide({side=sideName}).units
	RandomiseUnitTableProficiency(sideUnits)
end

ReturnUnitProficiencyAsNumber

指定ユニットの練度を数値に変換する

function ReturnUnitProficiencyAsNumber (unitGUID)
	local proficiencyList = {
		{stringName = 'Novice', numberValue = 0},
		{stringName = 'Cadet', numberValue = 1},
		{stringName = 'Regular', numberValue = 2},
		{stringName = 'Veteran', numberValue = 3},
		{stringName = 'Ace', numberValue = 4},
	}
	local unitProficiencyString = ScenEdit_GetUnit({guid=unitGUID}).proficiency
	for k,v in ipairs (proficiencyList) do
		if unitProficiencyString == v.stringName then
			matchedValue = v.numberValue
		end
	end
	if matchedValue == nil then
		return 'Error! No match found'
	else
		return matchedValue
	end
end

2陣営シナリオ用関数

プレイ可能な陣営が2つあるシナリオで使用される関数(コードは"Duelist"から)

ThisIsFirstLoad(booleanValue)

Scenファイルからの初回ロードであればtrue(関数実行部の最後でfalseに設定される)

function ThisIsFirstLoad(booleanValue)
	local result
	if booleanValue == nil then
		result = ScenEdit_GetKeyValue('firstLoad')
		if result == '' or result == nil then result = true end
		if result == 'false' then result = false end
	else
		if booleanValue == true then
			ScenEdit_ClearKeyValue('firstLoad')
			result = true
		elseif booleanValue == false then
			ScenEdit_SetKeyValue('firstLoad','false')
			result = false
		end
	end
	return result
end

ClearSideReferencePoints(sideName)

プレイ陣営のRPをすべて消す...がエディタモードで開いてもRPが消えるという無能関数

function ClearSideReferencePoints(sideName)
	local sideRPs=VP_GetSide({side=sideName}).rps
	for k,v in ipairs(sideRPs) do
		ScenEdit_DeleteReferencePoint({side=sideName,guid=v.guid})
	end
end

SetupSideAsAI(sideName)

AI陣営に対してCivilianとNature陣営を友軍に設定する(誤射の回避)

function SetupSideAsAI(sideName)
	ScenEdit_SetSidePosture('Civilian', sideName, 'F')
	ScenEdit_SetSidePosture('Nature', sideName, 'F')
	ScenEdit_SetDoctrine({side=sideName}, {
		weapon_control_status_air = 1,
		weapon_control_status_surface = 1,
		weapon_control_status_subsurface = 1,
		weapon_control_status_land = 1,
		})
end

関数実行部

  • 初回ロード時プレイヤー陣営とAI陣営判定
  • inDevelopment=trueならClearSideReferencePoints()とSetupSideAsAI()の実行,ThisIsFirstLoad()をfalseに設定する各確認,falseなら自動実行
    if ThisIsFirstLoad() then
    	math.randomseed(os.time())
    	local playerSide = ScenEdit_PlayerSide()
    	local enemySide = 'USSR'
    	if playerSide == 'USSR' then enemySide = 'United Kingdom' end
if inDevelopment then --ask to do stuff
	local userInput = string.upper(ScenEdit_MsgBox('Clear RPs for '..playerSide..'?',1))
	if userInput == 'OK' then
		ClearSideReferencePoints(playerSide)
	end

	userInput = string.upper(ScenEdit_MsgBox('Setup '..enemySide..' as AI opponent?',1))
	if userInput == 'OK' then
		SetupSideAsAI(enemySide)
	end

	userInput = string.upper(ScenEdit_MsgBox('Set firstLoad key value to false?',1))
	if userInput == 'OK' then
		ThisIsFirstLoad(false)
	end
else --don't give the option and just do it
	ClearSideReferencePoints(playerSide)
	SetupSideAsAI(enemySide)
	ThisIsFirstLoad(false)
end

ClearCoursesForAllUnitsOnSide(sideName)

陣営の全ウェイポイントを削除する
Tiger and DragonのLuaInitにのみ存在し「なぜこの関数がここにあるか分からないが多分使ってないんで消してOK」の旨がコメントされている

function ClearCoursesForAllUnitsOnSide(sideName)
	local sideUnits = VP_GetSide({side=sideName}).units
	for k,v in ipairs (sideUnits) do
		local unit = ScenEdit_SetUnit({guid=v.guid,course={}})
	end
end

ClearCoursesForAllUnitsOnSide('India')

RandomiseReadyTime

local function RandomiseReadyTime(aircraftGUID,maxHours)

	local unit = ScenEdit_GetUnit({guid=aircraftGUID})
	ScenEdit_SetLoadout({unitName=unit.guid,
		loadoutid=0,
		TimeToReady_Minutes=math.random(0,(maxHours*60))})

end

local function GetListOfAircraftOnSide(sideName)

	local aircraftTable = {}
	local sideUnits = VP_GetSide({side=sideName}).units
	for k,v in ipairs (sideUnits) do
		local unit = ScenEdit_GetUnit({guid=v.guid})
		if unit.type == 'Aircraft' then
			table.insert(aircraftTable,unit)
		end
	end
	return aircraftTable

end

local function RandomiseAircraftReadyTimesForSide(sideName,maxHours)

	local aircraftTable = GetListOfAircraftOnSide(sideName)
	for k,v in ipairs (aircraftTable) do
		RandomiseReadyTime(v.guid,maxHours)
	end

end

敵対設定用関数

HostilitiesHaveCommenced(boolValue)

keyvalue"hostilities"を設定する(ユニット破壊イベントが起きるなどして戦闘が始まったらtrue)

…ただし実際には使われていない模様

function HostilitiesHaveCommenced(boolValue)
	if boolValue == nil then
		return ConvertStringToBoolean(ScenEdit_GetKeyValue('hostilities'))
	elseif boolValue == true then
		ScenEdit_SetKeyValue('hostilities','true')
		return true
	elseif boolValue == false then
		ScenEdit_SetKeyValue('hostilities','false')
		return false
	else
		return nil
	end
end

SetSidesHostile()

陣営同士の関係を敵軍に設定する

function SetSidesHostile()
	ScenEdit_SetSidePosture ('United States', 'Iran', 'H')
	ScenEdit_SetSidePosture ('Iran', 'United States', 'H')
end

CommenceHostilities()

ActivateMissionsByList()と組み合わせてmissionListテーブルで指定した各ミッションをアクティブにする

この関数は"Iran_UnitDestroyed"など敵ユニットを破壊した際のイベントアクションに挿入されていて,この結果いずれかの敵ユニットを破壊すると敵軍の各ミッションがアクティブになる.

function CommenceHostilities()
	if not HostilitiesHaveCommenced() then
		SetSidesHostile()
		local missionList = {
			'Bandar Abbas Fishbed Intercept',
			'Bandar Lengeh Intercept',
			'Char Bahar Intercept',
			'Jask Intercept',
			'Maritime Strike A',
			'Maritime Strike B'
		}
		local missionSide = 'Iran'
		ActivateMissionsByList(missionSide, missionList)
	end
end

ActivateMissionsByList(side, missionList)

missionListに入っているミッションをそれぞれアクティブ化する.

function ActivateMissionsByList(side, missionList)
	for k,v in ipairs (missionList) do
		ScenEdit_SetMission (side, v, {isactive=true})
	end
end

SAR関連

GenerateSurvivors()関連

PilotSurvives()

パイロットが脱出に成功するか= survivorが生成されるかの判定(66%)

function PilotSurvives()
	local chanceOfBailout = 66
	local chance = math.random(1,100)
	if chance <= chanceOfBailout then
		return true
	else
		return false
	end
end

GenerateSurvivors(latitude,longitude,name)

PilotSurvives()の判定に成功したらPlaceSurvivor()およびBailoutMessage()を実行する

function GenerateSurvivors(latitude,longitude,name)
	math.randomseed(os.time())
	if PilotSurvives() then
		PlaceSurvivor(latitude,longitude)
		BailoutMessage(name,latitude,longitude)
	end
end

PlaceSurvivor()関連

DetermineTypeOfSurvivor(latitude,longitude)

survivorのユニットタイプを決定する

  • CWDB
    OverWater()がtrueなら:Ship #2286=Life Raft [5m]
    falseなら:Facility #1791=Standed Personnel (1x, immobiile)
  • DB3K
    OverWater()がtrueなら:Ship #2553=Life Raft [5m]
    falseなら:Facility #2441=Standed Personnel (1x, immobiile)
function DetermineTypeOfSurvivor(latitude,longitude)
	local liferaftDBID, strandedPersonDBID, survivorType = 2553, 2441, 'Facility'
	local survivorDBID = strandedPersonDBID
	if OverWater(latitude,longitude) then
		survivorType, survivorDBID = 'Ship', liferaftDBID
	end
	return {type=survivorType,dbid=survivorDBID}
end

GenerateRank()

survivorユニット名に使う階級名を生成する

function GenerateRank()
	local ranks = {"LTJG","LT","LCDR"}
	local chance = math.random(1,100)
	if chance < 40 then
		return ranks[1]
	elseif chance < 80 then
		return ranks[2]
	else
		return ranks[3]
	end
end

RandomLetter()

パイロットの名とミドルネームに使うアルファベットをランダム生成する

function RandomLetter()
	local alphabet = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'}
	return string.upper(alphabet[math.random(1,#alphabet)])
end

GenerateSurvivorName()

survivorユニット名に使うパイロット名を生成する

function GenerateSurvivorName()
	local rank = GenerateRank()
	local initials = RandomLetter()..". "..RandomLetter()..". "
	local surnames = {
--パイロット姓(テーブル長すぎるので省略)
	}
	return rank..' '..initials..surnames[math.random(1,#surnames)]
end

PlaceSurvivor(latitude,longitude)

元ユニット破壊位置から2nm以内のCircularRandomPosition()により指定される位置にsurvivorを生成する

function PlaceSurvivor(latitude,longitude)
	local randomPosition = CircularRandomPosition(latitude,longitude,2)
	local survivorData = DetermineTypeOfSurvivor(randomPosition.latitude,randomPosition.longitude)
	local survivorName = GenerateSurvivorName()
	local survivor = ScenEdit_AddUnit({
		side='Downed Pilots',
		type=survivorData.type,
		dbid=survivorData.dbid,
		name=survivorName,
		latitude=randomPosition.latitude,
		longitude=randomPosition.longitude
	})
end

BailoutMessage()関連

RandomBailoutString()

damagePrefixesとbailoutSuffixesを組み合わせて" I've lost control! Eject, eject, eject!!!"のようなメッセージ本文を生成する

function RandomBailoutString()
	local damagePrefixes = {
		"I've lost control! ",
		"They got me! ",
		"Taking hits! ",
		"I'm hit! ",
		"Flight controls are gone! ",
		"Taking heavy fire! "
	}
	local bailoutSuffixes = {
		"Going in!",
		"Going down!",
		"Eject, eject, eject!!!",
		"I can't hold it together!",
		"Bailing out!"
	}
	local result = damagePrefixes[math.random(1,#damagePrefixes)]..bailoutSuffixes[math.random(1,#bailoutSuffixes)]
	return result
end

BailoutMessage(name,latitude,longitude)

RandomBailoutString()とnameを組み合わせてRadioMessageとして表示されるメッセージを生成する
GenerateRadioMessageBody(theMessage,callsign)およびRadioMessage(band,frequency,theMessage,location)も参照

function BailoutMessage(name,latitude,longitude)
	local theMessage = RandomBailoutString()
	local theMessage = GenerateRadioMessageBody(theMessage,name)
	RadioMessage('VHF','134.75 MHz',theMessage,{latitude=latitude, longitude=longitude})
end

survivorとrescuerのデータ取得関連

ValidUnitSelection(selectedUnits)

SAR機能ではマップ上で要救助ユニット=survivorと救助ユニット=rescuerを選択し,特別アクション"Perform Search and Rescue"を実行するようになっているが,ここでsurvivorとrescuerが正しく選択されているかを判定する.tooMany(選択しているユニットあるいはコンタクトが多すぎるとtrue)とnoneSelected(ユニットを選択していないとtrue)がいずれもfalseのときtrueを返す

やや長いが解説してみよう.ここでselectedUnits = ScenEdit_SelectedUnits()として考える(GetSelectedUnitInformation()ではそうなっている)

ユニットを選択した際,次の状況が考えられる

  1. survivorが自動発見不可=コンタクトであるときsurvivorとrescuerを1つずつ選択している
    survivorとrescuerはそれぞれselectedUnits.contact,selectedUnits.unitテーブルとして返される:noneSelected=false
    selectedUnits.contact,selectedUnits.unitの要素数はそれぞれ1:tooMany=false
    結果:true
  2. survivorが自動発見可=ユニットであるときにsurvivorとrescuerを1つずつ選択している
    survivorとrescuerはいずれもselectedUnits.unitテーブルとして返される:noneSelected=false
    selectedUnits.unitの要素数は2つ:tooMany=false
    結果:true
  3. survivorが自動発見不可=コンタクトであるときにsurvivorを1つ,rescuerを2つだけ選択している
    survivorとrescuerはそれぞれselectedUnits.contact,selectedUnits.unitテーブルとして返される:noneSelected=false
    selectedUnits.contactの要素数は1,selectedUnits.unitの要素数は2:tooMany=false
    結果:true
  4. survivorが自動発見可=ユニットであるときにsurvivorとrescuerの合計選択数が3つ以上(survivor,rescuer1,rescuer2…)
    survivorとrescuerはいずれもselectedUnits.unitテーブルとして返される:noneSelected=false
    selectedUnits.unitの要素数は2を超える(survivor,rescuer1,rescuer2…):tooMany=true
    結果:false(rescuerがいすぎるのも良くないようだ)
  5. survivorが自動発見不可=コンタクトであるときにrescuerの選択数が3つ以上(rescuer1,rescuer2,rescuer3…)
    rescuerはいずれもselectedUnits.unitテーブルとして返される:noneSelected=false
    selectedUnits.unitの要素数は2を超える(rescuer1,rescuer2,rescuer3…):tooMany=true
    結果:false
  6. survivorが自動発見不可=コンタクトであるときにsurvivorを2つ以上選択している
    selectedUnits.contactの要素数は1を超える (survivor1, survivor2...):tooMany=true
    結果:false
  7. 'survivorが自動発見不可=コンタクトであるときにrescuerを選択していない
    selectedUnits.unitテーブルが存在せずnilを返す:noneSelected=true
    結果:false

なおsurvivorが自動発見可=ユニットであるときにsurvivorだけ選択してもtrueを返すが,rescuerがいないのでのちのReturnRescuer()で判定に失敗する

function ValidUnitSelection(selectedUnits)
	local tooMany, noneSelected = false, false
	if selectedUnits.units ~= nil then
		if #selectedUnits.units > 2 then
			tooMany = true
		end
	else
		noneSelected = true
	end

	if selectedUnits.contacts ~= nil then
		if #selectedUnits.contacts > 1 then
			tooMany = true
		end
	end

	if not tooMany and not noneSelected then
		return true
	else
		return false
	end
end

ReturnSurvivor(selectedUnits)

survivor(Downed Pilots陣営)のGUIDを取得する

function ReturnSurvivor(selectedUnits)
	local survivorSide = 'Downed Pilots'
	local rescuerSide = ScenEdit_PlayerSide()
	if selectedUnits.units then
		for k,v in ipairs(selectedUnits.units) do
			local unit = ScenEdit_GetUnit({guid=v.guid})
			if unit.side == survivorSide then
				return unit
			end
		end
	end
	if selectedUnits.contacts then
		for k,v in ipairs(selectedUnits.contacts) do
			local contact = ScenEdit_GetContact({side=rescuerSide,guid=v.guid})
			if contact.side == survivorSide then
				local actualUnit = ScenEdit_GetUnit({guid=survivor.actualunitid})
				return actualUnit
			end
		end
	end
	return nil
end

ReturnRescuer(selectedUnits)

rescuer(プレイヤー陣営)のGUIDを取得する

function ReturnRescuer(selectedUnits)
	local rescuerSide = ScenEdit_PlayerSide()
	for k,v in ipairs(selectedUnits.units) do
		local unit = ScenEdit_GetUnit({guid=v.guid})
		if unit.side == rescuerSide then
			return unit
		end
	end
	return nil
end

GetSelectedUnitInformation()

ReturnSurvivor()とReturnRescuer()でそれぞれ取得したsurvivorとrescuerのUnitテーブルをひとつのテーブルにおける入れ子としてまとめる

function GetSelectedUnitInformation()
	local selectedUnits = ScenEdit_SelectedUnits()
	if ValidUnitSelection(selectedUnits) then
		local rescuer = ReturnRescuer(selectedUnits)
		local survivor = ReturnSurvivor(selectedUnits)
		if rescuer == nil or survivor == nil then
			ScenEdit_MsgBox('Select both a downed pilot to rescue and a nearby friendly unit to perform this special action.',0)
			return nil
		else
			return {rescuer=rescuer,survivor=survivor}
		end
	end
end

SAR可能判定関数

ContactIsValidRescueTarget()

survivorが実際に被救助可能か,すなわちユニットタイプ/GUIDがShip #2553あるいはFacilty #2441ならtrue

function ContactIsValidRescueTarget()
	local selectedUnits = GetSelectedUnitInformation()
	if selectedUnits.survivor.type == 'Ship' and selectedUnits.survivor.dbid == 2553 then
		return true
	elseif selectedUnits.survivor.type == 'Facility' and selectedUnits.survivor.dbid == 2441 then
		return true
	else
		ScenEdit_MsgBox('The selected contact is not capable of being rescued!',0)
		return false
	end
end

ShipOrSubmarineIsWithinRescueParams()

rescuerが水上・潜水艦ユニットであるとき,

  • 深度が20m/66ft以浅
  • 速力が6kts以下
    をいずれも満たす場合true
function ShipOrSubmarineIsWithinRescueParams()
	local rescuer = GetSelectedUnitInformation().rescuer
	if RescuerIsCloseEnoughToRescueSurvivor() then
		if rescuer.altitude >= -20 and rescuer.speed <= 6 then
			return true
		else
			local errorString = rescuer.name..' is moving too fast to perform a rescue.\n\nShips performing rescues must be moving at 5 knots or less.'
			if rescuer.type == 'Submarine' then
				errorString = rescuer.name..' is either moving too fast or is too deeply submerged to perform a rescue.\n\nSubmarines performing rescues must be moving at 5 knots or less and be on the surface.'
			ScenEdit_MsgBox(errorString,0)
			return false
			end
		end
	else
		return false
	end
end

end

AircraftIsRescueCapable(dbid)

rescuer(航空ユニット)が実際に救助可能か
dbidListテーブルに登録したSAR可能航空ユニットのいずれかとdbidが一致するならtrue

function AircraftIsRescueCapable(dbid)
	local dbidList = {
		1986, --CH-53E Super Stallion
		2006, --MH-60R Seahawk
		2793, --MH-53E Sea Dragon
		2794, --MH-60S Knighthawk
		2844, --UH-1Y Venom [Huey]
		297, --MV-22B Osprey
		4356, --MH-60R Seahawk
		4681, --MH-60S Knighthawk
		571, --MH-60S Knighthawk
	}
	for k,v in ipairs (dbidList) do
		if v == dbid then
			return true
		end
	end
	return false
end

ReturnRangeBetweenRescuerAndSurvivor()

survivor-rescuer間の距離をメートルおよびフィート単位で取得する

function ReturnRangeBetweenRescuerAndSurvivor()
    local metresPerNMi, feetPerM  = 1852, 3.28084
    local selectedUnits = GetSelectedUnitInformation()
    local rangeMetres = Round(Tool_Range(selectedUnits.rescuer.guid, selectedUnits.survivor.guid) * metresPerNMi)
    local rangeFeet = Round(rangeMetres * feetPerM)
    return {feet=rangeFeet,metres=rangeMetres}
end

RescuerIsCloseEnoughToRescueSurvivor()

survivor-rescuer間の距離がmaxRescueRange =1000m未満,すなわちReturnRangeBetweenRescuerAndSurvivor()のmetres値が1000未満ならtrueを返す

feet表記は単にメッセージでの表示目的らしい(いらない)

function RescuerIsCloseEnoughToRescueSurvivor()
    local range = ReturnRangeBetweenRescuerAndSurvivor()
    local maxRescueRange, feetPerM = 1000, 3.28084
    local maxRescueRangeFeet =  Round(maxRescueRange * feetPerM)
    if range.metres < maxRescueRange then
        return true
    else
        ScenEdit_MsgBox('Rescuer must be within '..maxRescueRange..'m / '..maxRescueRangeFeet..'ft.\n\nCurrent range is '..range.metres..'m / '..range.feet..'ft.\n\nMove closer to attempt a rescue.',0)
        return false
    end
end

ReturnUnitAltitudeAGL(guid)

ユニット (rescuer)の位置における地上高をもとにユニット対地高度を計算する

function ReturnUnitAltitudeAGL(guid)
	local unit = ScenEdit_GetUnit({guid=guid})
	local altitudeAboveSeaLevel, terrainElevation =  unit.altitude, World_GetElevation({latitude=unit.latitude,longitude=unit.longitude})
	if terrainElevation < 0 then terrainElevation = 0 end
	local altitudeAboveGround = altitudeAboveSeaLevel - terrainElevation
	return altitudeAboveGround
end

AirUnitIsWithinRescueParams()

rescuerが航空ユニットであるとき,

  • ReturnUnitAltitudeAGL()で計算した対地高度が75m/246ft以下
  • 速力が50kts以下
    をいずれも満たす場合true
function AirUnitIsWithinRescueParams()
	local rescuer = GetSelectedUnitInformation().rescuer
	local altitudeAboveGround = ReturnUnitAltitudeAGL(rescuer.guid)
	if RescuerIsCloseEnoughToRescueSurvivor() then
		if altitudeAboveGround <= 75 and rescuer.speed <= 50 then
			return true
		else
			ScenEdit_MsgBox(rescuer.name..' is flying too high or too fast to perform a rescue.\n\nAircraft performing rescues must be flying at less than 245ft/75m AGL and slower than 50 knots.',0)
			return false
		end
	else
		return false
	end
end

PlayerUnitIsEligibleToRescue()

survivor,rescuerが前述の各条件を満たしSAR可能かを判定する

function PlayerUnitIsEligibleToRescue()
	local selectedUnits, result = GetSelectedUnitInformation(), false
	if ContactIsValidRescueTarget() then
		if selectedUnits.rescuer.type == 'Ship' or selectedUnits.rescuer.type == 'Submarine' then
			return ShipOrSubmarineIsWithinRescueParams()
		elseif selectedUnits.rescuer.type == 'Aircraft' and AircraftIsRescueCapable(selectedUnits.rescuer.dbid) then
			return AirUnitIsWithinRescueParams()
		else
			ScenEdit_MsgBox('Unit must be capable of rescue to attempt this special action.\n\nTry selecting a ship, submarine or helicopter.',0)
			return result
		end
	end

SAR実行関連関数

PerformRescue()

survivorを削除する+メッセージ表示

function PerformRescue()
    local selectedUnits = GetSelectedUnitInformation()
    ScenEdit_DeleteUnit({guid=selectedUnits.survivor.guid})
    local message = "We've rescued "..selectedUnits.survivor.name.."."
    local theMessage = GenerateRadioMessageBody(message,selectedUnits.rescuer.name)
    RadioMessage('VHF','134.25 MHz',theMessage,{latitude=selectedUnits.survivor.latitude, longitude=selectedUnits.survivor.longitude})
end

AttemptRescue()

特別アクション"Perform Search and Rescue"により実行
PlayerUnitIsEligibleToRescue()がtrueのときPerformRescue()を実行する+50点加点

function AttemptRescue()
    local selectedUnits = GetSelectedUnitInformation()
    if selectedUnits ~= nil then
        if PlayerUnitIsEligibleToRescue() then
			PerformRescue()
			ChangeScore('playerside',50,selectedUnits.survivor.name..' was rescued.')
        end
    end
end

通信攪乱関連

コードはDrass Drumから

GetListOfIADSUnits()

陣営の全ユニットからaffectedDBIDsで定義した地上対空ユニットを抽出する

function GetListOfIADSUnits()
	local sideUnits, result = VP_GetSide({side='Iran'}).units, {}
	local affectedDBIDs = {
		{type='Facility', dbid=1254, points=0, name='SAM Plt (SA-13 Gopher [9K35 Strela-10])', destroyedString='destroyed'},--SAM Plt (SA-13 Gopher [9K35 Strela-10])
        {type='Facility', dbid=547, points=0, name='SAM Plt (SA-11 Gadfly [9K37 Buk-M1])', destroyedString='destroyed'},--SAM Plt (SA-11 Gadfly [9K37 Buk-M1])
        {type='Facility', dbid=1815, points=0, name='AAA Bty (100mm KS-19 Auto [Sair] x 4, GFCR)', destroyedString='destroyed'},--AAA Bty (100mm KS-19 Auto [Sair] x 4, GFCR)
        --以下省略
	}
	for each, entry in ipairs (sideUnits) do
		local unit = ScenEdit_GetUnit({guid=entry.guid})
		for k,v in ipairs(affectedDBIDs) do
			if unit.dbid == v.dbid and unit.type == v.type then
				table.insert(result,unit)
				break
			end
		end
	end
	return result
end

Disrupt(陣営名)IADSComms()

GetListOfIADSUnits()で抽出した地上対空ユニットを各40%の確率で通信途絶状態にする
陣営のレーダーEMCONをアクティブにする
テレックスメッセージ表示

function DisruptIranIADSComms()
	local unitsToBeDisrupted = GetListOfIADSUnits()
	for k,v in ipairs (unitsToBeDisrupted) do
		local chanceOfDisruption = 40
		local chance = math.random(1,100)
		if chance < chanceOfDisruption then
			ScenEdit_SetUnit({guid=v.guid,outofcomms=true})
		end
	end
	ScenEdit_SetDoctrine({side="Iran"}, {weapon_control_status_air=1})
	ScenEdit_SetEMCON('Side', "Iran", 'Radar=Active')
	TelexMessageToPlayer(
		"CSG77",
		'CENTCOM',
		'i',
		'US central command',
		'carrier strike group 77',
		'top secret',
		'1. iranian AIR DEFENSE RADIO TRAFFIC ABRUPTLY CEASED MID TRANSMISSION <BR>2. SIMILAR DISRUPTION NOTED TO DATA TRAFFIC <BR>3. ASSESS THAT iranian AIR DEFENSE CONTROL COMMUNICATIONS ARE CURRENTLY INOPERABLE <BR>4. ANTICIPATE REDUCED COORDINATION OF iranian AIR DEFENSE ACTIVITIES UNTIL COMMUNICATIONS REESTABLISHED',
		nil
	)
end

GetListOfNavalUnits()

陣営の全ユニットからaffectedDBIDsで定義した地上対艦ミサイルユニット,水上ユニット,潜水艦ユニットを抽出する

function GetListOfNavalUnits()
	local sideUnits, result = VP_GetSide({side='Iran'}).units, {}
	local affectedDBIDs = {
		{type='Facility', dbid=1813, points=0, name='SSM Bn (C-704 [Nasr])', destroyedString='destroyed'},--SSM Bn (C-704 [Nasr])
		{type='Facility', dbid=904, points=0, name='SSM Bn (C-802)', destroyedString='destroyed'},--SSM Bn (C-802)
		{type='Ship', dbid=1198, points=0, name='P 313-1 Fath [Thondor Type 021 Houdong]', destroyedString='sunk'},--P 313-1 Fath [Thondor Type 021 Houdong]
		--以下省略
	}
	for each, entry in ipairs (sideUnits) do
		local unit = ScenEdit_GetUnit({guid=entry.guid})
		for k,v in ipairs(affectedDBIDs) do
			if unit.dbid == v.dbid and unit.type == v.type then
				table.insert(result,unit)
				break
			end
		end
	end
	return result
end

DisruptIranNavalComms()

GetListOfNavalUnits()で抽出したユニットを各40%の確率で通信途絶状態にする
(陣営のレーダーEMCONをアクティブにする―DisruptIranIADSComms()と重複するためか未使用)
テレックスメッセージ表示

function DisruptIranNavalComms()
	local unitsToBeDisrupted = GetListOfNavalUnits()
	for k,v in ipairs (unitsToBeDisrupted) do
		local chanceOfDisruption = 40
		local chance = math.random(1,100)
		if chance < chanceOfDisruption then
			ScenEdit_SetUnit({guid=v.guid,outofcomms=true})
		end
	end
	ScenEdit_SetDoctrine({side="Iran"}, {weapon_control_status_surface=1,weapon_control_status_subsurface=1})
	--ScenEdit_SetEMCON('Side', "Iran", 'Radar=Active')
	TelexMessageToPlayer(
		"CSG77",
		'CENTCOM',
		'i',
		'US central command',
		'carrier strike group 77',
		'top secret',
		'1. iranian NAVAL COORDINATION RADIO TRAFFIC ABRUPTLY CEASED MID TRANSMISSION <BR>2. SIMILAR DISRUPTION NOTED TO DATA TRAFFIC <BR>3. ASSESS THAT iranian NAVAL COORDINATION COMMUNICATIONS ARE CURRENTLY INOPERABLE <BR>4. ANTICIPATE REDUCED COORDINATION OF iranian NAVAL ACTIVITIES UNTIL COMMUNICATIONS REESTABLISHED',
		nil
	)
end

IADSMessageHasPlayed(boolValue)

DisruptIranIADSComms()が実行されメッセージが表示された後trueになると思われるが実例未確認

function IADSMessageHasPlayed(boolValue)
	if boolValue == nil then
		return ConvertStringToBoolean(ScenEdit_GetKeyValue('IADSMessage'))
	elseif boolValue == true then
		ScenEdit_SetKeyValue('IADSMessage','true')
		return true
	elseif boolValue == false then
		ScenEdit_SetKeyValue('IADSMessage','false')
		return false
	else
		return nil
	end
end

NavalHQMessageHasPlayed(boolValue)

DisruptIranNavalComms()が実行されメッセージが表示された後trueになると思われるが実例未確認

function NavalHQMessageHasPlayed(boolValue)
	if boolValue == nil then
		return ConvertStringToBoolean(ScenEdit_GetKeyValue('navalHQMessage'))
	elseif boolValue == true then
		ScenEdit_SetKeyValue('navalHQMessage','true')
		return true
	elseif boolValue == false then
		ScenEdit_SetKeyValue('navalHQMessage','false')
		return false
	else
		return nil
	end
end