'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: `
News
close
{{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(); }])