Skip to content

Commit 61cf622

Browse files
committed
Export more entities and entity properties
1 parent 9c646c6 commit 61cf622

2 files changed

Lines changed: 483 additions & 7 deletions

File tree

scripts/api/gm.lua

Lines changed: 207 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function getEntityExportString(entity)
4242
-- Likely a player ship
4343
for k, v in pairs(__ship_templates) do
4444
if v.__type == "playership" and v.typename.type_name == entity.components.typename.type_name then
45-
return "PlayerSpaceship():setTemplate('" .. k .. "')" .. __exportShipChanges(entity, v)
45+
return "PlayerSpaceship():setTemplate('" .. k .. "')" .. __exportShipChanges(entity, v, "full")
4646
end
4747
end
4848
end
@@ -120,10 +120,15 @@ function getEntityExportString(entity)
120120
if entity.components.allow_radar_link then
121121
return "ScanProbe()" .. __exportScanProbe(entity)
122122
end
123+
124+
-- No matching API function
123125
return ""
124126
end
125127

126-
function __exportBasics(entity)
128+
-- default_scan_state: the scan state the entity type has by default (before any explicit setScanState call).
129+
-- PlayerSpaceship() initialises all factions to "fullscan"; everything else defaults to "none".
130+
function __exportBasics(entity, default_scan_state)
131+
default_scan_state = default_scan_state or "none"
127132
local x, y = entity:getPosition()
128133
local extras = string.format(":setPosition(%.0f, %.0f)", x, y)
129134
local rotation = entity:getRotation()
@@ -135,7 +140,8 @@ function __exportBasics(entity)
135140
extras = extras .. ":setFaction('" .. faction .. "')"
136141
end
137142
if entity.components.callsign then
138-
extras = extras .. ":setCallSign('" .. entity.components.callsign.callsign .. "')"
143+
local cs = entity.components.callsign.callsign:gsub("\\", "\\\\"):gsub("'", "\\'")
144+
extras = extras .. ":setCallSign('" .. cs .. "')"
139145
end
140146
local ss = entity.components.scan_state
141147
if ss then
@@ -161,11 +167,11 @@ function __exportBasics(entity)
161167
end
162168
end
163169
if #states > 0 then
164-
if all_same and first_state ~= "none" then
170+
if all_same and first_state ~= default_scan_state then
165171
extras = extras .. ":setScanState('" .. first_state .. "')"
166172
elseif not all_same then
167173
for _, entry in ipairs(states) do
168-
if entry.state ~= "none" then
174+
if entry.state ~= default_scan_state then
169175
extras = extras .. ":setScanStateByFaction('" .. entry.name .. "', '" .. entry.state .. "')"
170176
end
171177
end
@@ -175,8 +181,8 @@ function __exportBasics(entity)
175181
return extras
176182
end
177183

178-
function __exportShipChanges(entity, template)
179-
local extras = __exportBasics(entity)
184+
function __exportShipChanges(entity, template, default_scan_state)
185+
local extras = __exportBasics(entity, default_scan_state)
180186

181187
-- Hull: export max if changed from template, current if damaged, allow_destruction if changed
182188
local hull = entity.components.hull
@@ -252,9 +258,203 @@ function __exportShipChanges(entity, template)
252258
end
253259
end
254260

