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 () {
}
}
});
context._map_dict.geo_obstacle_list.forEach(function (obstacle) {
context._map_dict.obstacle_list.forEach(function (obstacle) {
distance = calculateDistance(drone_position, obstacle.position, context);
if (distance <= VIEW_SCOPE) {
result.obstacles.push(obstacle);
......
......@@ -266,7 +266,7 @@
</tuple>
<state>
<tuple>
<float>1692989145.27</float>
<float>1694531183.74</float>
<string>UTC</string>
</tuple>
</state>
......
/*global BABYLON, RSVP, console, FixedWingDroneAPI, EnemyDroneAPI, document*/
/*global BABYLON, RSVP, console, FixedWingDroneAPI, EnemyDroneAPI, document, MapUtils*/
/*jslint nomen: true, indent: 2, maxlen: 80, todo: true,
unparam: true */
var GAMEPARAMETERS = {}, TEAM_USER = "user", TEAM_ENEMY = "enemy", R = 6371e3;
var GAMEPARAMETERS = {}, TEAM_USER = "user", TEAM_ENEMY = "enemy";
//for DEBUG/TEST mode
var baseLogFunction = console.log, console_log = "";
/******************************* MAP UTILS ************************************/
var MapUtils = /** @class */ (function () {
"use strict";
var FLAG_EPSILON = 15;
//** 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_height = _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_height));
_this.map_param = map_param;
_this.map_param.map_size = map_size;
_this.map_info = {
"depth": map_param.map_size,
"width": map_param.map_size,
"geo_flag_list": [],
"geo_obstacle_list": [],
"flag_distance_epsilon": map_param.flag_distance_epsilon || FLAG_EPSILON
};
Object.assign(_this.map_info, _this.map_param);
_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
};
};
MapUtils.prototype.randomize = function () {
//TODO randomize start_ASML and map height
var _this = this;
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(_this.map_param.map_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, coordinates;
//enemies
for (i = 0; i < n_enemies; i += 1) {
random_position = randomPosition(random_seed, _this.map_param.map_size);
enemy_list.push({
"id": i + 10000,
"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 = obstacle_list;
_this.map_param.enemy_list = enemy_list;
_this.map_param.flag_list = flag_list;
_this.map_param.starting_position = {
"x": 0,
"y": _this.map_param.map_size / 2 * -0.75,
"z": 15
};
_this.map_info.initial_position = _this.map_param.starting_position;
Object.assign(_this.map_info, _this.map_param);
_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.geo_flag_list.push(geo_flag_info);
});
_this.map_param.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.geo_obstacle_list.push(geo_obstacle);
});
return _this.map_info;
};
return MapUtils;
}());
/******************************************************************************/
/******************************* DRONE MANAGER ********************************/
var DroneManager = /** @class */ (function () {
"use strict";
......@@ -622,7 +405,6 @@ var DroneManager = /** @class */ (function () {
/******************************************************************************/
/******************************** MAP MANAGER *********************************/
var MapManager = /** @class */ (function () {
......@@ -649,20 +431,18 @@ var MapManager = /** @class */ (function () {
function MapManager(scene, map_param) {
var _this = this, max_sky, skybox, skyboxMat, largeGroundMat, flag_material,
largeGroundBottom, width, depth, terrain, max, flag_a, flag_b, mast, flag,
count = 0, new_obstacle;
count = 0, new_obstacle, obstacle, flag_info, enemy;
if (!map_param) {
// Use default map base parameters
map_param = MAP;
}
_this.mapUtils = new MapUtils(map_param);
if (!map_param.randomized) {
// Randomize map here
map_param = _this.mapUtils.randomize();
console.log("[INFO] using map randomly generated within the game");
} else {
console.log("[INFO] using map randomly generated outside game");
}
_this.map_info = map_param;
Object.assign(_this.map_info, _this.mapUtils.map_info);
_this.map_info.initial_position = _this.mapUtils.convertToLocalCoordinates(
_this.map_info.initial_position.x,
_this.map_info.initial_position.y,
_this.map_info.initial_position.z);
max = _this.map_info.width;
if (_this.map_info.depth > max) {
max = _this.map_info.depth;
......@@ -705,9 +485,26 @@ var MapManager = /** @class */ (function () {
terrain.position = BABYLON.Vector3.Zero();
terrain.scaling = new BABYLON.Vector3(depth / 50000, depth / 50000,
width / 50000);
// Enemies
_this._enemy_list = [];
_this.map_info.enemy_list.forEach(function (geo_enemy) {
enemy = {};
Object.assign(enemy, geo_enemy);
enemy.position = _this.mapUtils.convertToLocalCoordinates(
geo_enemy.position.x,
geo_enemy.position.y,
geo_enemy.position.z);
_this._enemy_list.push(enemy);
});
// Obstacles
_this._obstacle_list = [];
_this.map_info.obstacle_list.forEach(function (obstacle) {
_this.map_info.obstacle_list.forEach(function (geo_obstacle) {
obstacle = {};
Object.assign(obstacle, geo_obstacle);
obstacle.position = _this.mapUtils.convertToLocalCoordinates(
geo_obstacle.position.x,
geo_obstacle.position.y,
geo_obstacle.position.z);
switch (obstacle.type) {
case "box":
new_obstacle = BABYLON.MeshBuilder.CreateBox("obs_" + count,
......@@ -759,7 +556,13 @@ var MapManager = /** @class */ (function () {
'y': 1,
'z': 6
};
_this.map_info.flag_list.forEach(function (flag_info, index) {
_this.map_info.flag_list.forEach(function (geo_flag, index) {
flag_info = {};
Object.assign(flag_info, geo_flag);
flag_info.position = _this.mapUtils.convertToLocalCoordinates(
geo_flag.position.x,
geo_flag.position.y,
geo_flag.position.z);
flag_material = new BABYLON.StandardMaterial("flag_mat_" + index, scene);
flag_material.alpha = 1;
flag_material.diffuseColor = BABYLON.Color3.Green();
......@@ -769,7 +572,7 @@ var MapManager = /** @class */ (function () {
flag_a.material = flag_material;
flag_a.position = new BABYLON.Vector3(
flag_info.position.x + 1,
FLAG_SIZE.z + 1, //swap
flag_info.position.z + FLAG_SIZE.z + 1, //swap
flag_info.position.y - 1
);
flag_a.rotation = new BABYLON.Vector3(0, 1, 0);
......@@ -779,7 +582,7 @@ var MapManager = /** @class */ (function () {
flag_b.material = flag_material;
flag_b.position = new BABYLON.Vector3(
flag_info.position.x - 1,
FLAG_SIZE.z + 1, //swap
flag_info.position.z + FLAG_SIZE.z + 1, //swap
flag_info.position.y + 1
);
flag_b.rotation = new BABYLON.Vector3(0, 4, 0);
......@@ -787,7 +590,7 @@ var MapManager = /** @class */ (function () {
{ 'size': 1 }, scene);
mast.position = new BABYLON.Vector3(
flag_info.position.x,
FLAG_SIZE.z / 2, //swap
flag_info.position.z + FLAG_SIZE.z / 2, //swap
flag_info.position.y
);
mast.scaling = new BABYLON.Vector3(
......@@ -1341,7 +1144,7 @@ var GameManager = /** @class */ (function () {
_this._mapManager = new MapManager(ctx._scene, GAMEPARAMETERS.map);
ctx._spawnDrones(_this._mapManager.getMapInfo().initial_position,
GAMEPARAMETERS.drone.list, TEAM_USER, ctx);
ctx._spawnDrones(null, _this._mapManager.getMapInfo().enemy_list, TEAM_ENEMY, ctx);
ctx._spawnDrones(null, _this._mapManager._enemy_list, TEAM_ENEMY, ctx);
// Hide the drone prefab
DroneManager.Prefab.isVisible = false;
//Hack to make advanced texture work
......@@ -1434,6 +1237,8 @@ var GameManager = /** @class */ (function () {
.push(function () {
_this._canUpdate = true;
return _this.finish_deferred.promise;
}, function (error) {
throw new Error('Error on drone initialization msg -' + error.message);
});
};
......@@ -1604,12 +1409,6 @@ var runGame, updateGame;
}
};
/*// Resize canvas on window resize
window.addEventListener('resize', function () {
game_manager_instance._engine.resize();
});*/
}(this));
/******************************************************************************/
\ No newline at end of file
......@@ -246,7 +246,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1010.55458.31149.16742</string> </value>
<value> <string>1011.9946.55286.52770</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -266,7 +266,7 @@
</tuple>
<state>
<tuple>
<float>1693590015.02</float>
<float>1694792090.7</float>
<string>UTC</string>
</tuple>
</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;
}());
/******************************************************************************/
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Script" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_page_drone_capture_map_utils.js</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>drone_capture_flag_map_utils_js</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Drone Capture Map Utils</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>document_publication_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>edit_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>processing_status_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAU=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <unicode>zope</unicode> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1694185956.38</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>published_alive</string> </value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <unicode>zope</unicode> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1011.8487.12042.61832</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1694782467.44</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="5" aka="AAAAAAAAAAU=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <unicode>zope</unicode> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>external_processing_state</string> </key>
<value> <string>empty</string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="_reconstructor" module="copy_reg"/>
</klass>
<tuple>
<global name="DateTime" module="DateTime.DateTime"/>
<global name="object" module="__builtin__"/>
<none/>
</tuple>
<state>
<tuple>
<float>1694185881.0</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -29,6 +29,10 @@
margin: 0;
}
.documentation div {
vertical-align: middle !important;
}
.documentation .line {
width: 100%;
height: 1px;
......@@ -54,7 +58,6 @@
.item-name span:last-of-type {
font-style: italic; }
.line {
width: 100%;
height: 1px;
......
......@@ -242,7 +242,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1010.15231.63877.58538</string> </value>
<value> <string>1011.7107.64372.12151</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -262,7 +262,7 @@
</tuple>
<state>
<tuple>
<float>1691176394.73</float>
<float>1694621148.77</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -12,39 +12,47 @@
<h1>Game specifications</h1>
<h3>Start message with information about the flags</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>
<!-- Map JSON -->
<h3>Map parameter dictionary (JSON)</h3>
<div>
<h5 class="item-param-1">Flag info dictionary:</h5>
<h5 class="item-param-1">Map dictionary entries</h5>
<p class="item-descr"></p>
</div>
<div>
<h5 class="item-param-1">Param</h5>
<h5 class="item-param-1">Key</h5>
<h5 class="item-param-2">Description</h5>
</div>
<div>
<p class="item-param-1">id: Integer</p>
<p class="item-param-2">Flag id</p>
<p class="item-param-1">min_lat, max_lat, min_lon, max_lon: Float</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>
<p class="item-param-1">score: Integer</p>
<p class="item-param-2">Flag score for hit</p>
<p class="item-param-1">width, depth: Integer</p>
<p class="item-param-2">Map width and depth in meters (calculated from coordinates)</p>
</div>
<div>
<p class="item-param-1">weight: Integer</p>
<p class="item-param-2">Flag weight, number of hits to be captured</p>
<p class="item-param-1">height: Integer</p>
<p class="item-param-2">Map height in meters</p>
</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">
{<br>
&nbsp;&nbsp;x: number, //latitude (in degrees)<br>
......@@ -54,6 +62,72 @@
</p>
</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>
<h3>Game scoring</h3>
......
......@@ -244,7 +244,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1010.51249.55554.52258</string> </value>
<value> <string>1011.7129.2983.29201</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -264,7 +264,7 @@
</tuple>
<state>
<tuple>
<float>1693418554.45</float>
<float>1694622336.88</float>
<string>UTC</string>
</tuple>
</state>
......
.import-export {
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 {
div[data-gadget-url$="gadget_erp5_page_drone_capture_flag_script_page.html"] .item-label {
background-color: #F8F8F8;
padding: 8px;
margin: 8px 0;
......@@ -21,6 +6,18 @@
font-weight: bold;
}
.run-game {
margin-top: 0 !important;
div[data-gadget-url$="gadget_erp5_page_drone_capture_flag_script_page.html"] button {
color: #212529;
padding: 3pt;
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 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1010.62540.7060.5000</string> </value>
<value> <string>1010.65016.4453.48725</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -262,7 +262,7 @@
</tuple>
<state>
<tuple>
<float>1694015251.98</float>
<float>1694183879.39</float>
<string>UTC</string>
</tuple>
</state>
......
<!DOCTYPE html>
<html>
<!--
data-i18n=Others
data-i18n=Tools
-->
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
......@@ -19,52 +15,16 @@
<script src="jiodev.js" type="text/javascript"></script>
<script src="gadget_global.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/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>
</head>
<body>
<form>
<!-- parameters input form -->
<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>
<div class="captureflagpageheader"></div>
<div class="captureflagpagebody"></div>
<a data-i18n="Storages"></a> <!-- for zelenium test common macro -->
</form>
</body>
</html>
\ No newline at end of file
......@@ -244,7 +244,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1010.62539.25351.39680</string> </value>
<value> <string>1010.65526.46361.27613</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -264,7 +264,7 @@
</tuple>
<state>
<tuple>
<float>1694014905.34</float>
<float>1694194019.23</float>
<string>UTC</string>
</tuple>
</state>
......
/*jslint indent: 2, maxlen: 100*/
/*global window, rJS, domsugar, document, URLSearchParams, Blob, require, MapUtils*/
(function (window, rJS, domsugar, document, URLSearchParams, Blob, require) {
/*global window, rJS, domsugar, document, Blob, MapUtils, RSVP*/
/******************************* OPERATOR API ********************************/
var OperatorAPI = /** @class */ (function () {
"use strict";
//** CONSTRUCTOR
function OperatorAPI(json_map) {
this.message = "default init message";
this.json_map = json_map;
}
OperatorAPI.prototype.getMapJSON = function () {
return this.json_map;
};
OperatorAPI.prototype.sendMsg = function (msg) {
this.message = msg;
};
OperatorAPI.prototype.getDroneStartMessage = function () {
return this.message;
};
return OperatorAPI;
}());
(function (window, rJS, domsugar, document, Blob, MapUtils, RSVP) {
"use strict";
//Drone default values - TODO: get them from the drone API
var SIMULATION_SPEED = 10,
var SIMULATION_SPEED = 60,
SIMULATION_TIME = 270,
//default square map
MAP_HEIGHT = 700,
......@@ -13,21 +36,34 @@
MAX_LAT = 45.65,
MIN_LON = 14.265,
MAX_LON = 14.2766,
//SEED FORM PARAMETER IS BROKEN (used in randomization before user inputs)
// only way to set it and use it is via url parameter 'seed'
url_sp = new URLSearchParams(window.location.hash),
url_seed = url_sp.get("seed"),
SEED = url_seed ? url_seed : '6!',
//seed
//url_sp = new URLSearchParams(window.location.hash),
//url_seed = url_sp.get("seed"),
SEED = '6!',//url_seed ? url_seed : '6!',
MAP = {
"height": MAP_HEIGHT,
"start_AMSL": START_AMSL,
"map_seed": SEED,
"min_lat": MIN_LAT,
"max_lat": MAX_LAT,
"min_lon": MIN_LON,
"max_lon": MAX_LON
"max_lon": MAX_LON,
"flag_list": [{"position":
{"x": 45.6464947316632,
"y": 14.270747186236491,
"z": 10},
"score": 1,
"weight": 1}],
"obstacle_list": [{"type": "box",
"position": {"x": 45.6456815316444,
"y": 14.274667031215898,
"z": 15},
"scale": {"x": 132, "y": 56, "z": 10},
"rotation": {"x": 0, "y": 0, "z": 0}}],
"enemy_list": [{"type": "EnemyDroneAPI",
"position": {"x": 45.6455531,
"y": 14.270231599999988, "z": 15}}],
"initial_position": {"x": 45.642813275, "y": 14.270231599999988, "z": 15}
},
JSON_MAP,
DEFAULT_SPEED = 16,
MAX_ACCELERATION = 6,
MAX_DECELERATION = 1,
......@@ -38,13 +74,16 @@
MAX_PITCH = 25,
MAX_CLIMB_RATE = 8,
MAX_SINK_RATE = 3,
NUMBER_OF_DRONES = 10,
NUMBER_OF_DRONES = 5,
// Non-inputs parameters
EPSILON = 15,
DEFAULT_OPERATOR_SCRIPT = 'var map = operator.getMapJSON();\n' +
'operator.sendMsg({flag_positions: map.flag_list});\n',
DEFAULT_SCRIPT_CONTENT =
'var EPSILON = 15,\n' +
'var EPSILON = ' + EPSILON + ',\n' +
' DODGE_DISTANCE = 100;\n' +
'\n' +
'function distance(a, b) {\n' +
'function distance2D(a, b) {\n' +
' var R = 6371e3, // meters\n' +
' la1 = a.x * Math.PI / 180, // lat, lon in radians\n' +
' la2 = b.x * Math.PI / 180,\n' +
......@@ -56,6 +95,12 @@
' return 2 * R * Math.asin(Math.sqrt(h));\n' +
'}\n' +
'\n' +
'function distance(a, b) {\n' +
' return Math.sqrt(\n' +
' Math.pow(a.z - b.z, 2) + Math.pow(distance2D(a, b), 2)\n' +
' );\n' +
'}\n' +
'\n' +
'me.onStart = function () {\n' +
' me.direction_set = false;\n' +
' me.dodging = false;\n' +
......@@ -141,123 +186,348 @@
DRAW = true,
LOG = true,
LOG_TIME = 1662.7915426540285,
DRONE_LIST = [],
LOGIC_FILE_LIST = [
'gadget_erp5_page_drone_capture_flag_logic.js',
'gadget_erp5_page_drone_capture_map_utils.js',
'gadget_erp5_page_drone_capture_flag_fixedwingdrone.js',
'gadget_erp5_page_drone_capture_flag_enemydrone.js',
'./libraries/seedrandom.min.js'
];
function handleFileSelect(event, gadget, options) {
var reader = new FileReader()
reader.onload = (event) => handleFileLoad(event, gadget, options);
reader.readAsText(event.target.files[0]);
}
function handleFileLoad(event, gadget, options) {
options.operator_script = event.target.result;
return gadget.changeState(options);
}
function downloadFromTextContent(gadget, text_content, title) {
var element = gadget.element,
a = window.document.createElement("a"),
url = window.URL.createObjectURL(new Blob([text_content], {type: 'text/plain'})),
name_list = [title, "js"];
element.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = name_list.join('.');
a.click();
element.removeChild(a);
window.URL.revokeObjectURL(url);
}
//Randomize map before render, so it's available on operator editor
require(['gadget_erp5_page_drone_capture_flag_logic.js'], function () {
JSON_MAP = new MapUtils(MAP).randomize();
});
'gadget_erp5_page_drone_capture_flag_enemydrone.js'
],
DISPLAY_MAP_PARAMETER = 'display_map_parameter',
DISPLAY_RANDOMIZE = 'display_randomize',
DISPLAY_OPERATOR_PARAMETER = 'display_operator_parameter',
DISPLAY_DRONE_PARAMETER = 'display_drone_parameter',
DISPLAY_GAME_PARAMETER = 'display_game_parameter',
DISPLAY_PLAY = "display_play";
rJS(window)
/////////////////////////////////////////////////////////////////
// Acquired methods
/////////////////////////////////////////////////////////////////
.declareAcquiredMethod("updateHeader", "updateHeader")
.declareAcquiredMethod("notifySubmitted", "notifySubmitted")
function renderGadgetHeader(gadget, loading) {
var element_list = [],
game_map_icon = 'ui-icon-map-marker',
game_randomize_icon = 'ui-icon-random',
game_operator_icon = 'ui-icon-rss',
game_drone_icon = 'ui-icon-paper-plane',
game_parameter_icon = 'ui-icon-gears',
game_play_icon = 'ui-icon-play';
.allowPublicAcquisition('notifySubmit', function () {
return this.triggerSubmit();
if (loading) {
if (gadget.state.display_step === DISPLAY_MAP_PARAMETER) {
game_map_icon = 'ui-icon-spinner';
} else if (gadget.state.display_step === DISPLAY_RANDOMIZE) {
game_randomize_icon = 'ui-icon-spinner';
} else if (gadget.state.display_step === DISPLAY_OPERATOR_PARAMETER) {
game_operator_icon = 'ui-icon-spinner';
} else if (gadget.state.display_step === DISPLAY_DRONE_PARAMETER) {
game_drone_icon = 'ui-icon-spinner';
} else if (gadget.state.display_step === DISPLAY_GAME_PARAMETER) {
game_parameter_icon = 'ui-icon-spinner';
} else if (gadget.state.display_step === DISPLAY_PLAY) {
game_play_icon = 'ui-icon-spinner';
} else {
throw new Error("Can't render header state " +
gadget.state.display_step);
}
}
element_list.push(
domsugar('button', {
type: 'button',
text: "Map",
disabled: (gadget.state.display_step === DISPLAY_MAP_PARAMETER),
'class': 'display-map-parameter-btn ui-btn-icon-left ' + game_map_icon
}),
domsugar('button', {
type: 'button',
text: "Randomize",
disabled: (gadget.state.display_step === DISPLAY_RANDOMIZE),
'class': 'display-randomize-btn ui-btn-icon-left ' + game_randomize_icon
}),
domsugar('button', {
type: 'button',
text: "Parameters",
disabled: (gadget.state.display_step === DISPLAY_GAME_PARAMETER),
'class': 'display-game-parameter-btn ui-btn-icon-left ' + game_parameter_icon
}),
domsugar('button', {
type: 'button',
text: "Operator Script",
disabled: (gadget.state.display_step === DISPLAY_OPERATOR_PARAMETER),
'class': 'display-operator-script-btn ui-btn-icon-left ' + game_operator_icon
}),
domsugar('button', {
type: 'button',
text: "Drone Script",
disabled: (gadget.state.display_step === DISPLAY_DRONE_PARAMETER),
'class': 'display-drone-script-btn ui-btn-icon-left ' + game_drone_icon
}),
domsugar('button', {
type: 'button',
text: "Run",
// Always make this button clickable, so that user can run it twice
// disabled: (gadget.state.display_step === DISPLAY_PLAY),
'class': 'display-play-btn ui-btn-icon-left ' + game_play_icon
})
);
.declareMethod("triggerSubmit", function () {
return this.element.querySelector('input[type="submit"]').click();
domsugar(gadget.element.querySelector('div.captureflagpageheader'), element_list);
}
function getContentFromParameterForm(gadget) {
return gadget.getDeclaredGadget('parameter_form')
.push(function (form_gadget) {
return form_gadget.getContent();
})
.push(function (content_dict) {
var key;
for (key in content_dict) {
if (content_dict.hasOwnProperty(key)) {
gadget.state[key] = content_dict[key];
}
}
});
}
.onEvent('submit', function () {
var gadget = this, operator_init_msg, script_content;
return gadget.getDeclaredGadget('operator-editor')
.push(function (operator_editor) {
return operator_editor.getContent();
//////////////////////////////////////////////////
// Map parameters
//////////////////////////////////////////////////
function renderMapParameterView(gadget) {
var form_gadget;
renderGadgetHeader(gadget, true);
return gadget.declareGadget("gadget_erp5_form.html", {
scope: "parameter_form"
})
.push(function (content) {
/*jslint evil: true*/
try {
operator_init_msg = new Function(content.operator_editor)();
} catch (error) {
operator_init_msg = {'error': error};
.push(function (sub_gadget) {
form_gadget = sub_gadget;
return form_gadget.render({
erp5_document: {
"_embedded": {"_view": {
"my_map_json": {
"description": "",
"title": "Map JSON",
"default": gadget.state.map_json,
"css_class": "",
"required": 1,
"editable": 1,
"key": "map_json",
"hidden": 0,
"url": "gadget_editor.html",
"renderjs_extra": JSON.stringify({
"maximize": true,
"language": "en",
"editor": "codemirror"
}),
"type": "GadgetField"
}
/*jslint evil: false*/
if (!operator_init_msg) operator_init_msg = {};
return gadget.getDeclaredGadget('script-editor');
}},
"_links": {
"type": {
name: ""
}
}
},
form_definition: {
group_list: [[
"bottom",
[["my_map_json"]]
]]
}
});
})
.push(function (script_editor) {
return script_editor.getContent();
.push(function () {
renderGadgetHeader(gadget, false);
// Attach the form to the page
domsugar(gadget.element.querySelector('div.captureflagpagebody'), [
form_gadget.element
]);
});
}
//////////////////////////////////////////////////
// Map parameters
//////////////////////////////////////////////////
function renderRandomizeView(gadget) {
var form_gadget;
renderGadgetHeader(gadget, true);
return gadget.declareGadget("gadget_erp5_form.html", {
scope: "parameter_form"
})
.push(function (content) {
script_content = content.script_editor;
return gadget.getDeclaredGadget('form_view');
.push(function (sub_gadget) {
form_gadget = sub_gadget;
return form_gadget.render({
erp5_document: {
"_embedded": {"_view": {
"my_map_seed": {
"description": "Seed value to randomize the map",
"title": "Seed value (ex: " + SEED + ")",
"default": gadget.state.map_seed,
"placeholder": SEED,
"css_class": "",
"required": 0,
"editable": 1,
"key": "map_seed",
"hidden": 0,
"type": "StringField"
}
}},
"_links": {
"type": {
name: ""
}
}
},
form_definition: {
group_list: [[
"center",
[["my_map_seed"]]
]]
}
});
})
.push(function (form_gadget) {
return form_gadget.getContent();
.push(function () {
renderGadgetHeader(gadget, false);
// Attach the form to the page
domsugar(gadget.element.querySelector('div.captureflagpagebody'), [
/*
domsugar('label', {
'class': 'item-label',
text: 'Map'
}),*/
form_gadget.element
]);
});
}
//////////////////////////////////////////////////
// Operator parameters
//////////////////////////////////////////////////
function renderOperatorParameterView(gadget) {
var form_gadget;
renderGadgetHeader(gadget, true);
return gadget.declareGadget("gadget_erp5_form.html", {
scope: "parameter_form"
})
.push(function (input) {
input.operator_init_msg = operator_init_msg;
input.script = script_content;
gadget.runGame(input);
.push(function (sub_gadget) {
form_gadget = sub_gadget;
return form_gadget.render({
erp5_document: {
"_embedded": {"_view": {
"my_operator_script": {
"description": "",
"title": "Operator script",
"default": gadget.state.operator_script,
"css_class": "",
"required": 1,
"editable": 1,
"key": "operator_script",
"hidden": 0,
"url": "gadget_editor.html",
"renderjs_extra": JSON.stringify({
"maximize": true,
"language": "en",
"portal_type": "Web Script",
"editor": "codemirror"
}),
"type": "GadgetField"
}
}},
"_links": {
"type": {
name: ""
}
}
},
form_definition: {
group_list: [[
"bottom",
[["my_operator_script"]]
]]
}
});
})
.push(function () {
renderGadgetHeader(gadget, false);
// Attach the form to the page
domsugar(gadget.element.querySelector('div.captureflagpagebody'), [
form_gadget.element
]);
});
}
.onEvent('click', function (evt) {
var gadget = this;
if (evt.target.id === "import") {
return;
//////////////////////////////////////////////////
// Drone script parameter
//////////////////////////////////////////////////
function renderDroneParameterView(gadget) {
var form_gadget;
renderGadgetHeader(gadget, true);
return gadget.declareGadget("gadget_erp5_form.html", {
scope: "parameter_form"
})
.push(function (sub_gadget) {
form_gadget = sub_gadget;
return form_gadget.render({
erp5_document: {
"_embedded": {"_view": {
"my_drone_script": {
"description": "",
"title": "Drone script",
"default": gadget.state.drone_script,
"css_class": "",
"required": 1,
"editable": 1,
"key": "drone_script",
"hidden": 0,
"url": "gadget_editor.html",
"renderjs_extra": JSON.stringify({
"maximize": true,
"language": "en",
"portal_type": "Web Script",
"editor": "codemirror"
}),
"type": "GadgetField"
}
}},
"_links": {
"type": {
name: ""
}
if (evt.target.id === "export") {
return gadget.getDeclaredGadget('operator-editor')
.push(function (operator_editor) {
return operator_editor.getContent();
}
},
form_definition: {
group_list: [[
"bottom",
[["my_drone_script"]]
]]
}
});
})
.push(function (content) {
downloadFromTextContent(gadget, content.operator_editor, 'operator_script');
.push(function () {
renderGadgetHeader(gadget, false);
// Attach the form to the page
domsugar(gadget.element.querySelector('div.captureflagpagebody'), [
form_gadget.element
]);
});
}
}, false, false)
.declareMethod('render', function render(options) {
var gadget = this,
loadedFile = (event) => handleFileSelect(event, gadget, options);
gadget.element.querySelector('#import').addEventListener("change", loadedFile);
MAP.map_seed = SEED;
return gadget.getDeclaredGadget('form_view')
.push(function (form_gadget) {
//////////////////////////////////////////////////
// Game parameters
//////////////////////////////////////////////////
function renderGameParameterView(gadget) {
var form_gadget;
renderGadgetHeader(gadget, true);
return gadget.declareGadget("gadget_erp5_form.html", {
scope: "parameter_form"
})
.push(function (sub_gadget) {
form_gadget = sub_gadget;
return form_gadget.render({
erp5_document: {
"_embedded": {"_view": {
"my_simulation_speed": {
"description": "",
"title": "Simulation Speed",
"default": SIMULATION_SPEED,
"default": gadget.state.simulation_speed,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -268,7 +538,7 @@
"my_simulation_time": {
"description": "Duration of the simulation (in seconds)",
"title": "Simulation Time",
"default": SIMULATION_TIME,
"default": gadget.state.simulation_time,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -279,7 +549,7 @@
"my_drone_min_speed": {
"description": "",
"title": "Drone min speed",
"default": MIN_SPEED,
"default": gadget.state.drone_min_speed,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -290,7 +560,7 @@
"my_drone_speed": {
"description": "",
"title": "Drone speed",
"default": DEFAULT_SPEED,
"default": gadget.state.drone_speed,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -301,7 +571,7 @@
"my_drone_max_speed": {
"description": "",
"title": "Drone max speed",
"default": MAX_SPEED,
"default": gadget.state.drone_max_speed,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -312,7 +582,7 @@
"my_drone_max_acceleration": {
"description": "",
"title": "Drone max Acceleration",
"default": MAX_ACCELERATION,
"default": gadget.state.drone_max_acceleration,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -323,7 +593,7 @@
"my_drone_max_deceleration": {
"description": "",
"title": "Drone max Deceleration",
"default": MAX_DECELERATION,
"default": gadget.state.drone_max_deceleration,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -334,7 +604,7 @@
"my_drone_max_roll": {
"description": "",
"title": "Drone max roll",
"default": MAX_ROLL,
"default": gadget.state.drone_max_roll,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -345,7 +615,7 @@
"my_drone_min_pitch": {
"description": "",
"title": "Drone min pitch",
"default": MIN_PITCH,
"default": gadget.state.drone_min_pitch,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -356,7 +626,7 @@
"my_drone_max_pitch": {
"description": "",
"title": "Drone max pitch",
"default": MAX_PITCH,
"default": gadget.state.drone_max_pitch,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -367,7 +637,7 @@
"my_drone_max_sink_rate": {
"description": "",
"title": "Drone max sink rate",
"default": MAX_SINK_RATE,
"default": gadget.state.drone_max_sink_rate,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -378,7 +648,7 @@
"my_drone_max_climb_rate": {
"description": "",
"title": "Drone max climb rate",
"default": MAX_CLIMB_RATE,
"default": gadget.state.drone_max_climb_rate,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -386,21 +656,10 @@
"hidden": 0,
"type": "FloatField"
},
"my_map_seed": {
"description": "Seed value to randomize the map",
"title": "Seed value",
"default": url_seed ? url_seed : SEED,
"css_class": "",
"required": 1,
"editable": 1,
"key": "map_seed",
"hidden": 0,
"type": "StringField"
},
"my_number_of_drones": {
"description": "",
"title": "Number of drones",
"default": NUMBER_OF_DRONES,
"default": gadget.state.number_of_drones,
"css_class": "",
"required": 1,
"editable": 1,
......@@ -431,102 +690,112 @@
});
})
.push(function () {
return gadget.getDeclaredGadget("operator-editor");
})
.push(function (operator_editor) {
var operator_map = {}, DEFAULT_OPERATOR_SCRIPT_CONTENT;
Object.assign(operator_map, JSON_MAP);
delete operator_map.flag_list;
delete operator_map.obstacle_list;
delete operator_map.enemy_list;
delete operator_map.geo_obstacle_list;
delete operator_map.flag_distance_epsilon;
DEFAULT_OPERATOR_SCRIPT_CONTENT = 'var json_map = ' +
JSON.stringify(operator_map) + ';\n' +
'\n' +
'return {"flag_positions": json_map.geo_flag_list};\n';
return operator_editor.render({
"editor": "codemirror",
"maximize": true,
"portal_type": "Web Script",
"key": "operator_editor",
"value": DEFAULT_OPERATOR_SCRIPT_CONTENT,
"editable": 1,
"hidden": 0
});
})
.push(function () {
return gadget.getDeclaredGadget("script-editor");
})
.push(function (script_editor) {
return script_editor.render({
"editor": "codemirror",
"maximize": true,
"portal_type": "Web Script",
"key": "script_editor",
"value": DEFAULT_SCRIPT_CONTENT,
"editable": 1,
"hidden": 0
});
})
.push(function () {
return gadget.updateHeader({
page_title: 'Drone Capture Flag',
page_icon: 'puzzle-piece'
});
renderGadgetHeader(gadget, false);
// Attach the form to the page
domsugar(gadget.element.querySelector('div.captureflagpagebody'), [
form_gadget.element
]);
});
}
//////////////////////////////////////////////////
// Play
//////////////////////////////////////////////////
function renderPlayView(gadget) {
renderGadgetHeader(gadget, true);
// XXX Load babylonjs and run game
return gadget.declareGadget("gadget_erp5_form.html", {
scope: "form_view_babylonjs"
})
.push(function (sub_gadget) {
renderGadgetHeader(gadget, false);
// Attach the form to the page
domsugar(gadget.element.querySelector('div.captureflagpagebody'), [
domsugar('div', {
'class': 'simulator_div'
}),
sub_gadget.element
]);
.onStateChange(function (modification_dict) {
if (modification_dict.hasOwnProperty('operator_script')) {
return this.getDeclaredGadget('operator-editor')
.push(function (operator_editor) {
return operator_editor.render({
"editor": "codemirror",
"maximize": true,
"portal_type": "Web Script",
"key": "operator_editor",
"value": modification_dict.operator_script,
"editable": 1,
"hidden": 0
});
var operator_code = "let operator = function(operator){" +
gadget.state.operator_script +
"return operator.getDroneStartMessage();" +
"}; return operator(new OperatorAPI(" + gadget.state.map_json + "));";
/*jslint evil: true*/
try {
gadget.state.operator_init_msg = new Function(operator_code)();
} catch (error) {
return gadget.notifySubmitted({message: "Error in operator script: " +
error.message, status: 'error'});
}
/*jslint evil: false*/
gadget.runGame();
});
}
rJS(window)
/////////////////////////////////////////////////////////////////
// Acquired methods
/////////////////////////////////////////////////////////////////
.declareAcquiredMethod("updateHeader", "updateHeader")
.declareAcquiredMethod("notifySubmitted", "notifySubmitted")
.allowPublicAcquisition('notifySubmit', function () {
return this.triggerSubmit();
})
.declareMethod("triggerSubmit", function () {
return;
})
.declareJob('runGame', function runGame(options) {
var gadget = this, i,
.declareJob('runGame', function runGame(do_nothing) {
if (do_nothing) {
// Cancel the previous job execution
return;
}
var gadget = this,
i,
parsed_map,
fragment = gadget.element.querySelector('.simulator_div'),
game_parameters_json, map_json;
DRONE_LIST = [];
game_parameters_json,
drone_list = [];
fragment = domsugar(gadget.element.querySelector('.simulator_div'),
[domsugar('div')]).firstElementChild;
for (i = 0; i < options.number_of_drones; i += 1) {
DRONE_LIST[i] = {"id": i, "type": "FixedWingDroneAPI",
"script_content": options.script};
for (i = 0; i < gadget.state.number_of_drones; i += 1) {
drone_list[i] = {"id": i, "type": "FixedWingDroneAPI",
"script_content": gadget.state.drone_script};
}
try {
parsed_map = JSON.parse(gadget.state.map_json);
} catch (error) {
return gadget.notifySubmitted({message: "Error: " + error.message,
status: 'error'});
}
game_parameters_json = {
"drone": {
"maxAcceleration": parseInt(options.drone_max_acceleration, 10),
"maxDeceleration": parseInt(options.drone_max_deceleration, 10),
"minSpeed": parseInt(options.drone_min_speed, 10),
"speed": parseFloat(options.drone_speed),
"maxSpeed": parseInt(options.drone_max_speed, 10),
"maxRoll": parseFloat(options.drone_max_roll),
"minPitchAngle": parseFloat(options.drone_min_pitch),
"maxPitchAngle": parseFloat(options.drone_max_pitch),
"maxSinkRate": parseFloat(options.drone_max_sink_rate),
"maxClimbRate": parseFloat(options.drone_max_climb_rate),
"list": DRONE_LIST
"maxAcceleration": parseInt(gadget.state.drone_max_acceleration, 10),
"maxDeceleration": parseInt(gadget.state.drone_max_deceleration, 10),
"minSpeed": parseInt(gadget.state.drone_min_speed, 10),
"speed": parseFloat(gadget.state.drone_speed),
"maxSpeed": parseInt(gadget.state.drone_max_speed, 10),
"maxRoll": parseFloat(gadget.state.drone_max_roll),
"minPitchAngle": parseFloat(gadget.state.drone_min_pitch),
"maxPitchAngle": parseFloat(gadget.state.drone_max_pitch),
"maxSinkRate": parseFloat(gadget.state.drone_max_sink_rate),
"maxClimbRate": parseFloat(gadget.state.drone_max_climb_rate),
"list": drone_list
},
"gameTime": parseInt(options.simulation_time, 10),
"simulation_speed": parseInt(options.simulation_speed, 10),
"gameTime": parseInt(gadget.state.simulation_time, 10),
"simulation_speed": parseInt(gadget.state.simulation_speed, 10),
"latency": {
"information": 0,
"communication": 0
},
"map": JSON_MAP || MAP,
"operator_init_msg": options.operator_init_msg,
"map": parsed_map,
"operator_init_msg": gadget.state.operator_init_msg,
"draw_flight_path": DRAW,
"temp_flight_path": true,
"log_drone_flight": LOG,
......@@ -578,7 +847,7 @@
return form_gadget.getContent();
})
.push(function (result) {
var a, blob, div, key, log, log_content, aux, label;
var a, blob, div, key, log, log_content, label;
i = 0;
div = domsugar('div', { text: result.message });
label = domsugar('label', { text: "Results" });
......@@ -613,7 +882,7 @@
document.querySelector('.container').parentNode.appendChild(div);
document.querySelector('.container').parentNode.appendChild(log);
i += 1;
if (i === DRONE_LIST.length) {
if (i === drone_list.length) {
break;
//Do not show enemy drone logs for now
/*aux = domsugar('div', { text: "Enemy drones logs:" });
......@@ -625,6 +894,206 @@
return gadget.notifySubmitted({message: "Error: " + error.message,
status: 'error'});
});
})
.setState({
operator_script: DEFAULT_OPERATOR_SCRIPT,
drone_script: DEFAULT_SCRIPT_CONTENT,
number_of_drones: NUMBER_OF_DRONES,
drone_max_climb_rate: MAX_CLIMB_RATE,
drone_max_sink_rate: MAX_SINK_RATE,
drone_max_pitch: MAX_PITCH,
drone_min_pitch: MIN_PITCH,
drone_max_roll: MAX_ROLL,
drone_max_deceleration: MAX_DECELERATION,
drone_max_acceleration: MAX_ACCELERATION,
drone_max_speed: MAX_SPEED,
drone_speed: DEFAULT_SPEED,
drone_min_speed: MIN_SPEED,
simulation_time: SIMULATION_TIME,
simulation_speed: SIMULATION_SPEED,
operator_init_msg: {},
// Force user to fill a value, to prevent
// deleting the map by accident
map_seed: null,
map_json: JSON.stringify(MAP, undefined, 4)
})
.declareMethod('render', function render() {
var gadget = this;
return gadget.changeState({
display_step: DISPLAY_PLAY
})
.push(function () {
return gadget.updateHeader({
page_title: 'Drone Capture Flag',
page_icon: 'puzzle-piece'
});
});
})
.onStateChange(function (modification_dict) {
var gadget = this;
if (gadget.state.display_step === DISPLAY_MAP_PARAMETER) {
if (modification_dict.hasOwnProperty('display_step')) {
// do not update the form if it is already displayed
return renderMapParameterView(gadget);
}
}
if (gadget.state.display_step === DISPLAY_RANDOMIZE) {
if (modification_dict.hasOwnProperty('display_step')) {
// do not update the form if it is already displayed
return renderRandomizeView(gadget);
}
}
if (gadget.state.display_step === DISPLAY_GAME_PARAMETER) {
if (modification_dict.hasOwnProperty('display_step')) {
// do not update the form if it is already displayed
return renderGameParameterView(gadget);
}
}
if (gadget.state.display_step === DISPLAY_OPERATOR_PARAMETER) {
if (modification_dict.hasOwnProperty('display_step')) {
// do not update the form if it is already displayed
return renderOperatorParameterView(gadget);
}
}
if (gadget.state.display_step === DISPLAY_DRONE_PARAMETER) {
if (modification_dict.hasOwnProperty('display_step')) {
// do not update the form if it is already displayed
return renderDroneParameterView(gadget);
}
}
if (gadget.state.display_step === DISPLAY_PLAY) {
return renderPlayView(gadget);
}
if (modification_dict.hasOwnProperty('display_step')) {
throw new Error('Unhandled display step: ' + gadget.state.display_step);
}
})
//////////////////////////////////////////////////
// Used when submitting the form
//////////////////////////////////////////////////
.declareMethod('getContent', function () {
var gadget = this,
display_step = gadget.state.display_step,
queue;
if ([DISPLAY_OPERATOR_PARAMETER,
DISPLAY_DRONE_PARAMETER,
DISPLAY_MAP_PARAMETER,
DISPLAY_GAME_PARAMETER].indexOf(gadget.state.display_step) !== -1) {
queue = new RSVP.Queue(getContentFromParameterForm(gadget));
} else if (gadget.state.display_step === DISPLAY_RANDOMIZE) {
// Randomizing function is called, only if user entered a feed
queue = new RSVP.Queue(getContentFromParameterForm(gadget))
.push(function () {
if (gadget.state.map_seed) {
gadget.state.map_json = JSON.stringify(
new MapUtils(MAP).randomize(gadget.state.map_seed),
undefined,
4
);
}
});
} else if (gadget.state.display_step === DISPLAY_PLAY) {
// Cancel the run execution, by triggering the job again
// Out job does nothing if no parameter is passed
gadget.runGame(true);
// Nothing to store in the play view
queue = new RSVP.Queue();
} else {
throw new Error('getContent form not handled: ' + display_step);
}
return queue;
}, {mutex: 'changestate'})
.onEvent("click", function (evt) {
// Only handle click on BUTTON element
var gadget = this,
tag_name = evt.target.tagName,
queue;
if (tag_name !== 'BUTTON') {
return;
}
// Disable any button. It must be managed by this gadget
evt.preventDefault();
// Always get content to ensure the possible displayed form
// is checked and content propagated to the gadget state value
queue = gadget.getContent();
if (evt.target.className.indexOf("display-map-parameter-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_MAP_PARAMETER
});
});
}
if (evt.target.className.indexOf("display-randomize-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_RANDOMIZE
});
});
}
if (evt.target.className.indexOf("display-operator-script-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_OPERATOR_PARAMETER
});
});
}
if (evt.target.className.indexOf("display-drone-script-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_DRONE_PARAMETER
});
});
}
if (evt.target.className.indexOf("display-game-parameter-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_GAME_PARAMETER
});
});
}
if (evt.target.className.indexOf("display-play-btn") !== -1) {
return queue
.push(function () {
return gadget.changeState({
display_step: DISPLAY_PLAY,
force_timestamp: new Date()
});
});
}
throw new Error('Unhandled button: ' + evt.target.textContent);
}, false, false);
}(window, rJS, domsugar, document, URLSearchParams, Blob, require));
\ No newline at end of file
}(window, rJS, domsugar, document, Blob, MapUtils, RSVP));
\ No newline at end of file
......@@ -246,7 +246,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>1010.62683.28686.10205</string> </value>
<value> <string>1011.10069.44691.10922</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -266,7 +266,7 @@
</tuple>
<state>
<tuple>
<float>1694023525.57</float>
<float>1694798760.01</float>
<string>UTC</string>
</tuple>
</state>
......
/** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.3.6 Copyright jQuery Foundation and other contributors.
* Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE
*/
var requirejs,require,define;!function(global,setTimeout){var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.6",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){var i;if(e)for(i=0;i<e.length&&(!e[i]||!t(e[i],i,e));i+=1);}function eachReverse(e,t){var i;if(e)for(i=e.length-1;-1<i&&(!e[i]||!t(e[i],i,e));i-=1);}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(i,e,r,n){return e&&eachProp(e,function(e,t){!r&&hasProp(i,t)||(!n||"object"!=typeof e||!e||isArray(e)||isFunction(e)||e instanceof RegExp?i[t]=e:(i[t]||(i[t]={}),mixin(i[t],e,r,n)))}),i}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttps://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}if(void 0===define){if(void 0!==requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}void 0===require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),(n=getOwn(contexts,a))||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick=void 0!==setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(t){req[t]=function(){var e=contexts[defContextName];return e.require[t].apply(e,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],baseElement=document.getElementsByTagName("base")[0],baseElement&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(t,i,r){var e,n=t&&t.config||{};if(isBrowser)return(e=req.createNode(n,i,r)).setAttribute("data-requirecontext",t.contextName),e.setAttribute("data-requiremodule",i),!e.attachEvent||e.attachEvent.toString&&e.attachEvent.toString().indexOf("[native code")<0||isOpera?(e.addEventListener("load",t.onScriptLoad,!1),e.addEventListener("error",t.onScriptError,!1)):(useInteractive=!0,e.attachEvent("onreadystatechange",t.onScriptLoad)),e.src=r,n.onNodeCreated&&n.onNodeCreated(e,n,i,r),currentlyAddingScript=e,baseElement?head.insertBefore(e,baseElement):head.appendChild(e),currentlyAddingScript=null,e;if(isWebWorker)try{setTimeout(function(){},0),importScripts(r),t.completeLoad(i)}catch(e){t.onError(makeError("importscripts","importScripts failed for "+i+" at "+r,e,[i]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||-1!==mainScript.indexOf("!")||(mainScript=(src=mainScript.split("/")).pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,i,t){var r,n;"string"!=typeof e&&(t=i,i=e,e=null),isArray(i)||(t=i,i=null),!i&&isFunction(t)&&(i=[],t.length&&(t.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,t){i.push(t)}),i=(1===t.length?["require"]:["require","exports","module"]).concat(i))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript())&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")]),n?(n.defQueue.push([e,i,t]),n.defQueueMap[e]=!0):globalDefQueue.push([e,i,t])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}function newContext(u){var i,e,l,c,d,g={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},p={},f={},r={},h=[],m={},n={},v={},x=1,b=1;function q(e,t,i){var r,n,o,a,s,u,c,d,p,f,l=t&&t.split("/"),h=g.map,m=h&&h["*"];if(e&&(u=(e=e.split("/")).length-1,g.nodeIdCompat&&jsSuffixRegExp.test(e[u])&&(e[u]=e[u].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&l&&(e=l.slice(0,l.length-1).concat(e)),function(e){var t,i;for(t=0;t<e.length;t++)if("."===(i=e[t]))e.splice(t,1),t-=1;else if(".."===i){if(0===t||1===t&&".."===e[2]||".."===e[t-1])continue;0<t&&(e.splice(t-1,2),t-=2)}}(e),e=e.join("/")),i&&h&&(l||m)){e:for(o=(n=e.split("/")).length;0<o;o-=1){if(s=n.slice(0,o).join("/"),l)for(a=l.length;0<a;a-=1)if((r=getOwn(h,l.slice(0,a).join("/")))&&(r=getOwn(r,s))){c=r,d=o;break e}!p&&m&&getOwn(m,s)&&(p=getOwn(m,s),f=o)}!c&&p&&(c=p,d=f),c&&(n.splice(0,d,c),e=n.join("/"))}return getOwn(g.pkgs,e)||e}function E(t){isBrowser&&each(scripts(),function(e){if(e.getAttribute("data-requiremodule")===t&&e.getAttribute("data-requirecontext")===l.contextName)return e.parentNode.removeChild(e),!0})}function w(e){var t=getOwn(g.paths,e);if(t&&isArray(t)&&1<t.length)return t.shift(),l.require.undef(e),l.makeRequire(null,{skipMap:!0})([e]),!0}function y(e){var t,i=e?e.indexOf("!"):-1;return-1<i&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function S(e,t,i,r){var n,o,a,s,u=null,c=t?t.name:null,d=e,p=!0,f="";return e||(p=!1,e="_@r"+(x+=1)),u=(s=y(e))[0],e=s[1],u&&(u=q(u,c,r),o=getOwn(m,u)),e&&(u?f=i?e:o&&o.normalize?o.normalize(e,function(e){return q(e,c,r)}):-1===e.indexOf("!")?q(e,c,r):e:(u=(s=y(f=q(e,c,r)))[0],f=s[1],i=!0,n=l.nameToUrl(f))),{prefix:u,name:f,parentMap:t,unnormalized:!!(a=!u||o||i?"":"_unnormalized"+(b+=1)),url:n,originalName:d,isDefine:p,id:(u?u+"!"+f:f)+a}}function k(e){var t=e.id,i=getOwn(p,t);return i||(i=p[t]=new l.Module(e)),i}function M(e,t,i){var r=e.id,n=getOwn(p,r);!hasProp(m,r)||n&&!n.defineEmitComplete?(n=k(e)).error&&"error"===t?i(n.error):n.on(t,i):"defined"===t&&i(m[r])}function O(i,e){var t=i.requireModules,r=!1;e?e(i):(each(t,function(e){var t=getOwn(p,e);t&&(t.error=i,t.events.error&&(r=!0,t.emit("error",i)))}),r||req.onError(i))}function j(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(l.defQueueMap[t]=!0),h.push(e)}),globalDefQueue=[])}function P(e){delete p[e],delete f[e]}function R(){var e,r,t=1e3*g.waitSeconds,n=t&&l.startTime+t<(new Date).getTime(),o=[],a=[],s=!1,u=!0;if(!i){if(i=!0,eachProp(f,function(e){var t=e.map,i=t.id;if(e.enabled&&(t.isDefine||a.push(e),!e.error))if(!e.inited&&n)w(i)?s=r=!0:(o.push(i),E(i));else if(!e.inited&&e.fetched&&t.isDefine&&(s=!0,!t.prefix))return u=!1}),n&&o.length)return(e=makeError("timeout","Load timeout for modules: "+o,null,o)).contextName=l.contextName,O(e);u&&each(a,function(e){!function n(o,a,s){var e=o.map.id;o.error?o.emit("error",o.error):(a[e]=!0,each(o.depMaps,function(e,t){var i=e.id,r=getOwn(p,i);!r||o.depMatched[t]||s[i]||(getOwn(a,i)?(o.defineDep(t,m[i]),o.check()):n(r,a,s))}),s[e]=!0)}(e,{},{})}),n&&!r||!s||!isBrowser&&!isWebWorker||d||(d=setTimeout(function(){d=0,R()},50)),i=!1}}function a(e){hasProp(m,e[0])||k(S(e[0],null,!0)).init(e[1],e[2])}function o(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function s(e){var t=e.currentTarget||e.srcElement;return o(t,l.onScriptLoad,"load","onreadystatechange"),o(t,l.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function T(){var e;for(j();h.length;){if(null===(e=h.shift())[0])return O(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));a(e)}l.defQueueMap={}}return c={require:function(e){return e.require?e.require:e.require=l.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?m[e.map.id]=e.exports:e.exports=m[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(g.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},(e=function(e){this.events=getOwn(r,e.id)||{},this.map=e,this.shim=getOwn(g.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0}).prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,l.startTime=(new Date).getTime();var e=this.map;if(!this.shim)return e.prefix?this.callPlugin():this.load();l.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()}))}},load:function(){var e=this.map.url;n[e]||(n[e]=!0,l.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var t,e,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=l.execCb(i,o,r,n)}catch(e){t=e}else n=l.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&((e=this.module)?n=e.exports:this.usingExports&&(n=this.exports)),t)return t.requireMap=this.map,t.requireModules=this.map.isDefine?[this.map.id]:null,t.requireType=this.map.isDefine?"define":"require",O(this.error=t)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(m[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(l,this.map,a)}P(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(l.defQueueMap,i)||this.fetch()}},callPlugin:function(){var u=this.map,c=u.id,e=S(u.prefix);this.depMaps.push(e),M(e,"defined",bind(this,function(e){var o,t,i,r=getOwn(v,this.map.id),n=this.map.name,a=this.map.parentMap?this.map.parentMap.name:null,s=l.makeRequire(u.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(e.normalize&&(n=e.normalize(n,function(e){return q(e,a,!0)})||""),M(t=S(u.prefix+"!"+n,this.map.parentMap,!0),"defined",bind(this,function(e){this.map.normalizedMap=t,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),void((i=getOwn(p,t.id))&&(this.depMaps.push(t),this.events.error&&i.on("error",bind(this,function(e){this.emit("error",e)})),i.enable()))):r?(this.map.url=l.nameToUrl(r),void this.load()):((o=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})})).error=bind(this,function(e){this.inited=!0,(this.error=e).requireModules=[c],eachProp(p,function(e){0===e.map.id.indexOf(c+"_unnormalized")&&P(e.map.id)}),O(e)}),o.fromText=bind(this,function(e,t){var i=u.name,r=S(i),n=useInteractive;t&&(e=t),n&&(useInteractive=!1),k(r),hasProp(g.config,c)&&(g.config[i]=g.config[c]);try{req.exec(e)}catch(e){return O(makeError("fromtexteval","fromText eval for "+c+" failed: "+e,e,[c]))}n&&(useInteractive=!0),this.depMaps.push(r),l.completeLoad(i),s([i],o)}),void e.load(u.name,s,o,g))})),l.enable(e,this),this.pluginMaps[e.id]=e},enable:function(){(f[this.map.id]=this).enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=S(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(c,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,M(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?M(e,"error",bind(this,this.errback)):this.events.error&&M(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=p[i],hasProp(c,i)||!r||r.enabled||l.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(p,e.id);t&&!t.enabled&&l.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},(l={config:g,contextName:u,registry:p,defined:m,urlFetched:n,defQueue:h,defQueueMap:{},Module:e,makeModuleMap:S,nextTick:req.nextTick,onError:O,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var i=e.urlArgs;e.urlArgs=function(e,t){return(-1===t.indexOf("?")?"?":"&")+i}}var r=g.shim,n={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){n[t]?(g[t]||(g[t]={}),mixin(g[t],e,!0,!0)):g[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(v[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=l.makeShimExports(e)),r[t]=e}),g.shim=r),e.packages&&each(e.packages,function(e){var t;t=(e="string"==typeof e?{name:e}:e).name,e.location&&(g.paths[t]=e.location),g.pkgs[t]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(p,function(e,t){e.inited||e.map.unnormalized||(e.map=S(t,null,!0))}),(e.deps||e.callback)&&l.require(e.deps||[],e.callback)},makeShimExports:function(t){return function(){var e;return t.init&&(e=t.init.apply(global,arguments)),e||t.exports&&getGlobal(t.exports)}},makeRequire:function(o,a){function s(e,t,i){var r,n;return a.enableBuildCallback&&t&&isFunction(t)&&(t.__requireJsBuild=!0),"string"==typeof e?isFunction(t)?O(makeError("requireargs","Invalid require call"),i):o&&hasProp(c,e)?c[e](p[o.id]):req.get?req.get(l,e,o,s):(r=S(e,o,!1,!0).id,hasProp(m,r)?m[r]:O(makeError("notloaded",'Module name "'+r+'" has not been loaded yet for context: '+u+(o?"":". Use require([])")))):(T(),l.nextTick(function(){T(),(n=k(S(null,o))).skipMap=a.skipMap,n.init(e,t,i,{enabled:!0}),R()}),s)}return a=a||{},mixin(s,{isBrowser:isBrowser,toUrl:function(e){var t,i=e.lastIndexOf("."),r=e.split("/")[0];return-1!==i&&(!("."===r||".."===r)||1<i)&&(t=e.substring(i,e.length),e=e.substring(0,i)),l.nameToUrl(q(e,o&&o.id,!0),t,!0)},defined:function(e){return hasProp(m,S(e,o,!1,!0).id)},specified:function(e){return e=S(e,o,!1,!0).id,hasProp(m,e)||hasProp(p,e)}}),o||(s.undef=function(i){j();var e=S(i,o,!0),t=getOwn(p,i);t.undefed=!0,E(i),delete m[i],delete n[e.url],delete r[i],eachReverse(h,function(e,t){e[0]===i&&h.splice(t,1)}),delete l.defQueueMap[i],t&&(t.events.defined&&(r[i]=t.events),P(i))}),s},enable:function(e){getOwn(p,e.id)&&k(e).enable()},completeLoad:function(e){var t,i,r,n=getOwn(g.shim,e)||{},o=n.exports;for(j();h.length;){if(null===(i=h.shift())[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);a(i)}if(l.defQueueMap={},r=getOwn(p,e),!t&&!hasProp(m,e)&&r&&!r.inited){if(!(!g.enforceDefine||o&&getGlobal(o)))return w(e)?void 0:O(makeError("nodefine","No define call for "+e,null,[e]));a([e,n.deps||[],n.exportsFn])}R()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c=getOwn(g.pkgs,e);if(c&&(e=c),u=getOwn(v,e))return l.nameToUrl(u,t,i);if(req.jsExtRegExp.test(e))a=e+(t||"");else{for(r=g.paths,o=(n=e.split("/")).length;0<o;o-=1)if(s=getOwn(r,n.slice(0,o).join("/"))){isArray(s)&&(s=s[0]),n.splice(0,o,s);break}a=n.join("/"),a=("/"===(a+=t||(/^data\:|^blob\:|\?/.test(a)||i?"":".js")).charAt(0)||a.match(/^[\w\+\.\-]+:/)?"":g.baseUrl)+a}return g.urlArgs&&!/^blob\:/.test(a)?a+g.urlArgs(e,a):a},load:function(e,t){req.load(l,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=s(e);l.completeLoad(t.id)}},onScriptError:function(e){var i=s(e);if(!w(i.id)){var r=[];return eachProp(p,function(e,t){0!==t.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===i.id)return r.push(t),!0})}),O(makeError("scripterror",'Script error for "'+i.id+(r.length?'", needed by: '+r.join(", "):'"'),e,[i.id]))}}}).require=l.makeRequire(),l}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState||eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript}}(this,"undefined"==typeof setTimeout?void 0:setTimeout);
\ No newline at end of file
<?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 = [
"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.js",
"gadget_erp5_page_drone_capture_flag_script_page.css",
"gadget_erp5_panel_drone_capture_flag.html",
"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_enemydrone.js",
"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