// ACCleanupAndSpawnBot.js // // Created by Thijs Wenker on 9/26/2016 // // This Assignment Client script cleans up a the defined // Copyright 2016 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // Times when the cleanup script runs (on a daily basis 00:00 -> 23:59) var RUN_WHEN = [ '0:00', '6:00', '7:00', '8:00', '9:00', '10:00', '11:00', '12:00', '1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00', '8:00', '9:00', '10:00' ]; // Cleanup filters // // Allows the following entry-types: // Name (string) eg: 'This is a cube' // Name Regular Expression (RexExp) eg: /Cube[0-9]+/ // Filter Object (JS Object eg: // { // name: 'Cube', // locked: false, // visible: true // } var CLEANUP_FILTER = [ { locked: 0 } ]; // The script will only run in the domain with the following placename: // (The PLACENAME_LOCK is only effective when the script is ran in Interface, not for Assignment Client scripts) var PLACENAME_LOCK = 'dev-welcome', 'welcome', 'welcome2', 'welcome3'; // IMPORT_ENTITIES supports JSON exported entities using importEntitiesJSON(URL, parentProperties); e.g: // importEntitiesJSON('https://www.acme.com/example/cubes.json', {position: MyAvatar.position}) var IMPORT_ENTITIES = [ importEntitiesJSON('http://hifi-content.s3.amazonaws.com/DomainContent/Welcome%20Area/Scripts/seesaw.json', {position: {x:55.613121032714844, y: 0.29320013523101807, z: -44.694454193115234}}), importEntitiesJSON('http://hifi-content.s3.amazonaws.com/DomainContent/Welcome%20Area/Scripts/domino.json', {position: {"x":33.79003143310547,"y":0.34712982177734375,"z":-103.43145751953125}}), importEntitiesJSON('http://hifi-content.s3.amazonaws.com/DomainContent/Welcome%20Area/Scripts/castle7.json', {position: {"x":-20.238300323486328,"y":-0.2793,"z":-55.61320114135742}}) ]; // You can use DRY RUN mode to test the cleanup filter and see which entities will be // deleted without modifying anything var DRY_RUN = false; var SEARCH_CENTER = {x: -26.7, y: 0.1, z: -0.4}; var SEARCH_AREA = 60000; // search area (sphere) in meters radius // Allows you to manually trigger the cleanups through the messages system var REMOTE_TRIGGER_ENABLED = true; var TRIGGER_CHANNEL = 'cleanUpAndSpawnBot'; var CLEAN_MESSAGE = 'cleanUp'; var SPAWN_MESSAGE = 'spawn'; var CLEAN_AND_SPAWN_MESSAGE = 'cleanUpAndSpawn'; var DEBUG_SCRIPT_OVER_CHANNEL = ''; // Constants const HOURS_IN_DAY = 24; const MINUTES_IN_HOUR = 60; const SECONDS_PER_MINUTE = 60; const MILLISECONDS_PER_SECOND = 1000; const MINUTES_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR; const TIMER_TICKS_PER_MINUTE = SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; const ZERO_UUID = '{00000000-0000-0000-0000-000000000000}'; const IS_AC_SCRIPT = (this.Agent !== undefined); const SANETIZE_PROPERTIES = ['childEntities', 'parentID', 'id']; function isCorrectPlace() { return location !== undefined && location.hostname === PLACENAME_LOCK; } var originalPrint = print; function networkedDebugPrint(message) { originalPrint(message); Messages.sendMessage(DEBUG_SCRIPT_OVER_CHANNEL, message); } if (DEBUG_SCRIPT_OVER_CHANNEL !== '') { print = networkedDebugPrint; } function entityListToTree(entitiesList) { function entityListToTreeRecursive(properties) { properties.childEntities = []; entitiesList.forEach(function(entityProperties) { if (properties.id === entityProperties.parentID) { properties.childEntities.push(entityListToTreeRecursive(entityProperties)); } }); return properties; } var entityTree = []; entitiesList.forEach(function(entityProperties) { if (entityProperties.parentID === undefined || entityProperties.parentID === ZERO_UUID) { entityTree.push(entityListToTreeRecursive(entityProperties)); } }); return entityTree; } // TODO: ATP support (currently the JS API for ATP does not support file links, only hashes) function importEntitiesJSON(importLink, parentProperties) { if (parentProperties === undefined) { parentProperties = {}; } var request = new XMLHttpRequest(); request.open('GET', importLink, false); request.send(); try { var response = JSON.parse(request.responseText); parentProperties.childEntities = entityListToTree(response.Entities); return parentProperties; } catch (e) { print('Failed importing entities JSON because: ' + JSON.stringify(e)); } return null; } //Creates an entity and returns a mixed object of the creation properties and the assigned entityID var createEntity = function(entityProperties, parent) { if (parent.rotation !== undefined) { if (entityProperties.rotation !== undefined) { entityProperties.rotation = Quat.multiply(parent.rotation, entityProperties.rotation); } else { entityProperties.rotation = parent.rotation; } } if (parent.position !== undefined) { var localPosition = (parent.rotation !== undefined) ? Vec3.multiplyQbyV(parent.rotation, entityProperties.position) : entityProperties.position; entityProperties.position = Vec3.sum(localPosition, parent.position) } if (parent.id !== undefined) { entityProperties.parentID = parent.id; } entityProperties.id = Entities.addEntity(entityProperties); return entityProperties; }; var createEntitiesFromTree = function(entityTree, parent) { if (parent === undefined) { parent = {}; } var createdTree = []; entityTree.forEach(function(entityProperties) { var sanetizedProperties = {}; Object.keys(entityProperties).forEach(function(propertyKey) { if (!entityProperties.hasOwnProperty(propertyKey) || SANETIZE_PROPERTIES.indexOf(propertyKey) !== -1) { return true; } sanetizedProperties[propertyKey] = entityProperties[propertyKey]; }); // Allow for non-entity parent objects, this allows us to offset groups of entities to a specific position/rotation var parentProperties = sanetizedProperties; if (entityProperties.type !== undefined) { parentProperties = createEntity(sanetizedProperties, parent); } if (entityProperties.childEntities !== undefined) { parentProperties.childEntities = createEntitiesFromTree(entityProperties.childEntities, parentProperties); } createdTree.push(parentProperties); }); return createdTree; }; function getTimeUntilNextRun(times) { var parsedTimes = []; var now = new Date(); var currentMinuteInDay = (MINUTES_IN_HOUR * now.getHours()) + now.getMinutes(); times.forEach(function(timeText) { try { var timeArray = timeText.split(':'); var hour = parseInt(timeArray[0]); var minute = parseInt(timeArray[1]); var minuteInDay = (MINUTES_IN_HOUR * hour) + minute; var minutesToNextRun = minuteInDay - currentMinuteInDay + (minuteInDay <= currentMinuteInDay ? MINUTES_IN_DAY : 0); parsedTimes.push(TIMER_TICKS_PER_MINUTE * minutesToNextRun); } catch (e) { print('Had some trouble while parsing the following time: ' + timeText); } }); return Math.min.apply(Math, parsedTimes); } function triggerEventOn(times, callback) { var timeout = getTimeUntilNextRun(times); print('Set timeout to ' + timeout + ' ms.'); Script.setTimeout(callback, timeout); } var doCleanup = function() { print('Doing Cleanup and Spawn.'); purgeEntities(); importEntities(); triggerEventOn(RUN_WHEN, doCleanup); }; var testFilter = function(filter, properties) { var testFilter = typeof(filter) === 'object' ? filter : {name: filter}; for (key in testFilter) { if (!testFilter.hasOwnProperty(key)) { continue; } if (testFilter[key] !== properties[key]) { return false; } } return true; }; var purgeEntities = function() { if (!IS_AC_SCRIPT && !isCorrectPlace()) { print('The PLACENAME_LOCK does not match the current placename, aborting purgeEntities.') return; } var propertiesToRequest = ['name']; CLEANUP_FILTER.forEach(function(filter) { if (typeof(filter) === 'object') { Object.keys(filter).forEach(function(property) { if (propertiesToRequest.indexOf(property) === -1) { propertiesToRequest.push(property); } }); } }); Entities.findEntities(SEARCH_CENTER, SEARCH_AREA).forEach(function(entityID) { var entityProperties = Entities.getEntityProperties(entityID, propertiesToRequest); var deleteEntity = false; CLEANUP_FILTER.forEach(function(filter) { if (testFilter(filter, entityProperties)) { deleteEntity = true; return true; } }); if (deleteEntity) { print(DRY_RUN ? '[DRY_RUN] Would have removed: ' : 'removing: ' + entityProperties.id + ' ' + entityProperties.name); if (!DRY_RUN) { Entities.deleteEntity(entityProperties.id); } } }); }; var importEntities = function() { if (DRY_RUN) { print('Skipping importEntities for the DRY_RUN.'); return {}; } if (!IS_AC_SCRIPT && !isCorrectPlace()) { print('The PLACENAME_LOCK does not match the current placename, aborting importEntities.') return {}; } return createEntitiesFromTree(IMPORT_ENTITIES); }; triggerEventOn(RUN_WHEN, doCleanup); if (REMOTE_TRIGGER_ENABLED) { Messages.messageReceived.connect(function(channel, message, senderUUID, localOnly) { if (channel !== TRIGGER_CHANNEL) { return; } if (message === CLEAN_MESSAGE || message === CLEAN_AND_SPAWN_MESSAGE) { purgeEntities(); } if (message === SPAWN_MESSAGE || message === CLEAN_AND_SPAWN_MESSAGE) { importEntities(); } }); Messages.subscribe(TRIGGER_CHANNEL); } // Assignment Client related code: if (IS_AC_SCRIPT) { Agent.isAvatar = true; Avatar.skeletonModelURL = 'http://hifi-content.s3.amazonaws.com/ozan/dev/avatars/invisible_avatar/invisible_avatar.fst'; var initialized = false; function update(deltaTime) { if (!initialized) { if (Entities.serversExist() && Entities.canRez()) { Entities.setPacketsPerSecond(60000); EntityViewer.setPosition(SEARCH_CENTER); EntityViewer.setCenterRadius(SEARCH_AREA); Script.setInterval(function() { EntityViewer.queryOctree(); }, 1000); initialized = true; Script.update.disconnect(update); } return; } } Script.update.connect(update); }