261+
-- Impulse engine
262+
local ie = entity.components.impulse_engine
263+
local t_ie = template.impulse_engine
264+
if ie then
265+
if not t_ie or ie.max_speed_forward ~= t_ie.max_speed_forward or ie.max_speed_reverse ~= t_ie.max_speed_reverse then
266+
if ie.max_speed_forward == ie.max_speed_reverse then
267+
extras = extras .. string.format(":setImpulseMaxSpeed(%.1f)", ie.max_speed_forward)
268+
else
269+
extras = extras .. string.format(":setImpulseMaxSpeed(%.1f, %.1f)", ie.max_speed_forward, ie.max_speed_reverse)
270+
end
271+
end
272+
if not t_ie or ie.acceleration_forward ~= t_ie.acceleration_forward or ie.acceleration_reverse ~= t_ie.acceleration_reverse then
273+
if ie.acceleration_forward == ie.acceleration_reverse then
274+
extras = extras .. string.format(":setAcceleration(%.1f)", ie.acceleration_forward)
275+
else
276+
extras = extras .. string.format(":setAcceleration(%.1f, %.1f)", ie.acceleration_forward, ie.acceleration_reverse)
277+
end
278+
end
279+
if ie.sound and ie.sound ~= "" and (not t_ie or ie.sound ~= t_ie.sound) then
280+
extras = extras .. ":setImpulseSoundFile('" .. ie.sound .. "')"
281+
end
282+
end
283+
284+
-- Maneuvering thrusters
285+
local man_thrusters = entity.components.maneuvering_thrusters
286+
local t_man_thrusters = template.maneuvering_thrusters
287+
if man_thrusters and (not t_man_thrusters or man_thrusters.speed ~= t_man_thrusters.speed) then
288+
extras = extras .. string.format(":setRotationMaxSpeed(%.1f)", man_thrusters.speed)
289+
end
290+
291+
-- Combat maneuvering thrusters
292+
local cmt = entity.components.combat_maneuvering_thrusters
293+
local t_cmt = template.combat_maneuvering_thrusters
294+
if cmt and (not t_cmt or cmt.boost_speed ~= t_cmt.boost_speed or cmt.strafe_speed ~= t_cmt.strafe_speed) then
295+
extras = extras .. string.format(":setCombatManeuver(%.0f, %.0f)", cmt.boost_speed, cmt.strafe_speed)
296+
end
297+
298+
-- Warp drive (C++ default speed_per_level=1000; setWarpDrive(true) without setWarpSpeed leaves it nil in template)
299+
local wd = entity.components.warp_drive
300+
local t_wd = template.warp_drive
301+
if wd then
302+
if not t_wd then
303+
extras = extras .. ":setWarpDrive(true)"
304+
end
305+
local t_warp_speed = (t_wd and t_wd.speed_per_level) or 1000
306+
if wd.speed_per_level ~= t_warp_speed then
307+
extras = extras .. string.format(":setWarpSpeed(%.0f)", wd.speed_per_level)
308+
end
309+
end
310+
311+
-- Jump drive (C++ defaults min=5000, max=50000; setJumpDrive(true) without setJumpDriveRange leaves them nil)
312+
local jd = entity.components.jump_drive
313+
local t_jd = template.jump_drive
314+
if jd then
315+
if not t_jd then
316+
extras = extras .. ":setJumpDrive(true)"
317+
end
318+
local t_jd_min = (t_jd and t_jd.min_distance) or 5000
319+
local t_jd_max = (t_jd and t_jd.max_distance) or 50000
320+
if jd.min_distance ~= t_jd_min or jd.max_distance ~= t_jd_max then
321+
extras = extras .. string.format(":setJumpDriveRange(%.0f, %.0f)", jd.min_distance, jd.max_distance)
322+
end
323+
end
324+
325+
-- Beam weapons (0-based index for setters, 1-based for ECS component array)
326+
local bw = entity.components.beam_weapons
327+
local t_bw = template.beam_weapons
328+
if bw then
329+
for i = 1, #bw do
330+
local b = bw[i]
331+
local tb = t_bw and t_bw[i]
332+
local idx = i - 1
333+
if not tb or b.arc ~= tb.arc or b.direction ~= tb.direction or b.range ~= tb.range
334+
or b.cycle_time ~= tb.cycle_time or b.damage ~= tb.damage
335+
then
336+
extras = extras .. string.format(":setBeamWeapon(%d, %.1f, %.1f, %.0f, %.1f, %.1f)",
337+
idx, b.arc, b.direction, b.range, b.cycle_time, b.damage)
338+
end
339+
if b.turret_arc and b.turret_arc ~= 0 then
340+
if not tb or b.turret_arc ~= tb.turret_arc or b.turret_direction ~= tb.turret_direction
341+
or b.turret_rotation_rate ~= tb.turret_rotation_rate
342+
then
343+
extras = extras .. string.format(":setBeamWeaponTurret(%d, %.1f, %.1f, %.1f)",
344+
idx, b.turret_arc, b.turret_direction, b.turret_rotation_rate)
345+
end
346+
end
347+
-- For properties not stored in the Lua template, fall back to C++ struct defaults
348+
local tb_texture = (tb and tb.texture) or "texture/beam_orange.png"
349+
if b.texture and b.texture ~= "" and b.texture ~= tb_texture then
350+
extras = extras .. string.format(":setBeamWeaponTexture(%d, '%s')", idx, b.texture)
351+
end
352+
local tb_energy = (tb and tb.energy_per_beam_fire) or 3.0
353+
if b.energy_per_beam_fire and b.energy_per_beam_fire ~= tb_energy then
354+
extras = extras .. string.format(":setBeamWeaponEnergyPerFire(%d, %.2f)", idx, b.energy_per_beam_fire)
355+
end
356+
local tb_heat = (tb and tb.heat_per_beam_fire) or 0.02
357+
if b.heat_per_beam_fire and math.abs(b.heat_per_beam_fire - tb_heat) > 1e-5 then
358+
extras = extras .. string.format(":setBeamWeaponHeatPerFire(%d, %.3f)", idx, b.heat_per_beam_fire)
359+
end
360+
-- arc_color is u8vec4 {r,g,b,a} (0-255); setBeamWeaponArcColor takes floats 0-1.
361+
-- ShipTemplate has no setBeamWeaponArcColor, so compare against C++ defaults:
362+
-- arc_color default={255,0,0,128}, arc_color_fire default={255,255,0,128}
363+
local ac = b.arc_color
364+
local acf = b.arc_color_fire
365+
if ac and (ac[1] ~= 255 or ac[2] ~= 0 or ac[3] ~= 0
366+
or (acf and (acf[1] ~= 255 or acf[2] ~= 255 or acf[3] ~= 0)))
367+
then
368+
local fr = acf and acf[1] / 255.0 or 1.0
369+
local fg = acf and acf[2] / 255.0 or 1.0
370+
local fb = acf and acf[3] / 255.0 or 0.0
371+
extras = extras .. string.format(":setBeamWeaponArcColor(%d, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f)",
372+
idx, ac[1] / 255.0, ac[2] / 255.0, ac[3] / 255.0, fr, fg, fb)
373+
end
374+
if b.damage_type and b.damage_type ~= "energy" and (not tb or b.damage_type ~= tb.damage_type) then
375+
extras = extras .. string.format(":setBeamWeaponDamageType(%d, '%s')", idx, b.damage_type)
376+
end
377+
end
378+
end
379+
380+
-- Docking bay: DockingBay uses a bitfield (default flags=0), so unset template fields are nil
381+
-- while entity fields are false. Normalise both to boolean with == true before comparing.
382+
local db = entity.components.docking_bay
383+
local t_db = template.docking_bay
384+
if db then
385+
local e_share = db.share_energy == true
386+
local e_repair = db.repair == true
387+
local e_probes = db.restock_probes == true
388+
local e_missiles = db.restock_missiles == true
389+
local t_share = t_db and t_db.share_energy == true
390+
local t_repair = t_db and t_db.repair == true
391+
local t_probes = t_db and t_db.restock_probes == true
392+
local t_missiles = t_db and t_db.restock_missiles == true
393+
if e_share ~= t_share then
394+
extras = extras .. ":setSharesEnergyWithDocked(" .. tostring(e_share) .. ")"
395+
end
396+
if e_repair ~= t_repair then
397+
extras = extras .. ":setRepairDocked(" .. tostring(e_repair) .. ")"
398+
end
399+
if e_probes ~= t_probes then
400+
extras = extras .. ":setRestocksScanProbes(" .. tostring(e_probes) .. ")"
401+
end
402+
if e_missiles ~= t_missiles then
403+
extras = extras .. ":setRestocksMissilesDocked(" .. tostring(e_missiles) .. ")"
404+
end
405+
end
406+
407+
-- Repair crew: only export if we found actual crew entities and the count differs from template.
408+
-- internal_crew entities are not always instantiated, so a count of 0 is not reliable.
409+
local crew_count = __countShipCrew(entity)
410+
local template_crew_count = template.__repair_crew_count or 0
411+
if crew_count > 0 and crew_count ~= template_crew_count then
412+
extras = extras .. string.format(":setRepairCrewCount(%d)", crew_count)
413+
end
414+
415+
-- AI controller (CPU ships only): export AI name and non-entity-targeted orders
416+
local ai = entity.components.ai_controller
417+
local t_ai = template.ai_controller
418+
if ai then
419+
if ai.new_name and ai.new_name ~= "" and ai.new_name ~= "default" and ai.new_name ~= t_ai.new_name then
420+
extras = extras .. ":setAI('" .. ai.new_name .. "')"
421+
end
422+
local orders = ai.orders
423+
local loc = ai.order_target_location
424+
if orders == "idle" then
425+
extras = extras .. ":orderIdle()"
426+
elseif orders == "roaming" then
427+
if loc and (loc[1] ~= 0 or loc[2] ~= 0) then
428+
extras = extras .. string.format(":orderRoamingAt(%.0f, %.0f)", loc[1], loc[2])
429+
else
430+
extras = extras .. ":orderRoaming()"
431+
end
432+
elseif orders == "stand_ground" then
433+
extras = extras .. ":orderStandGround()"
434+
elseif orders == "defend_location" and loc then
435+
extras = extras .. string.format(":orderDefendLocation(%.0f, %.0f)", loc[1], loc[2])
436+
elseif orders == "fly_towards" and loc then
437+
extras = extras .. string.format(":orderFlyTowards(%.0f, %.0f)", loc[1], loc[2])
438+
elseif orders == "fly_towards_blind" and loc then
439+
extras = extras .. string.format(":orderFlyTowardsBlind(%.0f, %.0f)", loc[1], loc[2])
440+
-- entity-targeted orders (defend_target, attack, dock, fly_formation) cannot be serialized
441+
end
442+
end
443+
255444
return extras
256445
end
257446

447+
-- Returns the number of internal repair crew belonging to `entity`.
448+
function __countShipCrew(entity)
449+
local n = 0
450+
for _, crew in ipairs(getEntitiesWithComponent("internal_crew")) do
451+
if crew.components.internal_crew and crew.components.internal_crew.ship == entity then
452+
n = n + 1
453+
end
454+
end
455+
return n
456+
end
457+
258458
function __exportPlanet(entity)
259459
local extras = __exportBasics(entity)
260460
local pr = entity.components.planet_render

0 commit comments

Comments
 (0)