Commit 26d6772a authored by Roque's avatar Roque

erp5_officejs_drone_capture_flag: app display using dialog steps pattern

- drop import/export json
- API for operator script
- map utils class update
- doc api update
- all visible map parameters are geo
- allow to run twice
- fix default ai drone script distance fn
- fix flag elements position (altitude)
- better error handling
parent cbf008fa
...@@ -431,7 +431,7 @@ var FixedWingDroneAPI = /** @class */ (function () { ...@@ -431,7 +431,7 @@ var FixedWingDroneAPI = /** @class */ (function () {
} }
} }
}); });
context._map_dict.geo_obstacle_list.forEach(function (obstacle) { context._map_dict.obstacle_list.forEach(function (obstacle) {
distance = calculateDistance(drone_position, obstacle.position, context); distance = calculateDistance(drone_position, obstacle.position, context);
if (distance <= VIEW_SCOPE) { if (distance <= VIEW_SCOPE) {
result.obstacles.push(obstacle); result.obstacles.push(obstacle);
......
...@@ -266,7 +266,7 @@ ...@@ -266,7 +266,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1692989145.27</float> <float>1694531183.74</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -246,7 +246,7 @@ ...@@ -246,7 +246,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1010.55458.31149.16742</string> </value> <value> <string>1011.9946.55286.52770</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -266,7 +266,7 @@ ...@@ -266,7 +266,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1693590015.02</float> <float>1694792090.7</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
/******************************* MAP UTILS ************************************/
var MapUtils = /** @class */ (function () {
"use strict";
var FLAG_EPSILON = 15, R = 6371e3;
//** CONSTRUCTOR
function MapUtils(map_param) {
var _this = this, max_width = _this.latLonDistance(
[map_param.min_lat, map_param.min_lon],
[map_param.min_lat, map_param.max_lon]),
max_depth = _this.latLonDistance(
[map_param.min_lat, map_param.min_lon],
[map_param.max_lat, map_param.min_lon]),
map_size = Math.ceil(Math.max(max_width, max_depth));
_this.map_param = {};
_this.map_param.height = map_param.height;
_this.map_param.start_AMSL = map_param.start_AMSL;
_this.map_param.min_lat = map_param.min_lat;
_this.map_param.max_lat = map_param.max_lat;
_this.map_param.min_lon = map_param.min_lon;
_this.map_param.max_lon = map_param.max_lon;
_this.map_param.depth = map_size;
_this.map_param.width = map_size;
_this.map_param.map_size = map_size;
_this.map_info = {
"depth": _this.map_param.depth,
"width": _this.map_param.width,
"flag_distance_epsilon": map_param.flag_distance_epsilon || FLAG_EPSILON
};
_this.map_info.map_size = _this.map_param.map_size;
_this.map_info.height = _this.map_param.height;
_this.map_info.start_AMSL = _this.map_param.start_AMSL;
_this.map_info.min_x = _this.longitudToX(map_param.min_lon);
_this.map_info.min_y = _this.latitudeToY(map_param.min_lat);
_this.map_info.max_x = _this.longitudToX(map_param.max_lon);
_this.map_info.max_y = _this.latitudeToY(map_param.max_lat);
}
MapUtils.prototype.latLonDistance = function (c1, c2) {
var q1 = c1[0] * Math.PI / 180,
q2 = c2[0] * Math.PI / 180,
dq = (c2[0] - c1[0]) * Math.PI / 180,
dl = (c2[1] - c1[1]) * Math.PI / 180,
a = Math.sin(dq / 2) * Math.sin(dq / 2) +
Math.cos(q1) * Math.cos(q2) *
Math.sin(dl / 2) * Math.sin(dl / 2),
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
};
MapUtils.prototype.longitudToX = function (lon) {
return (this.map_info.map_size / 360.0) * (180 + lon);
};
MapUtils.prototype.latitudeToY = function (lat) {
return (this.map_info.map_size / 180.0) * (90 - lat);
};
MapUtils.prototype.convertToLocalCoordinates =
function (latitude, longitude, altitude) {
var map_info = this.map_info,
x = this.longitudToX(longitude),
y = this.latitudeToY(latitude);
return {
x: ((x - map_info.min_x) / (map_info.max_x - map_info.min_x)) *
1000 - map_info.width / 2,
y: ((y - map_info.min_y) / (map_info.max_y - map_info.min_y)) *
1000 - map_info.depth / 2,
z: altitude
};
};
MapUtils.prototype.convertToGeoCoordinates = function (x, y, z) {
var lon = x + this.map_info.width / 2,
lat = y + this.map_info.depth / 2;
lon = lon / 1000;
lon = lon * (this.map_info.max_x - this.map_info.min_x) +
this.map_info.min_x;
lon = lon / (this.map_info.map_size / 360.0) - 180;
lat = lat / 1000;
lat = lat * (this.map_info.max_y - this.map_info.min_y) +
this.map_info.min_y;
lat = 90 - lat / (this.map_info.map_size / 180.0);
return {
x: lat,
y: lon,
z: z
};
};
/*
** Randomizes all map elements: starting point, enemies, flags, obstacles
*/
MapUtils.prototype.randomize = function (seed) {
//TODO randomize start_ASML, map height, depth and width?
var _this = this, randomized_map = {};
function randomIntFromInterval(min, max, random_seed) {
return Math.floor(random_seed.quick() * (max - min + 1) + min);
}
function randomPosition(random_seed, map_size) {
var sign_x = random_seed.quick() < 0.5 ? -1 : 1,
sign_y = random_seed.quick() < 0.5 ? -1 : 1,
pos_x = sign_x * random_seed.quick() * map_size / 2,
pos_y = sign_y * random_seed.quick() * map_size / 2;
return [pos_x, pos_y];
}
var random_seed = new Math.seedrandom(seed), i,
n_enemies = randomIntFromInterval(5, 10, random_seed),
n_flags = randomIntFromInterval(5, 10, random_seed), //TODO change range
n_obstacles = randomIntFromInterval(5, 15, random_seed),
flag_list = [], obstacle_list = [], enemy_list = [], random_position,
obstacles_types = ["box", "cylinder"], type,
obstacle_limit = [_this.map_param.map_size / 6, _this.map_param.map_size / 100,
_this.map_param.map_size / 6, 30],
geo_flag_info, geo_obstacle, geo_enemy, coordinates;
//enemies
for (i = 0; i < n_enemies; i += 1) {
random_position = randomPosition(random_seed, _this.map_param.map_size);
enemy_list.push({
"type": "EnemyDroneAPI",
"position": {
"x": random_position[0],
"y": random_position[1],
"z": 15 //TODO random z? yes
}
});
}
//flags
for (i = 0; i < n_flags; i += 1) {
//avoid flags near the limits
random_position = randomPosition(random_seed, _this.map_param.map_size * 0.75);
flag_list.push({
"position": {
"x": random_position[0],
"y": random_position[1],
"z": 10
},
"score": randomIntFromInterval(1, 5, random_seed),
"weight": randomIntFromInterval(1, 5, random_seed)
});
}
function checkDistance(position, position_list) {
function distance(a, b) {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
var el;
for (el = 0; el < position_list.length; el += 1) {
if (distance(position, position_list[el].position) < _this.map_param.map_size / 6) {
return true;
}
}
return false;
}
//obstacles
for (i = 0; i < n_obstacles; i += 1) {
random_position = randomPosition(random_seed, _this.map_param.map_size);
if (checkDistance({ 'x': random_position[0],
'y': random_position[1]}, flag_list)) {
i -= 1;
} else {
type = randomIntFromInterval(0, 2, random_seed);
obstacle_list.push({
"type": obstacles_types[type],
"position": {
"x": random_position[0],
"y": random_position[1],
"z": 15 //TODO random z?
},
"scale": {
"x": randomIntFromInterval(20, obstacle_limit[type], random_seed),
"y": randomIntFromInterval(20, obstacle_limit[type], random_seed),
"z": randomIntFromInterval(5, obstacle_limit[3], random_seed)
},
"rotation": {
"x": 0,
"y": 0,
"z": 0
}
});
}
}
_this.map_param.obstacle_list = [];
_this.map_param.enemy_list = [];
_this.map_param.flag_list = [];
//TODO make it random
_this.map_info.initial_position = _this.convertToGeoCoordinates(
0, _this.map_param.map_size / 2 * -0.75, 15
);
//convert all map elements positions to geo coordinates
Object.assign(_this.map_info, _this.map_param);
flag_list.forEach(function (flag_info, index) {
coordinates = _this.convertToGeoCoordinates(
flag_info.position.x,
flag_info.position.y,
flag_info.position.z
);
geo_flag_info = {
'id': flag_info.id,
'score': flag_info.score,
'weight': flag_info.weight,
'position': {
'x': coordinates.x,
'y': coordinates.y,
'z': coordinates.z
}
};
_this.map_info.flag_list.push(geo_flag_info);
});
obstacle_list.forEach(function (obstacle_info, index) {
geo_obstacle = {};
Object.assign(geo_obstacle, obstacle_info);
geo_obstacle.position = _this.convertToGeoCoordinates(
obstacle_info.position.x,
obstacle_info.position.y,
obstacle_info.position.z
);
_this.map_info.obstacle_list.push(geo_obstacle);
});
enemy_list.forEach(function (enemy_info, index) {
geo_enemy = {};
Object.assign(geo_enemy, enemy_info);
geo_enemy.position = _this.convertToGeoCoordinates(
enemy_info.position.x,
enemy_info.position.y,
enemy_info.position.z
);
_this.map_info.enemy_list.push(geo_enemy);
});
//return only base parameters
randomized_map.min_lat = _this.map_info.min_lat;
randomized_map.max_lat = _this.map_info.max_lat;
randomized_map.min_lon = _this.map_info.min_lon;
randomized_map.max_lon = _this.map_info.max_lon;
randomized_map.height = _this.map_info.height;
randomized_map.start_AMSL = _this.map_info.start_AMSL;
randomized_map.flag_list = _this.map_info.flag_list;
randomized_map.obstacle_list = _this.map_info.obstacle_list;
randomized_map.enemy_list = _this.map_info.enemy_list;
randomized_map.initial_position = _this.map_info.initial_position;
return randomized_map;
};
return MapUtils;
}());
/******************************************************************************/
...@@ -29,6 +29,10 @@ ...@@ -29,6 +29,10 @@
margin: 0; margin: 0;
} }
.documentation div {
vertical-align: middle !important;
}
.documentation .line { .documentation .line {
width: 100%; width: 100%;
height: 1px; height: 1px;
...@@ -54,7 +58,6 @@ ...@@ -54,7 +58,6 @@
.item-name span:last-of-type { .item-name span:last-of-type {
font-style: italic; } font-style: italic; }
.line { .line {
width: 100%; width: 100%;
height: 1px; height: 1px;
......
...@@ -242,7 +242,7 @@ ...@@ -242,7 +242,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1010.15231.63877.58538</string> </value> <value> <string>1011.7107.64372.12151</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -262,7 +262,7 @@ ...@@ -262,7 +262,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1691176394.73</float> <float>1694621148.77</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -12,39 +12,47 @@ ...@@ -12,39 +12,47 @@
<h1>Game specifications</h1> <h1>Game specifications</h1>
<h3>Start message with information about the flags</h3> <!-- Map JSON -->
<h3>Map parameter dictionary (JSON)</h3>
<!-- Flag message -->
<h4 class="item-name" id="flag_msg"><span>msg.flag_positions</span><span>: list</span></h4>
<p class="item-descr">List containing an information dictionary for each flag in the map</p>
<div> <div>
<h5 class="item-param-1">Flag info dictionary:</h5> <h5 class="item-param-1">Map dictionary entries</h5>
<p class="item-descr"></p> <p class="item-descr"></p>
</div> </div>
<div> <div>
<h5 class="item-param-1">Param</h5> <h5 class="item-param-1">Key</h5>
<h5 class="item-param-2">Description</h5> <h5 class="item-param-2">Description</h5>
</div> </div>
<div> <div>
<p class="item-param-1">id: Integer</p> <p class="item-param-1">min_lat, max_lat, min_lon, max_lon: Float</p>
<p class="item-param-2">Flag id</p> <p class="item-param-2">Min and max latitude and longitude coordinates of the map</p>
</div>
<div>
<p class="item-param-1">map_size: Integer</p>
<p class="item-param-2">Map size in meters (calculated from coordinates)</p>
</div> </div>
<div> <div>
<p class="item-param-1">score: Integer</p> <p class="item-param-1">width, depth: Integer</p>
<p class="item-param-2">Flag score for hit</p> <p class="item-param-2">Map width and depth in meters (calculated from coordinates)</p>
</div> </div>
<div> <div>
<p class="item-param-1">weight: Integer</p> <p class="item-param-1">height: Integer</p>
<p class="item-param-2">Flag weight, number of hits to be captured</p> <p class="item-param-2">Map height in meters</p>
</div> </div>
<div> <div>
<p class="item-param-1">position: dictionary</p> <p class="item-param-1">start_AMSL: Integer</p>
<p class="item-param-2">Map height above mean sea level in meters</p>
</div>
<div>
<p class="item-param-1">initial_position: dictionary</p>
<p class="item-param-2">Drones starting point coordinates</p>
<p class="item-param-2"> <p class="item-param-2">
{<br> {<br>
&nbsp;&nbsp;x: number, //latitude (in degrees)<br> &nbsp;&nbsp;x: number, //latitude (in degrees)<br>
...@@ -54,6 +62,72 @@ ...@@ -54,6 +62,72 @@
</p> </p>
</div> </div>
<div>
<p class="item-param-1">flag_list: list</p>
<p class="item-param-2">List of flags, each element:</p>
<p class="item-param-2">
{<br>
&nbsp;&nbsp;position {x,y,z}:<br>&nbsp;&nbsp;latitude, longitude and altitude<br>
&nbsp;&nbsp;score: number<br>
&nbsp;&nbsp;weight: number<br>
}<br>
</p>
</div>
<div>
<p class="item-param-1">obstacle_list: list</p>
<p class="item-param-2">List of obstacles, each element:</p>
<p class="item-param-2">
{<br>
&nbsp;&nbsp;position {x,y,z}:<br>&nbsp;&nbsp;latitude, longitude and altitude<br>
&nbsp;&nbsp;type: [box, cilinder, sphere]<br>
&nbsp;&nbsp;scale: {x,y,z}<br>
&nbsp;&nbsp;rotation: {x,y,z}<br>
}<br>
</p>
</div>
<div>
<p class="item-param-1">enemy_list: list</p>
<p class="item-param-2">List of enemies, each element:</p>
<p class="item-param-2">
{<br>
&nbsp;&nbsp;position {x,y,z}:<br>&nbsp;&nbsp;latitude, longitude and altitude<br>
&nbsp;&nbsp;type: drone-type<br>
&nbsp;&nbsp;id: number<br>
}<br>
</p>
</div>
<div class="line"></div>
<h3>Operator script</h3>
<!-- Operator script -->
<h4 class="item-name" id="scoring"><span>Operator</span></h4>
<p class="item-descr">The purpose of this script is to set the initial message that all the drones will get at the beginning of the game.</p>
<p class="item-descr">The map parameter dictionary can be accessed to get any relevant info.</p>
<p class="item-descr">An API is provided through the object <em>operator</em> that allows to get the map json and set the intial message.</p>
<h4 class="item-name" id="scoring"><span>API</span></h4>
<div>
<p class="item-param-1">getMapJSON(): dictionary</p>
<p class="item-param-2">Get the map JSON dictionary</p>
</div>
<div>
<p class="item-param-1">sendMsg(msg): void</p>
<p class="item-param-2">Set the initial msg all the drones will get at the start.</p>
<p class="item-param-2">Message parameter msg must be a dictionary</p>
</div>
<h5 class="item-param-1">Example</h5>
<p class="item-example">var map = operator.getMapJSON();<br>
operator.sendMsg({flag_positions: map.flag_list});
</p>
<div class="line"></div> <div class="line"></div>
<h3>Game scoring</h3> <h3>Game scoring</h3>
......
...@@ -244,7 +244,7 @@ ...@@ -244,7 +244,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1010.51249.55554.52258</string> </value> <value> <string>1011.7129.2983.29201</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -264,7 +264,7 @@ ...@@ -264,7 +264,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1693418554.45</float> <float>1694622336.88</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
.import-export { div[data-gadget-url$="gadget_erp5_page_drone_capture_flag_script_page.html"] .item-label {
white-space: nowrap;
}
.import-export input {
display: none;
}
.import-export label {
border: 1px solid #ccc;
display: inline-block;
padding: 6px 12px;
cursor: pointer;
}
.item-label {
background-color: #F8F8F8; background-color: #F8F8F8;
padding: 8px; padding: 8px;
margin: 8px 0; margin: 8px 0;
...@@ -21,6 +6,18 @@ ...@@ -21,6 +6,18 @@
font-weight: bold; font-weight: bold;
} }
.run-game { div[data-gadget-url$="gadget_erp5_page_drone_capture_flag_script_page.html"] button {
margin-top: 0 !important; color: #212529;
} padding: 3pt;
\ No newline at end of file border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
display: inline-block;
margin-right: 6pt;
}
div[data-gadget-url$="gadget_erp5_page_drone_capture_flag_script_page.html"] button:disabled,
div[data-gadget-url$="gadget_erp5_page_drone_capture_flag_script_page.html"] button[disabled] {
color: #999999;
}
div[data-gadget-url$="gadget_erp5_page_drone_capture_flag_script_page.html"] button:before {
padding-right: 0.2em;
}
...@@ -242,7 +242,7 @@ ...@@ -242,7 +242,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1010.62540.7060.5000</string> </value> <value> <string>1010.65016.4453.48725</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -262,7 +262,7 @@ ...@@ -262,7 +262,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1694015251.98</float> <float>1694183879.39</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<!--
data-i18n=Others
data-i18n=Tools
-->
<head> <head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
...@@ -19,52 +15,16 @@ ...@@ -19,52 +15,16 @@
<script src="jiodev.js" type="text/javascript"></script> <script src="jiodev.js" type="text/javascript"></script>
<script src="gadget_global.js" type="text/javascript"></script> <script src="gadget_global.js" type="text/javascript"></script>
<script src="domsugar.js" type="text/javascript"></script> <script src="domsugar.js" type="text/javascript"></script>
<script type="text/javascript" src="./libraries/seedrandom.min.js"></script> <script type="text/javascript" src="./libraries/seedrandom.min.js"></script>
<script type="text/javascript" src="./libraries/require.js"></script> <script src="gadget_erp5_page_drone_capture_map_utils.js" type="text/javascript"></script>
<script src="gadget_erp5_page_drone_capture_flag_script_page.js" type="text/javascript"></script> <script src="gadget_erp5_page_drone_capture_flag_script_page.js" type="text/javascript"></script>
</head> </head>
<body> <body>
<form> <div class="captureflagpageheader"></div>
<!-- parameters input form --> <div class="captureflagpagebody"></div>
<label class="item-label">Game parameters</label>
<div data-gadget-url="gadget_erp5_form.html"
data-gadget-scope="form_view"
data-gadget-sandbox="public">
</div>
<!-- operator script editor -->
<label class="item-label">Operator script</label>
<div data-gadget-url="gadget_editor.html"
data-gadget-scope="operator-editor"
data-gadget-sandbox="">
</div>
<div class="import-export">
<label class="import">
<input type="file" id="import" >
Import
</label>
<label class="export">
<input type="button" id="export" >
Export
</label>
</div>
<!-- AI script editor -->
<label class="item-label">AI script</label>
<div data-gadget-url="gadget_editor.html"
data-gadget-scope="script-editor"
data-gadget-sandbox="">
</div>
<!-- Game -->
<label class="item-label">Game</label>
<input name="action_run" class="run-game" type="submit" value="Run">
<div class="simulator_div"></div>
<div data-gadget-url="gadget_erp5_form.html"
data-gadget-scope="form_view_babylonjs"
data-gadget-sandbox="public">
</div>
<a data-i18n="Storages"></a> <!-- for zelenium test common macro -->
</form>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -244,7 +244,7 @@ ...@@ -244,7 +244,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1010.62539.25351.39680</string> </value> <value> <string>1010.65526.46361.27613</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -264,7 +264,7 @@ ...@@ -264,7 +264,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1694014905.34</float> <float>1694194019.23</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -246,7 +246,7 @@ ...@@ -246,7 +246,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>1010.62683.28686.10205</string> </value> <value> <string>1011.10069.44691.10922</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -266,7 +266,7 @@ ...@@ -266,7 +266,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1694023525.57</float> <float>1694798760.01</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="File" module="OFS.Image"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>__name__</string> </key>
<value> <string>require.js</string> </value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/javascript</string> </value>
</item>
<item>
<key> <string>precondition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -4,8 +4,10 @@ url_list = [ ...@@ -4,8 +4,10 @@ url_list = [
"gadget_erp5_page_drone_capture_flag_logic.js", "gadget_erp5_page_drone_capture_flag_logic.js",
"gadget_erp5_page_drone_capture_flag_script_page.html", "gadget_erp5_page_drone_capture_flag_script_page.html",
"gadget_erp5_page_drone_capture_flag_script_page.js", "gadget_erp5_page_drone_capture_flag_script_page.js",
"gadget_erp5_page_drone_capture_flag_script_page.css",
"gadget_erp5_panel_drone_capture_flag.html", "gadget_erp5_panel_drone_capture_flag.html",
"gadget_erp5_panel_drone_capture_flag.js", "gadget_erp5_panel_drone_capture_flag.js",
"gadget_erp5_page_drone_capture_map_utils.js",
"gadget_erp5_page_drone_capture_flag_fixedwingdrone.js", "gadget_erp5_page_drone_capture_flag_fixedwingdrone.js",
"gadget_erp5_page_drone_capture_flag_enemydrone.js", "gadget_erp5_page_drone_capture_flag_enemydrone.js",
"gadget_erp5_page_drone_capture_flag_api_page.html", "gadget_erp5_page_drone_capture_flag_api_page.html",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment