diff --git a/applications/flyAvatar/app-flyAvatar.js b/applications/flyAvatar/app-flyAvatar.js
new file mode 100644
index 0000000..69dd7f3
--- /dev/null
+++ b/applications/flyAvatar/app-flyAvatar.js
@@ -0,0 +1,163 @@
+//
+// app-flyAvatar.js
+//
+// Created by Alezia Kurdis, December 16th 2023.
+// Copyright 2023 Overte e.V.
+//
+// This automatically replace your avatar by a specific one as soon as you are flying.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+(function() {
+ var jsMainFileName = "app-flyAvatar.js";
+ var ROOT = Script.resolvePath('').split(jsMainFileName)[0];
+
+ var APP_NAME = "FLY-AV";
+ var APP_URL = ROOT + "flyAvatar.html";
+ var APP_ICON_INACTIVE = ROOT + "icon_inactive.png";
+ var APP_ICON_ACTIVE = ROOT + "icon_active.png"; // BLACK on
+ var ICON_CAPTION_COLOR = "#FFFFFF";
+ var appStatus = false;
+ var channel = "overte.application.more.flyAvatar";
+ var timestamp = 0;
+ var INTERCALL_DELAY = 200; //0.3 sec
+ var FLY_AVATAR_SETTING_KEY = "overte.application.more.flyAvatar.avatarUrl";
+ var flyAvatarUrl = "";
+ var originalAvatarUrl = "";
+ var isFlying = false;
+ var UPDATE_TIMER_INTERVAL = 500; // 5 sec
+ var processTimer = 0;
+
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+
+ tablet.screenChanged.connect(onScreenChanged);
+
+ var button = tablet.addButton({
+ text: APP_NAME,
+ icon: APP_ICON_INACTIVE,
+ activeIcon: APP_ICON_ACTIVE,
+ captionColor: ICON_CAPTION_COLOR
+ });
+
+
+ function clicked(){
+ var colorCaption;
+ if (appStatus === true) {
+ tablet.webEventReceived.disconnect(onAppWebEventReceived);
+ tablet.gotoHomeScreen();
+ colorCaption = ICON_CAPTION_COLOR;
+ appStatus = false;
+ }else{
+ //Launching the Application UI.
+ tablet.gotoWebScreen(APP_URL); // <== Data can be transmitted at opening of teh UI by using GET method, through paramater in the URL. + "?parameter=value"
+ tablet.webEventReceived.connect(onAppWebEventReceived);
+ colorCaption = "#000000";
+ appStatus = true;
+ }
+
+ button.editProperties({
+ isActive: appStatus,
+ captionColor: colorCaption
+ });
+ }
+
+ button.clicked.connect(clicked);
+
+ //This recieved the message from the UI(html) for a specific actions
+ function onAppWebEventReceived(message) {
+ if (typeof message === "string") {
+ var d = new Date();
+ var n = d.getTime();
+ var instruction = JSON.parse(message);
+ if (instruction.channel === channel) {
+ if (instruction.action === "HUMAN_CALLED_ACTION_NAME" && (n - timestamp) > INTERCALL_DELAY) { //<== Use this for action trigger by a human (button or any ui control). The delay prevent multiple call to destabilize everything.
+ d = new Date();
+ timestamp = d.getTime();
+ //Call a function to do something here
+ } else if (instruction.action === "REQUEST_INITIAL_DATA") {
+ sendCurrentFlyAvatarUrlToUI();
+ } else if (instruction.action === "UPDATE_URL") {
+ flyAvatarUrl = instruction.url;
+ Settings.setValue( FLY_AVATAR_SETTING_KEY, flyAvatarUrl);
+ updateAvatar();
+ } else if (instruction.action === "SELF_UNINSTALL" && (n - timestamp) > INTERCALL_DELAY) { //<== This is a good practice to add a "Uninstall this app" button for rarely used app. (toolbar has a limit in size)
+ d = new Date();
+ timestamp = d.getTime();
+ ScriptDiscoveryService.stopScript(Script.resolvePath(''), false);
+ }
+ }
+ }
+ }
+
+ function updateAvatar() {
+ if (MyAvatar.isFlying()) {
+ MyAvatar.useFullAvatarURL(flyAvatarUrl);
+ } else {
+ if (MyAvatar.skeletonModelURL === flyAvatarUrl) {
+ MyAvatar.useFullAvatarURL(originalAvatarUrl);
+ }
+ }
+ }
+
+ MyAvatar.skeletonModelURLChanged.connect(function () {
+ if (!MyAvatar.isFlying() && MyAvatar.skeletonModelURL !== flyAvatarUrl) {
+ originalAvatarUrl = MyAvatar.skeletonModelURL;
+ }
+ });
+
+ function myTimer(deltaTime) {
+ var today = new Date();
+ if ((today.getTime() - processTimer) > UPDATE_TIMER_INTERVAL ) {
+
+ if (isFlying !== MyAvatar.isFlying()) {
+ updateAvatar();
+ isFlying = MyAvatar.isFlying();
+ }
+
+ today = new Date();
+ processTimer = today.getTime();
+ }
+ }
+
+ function sendCurrentFlyAvatarUrlToUI() {
+ var message = {
+ "channel": channel,
+ "action": "FLY-AVATAR-URL",
+ "url": flyAvatarUrl
+ };
+ tablet.emitScriptEvent(JSON.stringify(message));
+ }
+
+ function onScreenChanged(type, url) {
+ if (type === "Web" && url.indexOf(APP_URL) !== -1) {
+ colorCaption = "#000000";
+ appStatus = true;
+ } else {
+ colorCaption = ICON_CAPTION_COLOR;
+ appStatus = false;
+ }
+
+ button.editProperties({
+ isActive: appStatus,
+ captionColor: colorCaption
+ });
+ }
+
+ function cleanup() {
+
+ if (appStatus) {
+ tablet.gotoHomeScreen();
+ tablet.webEventReceived.disconnect(onAppWebEventReceived);
+ }
+
+ tablet.screenChanged.disconnect(onScreenChanged);
+ tablet.removeButton(button);
+ Script.update.disconnect(myTimer);
+ }
+
+ Script.scriptEnding.connect(cleanup);
+ originalAvatarUrl = MyAvatar.skeletonModelURL;
+ flyAvatarUrl = Settings.getValue( FLY_AVATAR_SETTING_KEY, "" );
+ Script.update.connect(myTimer);
+}());
diff --git a/applications/flyAvatar/flyAvatar.html b/applications/flyAvatar/flyAvatar.html
new file mode 100644
index 0000000..a796dac
--- /dev/null
+++ b/applications/flyAvatar/flyAvatar.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
FLY AVATAR
+
+ Avatar Url to use while flying:
+
+
+
+
+
+
+
+
+
diff --git a/applications/flyAvatar/fonts/FiraSans-Regular.ttf b/applications/flyAvatar/fonts/FiraSans-Regular.ttf
new file mode 100644
index 0000000..d9fdc0e
Binary files /dev/null and b/applications/flyAvatar/fonts/FiraSans-Regular.ttf differ
diff --git a/applications/flyAvatar/fonts/FiraSans-SemiBold.ttf b/applications/flyAvatar/fonts/FiraSans-SemiBold.ttf
new file mode 100644
index 0000000..821a43d
Binary files /dev/null and b/applications/flyAvatar/fonts/FiraSans-SemiBold.ttf differ
diff --git a/applications/flyAvatar/fonts/FiraSans.license b/applications/flyAvatar/fonts/FiraSans.license
new file mode 100644
index 0000000..37c4a30
--- /dev/null
+++ b/applications/flyAvatar/fonts/FiraSans.license
@@ -0,0 +1,94 @@
+Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
+with Reserved Font Name < Fira >,
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/applications/flyAvatar/icon_active.png b/applications/flyAvatar/icon_active.png
new file mode 100644
index 0000000..9771569
Binary files /dev/null and b/applications/flyAvatar/icon_active.png differ
diff --git a/applications/flyAvatar/icon_inactive.png b/applications/flyAvatar/icon_inactive.png
new file mode 100644
index 0000000..2e25f76
Binary files /dev/null and b/applications/flyAvatar/icon_inactive.png differ
diff --git a/applications/metadata.js b/applications/metadata.js
index e8f6f7b..6be4461 100644
--- a/applications/metadata.js
+++ b/applications/metadata.js
@@ -269,6 +269,15 @@ var metadata = { "applications":
"jsfile": "inventory-app/dist/inventory.js",
"icon": "inventory-app/dist/inventory-i.svg",
"caption": "INVENTORY"
+ },
+ {
+ "isActive": true,
+ "directory": "flyAvatar",
+ "name": "Fly Avatar",
+ "description": "This application replaces your avatar for a specific one when you are flying. It reverts automatically the original avatar as soon as you land.",
+ "jsfile": "flyAvatar/app-flyAvatar.js",
+ "icon": "flyAvatar/icon_inactive.png",
+ "caption": "FLY-AV"
}
]
};
\ No newline at end of file