22-- RPC world actions
33-- https://wiki.hoggitworld.com/view/DCS_singleton_world
44--
5+ -- luacheck: globals world coalition Object env coord
56
67local world = world
78local coalition = coalition
9+ local Object = Object
810local GRPC = GRPC
911
1012GRPC .methods .getAirbases = function (params )
5355
5456GRPC .methods .getTheatre = function ()
5557 return GRPC .success ({theatre = env .mission .theatre })
58+ end
59+
60+ -- https://wiki.hoggitworld.com/view/DCS_func_searchObjects
61+ GRPC .methods .searchObjects = function (params )
62+ GRPC .logInfo (" searchObjects: invoked" )
63+ if params .categories == nil or # params .categories == 0 then
64+ return GRPC .errorInvalidArgument (" categories must not be empty" )
65+ end
66+
67+ if params .volume == nil then
68+ return GRPC .errorInvalidArgument (" volume is required" )
69+ end
70+
71+ local v = params .volume
72+ local volume
73+
74+ -- Debug: log incoming volume keys
75+ do
76+ local keys = {}
77+ for k , _ in pairs (v or {}) do keys [# keys + 1 ] = tostring (k ) end
78+ GRPC .logInfo (" searchObjects: volume keys=" .. table.concat (keys , " ," ))
79+ end
80+
81+ -- If grpcui/serde wrapped oneof under a `shape` table, unwrap it
82+ if v and v .shape and type (v .shape ) == " table" then
83+ v = v .shape
84+ local keys = {}
85+ for k , _ in pairs (v or {}) do keys [# keys + 1 ] = tostring (k ) end
86+ GRPC .logInfo (" searchObjects: unwrapped shape; inner keys=" .. table.concat (keys , " ," ))
87+ end
88+
89+ -- If grpcui sent a discriminator value, prefer it
90+ local discriminator = nil
91+ if params .volume and params .volume .shape and type (params .volume .shape ) ~= " table" then
92+ if type (params .volume .shape ) == " string" then
93+ discriminator = string.lower (params .volume .shape )
94+ elseif type (params .volume .shape ) == " number" then
95+ -- Best-effort mapping (fallback only)
96+ local map = { [1 ] = " sphere" , [2 ] = " box" , [3 ] = " segment" , [4 ] = " pyramid" }
97+ discriminator = map [params .volume .shape ]
98+ end
99+ GRPC .logInfo (" searchObjects: discriminator=" .. tostring (discriminator ))
100+ end
101+
102+ -- Detect sphere
103+ if (discriminator == " sphere" )
104+ or v .sphere ~= nil
105+ or (
106+ v .center ~= nil and v .radius ~= nil and
107+ v .min == nil and v .from == nil and v .forward == nil
108+ )
109+ then
110+ local s = v .sphere or v
111+ local center = s .center
112+ local radius = s .radius
113+ if center == nil or radius == nil then
114+ return GRPC .errorInvalidArgument (" sphere center and radius are required" )
115+ end
116+ local lo = coord .LLtoLO (center .lat , center .lon )
117+ GRPC .logInfo (" searchObjects: volume SPHERE radius=" .. tostring (radius ))
118+ volume = {
119+ id = world .VolumeType .SPHERE ,
120+ params = {
121+ point = { x = lo .x , y = center .alt or 0 , z = lo .z },
122+ radius = radius ,
123+ }
124+ }
125+ -- Detect box
126+ elseif (discriminator == " box" ) or v .box ~= nil or (v .min ~= nil and v .max ~= nil ) then
127+ local b = v .box or v
128+ local min = b .min
129+ local max = b .max
130+ if min == nil or max == nil then
131+ return GRPC .errorInvalidArgument (" box min and max are required" )
132+ end
133+ local minLo = coord .LLtoLO (min .lat , min .lon )
134+ local maxLo = coord .LLtoLO (max .lat , max .lon )
135+ GRPC .logInfo (" searchObjects: volume BOX" )
136+ volume = {
137+ id = world .VolumeType .BOX ,
138+ params = {
139+ min = { x = minLo .x , y = min .alt or 0 , z = minLo .z },
140+ max = { x = maxLo .x , y = max .alt or 0 , z = maxLo .z },
141+ }
142+ }
143+ -- Detect segment
144+ elseif (discriminator == " segment" ) or v .segment ~= nil or (v .from ~= nil and v .to ~= nil ) then
145+ local seg = v .segment or v
146+ local from = seg .from
147+ local to = seg .to
148+ if from == nil or to == nil then
149+ return GRPC .errorInvalidArgument (" segment from and to are required" )
150+ end
151+ local fromLo = coord .LLtoLO (from .lat , from .lon )
152+ local toLo = coord .LLtoLO (to .lat , to .lon )
153+ GRPC .logInfo (" searchObjects: volume SEGMENT" )
154+ volume = {
155+ id = world .VolumeType .SEGMENT ,
156+ params = {
157+ from = { x = fromLo .x , y = from .alt or 0 , z = fromLo .z },
158+ to = { x = toLo .x , y = to .alt or 0 , z = toLo .z },
159+ }
160+ }
161+ -- Detect pyramid
162+ elseif (discriminator == " pyramid" )
163+ or v .pyramid ~= nil
164+ or (
165+ v .center ~= nil and v .forward ~= nil and v .right ~= nil and v .up ~= nil and
166+ v .length ~= nil and (
167+ v .halfAngleHorizontal ~= nil and v .halfAngleVertical ~= nil
168+ )
169+ )
170+ then
171+ local pv = v .pyramid or v
172+ if pv .center == nil or pv .length == nil or pv .halfAngleHorizontal == nil or pv .halfAngleVertical == nil then
173+ return GRPC .errorInvalidArgument (" pyramid center, length and angles are required" )
174+ end
175+
176+ local fwd = pv .forward
177+ local up = pv .up
178+ local right = pv .right
179+
180+ local function normalize (vec )
181+ local mag = math.sqrt ((vec .x or 0 )^ 2 + (vec .y or 0 )^ 2 + (vec .z or 0 )^ 2 )
182+ if mag == 0 then return { x = 0 , y = 1 , z = 0 } end
183+ return { x = vec .x / mag , y = vec .y / mag , z = vec .z / mag }
184+ end
185+
186+ if fwd == nil or right == nil or up == nil then
187+ return GRPC .errorInvalidArgument (" pyramid requires forward/right/up vectors" )
188+ end
189+ fwd = normalize (fwd )
190+ right = normalize (right )
191+ up = normalize (up )
192+
193+ local centerLo = coord .LLtoLO (pv .center .lat , pv .center .lon )
194+ GRPC .logInfo (" searchObjects: volume PYRAMID length=" .. tostring (pv .length ))
195+ volume = {
196+ id = world .VolumeType .PYRAMID ,
197+ params = {
198+ pos = {
199+ p = { x = centerLo .x , y = pv .center .alt or 0 , z = centerLo .z },
200+ x = fwd ,
201+ y = up ,
202+ z = right ,
203+ },
204+ length = pv .length ,
205+ halfAngleHor = pv .halfAngleHorizontal ,
206+ halfAngleVer = pv .halfAngleVertical ,
207+ }
208+ }
209+ else
210+ GRPC .logWarning (" searchObjects: unknown volume type; expected sphere/box/segment/pyramid" )
211+ return GRPC .errorInvalidArgument (" unknown volume type" )
212+ end
213+
214+ local result = {}
215+ local function handler (object , _ )
216+ local cat = object :getCategory ()
217+ if cat == Object .Category .UNIT then
218+ result [# result + 1 ] = { target = { unit = GRPC .exporters .unit (object ) } }
219+ elseif cat == Object .Category .WEAPON then
220+ result [# result + 1 ] = { target = { weapon = GRPC .exporters .weapon (object ) } }
221+ elseif cat == Object .Category .STATIC then
222+ result [# result + 1 ] = { target = { static = GRPC .exporters .static (object ) } }
223+ elseif cat == Object .Category .SCENERY then
224+ result [# result + 1 ] = { target = { scenery = GRPC .exporters .scenery (object ) } }
225+ elseif cat == Object .Category .BASE then
226+ result [# result + 1 ] = { target = { airbase = GRPC .exporters .airbase (object ) } }
227+ elseif cat == Object .Category .CARGO then
228+ result [# result + 1 ] = { target = { cargo = GRPC .exporters .cargo () } }
229+ else
230+ result [# result + 1 ] = { target = { unknown = GRPC .exporters .unknown (object ) } }
231+ end
232+ return true
233+ end
234+
235+ for _ , cat in ipairs (params .categories ) do
236+ GRPC .logInfo (" searchObjects: calling world.searchObjects for category=" .. tostring (cat ))
237+ world .searchObjects (cat , volume , handler , nil )
238+ end
239+
240+ GRPC .logInfo (" searchObjects: returning objects count=" .. tostring (# result ))
241+ return GRPC .success ({objects = result })
56242end
0 commit comments