'use strict'
angular.module('beamng.stuff')
// no fucking clue, why this doesn't just work in the videoBackgroundDirective itself, but well you know
// idea from: http://stackoverflow.com/questions/28016476/angular-on-video-load-event
.directive('onVideoStart', function () {
return {
restrict: 'A',
scope: {
onVideoStart: '&'
},
link: function (scope, $element) {
$element[0].addEventListener('play', function () {
scope.onVideoStart()
})
}
}
})
.directive("fancyBackground", ["TechLicenseState", function (techLicenseState) {
return {
template: `
`,
scope: {
videoLoaded: "&",
},
link: function (scope, elem, attr) {
scope.files = [
"/ui/modules/mainmenu/drive/images/1.jpg",
"/ui/modules/mainmenu/drive/images/2.jpg",
"/ui/modules/mainmenu/drive/images/3.jpg",
"/ui/modules/mainmenu/drive/images/4.jpg",
"/ui/modules/mainmenu/drive/images/5.jpg",
"/ui/modules/mainmenu/drive/images/6.jpg",
"/ui/modules/mainmenu/drive/images/7.jpg",
"/ui/modules/mainmenu/drive/images/8.jpg",
];
bngApi.engineLua("sailingTheHighSeas", ahoy => {
if (ahoy)
scope.files = ["/ui/modules/mainmenu/unofficial_version.jpg"];
});
techLicenseState.state.then(licenseVerified => {
if (licenseVerified)
scope.files = ["/ui/modules/mainmenu/drive/tech_images/1.jpg"];
});
}
}
}])
.directive('menuNavbar', ['Utils', '$rootScope', function (Utils, $rootScope, ) {
return {
template: `
`,
scope: {
},
link: function (scope, $element, attr) {
// do quick check right in main menu, to ensure *some* of our UI/LUA code can work in languages with certain non-english characters
bngApi.serializeToLuaCheck("English: " + "hello")
bngApi.serializeToLuaCheck("Spanish: " + "güeñes")
bngApi.serializeToLuaCheck("French: " + "bâguéttè, garçon")
bngApi.serializeToLuaCheck("Czech: " + "Kl�vesnice")
bngApi.serializeToLuaCheck("Korean: " + decodeURIComponent("%0D%EF%BF%BD%EF%BF%BD%0B%EF%BF%BD%EF%BF%BD%0B%EF%BF%BD%EF%BF%BD"))
bngApi.serializeToLuaCheck("Chinese Sim: " + "欢迎来到简体中文版的")
bngApi.serializeToLuaCheck("Chinese Tr: " + "歡迎來到")
bngApi.serializeToLuaCheck("Japanese: " + "』日本語版にようこそ")
bngApi.serializeToLuaCheck("Polish: " + "źćłąóę")
bngApi.serializeToLuaCheck("Russian: " + "абвгеёьъ")
scope.nav = function(action, val) {
$rootScope.$broadcast('MenuItemNavigation', action, val)
}
scope.radialMenu = function(action, val) {
bngApi.engineLua("extensions.core_quickAccess.setEnabled(true)")
}
// VERSION INFO
scope.showBuildInfo = false
scope.versionStr = beamng.version
// convert from 1.2.3.4 to 1.2.3 as we do not want to attach the build number in the simple display
var versionSplit = scope.versionStr.split('.')
if(versionSplit.length == 5) versionSplit.pop() // remove build number (5th)
for(var i = 0; i < 3; i++) {
if(versionSplit[versionSplit.length - 1] == '0') versionSplit.pop(); // remove any '0' for simplicity reasons
}
scope.versionSimpleStr = versionSplit.join('.')
scope.buildInfoStr = beamng.buildinfo
// account info
scope.$on('SteamInfo', function (event, data) {
scope.$apply(function () {
scope.steamData = data
})
})
scope.onlineState = false
scope.$on('OnlineStateChanged', function (event, data) {
scope.$applyAsync(function () {
scope.onlineState = data
})
})
scope.$on('ShowEntertainingBackground', (ev, mainmenu) => {
scope.$evalAsync(() => {
scope.mainmenu = mainmenu
})
})
bngApi.engineLua('core_online.requestState()')
}
}
}])
.directive('onlineMessage', ['$sce', 'Utils', '$state', function ($sce, Utils, $state) {
return {
restrict: 'E',
replace: true,
template: `
{{btn.label | translate}}
`,
link: function (scope) {
var uids = {}
scope.$on('OnlineMessage', function (ev, data) {
scope.messageData = data
scope.$evalAsync(() => {
// uid = data.uid
for (var key in scope.messageData) {
switch (scope.messageData[key].contenttype) {
case 'html':
scope.messageData[key].msg = $sce.trustAsHtml(scope.messageData[key].msg)
break
case 'bbcode':
scope.messageData[key].msg = $sce.trustAsHtml(Utils.parseBBCode(scope.messageData[key].msg))
break
default:
scope.messageData[key].msg = scope.messageData[key].msg
}
uids[key] = scope.messageData[key].uid
scope.style = scope.messageData[key].css //{position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, 'background': ' repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75) 35px, rgba(0, 0, 0, 0.8) 35px, rgba(0, 0, 0, 0.8) 70px)'}
}
})
})
scope.close = function () {
for (var key in uids) {
bngApi.engineLua(`extensions.hook("onUIOnlineMessageHide", "${uids[key]}")`)
}
scope.messageData = undefined
}
scope.clicked = function (btn) {
switch (btn.type) {
case 'hide-temp':
scope.messageData = undefined
break
case 'hide-perm':
scope.messageData = undefined
bngApi.engineLua(`extensions.hook("onUIOnlineMessageHide", "${uid}")`)
break
case 'luacmd':
bngApi.engineLua(btn.cmd)
break
case 'url':
document.location.href = btn.url
break
case 'state':
$state.go(btn.state, btn.params || {})
break
default:
console.warn('Unknown action type in online message button')
}
}
bngApi.engineLua('extensions.hook("onUIOnlineMessageReady")')
}
}
}])
.controller('MainMenuController', ['$rootScope', '$scope', 'toastr', '$state', 'Settings', '$http', '$filter', 'Utils', 'gamepadNav', 'TechLicenseState', 'ConfirmationDialog', function($rootScope, $scope, toastr, $state, Settings, $http, $filter, Utils, gamepadNav, techLicenseState, ConfirmationDialog) {
let vm = this
vm.product = beamng.product
vm.techLicense = false
techLicenseState.state.then((licenseVerified) => {
vm.techLicense = licenseVerified
})
// account info
$scope.$on('SteamInfo', function (event, data) {
vm.steamData = data
})
// always hide apps in main menu
$scope.$parent.app.showApps = false
// settings
{
vm.settings = Settings.values
$scope.$on('SettingsChanged', (ev, data) => {
vm.settings = data.values
})
}
if (!vm.techLicense && (Settings.values.onlineFeatures === 'ask' || Settings.values.telemetry === 'ask')) {
$state.go('menu.onlineFeatures')
}
bngApi.engineLua('core_online.requestState()')
// hardwareinfo warnings, etc
{
bngApi.engineLua('core_hardwareinfo.requestInfo()')
$scope.$on('HardwareInfo', function (event, data) {
if (data.globalState !== 'ok') {
for (var key in data) {
if(data[key].warnings === undefined) continue
for (var i=0; i < data[key].warnings.length; i++) {
if(data[key].warnings[i].ack !== undefined) continue
var txt = $filter('translate')('ui.performance.warnings.' + data[key].warnings[i].msg)
var html = Utils.parseBBCode(txt)
toastr[(data.globalState === 'warn' ? 'warning' : 'error')](
$filter('translate')('ui.mainmenu.warningdetails'),//need a unique message
html,
{
positionClass: 'toast-top-right',
timeOut: 0,
extendedTimeOut: 0,
onTap: function () {
$state.go('menu.options.performance')
}
}
)
}
}
}
})
}
// repository button: X of Y mods active
{
vm.modsTotal = 0
vm.modsActive = 0
bngApi.engineLua('core_modmanager.requestState()')
$scope.$on('ModManagerModsChanged', function (event, data) {
var list = data.convertToArray()
vm.modsActive = list.filter((elem) => elem.modname != 'translations' && elem.active).length
vm.modsTotal = list.filter((elem) => elem.modname != 'translations').length
})
}
{
// navigation things
let prevCross = gamepadNav.crossfireEnabled()
let prevGame = gamepadNav.gamepadNavEnabled()
gamepadNav.enableCrossfire(true)
gamepadNav.enableGamepadNav(true)
$scope.$on('$destroy', () => {
gamepadNav.enableCrossfire(prevCross)
gamepadNav.enableGamepadNav(prevGame)
})
}
$scope.quit = function() {
// copypasted from entrypoints/main/main.js
bngApi.engineScript('quit();'); //It should work but doesn't, `Platform::postQuitMessage` is executed but nothing happens, maybe CEF catch that message
bngApi.engineLua("TorqueScript.eval('quit();')")
}
$scope.radialmenu = function() {
}
let buttonsDefault = [
// big
{
translateid: 'ui.playmodes.freeroam',
icon: '/ui/modules/mainmenu/drive/icons/play.svg',
targetState: 'menu.levels'
},
// rows with 4 elements each
{
translateid: 'ui.playmodes.campaigns',
icon: '/ui/modules/mainmenu/drive/icons/campaigns.svg',
targetState: 'menu.campaigns'
},
{
translateid: 'ui.playmodes.scenarios',
icon: '/ui/modules/mainmenu/drive/icons/scenarios.svg',
targetState: 'menu.scenarios'
},
{
translateid: 'ui.playmodes.quickrace',
icon: '/ui/modules/mainmenu/drive/icons/timetrials.svg',
targetState: 'menu.quickraceOverview'
},
{
translateid: 'ui.mainmenu.garage',
icon: '/ui/modules/mainmenu/drive/icons/garage.svg',
action: () => {
bngApi.engineLua(`gameplay_garageMode.start()`)
},
get disabled() { return $scope.$parent.app.gameState === "garage" },
},
{
translateid: 'ui.playmodes.bus',
icon: '/ui/modules/mainmenu/drive/icons/busroutes.svg',
targetState: 'menu.busRoutes'
},
{
translateid: 'ui.playmodes.lightRunner',
icon: '/ui/modules/mainmenu/drive/icons/lightrunner.svg',
targetState: 'menu.lightrunnerOverview'
},
// {
// translateid: 'ui.dashboard.trackBuilder',
// icon: '/ui/modules/mainmenu/drive/icons/autobahn.svg',
// action: () => bngApi.engineLua("extensions.trackbuilder_trackBuilder.toggleTrackBuilder()")
// },
{
translateid: 'ui.playmodes.trackBuilder',
icon: '/ui/modules/mainmenu/drive/icons/autobahn.svg',
action: () => { // TODO: toggle track builder in current level
// bngApi.engineLua("extensions.trackbuilder_trackBuilder.toggleTrackBuilder()")
bngApi.engineLua(`freeroam_freeroam.startTrackBuilder('glow_city')`)
},
get disabled() { return $scope.$parent.app.gameState === "garage" },
},
// {
// translateid: 'ui.quickrace.tracks.procedural',
// icon: '/ui/modules/mainmenu/drive/icons/infinity.svg',
// targetState: 'quickraceTrackSelect'
// },
{
translateid: 'ui.playmodes.career',
subtranslateid: 'ui.playmodes.comingSoon',
icon: '/ui/modules/mainmenu/drive/icons/career.svg',
disabled: true,
class: "semi-disabled", // semi-disabled will make the button clickable, not changing the style if it was disabled
// targetState: 'menu.career'
action: () => {
if (!$scope.inCareer) {
ConfirmationDialog.open(
"ui.career.experimentalTitle", "ui.career.experimentalPrompt",
[
{ label: "ui.common.no", key: false, isCancel: true },
// { label: "Enter and don't show this again", key: true },
{ label: "ui.career.experimentalAgree", key: true, default: true },
],
{ class: "experimental" }
).then(res => {
if (!res)
return;
$state.go("menu.career");
});
} else {
$state.go("menu.career");
}
},
},
];
let clickytmr, clickycnt = 0;
function clicky(cnt=6, timeout=300) {
clickytmr = clearInterval(clickytmr);
clickycnt++;
if (clickycnt === cnt) {
clickycnt = 0;
return true;
}
clickytmr = setInterval(() => {
clickycnt--;
if (clickycnt <= 0) {
clickycnt = 0;
clickytmr = clearInterval(clickytmr);
}
}, timeout);
return false;
}
$scope.inCareer = false;
bngApi.engineLua("career_career.isCareerActive()", data => {
$scope.inCareer = !!data;
});
vm.buttons = {
big: null,
groups: []
};
{
let buttons = [];
vm.addButton = function (button, draw=true) {
if (!vm.buttons.big) {
vm.buttons.big = button;
} else {
// add new button
buttons.push(button);
// rebuild layout
if (draw)
vm.rebuildButtons();
}
};
let max1 = 3, max2 = 4;
vm.rebuildButtons = function () {
let len = buttons.length;
// find out optimal count per row
let max = max1;
if ((len % max1 || max1) < (len % max2 || max2))
max = max2;
// calculate new layout
let top = len % max || max,
groupsmax = Math.ceil(len / max);
// clear/add rows
let groupslen = vm.buttons.groups.length;
for (let i = 0; i < groupsmax; i++) {
if (i < groupslen)
vm.buttons.groups[i].list = [];
else
vm.buttons.groups.push({ list: [] });
}
// rebuild the layout
let group = 0;
for (let button of buttons) {
if (vm.buttons.groups[group].list.length === (group === 0 ? top : max))
group++;
vm.buttons.groups[group].list.push(button);
}
// allow/disallow wrapping the buttons on even count
for (let grp of vm.buttons.groups) {
let len = grp.list.length;
grp.class = len > 2 && len / 2 % 1 === 0 ? "mainmenu-buttons-smwrap" : null;
}
// sync with fancy-background
setTimeout(fancySync, 100);
};
}
// populate menu with default buttons
for (let button of buttonsDefault)
vm.addButton(button, false);
vm.rebuildButtons();
// broadcast event for an additional buttons
$rootScope.$broadcast('MainMenuButtons', vm.addButton);
vm.openRepo = function () {
window.location.href = 'http-external://www.beamng.com/resources/?ingame=2'
}
vm.handleClick = function(card) {
if(card.action) {
card.action()
return
}
if(card.targetState) {
$state.go(card.targetState)
return
}
}
$scope.fancyblur = false;
function fancySync() {
// find fancy bg
const fancybg = document.querySelector("fancy-background > .img-carousel");
if (!fancybg) {
$scope.fancyblur = false;
return;
}
$scope.fancyblur = true;
$scope.$evalAsync(() => {
// get all target blur elements
const blurs = Array.from(document.querySelectorAll(".fancy-blur > .img-carousel"));
// and connect them to master so they will work in sync
fancybg.__connect(
blurs,
// function to modify images list for targets - function (orig_list) { return orig_list }
// here, we change images from "image.jpg" to "image_blur.jpg"
images => images.map(img => img.replace(/\.(.{3,4})$/, "_blur.$1"))
// note: blurred images are 1280x720 with gaussian blur 6.0 (resize, then blur)
);
});
}
fancySync();
}])