diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_enemydrone_js.js b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_enemydrone_js.js
new file mode 100644
index 0000000000000000000000000000000000000000..687f780a4bb65219a3f6c1e4a3b15d1f1069a831
--- /dev/null
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_enemydrone_js.js
@@ -0,0 +1,334 @@
+/*global BABYLON, console*/
+/*jslint nomen: true, indent: 2, maxlen: 80, todo: true */
+
+/************************** ENEMY DRONE API ****************************/
+var EnemyDroneAPI = /** @class */ (function () {
+  "use strict";
+
+  var DEFAULT_ACCELERATION = 1,
+    VIEW_SCOPE = 50,
+    DEFAULT_SPEED = 16.5,
+    MIN_SPEED = 12,
+    MAX_SPEED = 26;
+
+  //** CONSTRUCTOR
+  function EnemyDroneAPI(gameManager, drone_info, flight_parameters, id) {
+    this._gameManager = gameManager;
+    this._mapManager = this._gameManager._mapManager;
+    this._map_dict = this._mapManager.getMapInfo();
+    this._flight_parameters = flight_parameters;
+    this._id = id;
+    this._drone_info = drone_info;
+    this._drone_dict_list = [];
+    this._acceleration = DEFAULT_ACCELERATION;
+  }
+  /*
+  ** Function called on start phase of the drone, just before onStart AI script
+  */
+  EnemyDroneAPI.prototype.internal_start = function (drone) {
+    drone._targetCoordinates = drone.getCurrentPosition();
+    drone._maxAcceleration = this.getMaxAcceleration();
+    if (drone._maxAcceleration <= 0) {
+      throw new Error('max acceleration must be superior to 0');
+    }
+    drone._minSpeed = this.getMinSpeed();
+    if (drone._minSpeed <= 0) {
+      throw new Error('min speed must be superior to 0');
+    }
+    drone._maxSpeed = this.getMaxSpeed();
+    if (drone._minSpeed > drone._maxSpeed) {
+      throw new Error('min speed cannot be superior to max speed');
+    }
+    drone._speed = drone._targetSpeed = this.getInitialSpeed();
+    if (drone._speed < drone._minSpeed || drone._speed > drone._maxSpeed) {
+      throw new Error('Drone speed must be between min speed and max speed');
+    }
+    if (drone._maxSinkRate > drone._maxSpeed) {
+      throw new Error('max sink rate cannot be superior to max speed');
+    }
+    drone._maxOrientation = this.getMaxOrientation();
+    return;
+  };
+  /*
+  ** Function called on every drone update, right before onUpdate AI script
+  */
+  EnemyDroneAPI.prototype.internal_update = function (context, delta_time) {
+    context._speed += context._acceleration * delta_time / 1000;
+    if (context._speed > context._maxSpeed)
+      context._speed = context._maxSpeed;
+    if (context._speed < -context._maxSpeed)
+      context._speed = -context._maxSpeed;
+    var updateSpeed = context._speed * delta_time / 1000;
+    if (context._direction.x !== 0 ||
+        context._direction.y !== 0 ||
+        context._direction.z !== 0) {
+      context._controlMesh.position.addInPlace(
+        new BABYLON.Vector3(context._direction.x * updateSpeed,
+                            context._direction.y * updateSpeed,
+                            context._direction.z * updateSpeed));
+    }
+    var orientationValue = context._maxOrientation *
+        (context._speed / context._maxSpeed);
+    context._mesh.rotation = new BABYLON.Vector3(
+      orientationValue * context._direction.z, 0,
+      -orientationValue * context._direction.x);
+    context._controlMesh.computeWorldMatrix(true);
+    context._mesh.computeWorldMatrix(true);
+    return;
+  };
+  /*
+  ** Function called on every drone update, right after onUpdate AI script
+  */
+  EnemyDroneAPI.prototype.internal_post_update = function (drone) {
+    var _this = this, drone_position = drone.getCurrentPosition(), drone_info;
+    if (drone_position) {
+      drone_info = {
+        'altitudeRel' : drone_position.z,
+        'altitudeAbs' : _this._mapManager.getMapInfo().start_AMSL +
+          drone_position.z,
+        'latitude' : drone_position.x,
+        'longitude' : drone_position.y
+      };
+      _this._drone_dict_list[_this._id] = drone_info;
+      //broadcast drone info using internal msg
+      _this._gameManager._droneList.forEach(function (drone) {
+        if (drone.id !== _this._id) {
+          drone.internal_getMsg(drone_info, _this._id);
+        }
+      });
+    }
+  };
+
+  EnemyDroneAPI.prototype.setAltitude = function (drone, altitude) {
+    drone._targetCoordinates.z = altitude;
+  };
+
+  EnemyDroneAPI.prototype.setStartingPosition = function (drone, x, y, z) {
+    if (!drone._canPlay) {
+      if (z <= 0.05) {
+        z = 0.05;
+      }
+      drone._controlMesh.position = new BABYLON.Vector3(x, z, y);
+    }
+    drone._controlMesh.computeWorldMatrix(true);
+    drone._mesh.computeWorldMatrix(true);
+  };
+
+  EnemyDroneAPI.prototype.internal_getMsg = function (msg, id) {
+    this._drone_dict_list[id] = msg;
+  };
+
+  EnemyDroneAPI.prototype.internal_setTargetCoordinates =
+    function (drone, coordinates) {
+      if (!drone._canPlay) return;
+      var x = coordinates.x, y = coordinates.y, z = coordinates.z;
+      if (isNaN(x) || isNaN(y) || isNaN(z)) {
+        throw new Error('Target coordinates must be numbers');
+      }
+      x -= drone._controlMesh.position.x;
+      y -= drone._controlMesh.position.z;
+      z -= drone._controlMesh.position.y;
+      drone.setDirection(x, y, z);
+    };
+  EnemyDroneAPI.prototype.sendMsg = function (msg, to) {
+    var _this = this,
+      droneList = _this._gameManager._droneList;
+    _this._gameManager.delay(function () {
+      if (to < 0) {
+        // Send to all drones
+        droneList.forEach(function (drone) {
+          if (drone.infosMesh) {
+            try {
+              drone.onGetMsg(msg);
+            } catch (error) {
+              console.warn('Drone crashed on sendMsg due to error:', error);
+              drone._internal_crash();
+            }
+          }
+        });
+      } else {
+        // Send to specific drone
+        if (droneList[to].infosMesh) {
+          try {
+            droneList[to].onGetMsg(msg);
+          } catch (error) {
+            console.warn('Drone crashed on sendMsg due to error:', error);
+            droneList[to]._internal_crash();
+          }
+        }
+      }
+    }, _this._flight_parameters.latency.communication);
+  };
+  EnemyDroneAPI.prototype.log = function (msg) {
+    console.log("API say : " + msg);
+  };
+  EnemyDroneAPI.prototype.getGameParameter = function (name) {
+    if (["gameTime", "map"].includes(name)) {
+      return this._gameManager.gameParameter[name];
+    }
+  };
+  /*
+  ** Enemy drone works with cartesian, no geo conversion
+  */
+  EnemyDroneAPI.prototype.processCoordinates = function (x, y, z) {
+    if (isNaN(x) || isNaN(y) || isNaN(z)) {
+      throw new Error('Target coordinates must be numbers');
+    }
+    if (z > this._map_dict.start_AMSL) {
+      z -= this._map_dict.start_AMSL;
+    }
+    return {
+      'x': x,
+      'y': y,
+      'z': z
+    };
+  };
+  EnemyDroneAPI.prototype.getCurrentPosition = function (x, y, z) {
+    return this._mapManager.convertToGeoCoordinates(x, y, z);
+  };
+  EnemyDroneAPI.prototype.getDroneViewInfo = function (drone) {
+    var context = this, result = [], distance,
+      drone_position = drone.position, other_position;
+    function calculateDistance(a, b) {
+      return Math.sqrt(Math.pow((a.x - b.x), 2) + Math.pow((a.y - b.y), 2) +
+                       Math.pow((a.z - b.z), 2));
+    }
+    context._gameManager._droneList_user.forEach(function (other) {
+      if (other.can_play) {
+        other_position = other.position;
+        distance = calculateDistance(drone_position, other_position);
+        if (distance <= VIEW_SCOPE) {
+          result.push({
+            position: other_position,
+            direction: other.direction,
+            rotation: other.rotation,
+            speed: other.speed,
+            target: other._targetCoordinates, //check
+            team: other.team
+          });
+        }
+      }
+    });
+    return result;
+  };
+  EnemyDroneAPI.prototype.getDroneAI = function () {
+    //interception math based on https://www.codeproject.com/Articles/990452/Interception-of-Two-Moving-Objects-in-D-Space
+    return 'var BASE_DISTANCE = 300;\n' +
+      'function calculateInterception(hunter_position, prey_position, hunter_speed, prey_speed, prey_velocity_vector) {\n' +
+      '  var vector_from_drone, distance_to_prey, distance_to_prey_vector, a, b, c, t1, t2, interception_time, interception_point;\n' +
+      '  function dot(a, b) {\n' +
+      '    return a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n);\n' +
+      '  }\n' +
+      '  distance_to_prey_vector = [hunter_position.x - prey_position.x, hunter_position.y - prey_position.y, hunter_position.z - prey_position.z];\n' +
+      '  distance_to_prey = distance(hunter_position, prey_position);\n' +
+      '  a = hunter_speed * hunter_speed - prey_speed * prey_speed;\n' +
+      '  b = 2 * dot(distance_to_prey_vector, prey_velocity_vector);\n' +
+      '  c = - distance_to_prey * distance_to_prey;\n' +
+      '  t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);\n' +
+      '  t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / (2 * a);\n' +
+      '  if (t1 > 0 && t2 > 0) {\n' +
+      '    interception_time = Math.min( t1, t2 );\n' +
+      '  } else {\n' +
+      '    interception_time = Math.max( t1, t2 );\n' +
+      '  }\n' +
+      '  interception_point = [prey_position.x + prey_velocity_vector[0] * interception_time, prey_position.y + prey_velocity_vector[1] * interception_time, prey_position.z + prey_velocity_vector[2] * interception_time];\n' +
+      '  if (isNaN(interception_point[0]) || isNaN(interception_point[1]) || isNaN(interception_point[2])) {\n' +
+      '    return;\n' +
+      '  }\n' +
+      '  return interception_point;\n' +
+      '}\n' +
+      'function distance(a, b) {\n' +
+      '  return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2 + (a.z - b.z) ** 2);\n' +
+      '}\n' +
+      '\n' +
+      'me.onStart = function () {\n' +
+      '  me.base = me.position;\n' +
+      '  me.setDirection(0,0,0);\n' +
+      '  return;\n' +
+      '\n' +
+      '};\n' +
+      '\n' +
+      'me.onUpdate = function (timestamp) {\n' +
+      '  me.current_position = me.position;\n' +
+      '  var drone_position, drone_velocity_vector, interception_point, drone_view,\n' +
+      '  dist = distance(\n' +
+      '    me.current_position,\n' +
+      '    me.base\n' +
+      '  );\n' +
+      // return to base point if drone is too far
+      '  if (dist >= BASE_DISTANCE) {\n' +
+      '    me.chasing = false;\n' +
+      '    me.setTargetCoordinates(\n' +
+      '      me.base.x,\n' +
+      '      me.base.y,\n' +
+      '      me.base.z\n' +
+      '    );\n' +
+      '    return;\n' +
+      '  }\n' +
+      '  drone_view = me.getDroneViewInfo();\n' +
+      '  if (drone_view.length) {\n' +
+      '    drone_position = drone_view[0].position;\n' +
+      '    drone_velocity_vector = [drone_view[0].target.x - drone_position.x, drone_view[0].target.y - drone_position.y, drone_view[0].target.z - drone_position.z];\n' +
+      '    interception_point = calculateInterception(me.current_position, drone_position, me.speed, drone_view[0].speed, drone_velocity_vector);\n' +
+      '    if (!interception_point) {\n' +
+      '      return;\n' +
+      '    }\n' +
+      '    me.chasing = true;\n' +
+      '    me.setTargetCoordinates(interception_point[0], interception_point[1], interception_point[2]);\n' +
+      '  }\n' +
+      // return to base point if drone is too far
+      '  if (!me.chasing && dist <= 10) {\n' +
+      '    me.setDirection(0,0,0);\n' +
+      '  }\n' +
+      '};';
+  };
+  EnemyDroneAPI.prototype.getMinSpeed = function () {
+    return MIN_SPEED;
+    //return this._flight_parameters.drone.minSpeed;
+  };
+  EnemyDroneAPI.prototype.getMaxSpeed = function () {
+    return MAX_SPEED;
+    //return this._flight_parameters.drone.maxSpeed;
+  };
+  EnemyDroneAPI.prototype.getInitialSpeed = function () {
+    return DEFAULT_SPEED;
+    //return this._flight_parameters.drone.speed;
+  };
+  EnemyDroneAPI.prototype.getMaxDeceleration = function () {
+    return this._flight_parameters.drone.maxDeceleration;
+  };
+  EnemyDroneAPI.prototype.getMaxAcceleration = function () {
+    return this._flight_parameters.drone.maxAcceleration;
+  };
+  EnemyDroneAPI.prototype.getMaxOrientation = function () {
+    //TODO should be a game parameter (but how to force value to PI quarters?)
+    return Math.PI / 4;
+  };
+  EnemyDroneAPI.prototype.triggerParachute = function (drone) {
+    var drone_pos = drone.getCurrentPosition();
+    drone.setTargetCoordinates(drone_pos.x, drone_pos.y, 5);
+  };
+  EnemyDroneAPI.prototype.landed = function (drone) {
+    var drone_pos = drone.getCurrentPosition();
+    return Math.floor(drone_pos.z) < 10;
+  };
+  EnemyDroneAPI.prototype.exit = function () {
+    return;
+  };
+  EnemyDroneAPI.prototype.getInitialAltitude = function () {
+    return 0;
+  };
+  EnemyDroneAPI.prototype.getAltitudeAbs = function (altitude) {
+    return altitude;
+  };
+  EnemyDroneAPI.prototype.getMinHeight = function () {
+    return 0;
+  };
+  EnemyDroneAPI.prototype.getMaxHeight = function () {
+    return 800;
+  };
+  EnemyDroneAPI.prototype.getFlightParameters = function () {
+    return this._flight_parameters;
+  };
+  return EnemyDroneAPI;
+}());
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_enemydrone_js.xml b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_enemydrone_js.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1bcd5a1200ecd4903a88cfa29c62589fe6eedc33
--- /dev/null
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_enemydrone_js.xml
@@ -0,0 +1,346 @@
+<?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_flag_enemydrone.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_enemydrone_js</string> </value>
+        </item>
+        <item>
+            <key> <string>language</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Web Script</string> </value>
+        </item>
+        <item>
+            <key> <string>short_title</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>title</string> </key>
+            <value> <string>Enemy Drone (API)</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>1683915732.9</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>1009.37360.49772.3874</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>1688571911.82</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> <string>detect_converted_file</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>external_processing_state</string> </key>
+                      <value> <string>converted</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>1683913977.2</float>
+                              <string>UTC</string>
+                            </tuple>
+                          </state>
+                        </object>
+                      </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.js b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.js
index 8a374c6b762b78b54a6c59a3fd81f8d3198057e0..8739ee605b976fefdef88ddaa4efde13ad72278d 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.js
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.js
@@ -5,12 +5,9 @@
 var FixedWingDroneAPI = /** @class */ (function () {
   "use strict";
 
-  // var TAKEOFF_RADIUS = 60,
   var DEFAULT_SPEED = 16,
     EARTH_GRAVITY = 9.81,
     LOITER_LIMIT = 30,
-    LOITER_RADIUS_FACTOR = 0.60,
-    LOITER_SPEED_FACTOR = 1.5,
     MAX_ACCELERATION = 6,
     MAX_DECELERATION = 1,
     MIN_SPEED = 12,
@@ -19,7 +16,8 @@ var FixedWingDroneAPI = /** @class */ (function () {
     MIN_PITCH = -20,
     MAX_PITCH = 25,
     MAX_CLIMB_RATE = 8,
-    MAX_SINK_RATE = 3;
+    MAX_SINK_RATE = 3,
+    VIEW_SCOPE = 100;
 
   //** CONSTRUCTOR
   function FixedWingDroneAPI(gameManager, drone_info, flight_parameters, id) {
@@ -29,10 +27,8 @@ var FixedWingDroneAPI = /** @class */ (function () {
     this._flight_parameters = flight_parameters;
     this._id = id;
     this._drone_info = drone_info;
-    this._loiter_radius = 0;
-    this._last_loiter_point_reached = -1;
+    this._loiter_radius = 100;
     //this._start_altitude = 0;
-    //this._last_altitude_point_reached = -1;
     this._loiter_mode = false;
     this._drone_dict_list = [];
   }
@@ -40,6 +36,7 @@ var FixedWingDroneAPI = /** @class */ (function () {
   ** Function called on start phase of the drone, just before onStart AI script
   */
   FixedWingDroneAPI.prototype.internal_start = function (drone) {
+    drone._targetCoordinates = drone.getCurrentPosition();
     drone._maxDeceleration = this.getMaxDeceleration();
     if (drone._maxDeceleration <= 0) {
       throw new Error('max deceleration must be superior to 0');
@@ -146,9 +143,6 @@ var FixedWingDroneAPI = /** @class */ (function () {
   */
   FixedWingDroneAPI.prototype.internal_post_update = function (drone) {
     var _this = this, drone_position = drone.getCurrentPosition(), drone_info;
-    if (_this._loiter_mode) {
-      _this.loiter(drone);
-    }
     /*if (_this._start_altitude > 0) { //TODO move start_altitude here
       _this.reachAltitude(drone);
     }*/
@@ -158,7 +152,10 @@ var FixedWingDroneAPI = /** @class */ (function () {
         'altitudeAbs' : _this._mapManager.getMapInfo().start_AMSL +
           drone_position.z,
         'latitude' : drone_position.x,
-        'longitude' : drone_position.y
+        'longitude' : drone_position.y,
+        'yaw': drone.getYaw(),
+        'speed': drone.getAirSpeed(),
+        'climbRate': drone.getClimbRate()
       };
       _this._drone_dict_list[_this._id] = drone_info;
       //broadcast drone info using internal msg
@@ -171,7 +168,7 @@ var FixedWingDroneAPI = /** @class */ (function () {
   };
 
   FixedWingDroneAPI.prototype._updateSpeed = function (drone, delta_time) {
-    var speed = drone.getSpeed(), speedDiff, speedUpdate;
+    var speed = drone.getAirSpeed(), speedDiff, speedUpdate;
     if (speed !== this._targetSpeed) {
       speedDiff = this._targetSpeed - speed;
       speedUpdate = drone._acceleration * delta_time / 1000;
@@ -185,13 +182,30 @@ var FixedWingDroneAPI = /** @class */ (function () {
   };
 
   FixedWingDroneAPI.prototype._updateDirection = function (drone, delta_time) {
-    var horizontalCoeff, newX, newY, newZ;
-    [newX, newZ] = this._getNewYaw(drone, delta_time);
+    var horizontalCoeff, newX, newY, newZ, tangentYaw;
+
+    if (this._loiter_mode && Math.sqrt(
+      Math.pow(drone._targetCoordinates.x - drone.position.x, 2) +
+      Math.pow(drone._targetCoordinates.y - drone.position.y, 2)
+    ) <= this._loiter_radius) {
+      tangentYaw = this._computeBearing(
+        drone.position.x,
+        drone.position.y,
+        drone._targetCoordinates.x,
+        drone._targetCoordinates.y
+      ) - 90;
+      // trigonometric circle is east oriented, yaw angle is clockwise
+      tangentYaw = this._toRad(-tangentYaw + 90);
+      newX = Math.cos(tangentYaw);
+      newZ = Math.sin(tangentYaw);
+    } else {
+      [newX, newZ] = this._getNewYaw(drone, delta_time);
+    }
     newY = this._getNewAltitude(drone);
 
     horizontalCoeff = Math.sqrt(
       (
-        Math.pow(drone.getSpeed(), 2) - Math.pow(newY, 2)
+        Math.pow(drone.getAirSpeed(), 2) - Math.pow(newY, 2)
       ) / (
         Math.pow(newX, 2) + Math.pow(newZ, 2)
       )
@@ -235,14 +249,14 @@ var FixedWingDroneAPI = /** @class */ (function () {
       verticalSpeed = this._computeVerticalSpeed(
         altitudeDiff,
         this.getMaxClimbRate(),
-        drone.getSpeed(),
+        drone.getAirSpeed(),
         this.getMaxPitchAngle()
       );
     } else {
       verticalSpeed = -this._computeVerticalSpeed(
         Math.abs(altitudeDiff),
         this.getMaxSinkRate(),
-        drone.getSpeed(),
+        drone.getAirSpeed(),
         -this.getMinPitchAngle()
       );
     }
@@ -261,18 +275,14 @@ var FixedWingDroneAPI = /** @class */ (function () {
                                                 drone.rotation.z + y);
   };
 
-  FixedWingDroneAPI.prototype.setAltitude = function (drone, altitude) {
-    drone._targetCoordinates.z = altitude;
-  };
-
   FixedWingDroneAPI.prototype.setSpeed = function (drone, speed) {
     this._targetSpeed = Math.max(
       Math.min(speed, this.getMaxSpeed()),
       this.getMinSpeed()
     );
 
-    drone._acceleration = (this._targetSpeed > drone.getSpeed())
-      ? this.getMaxAcceleration() : -this.getMaxDeceleration();
+    drone._acceleration = (this._targetSpeed > drone.getAirSpeed()) ?
+      this.getMaxAcceleration() : -this.getMaxDeceleration();
   };
 
   FixedWingDroneAPI.prototype.setStartingPosition = function (drone, x, y, z) {
@@ -290,34 +300,18 @@ var FixedWingDroneAPI = /** @class */ (function () {
     this._drone_dict_list[id] = msg;
   };
 
-  FixedWingDroneAPI.prototype.set_loiter_mode = function (radius) {
-    this._loiter_mode = true;
-    if (radius && radius > LOITER_LIMIT) {
-      this._loiter_radius = radius * LOITER_RADIUS_FACTOR;
-      this._loiter_center = this._last_target;
-      this._loiter_coordinates = [];
-      this._last_loiter_point_reached = -1;
-      var x1, y1, angle;
-      //for (var angle = 0; angle <360; angle+=8){ //counter-clockwise
-      for (angle = 360; angle > 0; angle -= 8) { //clockwise
-        x1 = this._loiter_radius *
-          Math.cos(this._toRad(angle)) + this._loiter_center.x;
-        y1 = this._loiter_radius *
-          Math.sin(this._toRad(angle)) + this._loiter_center.y;
-        this._loiter_coordinates.push(
-          this.getCurrentPosition(x1, y1, this._loiter_center.z)
-        );
-      }
-    }
-  };
   FixedWingDroneAPI.prototype.internal_setTargetCoordinates =
-    function (drone, coordinates, loiter) {
-      if (!loiter) {
+    function (drone, coordinates, radius) {
+      if (radius) {
+        this._loiter_mode = true;
+        if (radius >= LOITER_LIMIT) {
+          this._loiter_radius = radius;
+        }
+      } else {
         this._loiter_mode = false;
-        //save last target point to use as next loiter center
-        this._last_target = coordinates;
       }
     };
+
   FixedWingDroneAPI.prototype.sendMsg = function (msg, to) {
     var _this = this,
       droneList = _this._gameManager._droneList;
@@ -362,79 +356,59 @@ var FixedWingDroneAPI = /** @class */ (function () {
     if (isNaN(lat) || isNaN(lon) || isNaN(z)) {
       throw new Error('Target coordinates must be numbers');
     }
-    var x = this._mapManager.longitudToX(lon, this._map_dict.width),
-      y = this._mapManager.latitudeToY(lat, this._map_dict.depth),
-      position = this._mapManager.normalize(x, y, this._map_dict),
-      processed_coordinates;
+    var point = this._mapManager.toLocalCoordinates(lat, lon,
+                                                    this._map_dict.map_size),
+      position = this._mapManager.normalize(point.x, point.y, this._map_dict);
     if (z > this._map_dict.start_AMSL) {
       z -= this._map_dict.start_AMSL;
     }
-    processed_coordinates = {
+    return {
       x: position[0],
       y: position[1],
       z: z
     };
-    //this._last_altitude_point_reached = -1;
-    //this.takeoff_path = [];
-    return processed_coordinates;
   };
   FixedWingDroneAPI.prototype.getCurrentPosition = function (x, y, z) {
     return this._mapManager.convertToGeoCoordinates(x, y, z, this._map_dict);
   };
-  FixedWingDroneAPI.prototype.loiter = function (drone) {
-    if (this._loiter_radius > LOITER_LIMIT) {
-      var drone_pos = drone.getCurrentPosition(),
-        min = 9999,
-        min_i,
-        i,
-        d,
-        next_point;
-      //shift loiter circle to nearest point
-      if (this._last_loiter_point_reached === -1) {
-        if (!this.shifted) {
-          drone._maxSpeed = drone._maxSpeed * LOITER_SPEED_FACTOR;
-          for (i = 0; i < this._loiter_coordinates.length; i += 1) {
-            d = this._mapManager.latLonDistance([drone_pos.x, drone_pos.y],
-                                                [this._loiter_coordinates[i].x,
-                                                this._loiter_coordinates[i].y]);
-            if (d < min) {
-              min = d;
-              min_i = i;
-            }
-          }
-          this._loiter_coordinates = this._loiter_coordinates.concat(
-            this._loiter_coordinates.splice(0, min_i)
-          );
-          this.shifted = true;
+  FixedWingDroneAPI.prototype.getDroneViewInfo = function (drone) {
+    var context = this, result = { "obstacles": [], "drones": [] }, distance,
+      other_position, drone_position = drone.getCurrentPosition();
+    function calculateDistance(a, b, _this) {
+      return _this._mapManager.latLonDistance([a.x, a.y], [b.x, b.y]);
+    }
+    context._gameManager._droneList.forEach(function (other) {
+      if (other.can_play && drone.id != other.id) {
+        other_position = other.getCurrentPosition();
+        distance = calculateDistance(drone_position, other_position, context);
+        if (distance <= VIEW_SCOPE) {
+          result.drones.push({
+            position: drone.getCurrentPosition(),
+            direction: drone.direction,
+            rotation: drone.rotation,
+            speed: drone.speed,
+            team: drone.team
+          });
         }
-      } else {
-        this.shifted = false;
       }
-      //stop
-      if (this._last_loiter_point_reached ===
-          this._loiter_coordinates.length - 1) {
-        if (drone._maxSpeed !== this.getMaxSpeed()) {
-          drone._maxSpeed = this.getMaxSpeed();
-        }
-        drone.setDirection(0, 0, 0);
-        return;
+    });
+    context._map_dict.geo_obstacle_list.forEach(function (obstacle) {
+      distance = calculateDistance(drone_position, obstacle.position, context);
+      if (distance <= VIEW_SCOPE) {
+        result.obstacles.push(obstacle);
       }
-      //loiter
-      next_point =
-        this._loiter_coordinates[this._last_loiter_point_reached + 1];
-      this.internal_setTargetCoordinates(drone, next_point, true);
-      if (this._mapManager.latLonDistance([drone_pos.x, drone_pos.y],
-                                          [next_point.x, next_point.y]) < 1) {
-        this._last_loiter_point_reached += 1;
-        if (this._last_loiter_point_reached ===
-            this._loiter_coordinates.length - 1) {
-          return;
+    });
+    if (drone.__is_getting_drone_view !== true) {
+      drone.__is_getting_drone_view = true;
+      context._gameManager.delay(function () {
+        drone.__is_getting_drone_view = false;
+        try {
+          drone.onDroneViewInfo(result);
+        } catch (error) {
+          console.warn('Drone crashed on drone view due to error:', error);
+          drone._internal_crash();
         }
-        next_point = this._loiter_coordinates[
-          this._last_loiter_point_reached + 1
-        ];
-        this.internal_setTargetCoordinates(drone, next_point, true);
-      }
+      }, 1000);
     }
   };
   FixedWingDroneAPI.prototype.getDroneAI = function () {
@@ -475,9 +449,9 @@ var FixedWingDroneAPI = /** @class */ (function () {
     return Math.PI / 4;
   };
   FixedWingDroneAPI.prototype.getYawVelocity = function (drone) {
-    return 360 * EARTH_GRAVITY
-      * Math.tan(this._toRad(this.getMaxRollAngle()))
-      / (2 * Math.PI * drone.getSpeed());
+    return 360 * EARTH_GRAVITY *
+      Math.tan(this._toRad(this.getMaxRollAngle())) /
+      (2 * Math.PI * drone.getAirSpeed());
   };
   FixedWingDroneAPI.prototype.getYaw = function (drone) {
     var direction = drone.worldDirection;
@@ -493,9 +467,10 @@ var FixedWingDroneAPI = /** @class */ (function () {
   };
   FixedWingDroneAPI.prototype._computeVerticalSpeed =
     function (altitude_diff, max_climb_rate, speed, max_pitch) {
-      var maxVerticalSpeed = Math.min(altitude_diff, Math.min(max_climb_rate, speed));
-      return (this._toDeg(Math.asin(maxVerticalSpeed / speed)) > max_pitch)
-        ? speed * Math.sin(this._toRad(max_pitch))
+      var maxVerticalSpeed =
+          Math.min(altitude_diff, Math.min(max_climb_rate, speed));
+      return (this._toDeg(Math.asin(maxVerticalSpeed / speed)) > max_pitch) ?
+        speed * Math.sin(this._toRad(max_pitch))
         : maxVerticalSpeed;
     };
   FixedWingDroneAPI.prototype._toRad = function (angle) {
@@ -505,13 +480,13 @@ var FixedWingDroneAPI = /** @class */ (function () {
     return angle * 180 / Math.PI;
   };
   FixedWingDroneAPI.prototype.getClimbRate = function (drone) {
-    return drone.worldDirection.y * drone.getSpeed();
+    return drone.worldDirection.y * drone.getAirSpeed();
   };
   FixedWingDroneAPI.prototype.getGroundSpeed = function (drone) {
     var direction = drone.worldDirection;
     return Math.sqrt(
-      Math.pow(direction.x * drone.getSpeed(), 2)
-        + Math.pow(direction.z * drone.getSpeed(), 2)
+      Math.pow(direction.x * drone.getAirSpeed(), 2) +
+      Math.pow(direction.z * drone.getAirSpeed(), 2)
     );
   };
   FixedWingDroneAPI.prototype.triggerParachute = function (drone) {
@@ -526,10 +501,10 @@ var FixedWingDroneAPI = /** @class */ (function () {
     return;
   };
   FixedWingDroneAPI.prototype.getInitialAltitude = function () {
-    return 0;
+    return this._map_dict.start_AMSL;
   };
   FixedWingDroneAPI.prototype.getAltitudeAbs = function (altitude) {
-    return altitude;
+    return altitude + this._map_dict.start_AMSL;
   };
   FixedWingDroneAPI.prototype.getMinHeight = function () {
     return 0;
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.xml b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.xml
index 0167003d5d84686f8fe18b96dec902e1fc6d47e9..c7c76f47bb945fc717faec379d8f1f8590d67024 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.xml
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_fixedwingdrone_js.xml
@@ -246,7 +246,7 @@
                   </item>
                   <item>
                       <key> <string>serial</string> </key>
-                      <value> <string>1007.63305.59225.51968</string> </value>
+                      <value> <string>1010.5034.64121.33006</string> </value>
                   </item>
                   <item>
                       <key> <string>state</string> </key>
@@ -266,7 +266,7 @@
                           </tuple>
                           <state>
                             <tuple>
-                              <float>1682437182.61</float>
+                              <float>1690565260.64</float>
                               <string>UTC</string>
                             </tuple>
                           </state>
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.js b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.js
index 221e7d7b5654580d36b5311ec69cf4f40a574457..9b71aee56243361a7a73666c21bcbb93ae58349b 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.js
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.js
@@ -1,15 +1,17 @@
-/*global BABYLON, RSVP, console, FixedWingDroneAPI, document*/
+/*global BABYLON, RSVP, console, FixedWingDroneAPI, EnemyDroneAPI, document*/
 /*jslint nomen: true, indent: 2, maxlen: 80, todo: true,
          unparam: true */
 
-var GAMEPARAMETERS = {};
+var GAMEPARAMETERS = {}, TEAM_USER = "user", TEAM_ENEMY = "enemy";
+//for DEBUG/TEST mode
+var baseLogFunction = console.log, console_log = "";
 
 /******************************* DRONE MANAGER ********************************/
 var DroneManager = /** @class */ (function () {
   "use strict";
 
   //** CONSTRUCTOR
-  function DroneManager(scene, id, API) {
+  function DroneManager(scene, id, API, team) {
     this._mesh = null;
     this._controlMesh = null;
     this._canPlay = false;
@@ -31,8 +33,9 @@ var DroneManager = /** @class */ (function () {
     this._scene = scene;
     this._canUpdate = true;
     this._id = id;
-    this._leader_id = 0;
+    this._team = team;
     this._API = API; // var API created on AI evel
+    this._score = 0;
     // Create the control mesh
     this._controlMesh = BABYLON.Mesh.CreateBox(
       "droneControl_" + id,
@@ -61,11 +64,6 @@ var DroneManager = /** @class */ (function () {
     // swap y and z axis so z axis represents altitude
     return new BABYLON.Vector3(vector.x, vector.z, vector.y);
   };
-  Object.defineProperty(DroneManager.prototype, "leader_id", {
-    get: function () { return this._leader_id; },
-    enumerable: true,
-    configurable: true
-  });
   Object.defineProperty(DroneManager.prototype, "drone_dict", {
     get: function () { return this._API._drone_dict_list; },
     enumerable: true,
@@ -73,6 +71,7 @@ var DroneManager = /** @class */ (function () {
   });
   Object.defineProperty(DroneManager.prototype, "can_play", {
     get: function () { return this._canPlay; },
+    set: function (value) { this._canPlay = value; },
     enumerable: true,
     configurable: true
   });
@@ -81,6 +80,17 @@ var DroneManager = /** @class */ (function () {
     enumerable: true,
     configurable: true
   });
+  Object.defineProperty(DroneManager.prototype, "score", {
+    get: function () { return this._score; },
+    set: function (value) { this._score = value; },
+    enumerable: true,
+    configurable: true
+  });
+  Object.defineProperty(DroneManager.prototype, "team", {
+    get: function () { return this._team; },
+    enumerable: true,
+    configurable: true
+  });
   Object.defineProperty(DroneManager.prototype, "colliderMesh", {
     get: function () { return this._mesh; },
     enumerable: true,
@@ -124,11 +134,10 @@ var DroneManager = /** @class */ (function () {
     enumerable: true,
     configurable: true
   });
-  DroneManager.prototype.internal_start = function (initial_position) {
+  DroneManager.prototype.internal_start = function () {
     this._API.internal_start(this);
     this._canPlay = true;
     this._canCommunicate = true;
-    this._targetCoordinates = initial_position;
     try {
       return this.onStart();
     } catch (error) {
@@ -140,15 +149,30 @@ var DroneManager = /** @class */ (function () {
    * Set a target point to move
    */
   DroneManager.prototype.setTargetCoordinates = function (x, y, z) {
-    if (!this._canPlay) {
-      return;
+    this._internal_setTargetCoordinates(x, y, z);
+  };
+  DroneManager.prototype._internal_setTargetCoordinates =
+    function (x, y, z, radius) {
+      if (!this._canPlay) {
+        return;
+      }
+      //convert real geo-coordinates to virtual x-y coordinates
+      this._targetCoordinates = this._API.processCoordinates(x, y, z);
+      return this._API.internal_setTargetCoordinates(
+        this,
+        this._targetCoordinates,
+        radius
+      );
+    };
+  /**
+   * Returns the list of things a drone "sees"
+   */
+  DroneManager.prototype.getDroneViewInfo = function () {
+    var context = this;
+    if (this._controlMesh) {
+      return context._API.getDroneViewInfo(context);
     }
-    //convert real geo-coordinates to virtual x-y coordinates
-    this._targetCoordinates = this._API.processCoordinates(x, y, z);
-    return this._API.internal_setTargetCoordinates(
-      this,
-      this._targetCoordinates
-    );
+    return;
   };
   DroneManager.prototype.internal_update = function (delta_time) {
     var context = this;
@@ -194,7 +218,7 @@ var DroneManager = /** @class */ (function () {
     }
     return this._API.setStartingPosition(this, x, y, z);
   };
-  DroneManager.prototype.setSpeed = function (speed) {
+  DroneManager.prototype.setAirSpeed = function (speed) {
     if (!this._canPlay) {
       return;
     }
@@ -292,20 +316,11 @@ var DroneManager = /** @class */ (function () {
     }
     return null;
   };
-  DroneManager.prototype.setAltitude = function (altitude) {
-    if (!this._canPlay) {
-      return;
-    }
-    return this._API.setAltitude(this, altitude);
-  };
   /**
    * Make the drone loiter (circle with a set radius)
    */
-  DroneManager.prototype.loiter = function (radius) {
-    if (!this._canPlay) {
-      return;
-    }
-    this._API.set_loiter_mode(radius);
+  DroneManager.prototype.loiter = function (x, y, z, radius) {
+    this._internal_setTargetCoordinates(x, y, z, radius);
   };
   DroneManager.prototype.getFlightParameters = function () {
     if (this._API.getFlightParameters) {
@@ -314,19 +329,31 @@ var DroneManager = /** @class */ (function () {
     return null;
   };
   DroneManager.prototype.getYaw = function () {
-    return this._API.getYaw(this);
+    if (typeof this._API.getYaw !== "undefined") {
+      return this._API.getYaw(this);
+    }
+    return;
   };
-  DroneManager.prototype.getSpeed = function () {
+  DroneManager.prototype.getAirSpeed = function () {
     return this._speed;
   };
   DroneManager.prototype.getGroundSpeed = function () {
-    return this._API.getGroundSpeed(this);
+    if (typeof this._API.getGroundSpeed !== "undefined") {
+      return this._API.getGroundSpeed(this);
+    }
+    return;
   };
   DroneManager.prototype.getClimbRate = function () {
-    return this._API.getClimbRate(this);
+    if (typeof this._API.getClimbRate !== "undefined") {
+      return this._API.getClimbRate(this);
+    }
+    return;
   };
   DroneManager.prototype.getSinkRate = function () {
-    return this._API.getSinkRate();
+    if (typeof this._API.getSinkRate !== "undefined") {
+      return this._API.getSinkRate(this);
+    }
+    return;
   };
   DroneManager.prototype.triggerParachute = function () {
     return this._API.triggerParachute(this);
@@ -364,6 +391,12 @@ var DroneManager = /** @class */ (function () {
    * @param msg The message
    */
   DroneManager.prototype.onGetMsg = function () { return; };
+  /**
+   * Function called when drone finished processing drone view
+   * (as result of getDroneViewInfo call)
+   */
+  DroneManager.prototype.onDroneViewInfo = function (drone_view) { return; };
+
   return DroneManager;
 }());
 
@@ -375,41 +408,70 @@ var DroneManager = /** @class */ (function () {
 
 var MapManager = /** @class */ (function () {
   "use strict";
-  function calculateMapInfo(map, map_dict, initial_position) {
-    var max_width = map.latLonDistance([map_dict.min_lat, map_dict.min_lon],
-                                       [map_dict.min_lat, map_dict.max_lon]),
-      max_height = map.latLonDistance([map_dict.min_lat, map_dict.min_lon],
-                                      [map_dict.max_lat, map_dict.min_lon]),
-      map_size = Math.ceil(Math.max(max_width, max_height)) * 0.6,
-      map_info = {
-        "depth": map_size,
-        "height": map_dict.height,
-        "width": map_size,
-        "map_size": map_size,
-        "min_x": map.longitudToX(map_dict.min_lon, map_size),
-        "min_y": map.latitudeToY(map_dict.min_lat, map_size),
-        "max_x": map.longitudToX(map_dict.max_lon, map_size),
-        "max_y": map.latitudeToY(map_dict.max_lat, map_size),
-        "start_AMSL": map_dict.start_AMSL
-      },
-      position = map.normalize(
-        map.longitudToX(initial_position.longitude, map_size),
-        map.latitudeToY(initial_position.latitude, map_size),
-        map_info
-      );
-    map_info.initial_position = {
-      "x": position[0],
-      "y": position[1],
-      "z": initial_position.z
+  //random geo-point:
+  var MIN_LAT = 45.64,
+      MIN_LON = 14.253,
+      EPSILON = 9.9,
+      START_Z = 15,
+      R = 6371e3;
+  function calculateMapInfo(map, map_dict) {
+    var min_lat = map_dict.min_lat || MIN_LAT,
+      min_lon =  map_dict.min_lon || MIN_LON,
+      offset = map.latLonOffset(min_lat, min_lon, map_dict.map_size),
+      max_lat = offset[0],
+      max_lon = offset[1],
+      starting_point = map_dict.map_size / 2 * -0.75,
+      local_min = map.toLocalCoordinates(min_lat, min_lon, map_dict.map_size),
+      local_max = map.toLocalCoordinates(max_lat, max_lon, map_dict.map_size);
+    map.map_info = {
+      "depth": map_dict.map_size,
+      "width": map_dict.map_size,
+      "map_size": map_dict.map_size,
+      "min_lat": min_lat,
+      "min_lon": min_lon,
+      "max_lat": max_lat,
+      "max_lon": max_lon,
+      "min_x": local_min.x,
+      "min_y": local_min.y,
+      "max_x": local_max.x,
+      "max_y": local_max.y,
+      "height": map_dict.height,
+      "start_AMSL": map_dict.start_AMSL,
+      "flag_list": map_dict.flag_list,
+      "geo_flag_list": [],
+      "flag_distance_epsilon": map_dict.flag_distance_epsilon || EPSILON,
+      "obstacle_list": map_dict.obstacle_list,
+      "geo_obstacle_list": [],
+      "initial_position": {
+        "x": 0,
+        "y": starting_point,
+        "z": START_Z
+      }
     };
-    return map_info;
+    map_dict.flag_list.forEach(function (flag_info, index) {
+      map.map_info.geo_flag_list.push(map.convertToGeoCoordinates(
+        flag_info.position.x,
+        flag_info.position.y,
+        flag_info.position.z
+      ));
+    });
+    map_dict.obstacle_list.forEach(function (obstacle_info, index) {
+      var geo_obstacle = {};
+      Object.assign(geo_obstacle, obstacle_info);
+      geo_obstacle.position = map.convertToGeoCoordinates(
+        obstacle_info.position.x,
+        obstacle_info.position.y,
+        obstacle_info.position.z
+      );
+      map.map_info.geo_obstacle_list.push(geo_obstacle);
+    });
   }
   //** CONSTRUCTOR
   function MapManager(scene) {
-    var _this = this, max_sky, skybox, skyboxMat, largeGroundMat,
-      largeGroundBottom, width, depth, terrain, max;
-    _this.map_info = calculateMapInfo(_this, GAMEPARAMETERS.map,
-                                      GAMEPARAMETERS.initialPosition);
+    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;
+    calculateMapInfo(_this, GAMEPARAMETERS.map);
     max = _this.map_info.width;
     if (_this.map_info.depth > max) {
       max = _this.map_info.depth;
@@ -419,30 +481,29 @@ var MapManager = /** @class */ (function () {
     }
     max = max < _this.map_info.depth ? _this.map_info.depth : max;
     // Skybox
-    max_sky = (max * 10 < 20000) ? max * 10 : 20000;
-    skybox = BABYLON.Mesh.CreateBox("skyBox", max_sky, scene);
-    skybox.infiniteDistance = true;
-    skybox.renderingGroupId = 0;
+    max_sky =  (max * 15 < 20000) ? max * 15 : 20000; //skybox scene limit
+    skybox = BABYLON.MeshBuilder.CreateBox("skyBox", { size: max_sky }, scene);
     skyboxMat = new BABYLON.StandardMaterial("skybox", scene);
     skyboxMat.backFaceCulling = false;
     skyboxMat.disableLighting = true;
+    skybox.material = skyboxMat;
+    skybox.infiniteDistance = true;
+    skyboxMat.disableLighting = true;
     skyboxMat.reflectionTexture = new BABYLON.CubeTexture("./assets/skybox/sky",
                                                           scene);
     skyboxMat.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
-    skyboxMat.infiniteDistance = true;
-    skybox.material = skyboxMat;
+    skybox.renderingGroupId = 0;
     // Plane from bottom
     largeGroundMat = new BABYLON.StandardMaterial("largeGroundMat", scene);
     largeGroundMat.specularColor = BABYLON.Color3.Black();
     largeGroundMat.alpha = 0.4;
     largeGroundBottom = BABYLON.Mesh.CreatePlane("largeGroundBottom",
-                                                     max * 11, scene);
+                                                 max * 11, scene);
     largeGroundBottom.position.y = -0.01;
     largeGroundBottom.rotation.x = -Math.PI / 2;
     largeGroundBottom.rotation.y = Math.PI;
     largeGroundBottom.material = largeGroundMat;
-    // Camera
-    scene.activeCamera.upperRadiusLimit = max * 4;
+    largeGroundBottom.renderingGroupId = 1;
     // Terrain
     // Give map some margin from the flight limits
     width = _this.map_info.width * 1.10;
@@ -453,19 +514,122 @@ var MapManager = /** @class */ (function () {
     terrain.position = BABYLON.Vector3.Zero();
     terrain.scaling = new BABYLON.Vector3(depth / 50000, depth / 50000,
                                           width / 50000);
+    // Obstacles
+    _this._obstacle_list = [];
+    _this.map_info.obstacle_list.forEach(function (obstacle) {
+      switch (obstacle.type) {
+      case "box":
+        new_obstacle = BABYLON.MeshBuilder.CreateBox("obs_" + count,
+                                                     { 'size': 1 }, scene);
+        break;
+      case "cylinder":
+        new_obstacle = BABYLON.MeshBuilder.CreateCylinder("obs_" + count, {
+          'diameterBottom': obstacle.diameterBottom,
+          'diameterTop': obstacle.diameterTop,
+          'height': 1
+        }, scene);
+        break;
+      case "sphere":
+        new_obstacle = BABYLON.MeshBuilder.CreateSphere("obs_" + count, {
+          'diameterX': obstacle.scale.x,
+          'diameterY': obstacle.scale.z,
+          'diameterZ': obstacle.scale.y
+        }, scene);
+        break;
+      default:
+        return;
+      }
+      new_obstacle.type = obstacle.type;
+      var convertion = Math.PI / 180;
+      if ("position" in obstacle)
+        new_obstacle.position = new BABYLON.Vector3(obstacle.position.x,
+                                                    obstacle.position.z,
+                                                    obstacle.position.y);
+      if ("rotation" in obstacle)
+        new_obstacle.rotation =
+          new BABYLON.Vector3(obstacle.rotation.x * convertion,
+                              obstacle.rotation.z * convertion,
+                              obstacle.rotation.y * convertion);
+      if ("scale" in obstacle)
+        new_obstacle.scaling = new BABYLON.Vector3(obstacle.scale.x,
+                                                   obstacle.scale.z,
+                                                   obstacle.scale.y);
+      var obs_material = new BABYLON.StandardMaterial("obsmat_" + count, scene);
+      obs_material.alpha = 1;
+      obs_material.diffuseColor = new BABYLON.Color3(255, 153, 0);
+      new_obstacle.material = obs_material;
+      _this._obstacle_list.push(new_obstacle);
+      count++;
+    });
+    // Flags
+    _this._flag_list = [];
+    var FLAG_SIZE = {
+      'x': 1,
+      'y': 1,
+      'z': 6
+    };
+    _this.map_info.flag_list.forEach(function (flag_info, index) {
+      flag_material = new BABYLON.StandardMaterial("flag_mat_" + index, scene);
+      flag_material.alpha = 1;
+      flag_material.diffuseColor = BABYLON.Color3.Green();
+      flag_a = BABYLON.MeshBuilder.CreateDisc("flag_a_" + index,
+                                              {radius: 7, tessellation: 3},
+                                              scene);
+      flag_a.material = flag_material;
+      flag_a.position = new BABYLON.Vector3(
+        flag_info.position.x + 1,
+        FLAG_SIZE.z + 1, //swap
+        flag_info.position.y - 1
+      );
+      flag_a.rotation = new BABYLON.Vector3(0, 1, 0);
+      flag_b = BABYLON.MeshBuilder.CreateDisc("flag_b_" + index,
+                                              {radius: 3, tessellation: 3},
+                                              scene);
+      flag_b.material = flag_material;
+      flag_b.position = new BABYLON.Vector3(
+        flag_info.position.x - 1,
+        FLAG_SIZE.z + 1, //swap
+        flag_info.position.y + 1
+      );
+      flag_b.rotation = new BABYLON.Vector3(0, 4, 0);
+      mast = BABYLON.MeshBuilder.CreateBox("mast_" + index,
+                                           { 'size': 1 }, scene);
+      mast.position = new BABYLON.Vector3(
+        flag_info.position.x,
+        FLAG_SIZE.z / 2, //swap
+        flag_info.position.y
+      );
+      mast.scaling = new BABYLON.Vector3(
+        FLAG_SIZE.x,
+        FLAG_SIZE.z, //swap
+        FLAG_SIZE.y);
+      mast.material = flag_material;
+      flag = BABYLON.Mesh.MergeMeshes([flag_a, flag_b, mast]);
+      flag.id = index;
+      //flag.weight = _this.map_info.flag_weight;
+      flag.location = flag_info.position;
+      flag.drone_collider_list = [];
+      _this._flag_list.push(flag);
+    });
   }
   MapManager.prototype.getMapInfo = function () {
     return this.map_info;
   };
-  MapManager.prototype.longitudToX = function (lon, map_size) {
-    return (map_size / 360.0) * (180 + lon);
+  MapManager.prototype.latLonOffset = function (lat, lon, offset_in_mt) {
+    var R = 6371e3, //Earth radius
+      lat_offset = offset_in_mt / R,
+      lon_offset = offset_in_mt / (R * Math.cos(Math.PI * lat / 180));
+    return [lat + lat_offset * 180 / Math.PI,
+            lon + lon_offset * 180 / Math.PI];
   };
-  MapManager.prototype.latitudeToY = function (lat, map_size) {
-    return (map_size / 180.0) * (90 - lat);
+  MapManager.prototype.toLocalCoordinates = function (lat, lon, map_size) {
+    return {
+      "x": (map_size / 360.0) * (180 + lon),
+      "y": (map_size / 180.0) * (90 - lat)
+    };
   };
   MapManager.prototype.latLonDistance = function (c1, c2) {
-    var R = 6371e3,
-      q1 = c1[0] * Math.PI / 180,
+    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,
@@ -481,8 +645,9 @@ var MapManager = /** @class */ (function () {
     return [n_x * 1000 - map_dict.width / 2,
             n_y * 1000 - map_dict.depth / 2];
   };
-  MapManager.prototype.convertToGeoCoordinates = function (x, y, z, map_dict) {
-    var lon = x + map_dict.width / 2,
+  MapManager.prototype.convertToGeoCoordinates = function (x, y, z) {
+    var map_dict = this.map_info,
+      lon = x + map_dict.width / 2,
       lat = y + map_dict.depth / 2;
     lon = lon / 1000;
     lon = lon * (map_dict.max_x - map_dict.min_x) +
@@ -511,11 +676,15 @@ var GameManager = /** @class */ (function () {
   "use strict";
   // *** CONSTRUCTOR ***
   function GameManager(canvas, game_parameters_json) {
-    var drone, header_list;
+    var drone, header_list, drone_count;
     this._canvas = canvas;
+    this._canvas_width = canvas.width;
+    this._canvas_height = canvas.height;
     this._scene = null;
     this._engine = null;
     this._droneList = [];
+    this._droneList_user = [];
+    this._droneList_enemy = [];
     this._canUpdate = false;
     this._max_step_animation_frame = game_parameters_json.simulation_speed;
     if (!this._max_step_animation_frame) { this._max_step_animation_frame = 5; }
@@ -524,39 +693,55 @@ var GameManager = /** @class */ (function () {
     this._map_swapped = false;
     this._log_count = [];
     this._flight_log = [];
-    if (GAMEPARAMETERS.draw_flight_path) {
-      this._last_position_drawn = [];
-      this._trace_objects_per_drone = [];
+    this._result_message = "";
+    if (GAMEPARAMETERS.log_drone_flight) {
       // ! Be aware that the following functions relies on this log format:
       // - getLogEntries at Drone Simulator Log Page
-      // - getLogEntries at Dron Log Follower API
+      // - getLogEntries at Drone Log Follower API
       header_list = ["timestamp (ms)", "latitude (°)", "longitude (°)",
                      "AMSL (m)", "rel altitude (m)", "yaw (°)",
                      "ground speed (m/s)", "climb rate (m/s)"];
-      for (drone = 0; drone < GAMEPARAMETERS.droneList.length; drone += 1) {
+      drone_count = GAMEPARAMETERS.map.drones.user.length +
+        GAMEPARAMETERS.map.drones.enemy.length;
+      for (drone = 0; drone < drone_count; drone += 1) {
         this._flight_log[drone] = [];
         this._flight_log[drone].push(header_list);
         this._log_count[drone] = 0;
-        this._last_position_drawn[drone] = null;
-        this._trace_objects_per_drone[drone] = [];
       }
-      this._colors = [
-        new BABYLON.Color3(255, 165, 0),
-        new BABYLON.Color3(0, 0, 255),
-        new BABYLON.Color3(255, 0, 0),
-        new BABYLON.Color3(0, 255, 0),
-        new BABYLON.Color3(0, 128, 128),
-        new BABYLON.Color3(0, 0, 0),
-        new BABYLON.Color3(255, 255, 255),
-        new BABYLON.Color3(128, 128, 0),
-        new BABYLON.Color3(128, 0, 128),
-        new BABYLON.Color3(0, 0, 128)
-      ];
+      if (GAMEPARAMETERS.draw_flight_path) {
+        this._last_position_drawn = [];
+        this._trace_objects_per_drone = [];
+        for (drone = 0; drone < drone_count; drone += 1) {
+          this._last_position_drawn[drone] = null;
+          this._trace_objects_per_drone[drone] = [];
+        }
+        this._colors = [
+          new BABYLON.Color3(255, 165, 0),
+          new BABYLON.Color3(0, 0, 255),
+          new BABYLON.Color3(255, 0, 0),
+          new BABYLON.Color3(0, 255, 0),
+          new BABYLON.Color3(0, 128, 128),
+          new BABYLON.Color3(0, 0, 0),
+          new BABYLON.Color3(255, 255, 255),
+          new BABYLON.Color3(128, 128, 0),
+          new BABYLON.Color3(128, 0, 128),
+          new BABYLON.Color3(0, 0, 128)
+        ];
+      }
     }
     this.APIs_dict = {
-      FixedWingDroneAPI: FixedWingDroneAPI/*,
-      DroneLogAPI: DroneLogAPI*/
+      FixedWingDroneAPI: FixedWingDroneAPI,
+      EnemyDroneAPI: EnemyDroneAPI
     };
+    if (this._game_parameters_json.debug_test_mode) {
+      console.log = function () {
+        baseLogFunction.apply(console, arguments);
+        var args = Array.prototype.slice.call(arguments);
+        for (var i = 0;i < args.length;i++) {
+          console_log += args[i] + "\n";
+        }
+      };
+    }
   }
 
   Object.defineProperty(GameManager.prototype, "gameParameter", {
@@ -571,11 +756,15 @@ var GameManager = /** @class */ (function () {
     var gadget = this;
     return gadget._init()
       .push(function () {
-        return gadget._flight_log;
+        return {
+          'message': gadget._result_message,
+          'content': gadget._flight_log,
+          'console_log': console_log
+        };
       });
   };
 
-  GameManager.prototype.update = function () {
+  GameManager.prototype.update = function (fullscreen) {
     // time delta means that drone are updated every virtual second
     // This is fixed and must not be modified
     // otherwise, it will lead to different scenario results
@@ -587,10 +776,8 @@ var GameManager = /** @class */ (function () {
     function triggerUpdateIfPossible() {
       if ((_this._canUpdate) && (_this.ongoing_update_promise === null) &&
           (0 < _this.waiting_update_count)) {
-        _this.ongoing_update_promise = _this._update(
-          TIME_DELTA,
-          (_this.waiting_update_count === 1)
-        ).push(function () {
+        _this.ongoing_update_promise = _this._update(TIME_DELTA, fullscreen)
+          .push(function () {
           _this.waiting_update_count -= 1;
           _this.ongoing_update_promise = null;
           triggerUpdateIfPossible();
@@ -617,16 +804,98 @@ var GameManager = /** @class */ (function () {
     this._flight_log[drone._id].push(error.stack);
   };
 
-  GameManager.prototype._checkDroneRules = function (drone) {
-    //TODO move this to API methods.
-    //each type of drone should define its rules
-    if (drone.getCurrentPosition()) {
-      return drone.getCurrentPosition().z > 1;
+  GameManager.prototype._checkDroneOut = function (drone) {
+    if (drone.position) {
+      var map_limit = this._mapManager.getMapInfo().map_size / 2;
+      return (drone.position.z > this._mapManager.getMapInfo().height) ||
+        (drone.position.x < -map_limit) ||
+        (drone.position.x > map_limit) ||
+        (drone.position.y < -map_limit) ||
+        (drone.position.y > map_limit);
     }
-    return false;
   };
 
-  GameManager.prototype._update = function (delta_time) {
+  GameManager.prototype._checkObstacleCollision = function (drone, obstacle) {
+    var closest = void 0, projected = BABYLON.Vector3.Zero();
+    if (drone.colliderMesh &&
+      drone.colliderMesh.intersectsMesh(obstacle, true)) {
+      drone._internal_crash(new Error('Drone ' + drone.id +
+                                      ' touched an obstacle.'));
+      //Following workaround seems not needed with new babylonjs versions
+      /**
+       * Closest facet check is needed for sphere and cylinder,
+       * but just seemed bugged with the box
+       * So only need to check intersectMesh for the box
+       */
+      /*if (obstacle.type == "box") {
+        closest = true;
+      } else {
+        obstacle.updateFacetData();
+        closest = obstacle.getClosestFacetAtCoordinates(
+          drone.infosMesh.position.x,
+          drone.infosMesh.position.y,
+          drone.infosMesh.position.z, projected);
+      }
+      if (closest !== null) {
+        drone._internal_crash(new Error('Drone ' + drone.id +
+                                        ' touched an obstacle.'));
+      }*/
+    }
+  };
+
+  GameManager.prototype._checkFlagCollision = function (drone, flag) {
+    if (drone.team == TEAM_ENEMY) return;
+    function distance(a, b) {
+      return Math.sqrt(Math.pow((a.x - b.x), 2) + Math.pow((a.y - b.y), 2) +
+                       Math.pow((a.z - b.z), 2));
+    }
+    if (drone.position) {
+      //TODO epsilon distance is 15 because of fixed wing loiter flights
+      //there is not a proper collision
+      if (distance(drone.position, flag.location) <=
+        this._mapManager.getMapInfo().flag_distance_epsilon) {
+        if (!flag.drone_collider_list.includes(drone.id)) {
+          //TODO notify the drone somehow? Or the AI script is in charge?
+          //console.log("flag " + flag.id + " hit by drone " + drone.id);
+          drone._internal_crash(new Error('Drone ' + drone.id +
+                                          ' touched a flag.'));
+          if (flag.drone_collider_list.length === 0) {
+            drone.score++;
+            flag.drone_collider_list.push(drone.id);
+          }
+        }
+      }
+    }
+  };
+
+  GameManager.prototype._checkCollision = function (drone, other) {
+    if (drone.colliderMesh && other.colliderMesh &&
+        drone.colliderMesh.intersectsMesh(other.colliderMesh, false)) {
+      var angle = Math.acos(BABYLON.Vector3.Dot(drone.worldDirection,
+                                                other.worldDirection) /
+                            (drone.worldDirection.length() *
+                             other.worldDirection.length()));
+      //TODO is this parameter set? keep it or make 2 drones die when intersect?
+      if (angle < GAMEPARAMETERS.drone.collisionSector) {
+        if (drone.speed > other.speed) {
+          other._internal_crash(new Error('Drone ' + drone.id +
+                                ' bump drone ' + other.id + '.'));
+        }
+        else {
+          drone._internal_crash(new Error('Drone ' + other.id +
+                               ' bumped drone ' + drone.id + '.'));
+        }
+      }
+      else {
+        drone._internal_crash(new Error('Drone ' + drone.id +
+                             ' touched drone ' + other.id + '.'));
+        other._internal_crash(new Error('Drone ' + drone.id +
+                             ' touched drone ' + other.id + '.'));
+      }
+    }
+  };
+
+  GameManager.prototype._update = function (delta_time, fullscreen) {
     var _this = this,
       queue = new RSVP.Queue(),
       i;
@@ -642,14 +911,48 @@ var GameManager = /** @class */ (function () {
       }
     }
 
+    if (fullscreen) {
+      //Only resize if size changes
+      if (this._canvas.width !== GAMEPARAMETERS.fullscreen.width) {
+        this._canvas.width = GAMEPARAMETERS.fullscreen.width;
+        this._canvas.height = GAMEPARAMETERS.fullscreen.height;
+      }
+    } else {
+      if (this._canvas.width !== this._canvas_width) {
+        this._canvas.width = this._canvas_width;
+        this._canvas.height = this._canvas_height;
+        this._engine.resize(true);
+      }
+    }
+
     this._droneList.forEach(function (drone) {
       queue.push(function () {
+        var msg = '';
         drone._tick += 1;
-        if (_this._checkDroneRules(drone)) {
-          return drone.internal_update(delta_time);
+        if (drone.can_play) {
+          if (drone.getCurrentPosition().z <= 0) {
+            drone._internal_crash(new Error('Drone ' + drone.id +
+                                            ' touched the floor.'));
+          }
+          else if (_this._checkDroneOut(drone)) {
+            drone._internal_crash(new Error('Drone ' + drone.id +
+                                            ' out of limits.'));
+          }
+          else {
+            _this._droneList.forEach(function (other) {
+              if (other.can_play && drone.id != other.id) {
+                _this._checkCollision(drone, other);
+              }
+            });
+            _this._mapManager._obstacle_list.forEach(function (obstacle) {
+              _this._checkObstacleCollision(drone, obstacle);
+            });
+            _this._mapManager._flag_list.forEach(function (flag) {
+              _this._checkFlagCollision(drone, flag);
+            });
+          }
         }
-        //TODO error must be defined by the api?
-        drone._internal_crash('Drone touched the floor');
+        return drone.internal_update(delta_time);
       });
     });
 
@@ -657,10 +960,17 @@ var GameManager = /** @class */ (function () {
       .push(function () {
         if (_this._timeOut()) {
           console.log("TIMEOUT!");
+          _this._result_message += "TIMEOUT!";
           return _this._finish();
         }
         if (_this._allDronesFinished()) {
-          console.log("ALL DRONES EXITED");
+          console.log("ALL DRONES DOWN");
+          _this._result_message += "ALL DRONES DOWN!";
+          return _this._finish();
+        }
+        if (_this._allFlagsCaptured()) {
+          console.log("ALL FLAGS CAPTURED");
+          _this._result_message += "ALL FLAGS CAPTURED!";
           return _this._finish();
         }
       });
@@ -674,7 +984,7 @@ var GameManager = /** @class */ (function () {
         seconds = Math.floor(this._game_duration / 1000), trace_objects;
 
       if (GAMEPARAMETERS.log_drone_flight || GAMEPARAMETERS.draw_flight_path) {
-        this._droneList.forEach(function (drone, index) {
+        this._droneList_user.forEach(function (drone, index) {
           if (drone.can_play) {
             drone_position = drone.position;
             if (GAMEPARAMETERS.log_drone_flight) {
@@ -687,8 +997,7 @@ var GameManager = /** @class */ (function () {
                 geo_coordinates = map_manager.convertToGeoCoordinates(
                   drone_position.x,
                   drone_position.y,
-                  drone_position.z,
-                  map_info
+                  drone_position.z
                 );
                 game_manager._flight_log[index].push([
                   game_manager._game_duration, geo_coordinates.x,
@@ -711,12 +1020,14 @@ var GameManager = /** @class */ (function () {
                 position_obj.position = new BABYLON.Vector3(drone_position.x,
                                                             drone_position.z,
                                                             drone_position.y);
+                //TODO base it on map_size
                 position_obj.scaling = new BABYLON.Vector3(4, 4, 4);
                 material = new BABYLON.StandardMaterial(game_manager._scene);
                 material.alpha = 1;
                 color = new BABYLON.Color3(255, 0, 0);
-                if (game_manager._colors[index]) {
-                  color = game_manager._colors[index];
+                var color_index = index % 10;
+                if (game_manager._colors[color_index]) {
+                  color = game_manager._colors[color_index];
                 }
                 material.diffuseColor = color;
                 position_obj.material = material;
@@ -742,7 +1053,7 @@ var GameManager = /** @class */ (function () {
 
   GameManager.prototype._allDronesFinished = function () {
     var finish = true;
-    this._droneList.forEach(function (drone) {
+    this._droneList_user.forEach(function (drone) {
       if (drone.can_play) {
         finish = false;
       }
@@ -750,8 +1061,31 @@ var GameManager = /** @class */ (function () {
     return finish;
   };
 
+  GameManager.prototype._allFlagsCaptured = function () {
+    var finish = true;
+    this._mapManager._flag_list.forEach(function (flag) {
+      //do not use flag weight for now, just 1 hit is enough
+      if (flag.drone_collider_list.length === 0) {
+      //if (flag.drone_collider_list.length < flag.weight) {
+        finish = false;
+      }
+    });
+    return finish;
+  };
+
+  GameManager.prototype._calculateUserScore = function () {
+    var score = 0;
+    this._droneList_user.forEach(function (drone) {
+      //if (drone.can_play) {
+      score += drone.score;
+      //}
+    });
+    return score;
+  };
+
   GameManager.prototype._finish = function () {
     console.log("Simulation finished");
+    this._result_message += " User score: " + this._calculateUserScore();
     this._canUpdate = false;
     return this.finish_deferred.resolve();
   };
@@ -763,7 +1097,8 @@ var GameManager = /** @class */ (function () {
   };
 
   GameManager.prototype._init = function () {
-    var _this = this, canvas, hemi_north, hemi_south, camera, on3DmodelsReady;
+    var _this = this,
+        canvas, hemi_north, hemi_south, camera, cam_radius, on3DmodelsReady;
     canvas = this._canvas;
     this._delayed_defer_list = [];
     this._dispose();
@@ -775,6 +1110,9 @@ var GameManager = /** @class */ (function () {
       audioEngine: false
     });
     this._scene = new BABYLON.Scene(this._engine);
+    //for DEBUG - fondo negro
+    //this._scene.clearColor = BABYLON.Color3.Black();
+    //deep ground color - light blue simile sky
     this._scene.clearColor = new BABYLON.Color4(
       88 / 255,
       171 / 255,
@@ -797,13 +1135,18 @@ var GameManager = /** @class */ (function () {
       this._scene
     );
     hemi_south.intensity = 0.75;
-    camera = new BABYLON.ArcRotateCamera("camera", 0, 1.25, 800,
+    cam_radius = (GAMEPARAMETERS.map.map_size * 1.10 < 6000) ?
+      GAMEPARAMETERS.map.map_size * 1.10 : 6000; //skybox scene limit
+    camera = new BABYLON.ArcRotateCamera("camera", 0, 1.25, cam_radius,
                                          BABYLON.Vector3.Zero(), this._scene);
     camera.wheelPrecision = 10;
+    //zoom out limit
+    camera.upperRadiusLimit = GAMEPARAMETERS.map.map_size * 10;
+    //scene.activeCamera.upperRadiusLimit = max * 4;
     //changed for event handling
     //camera.attachControl(this._scene.getEngine().getRenderingCanvas()); //orig
     camera.attachControl(canvas, true);
-    camera.maxz = 40000;
+    camera.maxz = 400000;
     this._camera = camera;
 
     // Render loop
@@ -819,8 +1162,9 @@ var GameManager = /** @class */ (function () {
       }
       // Init the map
       _this._mapManager = new MapManager(ctx._scene);
-      ctx._spawnDrones(_this._mapManager.map_info.initial_position,
-                       GAMEPARAMETERS.droneList, ctx);
+      ctx._spawnDrones(_this._mapManager.getMapInfo().initial_position,
+                       GAMEPARAMETERS.map.drones.user, TEAM_USER, ctx);
+      ctx._spawnDrones(null, GAMEPARAMETERS.map.drones.enemy, TEAM_ENEMY, ctx);
       // Hide the drone prefab
       DroneManager.Prefab.isVisible = false;
       //Hack to make advanced texture work
@@ -833,23 +1177,37 @@ var GameManager = /** @class */ (function () {
         ctx._scene
       );
       document = documentTmp;
-      for (count = 0; count < GAMEPARAMETERS.droneList.length; count += 1) {
-        controlMesh = ctx._droneList[count].infosMesh;
-        rect = new BABYLON.GUI.Rectangle();
-        rect.width = "10px";
-        rect.height = "10px";
-        rect.cornerRadius = 20;
-        rect.color = "white";
-        rect.thickness = 0.5;
-        rect.background = "grey";
-        advancedTexture.addControl(rect);
-        label = new BABYLON.GUI.TextBlock();
-        label.text = count.toString();
-        label.fontSize = 7;
-        rect.addControl(label);
-        rect.linkWithMesh(controlMesh);
-        rect.linkOffsetY = 0;
+      function colourDrones(drone_list, colour) {
+        for (count = 0; count < drone_list.length; count += 1) {
+          controlMesh = drone_list[count].infosMesh;
+          rect = new BABYLON.GUI.Rectangle();
+          rect.width = "10px";
+          rect.height = "10px";
+          rect.cornerRadius = 20;
+          rect.color = "white";
+          rect.thickness = 0.5;
+          rect.background = colour;
+          advancedTexture.addControl(rect);
+          rect.linkWithMesh(controlMesh);
+        }
       }
+      function colourFlags(flag_list) {
+        for (count = 0; count < flag_list.length; count += 1) {
+          controlMesh = flag_list[count].subMeshes[0]._mesh;
+          rect = new BABYLON.GUI.Rectangle();
+          rect.width = "15px";
+          rect.height = "10px";
+          rect.cornerRadius = 1;
+          rect.color = "white";
+          rect.thickness = 0.5;
+          rect.background = "green";
+          advancedTexture.addControl(rect);
+          rect.linkWithMesh(controlMesh);
+        }
+      }
+      colourFlags(_this._mapManager._flag_list);
+      colourDrones(ctx._droneList_user, "blue");
+      colourDrones(ctx._droneList_enemy, "red");
       console.log("on3DmodelsReady - advaced textures added");
       return ctx;
     };
@@ -862,6 +1220,8 @@ var GameManager = /** @class */ (function () {
       })
       .push(function () {
         on3DmodelsReady(_this);
+        _this._droneList =
+          _this._droneList_user.concat(_this._droneList_enemy);
         var result = new RSVP.Queue();
         result.push(function () {
           return RSVP.delay(1000);
@@ -871,7 +1231,7 @@ var GameManager = /** @class */ (function () {
   };
 
   GameManager.prototype._start = function () {
-    var _this = this, promise_list;
+    var _this = this, promise_list, start_msg;
     _this.waiting_update_count = 0;
     _this.ongoing_update_promise = null;
     _this.finish_deferred = RSVP.defer();
@@ -882,11 +1242,17 @@ var GameManager = /** @class */ (function () {
     return new RSVP.Queue()
       .push(function () {
         promise_list = [];
-        _this._droneList.forEach(function (drone) {
+        _this._droneList_user.forEach(function (drone) {
+          drone._tick = 0;
+          promise_list.push(drone.internal_start());
+        });
+        start_msg = {
+          'flag_positions': _this._mapManager.getMapInfo().geo_flag_list
+        };
+        promise_list.push(_this._droneList_user[0].sendMsg(start_msg));
+        _this._droneList_enemy.forEach(function (drone) {
           drone._tick = 0;
-          promise_list.push(drone.internal_start(
-            _this._mapManager.getMapInfo().initial_position
-          ));
+          promise_list.push(drone.internal_start());
         });
         return RSVP.all(promise_list);
       })
@@ -941,9 +1307,10 @@ var GameManager = /** @class */ (function () {
     return parameter;
   };
 
-  GameManager.prototype._spawnDrones = function (center, drone_list, ctx) {
+  GameManager.prototype._spawnDrones = function (init_position, drone_list,
+                                                 team, ctx, drone_location) {
     var position, i, position_list = [], max_collision = 10 * drone_list.length,
-      collision_nb = 0, api;
+      collision_nb = 0, api, center;
     function checkCollision(position, list) {
       var el;
       for (el = 0; el < list.length; el += 1) {
@@ -953,7 +1320,7 @@ var GameManager = /** @class */ (function () {
       }
       return false;
     }
-    function spawnDrone(x, y, z, index, drone_info, api) {
+    function spawnDrone(x, y, z, index, drone_info, api, team) {
       var default_drone_AI = api.getDroneAI(), code, base, code_eval;
       if (default_drone_AI) {
         code = default_drone_AI;
@@ -961,9 +1328,9 @@ var GameManager = /** @class */ (function () {
         code = drone_info.script_content;
       }
       code_eval = "let drone = new DroneManager(ctx._scene, " +
-          index + ', api);' +
+          index + ', api, team);' +
           "let droneMe = function(NativeDate, me, Math, window, DroneManager," +
-          " GameManager, FixedWingDroneAPI, BABYLON, " +
+          " GameManager, FixedWingDroneAPI, EnemyDroneAPI, BABYLON, " +
           "GAMEPARAMETERS) {" +
           "var start_time = (new Date(2070, 0, 0, 0, 0, 0, 0)).getTime();" +
           "Date.now = function () {" +
@@ -980,8 +1347,8 @@ var GameManager = /** @class */ (function () {
       }
       base = code_eval;
       code_eval += code + "}; droneMe(Date, drone, Math, {});";
-      base += "};ctx._droneList.push(drone)";
-      code_eval += "ctx._droneList.push(drone)";
+      base += "};ctx._droneList_" + team + ".push(drone)";
+      code_eval += "ctx._droneList_" + team + ".push(drone)";
       /*jslint evil: true*/
       try {
         eval(code_eval);
@@ -1001,6 +1368,11 @@ var GameManager = /** @class */ (function () {
       return new BABYLON.Vector3(x, y, z);
     }
     for (i = 0; i < drone_list.length; i += 1) {
+      if (!init_position) {
+        center = drone_list[i].position;
+      } else {
+        center = init_position;
+      }
       position = randomSpherePoint(center.x + i, center.y + i, center.z + i,
                                    0, 0, 0);
       if (checkCollision(position, position_list)) {
@@ -1010,14 +1382,18 @@ var GameManager = /** @class */ (function () {
         }
       } else {
         position_list.push(position);
+        var id_offset = 0;
+        if (team == TEAM_ENEMY) {
+          id_offset = GAMEPARAMETERS.map.drones.user.length;
+        }
         api = new this.APIs_dict[drone_list[i].type](
           this,
           drone_list[i],
           GAMEPARAMETERS,
-          i
+          i + id_offset
         );
-        spawnDrone(position.x, position.y, position.z, i,
-                   drone_list[i], api);
+        spawnDrone(position.x, position.y, position.z, i + id_offset,
+                   drone_list[i], api, team);
       }
     }
   };
@@ -1043,15 +1419,15 @@ var runGame, updateGame;
     return game_manager_instance.run();
   };
 
-  updateGame = function () {
+  updateGame = function (fullscreen) {
     if (game_manager_instance) {
-      return game_manager_instance.update();
+      return game_manager_instance.update(fullscreen);
     }
   };
 
   /*// Resize canvas on window resize
   window.addEventListener('resize', function () {
-    engine.resize();
+    game_manager_instance._engine.resize();
   });*/
 
 
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.xml b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.xml
index 16a01b32e042c25599decf8db9d8dc087e4c8f9f..8badaa2df1a903c1ae7a6d8d7f813de647ca4a56 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.xml
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/drone_capture_flag_logic_js.xml
@@ -246,7 +246,7 @@
                   </item>
                   <item>
                       <key> <string>serial</string> </key>
-                      <value> <string>1007.63308.6371.35532</string> </value>
+                      <value> <string>1010.5086.1058.34440</string> </value>
                   </item>
                   <item>
                       <key> <string>state</string> </key>
@@ -266,7 +266,7 @@
                           </tuple>
                           <state>
                             <tuple>
-                              <float>1682437964.37</float>
+                              <float>1690567567.2</float>
                               <string>UTC</string>
                             </tuple>
                           </state>
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.html b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.html
index a1735dc02102b321a11ef46a567a257989d8d857..261062f13a37f80653d1053a86197014fe175498 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.html
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.html
@@ -18,6 +18,7 @@
     <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 src="gadget_erp5_page_drone_capture_flag_script_page.js" type="text/javascript"></script>
 
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.xml b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.xml
index f9fb06b19adeb4f66264f4c621c9f1959e7041b8..3b4c055041ba0b1f2367c0c1437fced367a1b0f4 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.xml
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_html.xml
@@ -244,7 +244,7 @@
                   </item>
                   <item>
                       <key> <string>serial</string> </key>
-                      <value> <string>1007.63297.49586.20838</string> </value>
+                      <value> <string>1008.22253.2281.60074</string> </value>
                   </item>
                   <item>
                       <key> <string>state</string> </key>
@@ -264,7 +264,7 @@
                           </tuple>
                           <state>
                             <tuple>
-                              <float>1682436612.47</float>
+                              <float>1683820259.6</float>
                               <string>UTC</string>
                             </tuple>
                           </state>
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.js b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.js
index 3d7298c3546088dc136211f0be4e8e9ddc5a638c..a09bf696a788125a308cf6a9d4d0d3c80ed03fd5 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.js
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.js
@@ -1,16 +1,13 @@
 /*jslint indent: 2, maxlen: 100*/
-/*global window, rJS, domsugar, document, Blob*/
-(function (window, rJS, domsugar, document, Blob) {
+/*global window, rJS, domsugar, document, URLSearchParams, Blob*/
+(function (window, rJS, domsugar, document, URLSearchParams, Blob) {
   "use strict";
 
-  //Default values - TODO: get them from the drone API
-  var SIMULATION_SPEED = 200,
-    SIMULATION_TIME = 1500,
-    min_lat = 45.6364,
-    max_lat = 45.65,
-    min_lon = 14.2521,
-    max_lon = 14.2766,
-    map_height = 100,
+  //Drone default values - TODO: get them from the drone API
+  var SIMULATION_SPEED = 10,
+    SIMULATION_TIME = 270,
+    MAP_SIZE = 600,
+    map_height = 700,
     start_AMSL = 595,
     DEFAULT_SPEED = 16,
     MAX_ACCELERATION = 6,
@@ -22,65 +19,20 @@
     MAX_PITCH = 25,
     MAX_CLIMB_RATE = 8,
     MAX_SINK_RATE = 3,
-    INITIAL_POSITION = {
-      "latitude": 45.6412,
-      "longitude": 14.2658,
-      "z": 15
-    },
-    NUMBER_OF_DRONES = 2,
+    NUMBER_OF_DRONES = 10,
+    FLAG_WEIGHT = 5,
+    SEED = '6',
     // Non-inputs parameters
     DEFAULT_SCRIPT_CONTENT =
-      'var ALTITUDE = 100,\n' +
-      '  EPSILON = 9,\n' +
-      '  CHECKPOINT_LIST = [\n' +
-      '    {\n' +
-      '      altitude: 585.1806861589965,\n' +
-      '      latitude: 45.64492790560583,\n' +
-      '      longitude: 14.25334942966329\n' +
-      '    },\n' +
-      '    {\n' +
-      '      altitude: 589.8802607573035,\n' +
-      '      latitude: 45.64316335436476,\n' +
-      '      longitude: 14.26332880184475\n' +
-      '    },\n' +
-      '    {\n' +
-      '      altitude: 608.6648153348965,\n' +
-      '      latitude: 45.64911917196595,\n' +
-      '      longitude: 14.26214792790128\n' +
-      '    },\n' +
-      '    {\n' +
-      '      altitude: 606.1448368129072,\n' +
-      '      latitude: 45.64122685351364,\n' +
-      '      longitude: 14.26590493128597\n' +
-      '    },\n' +
-      '    {\n' +
-      '      altitude: 630.0829598206344,\n' +
-      '      latitude: 45.64543355564817,\n' +
-      '      longitude: 14.27242391207985\n' +
-      '    },\n' +
-      '    {\n' +
-      '      altitude: 616.1839898415284,\n' +
-      '      latitude: 45.6372792927328,\n' +
-      '      longitude: 14.27533492411138\n' +
-      '    },\n' +
-      '    {\n' +
-      '      altitude: 598.0603137354178,\n' +
-      '      latitude: 45.64061299543953,\n' +
-      '      longitude: 14.26161958465814\n' +
-      '    },\n' +
-      '    {\n' +
-      '      altitude: 607.1243119862851,\n' +
-      '      latitude: 45.64032340702919,\n' +
-      '      longitude: 14.2682896662383\n' +
-      '    }\n' +
-      '  ];\n' +
+      'var EPSILON = 15,\n' +
+      '  DODGE_DISTANCE = 100;\n' +
       '\n' +
-      'function distance(lat1, lon1, lat2, lon2) {\n' +
+      'function distance(a, b) {\n' +
       '  var R = 6371e3, // meters\n' +
-      '    la1 = lat1 * Math.PI / 180, // lat, lon in radians\n' +
-      '    la2 = lat2 * Math.PI / 180,\n' +
-      '    lo1 = lon1 * Math.PI / 180,\n' +
-      '    lo2 = lon2 * Math.PI / 180,\n' +
+      '    la1 = a.x * Math.PI / 180, // lat, lon in radians\n' +
+      '    la2 = b.x * Math.PI / 180,\n' +
+      '    lo1 = a.y * Math.PI / 180,\n' +
+      '    lo2 = b.y * Math.PI / 180,\n' +
       '    haversine_phi = Math.pow(Math.sin((la2 - la1) / 2), 2),\n' +
       '    sin_lon = Math.sin((lo2 - lo1) / 2),\n' +
       '    h = haversine_phi + Math.cos(la1) * Math.cos(la2) * sin_lon * sin_lon;\n' +
@@ -89,49 +41,88 @@
       '\n' +
       'me.onStart = function () {\n' +
       '  me.direction_set = false;\n' +
-      '  me.next_checkpoint = 0;\n' +
+      '  me.dodging = false;\n' +
+      '  me.ongoing_detection = false;\n' +
       '};\n' +
       '\n' +
-      'me.onUpdate = function (timestamp) {' +
+      'me.onGetMsg = function (msg) {\n' +
+      '  if (msg && msg.flag_positions) {\n' +
+      '    me.flag_positions = msg.flag_positions\n' +
+      '    me.next_checkpoint = me.id % me.flag_positions.length;\n' +
+      '  }\n' +
+      '};\n' +
+      '\n' +
+      'me.onUpdate = function (timestamp) {\n' +
+      '  if (!me.flag_positions) return;\n' +
+      '  if (me.dodging) {\n' +
+      '    me.current_position = me.getCurrentPosition();\n' +
+      '    var dist = distance(\n' +
+      '      me.current_position,\n' +
+      '      me.dodging.position\n' +
+      '    );\n' +
+      '    if (dist >= DODGE_DISTANCE) {\n' +
+      //'      console.log("Good distance to obstacle. DODGED.");\n' +
+      '      me.dodging = false;\n' +
+      '    }\n' +
+      '    return;\n' +
+      '  }\n' +
       '  if (!me.direction_set) {\n' +
-      '    if (me.next_checkpoint < CHECKPOINT_LIST.length) {\n' +
+      '    if (me.next_checkpoint < me.flag_positions.length) {\n' +
       '      me.setTargetCoordinates(\n' +
-      '        CHECKPOINT_LIST[me.next_checkpoint].latitude,\n' +
-      '        CHECKPOINT_LIST[me.next_checkpoint].longitude,\n' +
-      '        CHECKPOINT_LIST[me.next_checkpoint].altitude + ALTITUDE + ALTITUDE * me.id\n' +
+      '        me.flag_positions[me.next_checkpoint].x,\n' +
+      '        me.flag_positions[me.next_checkpoint].y,\n' +
+      '        me.flag_positions[me.next_checkpoint].z + me.id\n' +
       '      );\n' +
-      '      console.log("[DEMO] Going to Checkpoint %d", me.next_checkpoint);\n' +
+      //'      console.log("[DEMO] Going to Checkpoint %d", me.next_checkpoint);\n' +
       '    }\n' +
       '    me.direction_set = true;\n' +
       '    return;\n' +
       '  }\n' +
-      '  if (me.next_checkpoint < CHECKPOINT_LIST.length) {\n' +
+      '  if (me.next_checkpoint < me.flag_positions.length) {\n' +
+      '    if (!me.ongoing_detection) {\n' +
+      '      me.getDroneViewInfo();\n' +
+      '      me.ongoing_detection = true;\n' +
+      '    }\n' +
+      '  }\n' +
+      '  if (me.next_checkpoint < me.flag_positions.length) {\n' +
       '    me.current_position = me.getCurrentPosition();\n' +
       '    me.distance = distance(\n' +
-      '      me.current_position.x,\n' +
-      '      me.current_position.y,\n' +
-      '      CHECKPOINT_LIST[me.next_checkpoint].latitude,\n' +
-      '      CHECKPOINT_LIST[me.next_checkpoint].longitude\n' +
+      '      me.current_position,\n' +
+      '      me.flag_positions[me.next_checkpoint]\n' +
       '    );\n' +
       '    if (me.distance <= EPSILON) {\n' +
-      '      console.log("[DEMO] Reached Checkpoint %d", me.next_checkpoint);\n' +
+      //'      console.log("[DEMO] Reached Checkpoint %d", me.next_checkpoint);\n' +
       '      me.next_checkpoint += 1;\n' +
       '      me.direction_set = false;\n' +
       '    }\n' +
       '    return;\n' +
       '  }\n' +
-      '  me.exit(0);\n' +
+      '};\n' +
+      '\n' +
+      'me.onDroneViewInfo = function (drone_view) {\n' +
+      '  me.ongoing_detection = false;\n' +
+      '  if (drone_view && drone_view.obstacles && drone_view.obstacles.length) {\n' +
+      '    me.dodging = drone_view.obstacles[0];\n' +
+      '    me.direction_set = false;\n' +
+      '    var random = Math.random() < 0.5, dodge_point = {};\n' +
+      '    Object.assign(dodge_point, me.flag_positions[me.next_checkpoint]);\n' +
+      '    if (random) {\n' +
+      '      dodge_point.x = dodge_point.x * -1;\n' +
+      '    } else {\n' +
+      '      dodge_point.y = dodge_point.y * -1;\n' +
+      '    }\n' +
+      '    me.setTargetCoordinates(dodge_point.x, dodge_point.y, me.getCurrentPosition().z);\n' +
+      '    return;\n' +
+      '  }\n' +
       '};',
     DRAW = true,
     LOG = true,
     LOG_TIME = 1662.7915426540285,
     DRONE_LIST = [],
-    WIDTH = 680,
-    HEIGHT = 340,
     LOGIC_FILE_LIST = [
       'gadget_erp5_page_drone_capture_flag_logic.js',
-      'gadget_erp5_page_drone_capture_flag_fixedwingdrone.js'/*,
-      'gadget_erp5_page_drone_capture_flag_dronelogfollower.js'*/
+      'gadget_erp5_page_drone_capture_flag_fixedwingdrone.js',
+      'gadget_erp5_page_drone_capture_flag_enemydrone.js'
     ];
 
   rJS(window)
@@ -161,7 +152,8 @@
     })
 
     .declareMethod('render', function render() {
-      var gadget = this;
+      var gadget = this, url_sp = new URLSearchParams(window.location.hash),
+        url_seed = url_sp.get("seed");
       return gadget.getDeclaredGadget('form_view')
         .push(function (form_gadget) {
           return form_gadget.render({
@@ -299,60 +291,38 @@
                   "hidden": 0,
                   "type": "FloatField"
                 },
-                "my_minimum_latitud": {
+                "my_map_size": {
                   "description": "",
-                  "title": "Minimum latitude",
-                  "default": min_lat,
+                  "title": "Map size",
+                  "default": MAP_SIZE,
                   "css_class": "",
                   "required": 1,
                   "editable": 1,
-                  "key": "min_lat",
+                  "key": "map_size",
                   "hidden": 0,
                   "type": "FloatField"
                 },
-                "my_maximum_latitud": {
-                  "description": "",
-                  "title": "Maximum latitude",
-                  "default": max_lat,
-                  "css_class": "",
-                  "required": 1,
-                  "editable": 1,
-                  "key": "max_lat",
-                  "hidden": 0,
-                  "type": "FloatField"
-                },
-                "my_minimum_longitud": {
+                "my_start_AMSL": {
                   "description": "",
-                  "title": "Minimum longitude",
-                  "default": min_lon,
+                  "title": "Start AMSL",
+                  "default": start_AMSL,
                   "css_class": "",
                   "required": 1,
                   "editable": 1,
-                  "key": "min_lon",
+                  "key": "start_AMSL",
                   "hidden": 0,
                   "type": "FloatField"
                 },
-                "my_maximum_longitud": {
-                  "description": "",
-                  "title": "Maximum longitude",
-                  "default": max_lon,
+                "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": "max_lon",
+                  "key": "map_seed",
                   "hidden": 0,
-                  "type": "FloatField"
-                },
-                "my_start_AMSL": {
-                  "description": "",
-                  "title": "Start AMSL",
-                  "default": start_AMSL,
-                  "css_class": "",
-                  "required": 1,
-                  "editable": 1,
-                  "key": "start_AMSL",
-                  "hidden": 0,
-                  "type": "FloatField"
+                  "type": "StringField"
                 },
                 "my_map_height": {
                   "description": "",
@@ -365,39 +335,17 @@
                   "hidden": 0,
                   "type": "IntegerField"
                 },
-                "my_init_pos_lon": {
-                  "description": "",
-                  "title": "Initial drone longitude",
-                  "default": INITIAL_POSITION.longitude,
-                  "css_class": "",
-                  "required": 1,
-                  "editable": 1,
-                  "key": "init_pos_lon",
-                  "hidden": 0,
-                  "type": "FloatField"
-                },
-                "my_init_pos_lat": {
+                /*"my_flag_weight": {
                   "description": "",
-                  "title": "Initial drone latitude",
-                  "default": INITIAL_POSITION.latitude,
+                  "title": "Flag Weight",
+                  "default": FLAG_WEIGHT,
                   "css_class": "",
                   "required": 1,
                   "editable": 1,
-                  "key": "init_pos_lat",
+                  "key": "flag_weight",
                   "hidden": 0,
-                  "type": "FloatField"
-                },
-                "my_init_pos_z": {
-                  "description": "",
-                  "title": "Initial drone position Z",
-                  "default": INITIAL_POSITION.z,
-                  "css_class": "",
-                  "required": 1,
-                  "editable": 1,
-                  "key": "init_pos_z",
-                  "hidden": 0,
-                  "type": "FloatField"
-                },
+                  "type": "IntegerField"
+                },*/
                 "my_number_of_drones": {
                   "description": "",
                   "title": "Number of drones",
@@ -432,13 +380,11 @@
               group_list: [[
                 "left",
                 [["my_simulation_speed"], ["my_simulation_time"], ["my_number_of_drones"],
-                  ["my_minimum_latitud"], ["my_maximum_latitud"],
-                  ["my_minimum_longitud"], ["my_maximum_longitud"],
-                  ["my_init_pos_lat"], ["my_init_pos_lon"], ["my_init_pos_z"],
-                  ["my_map_height"]]
+                 ["my_map_size"], ["my_map_height"],// ["my_flag_weight"],
+                 ["my_start_AMSL"], ["my_map_seed"]]
               ], [
                 "right",
-                [["my_start_AMSL"], ["my_drone_min_speed"], ["my_drone_speed"], ["my_drone_max_speed"],
+                [["my_drone_min_speed"], ["my_drone_speed"], ["my_drone_max_speed"],
                   ["my_drone_max_acceleration"], ["my_drone_max_deceleration"],
                   ["my_drone_max_roll"], ["my_drone_min_pitch"], ["my_drone_max_pitch"],
                   ["my_drone_max_sink_rate"], ["my_drone_max_climb_rate"]]
@@ -460,14 +406,119 @@
     .declareJob('runGame', function runGame(options) {
       var gadget = this, i,
         fragment = gadget.element.querySelector('.simulator_div'),
-        game_parameters_json;
+        game_parameters_json, map_json;
+      DRONE_LIST = [];
       fragment = domsugar(gadget.element.querySelector('.simulator_div'),
                               [domsugar('div')]).firstElementChild;
-      DRONE_LIST = [];
       for (i = 0; i < options.number_of_drones; i += 1) {
         DRONE_LIST[i] = {"id": i, "type": "FixedWingDroneAPI",
                          "script_content": options.script};
       }
+
+      function randomizeMap(json_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 seed_value = options.map_seed,
+          random_seed = new Math.seedrandom(seed_value), i,
+          n_enemies = randomIntFromInterval(5, 10, random_seed),
+          n_flags = randomIntFromInterval(Math.floor(DRONE_LIST.length / 2),
+                                          DRONE_LIST.length, random_seed),
+          n_obstacles = randomIntFromInterval(5, 15, random_seed),
+          flag_list = [], obstacle_list = [], enemy_list = [], random_position,
+          obstacles_types = ["box"/*, "sphere"*/, "cylinder"], type,
+          obstacle_limit = [options.map_size / 6, options.map_size / 100,
+                            options.map_size / 6, 30];
+        //enemies
+        for (i = 0; i < n_enemies; i += 1) {
+          random_position = randomPosition(random_seed, options.map_size);
+          enemy_list.push({
+            "id": i + parseInt(options.number_of_drones),
+            "type": "EnemyDroneAPI",
+            "position": {
+              "x": random_position[0],
+              "y": random_position[1],
+              "z": 15 //TODO random z?
+            }
+          });
+        }
+        //flags
+        for (i = 0; i < n_flags; i += 1) {
+          //avoid flags near the limits
+          random_position = randomPosition(random_seed, options.map_size * 0.75);
+          flag_list.push({
+            "position": {
+              "x": random_position[0],
+              "y": random_position[1],
+              "z": 10
+            }
+          });
+        }
+        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) < options.map_size / 6) {
+              return true;
+            }
+          }
+          return false;
+        }
+        //obstacles
+        for (i = 0; i < n_obstacles; i += 1) {
+          random_position = randomPosition(random_seed, options.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
+              }
+            });
+          }
+        }
+        json_map.obstacle_list = obstacle_list;
+        json_map.drones.enemy = enemy_list;
+        json_map.flag_list = flag_list;
+        return json_map;
+      }
+
+      map_json = {
+        "map_size": parseFloat(options.map_size),
+        "height": parseInt(options.map_height, 10),
+        "start_AMSL": parseFloat(options.start_AMSL),
+        "flag_list": [],
+        "obstacle_list" : [],
+        "drones": {
+          "user": DRONE_LIST,
+          "enemy": []
+        }
+      };
+
       game_parameters_json = {
         "drone": {
           "maxAcceleration": parseInt(options.drone_max_acceleration, 10),
@@ -487,24 +538,11 @@
           "information": 0,
           "communication": 0
         },
-        "map": {
-          "min_lat": parseFloat(options.min_lat),
-          "max_lat": parseFloat(options.max_lat),
-          "min_lon": parseFloat(options.min_lon),
-          "max_lon": parseFloat(options.max_lon),
-          "height": parseInt(options.map_height, 10),
-          "start_AMSL": parseFloat(options.start_AMSL)
-        },
-        "initialPosition": {
-          "longitude": parseFloat(options.init_pos_lon),
-          "latitude": parseFloat(options.init_pos_lat),
-          "z": parseFloat(options.init_pos_z)
-        },
+        "map": randomizeMap(map_json),
         "draw_flight_path": DRAW,
         "temp_flight_path": true,
         "log_drone_flight": LOG,
-        "log_interval_time": LOG_TIME,
-        "droneList": DRONE_LIST
+        "log_interval_time": LOG_TIME
       };
       return gadget.declareGadget("babylonjs.gadget.html",
                                   {element: fragment, scope: 'simulator'})
@@ -525,8 +563,7 @@
                   "type": "GadgetField",
                   "url": "babylonjs.gadget.html",
                   "sandbox": "public",
-                  "renderjs_extra": '{"autorun": false, "width": ' + WIDTH + ', ' +
-                    '"height": ' + HEIGHT + ', ' +
+                  "renderjs_extra": '{"autorun": false, ' +
                     '"logic_file_list": ' + JSON.stringify(LOGIC_FILE_LIST) + ', ' +
                     '"game_parameters": ' + JSON.stringify(game_parameters_json) +
                     '}'
@@ -553,35 +590,44 @@
           return form_gadget.getContent();
         })
         .push(function (result) {
-          var a, blob, div, key, log, log_content;
+          var a, blob, div, key, log, log_content, aux;
           i = 0;
-          for (key in result) {
-            if (result.hasOwnProperty(key)) {
-              log_content = result[key].join('\n').replaceAll(",", ";");
+          div = domsugar('div', { text: result.message });
+          document.querySelector('.container').parentNode.appendChild(div);
+          for (key in result.content) {
+            if (result.content.hasOwnProperty(key)) {
+              log_content = result.content[key].join('\n').replaceAll(",", ";");
               blob = new Blob([log_content], {type: 'text/plain'});
               a = domsugar('a', {
                 text: 'Download Simulation LOG ' + i,
-                download: 'simulation_log_' + i
-                  + '_speed_' + game_parameters_json.drone.speed
-                  + '_min-speed_' + game_parameters_json.drone.minSpeed
-                  + '_max-speed_' + game_parameters_json.drone.maxSpeed
-                  + '_max-accel_' + game_parameters_json.drone.maxAcceleration
-                  + '_max-decel_' + game_parameters_json.drone.maxDeceleration
-                  + '_max-roll_' + game_parameters_json.drone.maxRoll
-                  + '_min-pitch_' + game_parameters_json.drone.minPitchAngle
-                  + '_max-pitch_' + game_parameters_json.drone.maxPitchAngle
-                  + '_max-sink_' + game_parameters_json.drone.maxSinkRate
-                  + '_max-climb_' + game_parameters_json.drone.maxClimbRate
-                  + '.txt',
+                download: 'simulation_log_' + i +
+                '_speed_' + game_parameters_json.drone.speed +
+                '_min-speed_' + game_parameters_json.drone.minSpeed +
+                '_max-speed_' + game_parameters_json.drone.maxSpeed +
+                '_max-accel_' + game_parameters_json.drone.maxAcceleration +
+                '_max-decel_' + game_parameters_json.drone.maxDeceleration +
+                '_max-roll_' + game_parameters_json.drone.maxRoll +
+                '_min-pitch_' + game_parameters_json.drone.minPitchAngle +
+                '_max-pitch_' + game_parameters_json.drone.maxPitchAngle +
+                '_max-sink_' + game_parameters_json.drone.maxSinkRate +
+                '_max-climb_' + game_parameters_json.drone.maxClimbRate +
+                '.txt',
                 href: window.URL.createObjectURL(blob)
               });
-              log = domsugar('textarea', { value: log_content, id: 'log_result_' + i });
+              log = domsugar('textarea',
+                             { value: log_content, id: 'log_result_' + i });
               div = domsugar('div', [a]);
               a.dataset.downloadurl =  ['text/plain', a.download,
                                         a.href].join(':');
-              document.querySelector('.container').appendChild(div);
-              document.querySelector('.container').appendChild(log);
+              document.querySelector('.container').parentNode.appendChild(div);
+              document.querySelector('.container').parentNode.appendChild(log);
               i += 1;
+              if (i === DRONE_LIST.length) {
+                break;
+                //Do not show enemy drone logs for now
+                /*aux = domsugar('div', { text: "Enemy drones logs:" });
+                document.querySelector('.container').parentNode.appendChild(aux);*/
+              }
             }
           }
         }, function (error) {
@@ -590,4 +636,4 @@
         });
     });
 
-}(window, rJS, domsugar, document, Blob));
\ No newline at end of file
+}(window, rJS, domsugar, document, URLSearchParams, Blob));
\ No newline at end of file
diff --git a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.xml b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.xml
index f289d7c6eb7c768cb19a3a9b21d0df3b4c8968a5..e577a837a4405c956cc862fba0497fb06d2e3c99 100644
--- a/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.xml
+++ b/bt5/erp5_officejs_drone_capture_flag/PathTemplateItem/web_page_module/ojs_drone_capture_flag_script_page_js.xml
@@ -246,7 +246,7 @@
                   </item>
                   <item>
                       <key> <string>serial</string> </key>
-                      <value> <string>1007.63299.40871.39799</string> </value>
+                      <value> <string>1009.57725.14056.1911</string> </value>
                   </item>
                   <item>
                       <key> <string>state</string> </key>
@@ -266,7 +266,7 @@
                           </tuple>
                           <state>
                             <tuple>
-                              <float>1682437874.39</float>
+                              <float>1689793877.59</float>
                               <string>UTC</string>
                             </tuple>
                           </state>
diff --git a/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.jpg b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.jpg
index ded8ef17f20055d0fdc9ebd5eb23bc5b3547ac03..2620ca01daa08896d28e7f24bd1d8c639dbd58dc 100644
Binary files a/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.jpg and b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.jpg differ
diff --git a/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.xml b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.xml
index 4cc82e4c603ab402fb51d5d43a436ecfab2f5f37..88cde27e831d098e5c9edfd082157554d34f68ec 100644
--- a/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.xml
+++ b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/assets/map/terrain.jpg.xml
@@ -16,7 +16,7 @@
         </item>
         <item>
             <key> <string>height</string> </key>
-            <value> <int>512</int> </value>
+            <value> <int>1024</int> </value>
         </item>
         <item>
             <key> <string>precondition</string> </key>
@@ -28,7 +28,7 @@
         </item>
         <item>
             <key> <string>width</string> </key>
-            <value> <int>512</int> </value>
+            <value> <int>1024</int> </value>
         </item>
       </dictionary>
     </pickle>
diff --git a/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries.xml b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f3a8eab5f9153f8ba30df2cff5df497814514b2b
--- /dev/null
+++ b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Folder" module="OFS.Folder"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_objects</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>libraries</string> </value>
+        </item>
+        <item>
+            <key> <string>title</string> </key>
+            <value> <string></string> </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries/seedrandom.min.js.js b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries/seedrandom.min.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..5cc872e11a572fb30aac0b49afe17cf133003c9e
--- /dev/null
+++ b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries/seedrandom.min.js.js
@@ -0,0 +1 @@
+!function(f,a,c){var s,l=256,p="random",d=c.pow(l,6),g=c.pow(2,52),y=2*g,h=l-1;function n(n,t,r){function e(){for(var n=u.g(6),t=d,r=0;n<g;)n=(n+r)*l,t*=l,r=u.g(1);for(;y<=n;)n/=2,t/=2,r>>>=1;return(n+r)/t}var o=[],i=j(function n(t,r){var e,o=[],i=typeof t;if(r&&"object"==i)for(e in t)try{o.push(n(t[e],r-1))}catch(n){}return o.length?o:"string"==i?t:t+"\0"}((t=1==t?{entropy:!0}:t||{}).entropy?[n,S(a)]:null==n?function(){try{var n;return s&&(n=s.randomBytes)?n=n(l):(n=new Uint8Array(l),(f.crypto||f.msCrypto).getRandomValues(n)),S(n)}catch(n){var t=f.navigator,r=t&&t.plugins;return[+new Date,f,r,f.screen,S(a)]}}():n,3),o),u=new m(o);return e.int32=function(){return 0|u.g(4)},e.quick=function(){return u.g(4)/4294967296},e.double=e,j(S(u.S),a),(t.pass||r||function(n,t,r,e){return e&&(e.S&&v(e,u),n.state=function(){return v(u,{})}),r?(c[p]=n,t):n})(e,i,"global"in t?t.global:this==c,t.state)}function m(n){var t,r=n.length,u=this,e=0,o=u.i=u.j=0,i=u.S=[];for(r||(n=[r++]);e<l;)i[e]=e++;for(e=0;e<l;e++)i[e]=i[o=h&o+n[e%r]+(t=i[e])],i[o]=t;(u.g=function(n){for(var t,r=0,e=u.i,o=u.j,i=u.S;n--;)t=i[e=h&e+1],r=r*l+i[h&(i[e]=i[o=h&o+t])+(i[o]=t)];return u.i=e,u.j=o,r})(l)}function v(n,t){return t.i=n.i,t.j=n.j,t.S=n.S.slice(),t}function j(n,t){for(var r,e=n+"",o=0;o<e.length;)t[h&o]=h&(r^=19*t[h&o])+e.charCodeAt(o++);return S(t)}function S(n){return String.fromCharCode.apply(0,n)}if(j(c.random(),a),"object"==typeof module&&module.exports){module.exports=n;try{s=require("crypto")}catch(n){}}else"function"==typeof define&&define.amd?define(function(){return n}):c["seed"+p]=n}("undefined"!=typeof self?self:this,[],Math);
diff --git a/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries/seedrandom.min.js.xml b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries/seedrandom.min.js.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ae51021f95c5723bfedfc501c57f801931a8dad5
--- /dev/null
+++ b/bt5/erp5_officejs_drone_capture_flag/SkinTemplateItem/portal_skins/erp5_drone_capture_flag/libraries/seedrandom.min.js.xml
@@ -0,0 +1,28 @@
+<?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>seedrandom.min.js</string> </value>
+        </item>
+        <item>
+            <key> <string>content_type</string> </key>
+            <value> <string>application/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>