Merge remote-tracking branch 'origin/master' into master-prd

This commit is contained in:
Maxim
2025-06-17 11:38:03 +03:00
12 changed files with 197 additions and 124 deletions

View File

@@ -19,7 +19,7 @@
- Запустить скрипт `build.sh`
# Взаимодействие с 5p28
# Взаимодействие с 5П-28
- Для получения навигационных данных, должно быть настроено дублирование udp-пакетов
приходящих на соответствующий порт. Скрипт `ip-tables-navigation.sh` выполняет

View File

@@ -29,9 +29,17 @@ func get_radius_outter() -> float: return material.get('shader_parameter/radius
func get_radius_inner_0() -> float: return material.get('shader_parameter/radius_inner_0')
func get_radius_inner_1() -> float: return material.get('shader_parameter/radius_inner_1')
func set_view_mode(val: int): material.set('shader_parameter/mode', val);
func set_band_colors(val: Array): material.set('shader_parameter/ant_band_c', val)
func set_view_mode(val: int):
if val == 1:
$строб.hide()
else:
$строб.show()
material.set('shader_parameter/mode', val)
## Закрашивание по умолчанию блоков ФС цветами из настроек.
func set_def_colors():
var color0: Vector3 = ProjectSettings.get_setting('application/interfer/default_color0', Vector3(0.38, 0.47, 0.51))

BIN
scenes/tilemap/dot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cgqo5gucd2be0"
path="res://.godot/imported/dot.png-e35f44878005aaa12b3432f2608ddde5.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://scenes/tilemap/dot.png"
dest_files=["res://.godot/imported/dot.png-e35f44878005aaa12b3432f2608ddde5.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

4
scenes/tilemap/mark.gd Normal file
View File

@@ -0,0 +1,4 @@
extends Control
func set_text(t: String):
$Label.text = t

View File

@@ -0,0 +1 @@
uid://u811i0l1cvsd

13
scenes/tilemap/mark.tscn Normal file
View File

@@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=3 uid="uid://ce5mtxqd1ndru"]
[ext_resource type="Texture2D" uid="uid://cgqo5gucd2be0" path="res://scenes/tilemap/dot.png" id="2_rq0y0"]
[node name="rect" type="TextureRect"]
anchors_preset = -1
offset_left = -16.0
offset_top = -16.0
offset_right = 16.0
offset_bottom = 16.0
pivot_offset = Vector2(16, 16)
texture = ExtResource("2_rq0y0")
expand_mode = 1

View File

@@ -13,7 +13,7 @@ class_name MercatorTileMap
_clean_all()
queue_redraw()
@export var zoom_step: float = 0.025
@export var zoom_step: float = 0.1
@export var tile_width: float = 256.0
@export var tile_height: float = 256.0
@export_range(1, 1000, 1) var max_concurrent_requests:int = 5
@@ -24,8 +24,8 @@ class_name MercatorTileMap
@export_color_no_alpha() var back_color: = Color.BLACK
const default_size: = 400.0
var is_centered: = false
var is_anim_playng: = false
var is_out_border1: = true
var is_out_border2: = true
@export var animation_time: float = 0.1:
set(val):
@@ -65,7 +65,7 @@ var is_anim_playng: = false
return _xyz.z
var _xyz: Vector3 = Vector3(0, 0, min_zoom_level)
var _xyz: Vector3 = Vector3(0, 0, 0)
var _cache: Dictionary = {}
var _queue: Dictionary = {}
var _dragging: bool = false
@@ -78,9 +78,9 @@ var image: = Image.new()
var image_load_proc: Callable
var now: = 0
var visible_marks_count: = 0
var mouse_wheel_count: = 0
var map_server_available: = false
var border1: ImageTexture
var border2: ImageTexture
## Вызывается после того, как загрузка плиток завершена
signal load_completed()
@@ -112,6 +112,15 @@ func get_marks_count(): return _marks.size()
func _ready() -> void:
var img: = Image.create(1, 2, false, Image.FORMAT_RGBA8)
img.set_pixel(0, 0, Color.CORAL)
img.set_pixel(0, 1, Color(1, 1, 1, 0))
border1 = ImageTexture.create_from_image(img)
img.set_pixel(0, 0, Color(1, 1, 1, 0))
img.set_pixel(0, 1, Color.CORAL)
border2 = ImageTexture.create_from_image(img)
_xyz.z = min_zoom_level
image_type = 'png'
$canvas.connect('mouse_entered', func(): _rollover = true)
$canvas.connect('mouse_exited', func(): _rollover = false)
@@ -142,16 +151,12 @@ func _on_resize() -> void:
## Создаёт новую отметку из координат экрана
func add_mark_from_screen_pos(id: int, x: float, y: float, mark: Node) -> bool:
func add_mark_from_screen_pos(id: int, scr_xy: Vector2, mark: Node) -> bool:
if _marks.has(id):
return false
var wrld_pos = screen_to_world(_xyz, x, y)
var p = world_to_tile(wrld_pos.x, wrld_pos.y, _xyz.z)
var tile_index = xyz_to_idx(p.x, p.y, _xyz.z)
var dot = { 'position': wrld_pos, 'tile_index': tile_index, 'mark': mark }
_marks[id] = dot
var wrld_pos = screen_to_world(_xyz, scr_xy)
_marks[id] = { 'position': wrld_pos, 'mark': mark, 'gap_size': 0.0 }
add_child(mark)
fix_mark_position(dot)
return true
@@ -169,9 +174,7 @@ func add_mark_from_lon_lat(id: int, lon: float, lat: float, mark: Node, gap_size
if _marks.has(id):
return false
var wrld_pos = lonlat_to_world(lon, lat)
var scr_pos = world_to_screen(wrld_pos.x, wrld_pos.y)
mark.set_global_position(scr_pos)
_marks[id] = {'position': wrld_pos, 'mark': mark, 'gap_size': gap_size }
_marks[id] = {'position': wrld_pos, 'mark': mark, 'gap_size': gap_size}
add_child(mark)
queue_redraw()
return true
@@ -189,106 +192,109 @@ func fix_mark_position(dot) -> void:
func is_mark_visible(dot) -> bool:
var screen_pos = world_to_screen(dot.position.x, dot.position.y)
var screen_pos = world_to_screen(dot.position)
return screen_pos.x > 0 and screen_pos.x < $canvas.size.x
func _input(e) -> void:
var lmp = get_local_mouse_position()
if not _rollover:
_dragging = false
return
if e is InputEventMouseButton:
if e.pressed:
if e.button_index == MOUSE_BUTTON_WHEEL_DOWN:
mouse_wheel_count -= 1
queue_redraw()
if (not is_out_border1) and (not is_out_border2):
increment_zoom(-zoom_step, lmp)
queue_redraw()
elif e.button_index == MOUSE_BUTTON_WHEEL_UP:
mouse_wheel_count += 1
increment_zoom( zoom_step, lmp)
queue_redraw()
if e.button_index == MOUSE_BUTTON_LEFT:
_dragging = e.pressed
_drag_pos = screen_to_world(_xyz, lmp.x, lmp.y)
_drag_pos = screen_to_world(_xyz, lmp)
elif e is InputEventMouseMotion:
if _dragging:
var wp = screen_to_world(_xyz, lmp.x, lmp.y)
var wp = screen_to_world(_xyz, lmp)
var diff = _drag_pos - wp
_xyz.x += diff.x
_xyz.y += diff.y
if (not is_out_border1) and diff.y >= 0:
_xyz.y += diff.y
if is_out_border1 and diff.y <= 0:
_xyz.y += diff.y
if is_out_border2 and diff.y >= 0:
_xyz.y += diff.y
if (not is_out_border2) and diff.y <= 0:
_xyz.y += diff.y
queue_redraw()
if diff.length() > 0:
signaller.emmit(signaller.map_user_panning)
## Возвращает true, если видна область за краем карты
func is_border_visible():
var z = min(max_zoom_level, _xyz.z)
return screen_to_tile(0, 0, z).y != 0
func multiply_zoom(multiplier: float, pivot: Vector2) -> void:
var p1: = screen_to_world(_xyz, pivot.x, pivot.y)
var p1: = screen_to_world(_xyz, pivot)
_xyz.z = clampf(_xyz.z * multiplier, min_zoom_level, max_zoom_level)
var p2: = screen_to_world(_xyz, pivot.x, pivot.y)
var p2: = screen_to_world(_xyz, pivot)
_xyz.x -= p2.x - p1.x
_xyz.y -= p2.y - p1.y
func set_zoom(zoom: float, pivot=null) -> void:
if pivot == null:
pivot = $canvas.size / 2.0
func increment_zoom(increment: float, pivot: Vector2) -> void:
var p1: = screen_to_world(_xyz, pivot)
_xyz.z = clampf(_xyz.z + increment, min_zoom_level, max_zoom_level)
var p2: = screen_to_world(_xyz, pivot)
_xyz.x -= p2.x - p1.x
_xyz.y -= p2.y - p1.y
var p1: = screen_to_world(_xyz, pivot.x, pivot.y)
func set_zoom(zoom: float, pivot: Vector2) -> void:
var p1: = screen_to_world(_xyz, pivot)
_xyz.z = clampf(zoom, max_zoom_level, min_zoom_level)
var p2: = screen_to_world(_xyz, pivot.x, pivot.y)
var p2: = screen_to_world(_xyz, pivot)
_xyz.x -= p2.x - p1.x
_xyz.y -= p2.y - p1.y
func _draw() -> void:
var lmp = get_local_mouse_position()
if mouse_wheel_count > 0:
multiply_zoom(1.0 + zoom_step, lmp)
mouse_wheel_count -= 1
elif mouse_wheel_count < 0:
multiply_zoom(1.0 - zoom_step, lmp)
mouse_wheel_count += 1
# Рисование плиток
var z = min(max_zoom_level, _xyz.z)
var t1 = screen_to_tile(0, 0, z)
var t2 = screen_to_tile($canvas.size.x, $canvas.size.y, z)
for tx in range(t1.x, t2.x + 1):
for ty in range(t1.y, t2.y + 1):
var t1 = screen_to_tile(Vector2(0, 0), z)
var t2 = screen_to_tile($canvas.size, z) + Vector2(1, 1)
is_out_border1 = true
is_out_border2 = true
for tx in range(t1.x, t2.x):
for ty in range(t1.y, t2.y):
_draw_tile(tx, ty, z)
# Рисование отметок
var n = pow(2, z)
var dx = n * tile_width
var c = $canvas.size / 2.0
var r = min($canvas.size.x, $canvas.size.y) / 2.0
var c: Vector2 = $canvas.size / 2.0
var r = c.length()
visible_marks_count = 0
for dot in _marks.values():
var pos = world_to_screen(dot.position.x, dot.position.y)
if pos.x >= 0:
pos.x = fmod(pos.x, dx)
else:
pos.x = -pos.x - 1
pos.x = int(dx - 1) - fmod(pos.x, dx)
dot.mark.position = pos
dot.mark.visible = c.distance_to(pos) < (r - dot.gap_size)
var scr_pos = world_to_screen(dot.position)
scr_pos.x = posmod(scr_pos.x, dx)
dot.mark.position = scr_pos
dot.mark.visible = c.distance_to(scr_pos) < (r - dot.gap_size)
visible_marks_count += int(dot.mark.visible)
# Рисование границ
var p1 = $canvas.position
var p2 = $canvas.size / Vector2(1, 20)
if is_out_border1 and _dragging:
draw_texture_rect(border1, Rect2(p1, p2), false, Color.WHITE, false)
p1.y = p1.y + $canvas.size.y - p2.y
if is_out_border2 and _dragging:
draw_texture_rect(border2, Rect2(p1, p2), false, Color.WHITE, false)
func _clean_all() -> void:
_queue.clear()
_cache.clear()
_marks.clear()
_req_count = 0
for c in get_children().filter(func (item): return not item.is_in_group('gui_items')):
remove_child(c)
c.queue_free()
# get the tile from queue with the newest timestamp
@@ -301,7 +307,6 @@ func get_next_in_queue() -> Tile:
return tile
# delete oldest tiles from cache
func _clean_cache() -> void:
var overflow = _cache.size() - max_cached_tiles
if overflow <= 0:
@@ -316,7 +321,7 @@ func _clean_cache() -> void:
func _process(delta) -> void:
now += delta
# if cache is overflowing - clean it
# Если кэш переполнен - очистить его.
if not _last_cache_check or now - _last_cache_check > 5:
_last_cache_check = now
_clean_cache()
@@ -371,11 +376,9 @@ func _response(result, code, _headers: Array, body: PackedByteArray, req, tile:
push_error('не удалось получить изображение (неверный формат?) из \"%s\"' % tile.url)
return
# create texture from image and add it to the cache
tile.texture = ImageTexture.create_from_image(image)
_cache[tile.i] = tile
# redraw the map
queue_redraw()
@@ -401,28 +404,37 @@ func _draw_subtile(tx: int, ty: int, tz: int, origx: int, origy: int, origz: flo
var rect = Rect2(xrat1 * tile_width, yrat1 * tile_height, xwidth * tile_width, yheight * tile_height)
if subtile.texture:
check_borders(p1, p2, $canvas.size.y)
draw_texture_rect_region(subtile.texture, Rect2(p1, p2 - p1), rect)
return true
return false
func check_borders(p1, p2, h):
if p1.y < 0:
is_out_border1 = false
if p1.y + (p2 - p1).y > h:
is_out_border2 = false
func _draw_tile(tx: int, ty: int, z: float) -> void:
var tz = floor(z)
var p1 = tile_to_screen(tx, ty, z)
var p2 = tile_to_screen(tx + 1, ty + 1, z)
var tile = get_tile(tx, ty, tz)
var tz: float = floor(z)
var p1: = tile_to_screen(tx, ty, z)
var p2: = tile_to_screen(tx + 1, ty + 1, z)
var tile: = get_tile(tx, ty, tz)
if tile:
if tile.texture:
check_borders(p1, p2, $canvas.size.y)
draw_texture_rect(tile.texture, Rect2(p1, p2 - p1), false, Color.WHITE, false)
else:
var zzz = tz
var txx = tx
var tyy = ty
var zzz: float = tz
var txx: = tx
var tyy: = ty
while zzz > 1:
zzz -= 1
txx = floor(txx / 2.0)
tyy = floor(tyy / 2.0)
if _draw_subtile(txx, tyy, zzz, tx, ty,z):
if _draw_subtile(txx, tyy, zzz, tx, ty, z):
break
@@ -437,7 +449,7 @@ func lonlat_to_world(lon: float, lat: float) -> Vector2:
## convert lon/lat to screen coords
func lonlat_to_screen(lon: float, lat: float) -> Vector2:
var w = lonlat_to_world(lon, lat)
return world_to_screen(w.x, w.y)
return world_to_screen(w)
## convert lon/lat to tile coords
@@ -454,8 +466,8 @@ func world_to_lonlat(wx: float, wy: float) -> Vector2:
## convert screen coords to lon/lat
func screen_to_lonlat(sx: float, sy: float) -> Vector2:
var w = screen_to_world(_xyz, sx, sy)
func screen_to_lonlat(scr_xy: Vector2) -> Vector2:
var w = screen_to_world(_xyz, scr_xy)
return world_to_lonlat(w.x, w.y)
@@ -466,12 +478,12 @@ func tile_to_lonlat(tx: float, ty: float, tz: float) -> Vector2:
## convert screen coords to world coords
func screen_to_world(xyz: Vector3, sx: float, sy: float) -> Vector2:
func screen_to_world(xyz: Vector3, s: Vector2) -> Vector2:
var n = pow(2.0, xyz.z)
var span_w = n * tile_width
var span_h = n * tile_height
var px = sx - $canvas.size.x / 2 + span_w / 2
var py = sy - $canvas.size.y / 2 + span_h / 2
var px = s.x - $canvas.size.x / 2 + span_w / 2
var py = s.y - $canvas.size.y / 2 + span_h / 2
var xr = px / span_w
var yr = py / span_h
var x = (xr * 2.0 - 1.0) + xyz.x
@@ -480,15 +492,15 @@ func screen_to_world(xyz: Vector3, sx: float, sy: float) -> Vector2:
## convert screen coords to tile coords
func screen_to_tile(sx: float, sy: float, z: float) -> Vector2:
var world = screen_to_world(_xyz, sx, sy)
func screen_to_tile(scr_xy: Vector2, z: float) -> Vector2:
var world = screen_to_world(_xyz, scr_xy)
return world_to_tile(world.x, world.y, z)
## convert tile coords to screen coords
func tile_to_screen(tx: float, ty: float, tz: float) -> Vector2:
var w = tile_to_world(tx, ty, tz)
return world_to_screen(w.x, w.y)
return world_to_screen(w)
## convert tile coords to world coords
@@ -501,34 +513,28 @@ func tile_to_world(tx: float, ty: float, tz: float) -> Vector2:
## convert world coords to tile coords
func world_to_tile(wx: float, wy: float, z: float) -> Vector2:
var n: = pow(2.0, floor(z))
var tx: = ((wx + 1.0) / 2.0) * n
var ty: = ((-wy + 1.0) / 2.0) * n
tx = floor(tx)
ty = floor(ty)
var n: float = pow(2.0, floor(z))
var tx: float = floor(((wx + 1.0) / 2.0) * n)
var ty: float = floor(((-wy + 1.0) / 2.0) * n)
return Vector2(tx, ty)
## convert world coords to screen coords
func world_to_screen(wx: float, wy: float) -> Vector2:
func world_to_screen(pos: Vector2) -> Vector2:
var n: = pow(2.0, _xyz.z)
var w: = n * tile_width
var h: = n * tile_height
var xr: = ((wx - _xyz.x) + 1.0) / 2.0
var yr: = (-(wy - _xyz.y) + 1.0) / 2.0
var x = w * xr - w / 2.0 + $canvas.size.x / 2.0
var y = h * yr - h / 2.0 + $canvas.size.y / 2.0
return Vector2(x, y)
var wh: = Vector2(n * tile_width, n * tile_height)
var rxy: = Vector2(((pos.x - _xyz.x) + 1.0) / 2.0, (-(pos.y - _xyz.y) + 1.0) / 2.0)
return wh * rxy + ($canvas.size - wh) / 2.0
## get the tile index from xyz tile coords
func xyz_to_idx(x: float, y: float, z: float) -> int:
var i: = (pow(4.0, z) - 1.0) / 3.0
var n: = pow(2.0, z)
var n: = pow(2.0, z)
return int(i + (y * n + x))
func get_tile(x: int, y: int, z: int, create: bool = true) -> Tile:
func get_tile(x: int, y: int, z: int) -> Tile:
var n = pow(2, z)
if x >= 0:
@@ -560,13 +566,11 @@ func get_tile(x: int, y: int, z: int, create: bool = true) -> Tile:
return tile
var queue_size: = _queue.size()
if create and (not $canvas/player.is_playing()):
if not tile:
tile = Tile.new(idx, x, y, z)
tile.url = base_url.replace('{x}', str(x)).replace('{y}', str(y)).replace('{z}', str(z))
tile.t = now
if z <= max_zoom_level_server:
_queue[idx] = tile
tile = Tile.new(idx, x, y, z)
tile.url = base_url.replace('{x}', str(x)).replace('{y}', str(y)).replace('{z}', str(z))
tile.t = now
if z <= max_zoom_level_server:
_queue[idx] = tile
if queue_size == 0 and _queue.size():
emit_signal('load_started')
@@ -616,7 +620,7 @@ func haversine_m(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
var rlon1: = deg_to_rad(lon1)
var rlat2: = deg_to_rad(lat2)
var rlon2: = deg_to_rad(lon2)
# вычисления длины большого круга
# вычисление длины большого круга
var cl1: = cos(rlat1)
var cl2: = cos(rlat2)
var sl1: = sin(rlat1)
@@ -645,7 +649,7 @@ func find_at_screen_position(screen_pos: Vector2) -> Node:
var closest_dot = null
var closest_distance = INF
for mark in _marks.values():
var dot_screen_pos = world_to_screen(mark.position.x, mark.position.y)
var dot_screen_pos = world_to_screen(mark.position)
var distance = dot_screen_pos.distance_to(screen_pos)
if distance < closest_distance:
closest_distance = distance

View File

@@ -28,10 +28,8 @@ size = Vector2(400, 400)
[node name="tilemap" type="CanvasGroup" groups=["gui_items"]]
script = ExtResource("1_l64tp")
base_url = "http://192.168.11.105:8001/osm_tiles/{z}/{x}/{y}.png"
max_zoom_level = 15
max_zoom_level_server = 10
az = 2.0
border_scene = "uid://bumg0moa26skt"
zoom_step = 0.1
[node name="canvas" type="TextureRect" parent="." groups=["gui_items"]]
clip_contents = true

View File

@@ -29,7 +29,7 @@ var shift_pressed: bool ## Кнопка SHIFT нажата.
var strob_band_begin: float ## Ширина строба в момент начала перетаскивания.
var strob_dir_begin: float ## Направление антенны в момент начала перетаскивания.
var strob_width_begin: float ## Ширина строба по углу в момент начала перетаскивания.
var view_mode: MapViewMode ## Режим отображения карты
signal drag_begin ## Вызывается в момент начала перетаскивания.
signal drag_continue ## Вызывается во время продолжения перетаскивания.
@@ -59,6 +59,13 @@ enum BtnSel {
}
## Режим отображения карты
enum MapViewMode {
RTO = 0, ## Режим приоритет РТО (малая карта, широкие секторы)
RLS = 1 ## Режим приоритет РЛС (большая карта, узкие секторы)
}
const VAL_TO_EVENT = {
false: 'manual_control', ## Выбор целей и установка помех вручную.
true: 'full_auto' } ## Выбор целей и установка помех автоматически.
@@ -86,6 +93,7 @@ func on_button_view_toggled(toggled: bool):
var s = map_sizes[int(toggled)]
$tilemap.size = s.size
$tilemap.position = s.position
view_mode = MapViewMode.RLS if toggled else MapViewMode.RTO
## Встроенный обратный вызов.
@@ -379,7 +387,7 @@ func _on_drag_begin(event) -> void:
## Обработчик событий ввода. Вызывается godot.
func _input(event) -> void:
if not is_visible_in_tree(): return
if event is InputEventMouseMotion:
if (event is InputEventMouseMotion) and (view_mode == MapViewMode.RTO):
if drag_fsm == DragFSM.DRAG:
emit_signal('drag_continue', event)
elif event is InputEventMouseButton:

View File

@@ -84,6 +84,9 @@ metadata/_edit_lock_ = true
[node name="tilemap" parent="." instance=ExtResource("10_gtwyg")]
self_modulate = Color(1, 1, 1, 0.415686)
position = Vector2(360, 360)
min_zoom_level = 3
max_zoom_level_server = 11
az = 3.0
[node name="player" type="AnimationPlayer" parent="tilemap"]
libraries = {
@@ -285,6 +288,6 @@ texture_pressed = ExtResource("13_ggrwd")
[connection signal="drag_continue" from="." to="." method="_on_drag_continue"]
[connection signal="toggled" from="btn_view" to="." method="on_button_view_toggled"]
[connection signal="toggled" from="chk_auto" to="." method="on_value_changed"]
[connection signal="toggled" from="btn_activate" to="." method="on_btn_activate"]
[connection signal="toggled" from="btn_activate" to="." method="on_btn_activate_toggled"]
[connection signal="toggled" from="btn_activate" to="." method="on_btn_activate"]
[connection signal="toggled" from="btn_center" to="." method="on_btn_center_toggled"]

View File

@@ -350,10 +350,10 @@ func check_dir():
## Производит приём состояния модулей ФС.
func on_data_capsrpb_received(unit: capsrpb.CapsRpb):
if not unit.json_dic.has('ts'): return
if unit.json_dic['ts'] != 7: return
if not unit.json_dic.has('msfs'): return
func on_data_capsrpb_received(unit0: capsrpb.CapsRpb):
if not unit0.json_dic.has('ts'): return
if unit0.json_dic['ts'] != 7: return
if not unit0.json_dic.has('msfs'): return
var msfs = unit.json_dic['msfs']
for fs in msfs: