mirror of
https://github.com/lubosz/overte.git
synced 2025-04-26 06:15:32 +02:00
641 lines
21 KiB
JavaScript
641 lines
21 KiB
JavaScript
|
|
if (!Function.prototype.bind) {
|
|
Function.prototype.bind = function(oThis) {
|
|
if (typeof this !== 'function') {
|
|
// closest thing possible to the ECMAScript 5
|
|
// internal IsCallable function
|
|
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
|
|
}
|
|
|
|
var aArgs = Array.prototype.slice.call(arguments, 1),
|
|
fToBind = this,
|
|
fNOP = function() {},
|
|
fBound = function() {
|
|
return fToBind.apply(this instanceof fNOP
|
|
? this
|
|
: oThis,
|
|
aArgs.concat(Array.prototype.slice.call(arguments)));
|
|
};
|
|
|
|
if (this.prototype) {
|
|
// Function.prototype doesn't have a prototype property
|
|
fNOP.prototype = this.prototype;
|
|
}
|
|
fBound.prototype = new fNOP();
|
|
|
|
return fBound;
|
|
};
|
|
}
|
|
Script.include("entityData.js");
|
|
|
|
//
|
|
// var FAR_GRAB_INPUTS = [
|
|
// Controller.Standard.RT
|
|
// Controller.Standard.RTClick
|
|
// Controller.Standard.LT
|
|
// Controller.Standard.LTClick
|
|
// ];
|
|
//
|
|
// var TELEPORT_INPUTS = [
|
|
// Controller.Standard.LeftPrimaryThumb
|
|
// Controller.Standard.RightPrimaryThumb
|
|
// ];
|
|
//
|
|
// function noop(value) { }
|
|
// var FAR_GRAB_MAPPING_NAME = "com.highfidelity.farGrab.disable";
|
|
// var farGrabMapping = Controller.newMapping(FAR_GRAB_MAPPING_NAME);
|
|
// for (var i = 0; i < FAR_GRAB_INPUTS.length; ++i) {
|
|
// mapping.from([FAR_GRAB_INPUTS[i]]).to(noop);
|
|
// }
|
|
//
|
|
// var TELEPORT_MAPPING_NAME = "com.highfidelity.teleport.disable";
|
|
// var teleportMapping = Controller.newMapping(TELEPORT_MAPPING_NAME);
|
|
// for (var i = 0; i < FAR_GRAB_INPUTS.length; ++i) {
|
|
// mapping.from([TELEPORT_INPUTS[i]]).to(noop);
|
|
// }
|
|
//
|
|
// mapping.from([Controller.Standard.RT]).to(noop);
|
|
// mapping.from([Controller.Standard.RTClick]).to(noop);
|
|
//
|
|
// mapping.from([Controller.Standard.LT]).to(noop);
|
|
// mapping.from([Controller.Standard.LTClick]).to(noop);
|
|
//
|
|
// mapping.from([Controller.Standard.RB]).to(noop);
|
|
// mapping.from([Controller.Standard.LB]).to(noop);
|
|
// mapping.from([Controller.Standard.LeftGrip]).to(noop);
|
|
// mapping.from([Controller.Standard.RightGrip]).to(noop);
|
|
//
|
|
// mapping.from([Controller.Standard.LeftPrimaryThumb]).to(noop);
|
|
// mapping.from([Controller.Standard.RightPrimaryThumb]).to(noop);
|
|
//
|
|
// Script.scriptEnding.connect(function() {
|
|
// Controller.disableMapping(MAPPING_NAME);
|
|
// });
|
|
//
|
|
// Controller.enableMapping(MAPPING_NAME);
|
|
|
|
var BASKET_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/Trach-Can-3.fbx";
|
|
var BASKET_COLLIDER_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/Trash-Can-4.obj";
|
|
var successSound = SoundCache.getSound(Script.resolvePath("success48.wav"));
|
|
|
|
function beginsWithFilter(value, key) {
|
|
return value.indexOf(properties[key]) == 0;
|
|
}
|
|
|
|
findEntity = function(properties, searchRadius, filterFn) {
|
|
var entities = findEntities(properties, searchRadius, filterFn);
|
|
return entities.length > 0 ? entities[0] : null;
|
|
}
|
|
|
|
// Return all entities with properties `properties` within radius `searchRadius`
|
|
findEntities = function(properties, searchRadius, filterFn) {
|
|
if (!filterFn) {
|
|
filterFn = function(properties, key, value) {
|
|
return value == properties[key];
|
|
}
|
|
}
|
|
searchRadius = searchRadius ? searchRadius : 100000;
|
|
var entities = Entities.findEntities({ x: 0, y: 0, z: 0 }, searchRadius);
|
|
var matchedEntities = [];
|
|
var keys = Object.keys(properties);
|
|
for (var i = 0; i < entities.length; ++i) {
|
|
var match = true;
|
|
var candidateProperties = Entities.getEntityProperties(entities[i], keys);
|
|
for (var key in properties) {
|
|
if (!filterFn(properties, key, candidateProperties[key])) {
|
|
// This isn't a match, move to next entity
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match) {
|
|
matchedEntities.push(entities[i]);
|
|
}
|
|
}
|
|
|
|
return matchedEntities;
|
|
}
|
|
// On start tutorial...
|
|
|
|
// Load assets
|
|
var NEAR_BOX_SPAWN_NAME = "tutorial/nearGrab/box_spawn";
|
|
var FAR_BOX_SPAWN_NAME = "tutorial/farGrab/box_spawn";
|
|
var NEAR_BASKET_COLLIDER_NAME = "tutorial/nearGrab/basket_collider";
|
|
var FAR_BASKET_COLLIDER_NAME = "tutorial/farGrab/basket_collider";
|
|
var GUN_BASKET_COLLIDER_NAME = "tutorial/equip/basket_collider";
|
|
var GUN_SPAWN_NAME = "tutorial/gun_spawn";
|
|
var GUN_AMMO_NAME = "Tutorial Ping Pong Ball"
|
|
|
|
function spawn(entityData, transform, modifyFn) {
|
|
print("Creating: ", entityData);
|
|
if (!transform) {
|
|
transform = {
|
|
position: { x: 0, y: 0, z: 0 },
|
|
rotation: { x: 0, y: 0, z: 0, w: 1 }
|
|
}
|
|
}
|
|
var ids = [];
|
|
for (var i = 0; i < entityData.length; ++i) {
|
|
var data = entityData[i];
|
|
print("Creating: ", data.name);
|
|
data.position = Vec3.sum(transform.position, data.position);
|
|
data.rotation = Quat.multiply(data.rotation, transform.rotation);
|
|
if (modifyFn) {
|
|
data = modifyFn(data);
|
|
}
|
|
var id = Entities.addEntity(data);
|
|
ids.push(id);
|
|
print("data:", JSON.stringify(data));
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
function parseJSON(jsonString) {
|
|
var data;
|
|
try {
|
|
data = JSON.parse(jsonString);
|
|
} catch(e) {
|
|
data = {};
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function spawnWithTag(entityData, transform, tag) {
|
|
function modifyFn(data) {
|
|
var userData = parseJSON(data.userData);
|
|
userData.tag = tag;
|
|
data.userData = JSON.stringify(userData);
|
|
print("In modify", tag, userData, data.userData);
|
|
return data;
|
|
}
|
|
return spawn(entityData, transform, modifyFn);
|
|
}
|
|
|
|
function deleteEntitiesWithTag(tag) {
|
|
print("searching for...:", tag);
|
|
var entityIDs = findEntitiesWithTag(tag);
|
|
for (var i = 0; i < entityIDs.length; ++i) {
|
|
print("Deleteing:", entityIDs[i]);
|
|
Entities.deleteEntity(entityIDs[i]);
|
|
}
|
|
}
|
|
function editEntitiesWithTag(tag, propertiesOrFn) {
|
|
print("Editing:", tag);
|
|
var entityIDs = findEntitiesWithTag(tag);
|
|
print("Editing...", entityIDs);
|
|
for (var i = 0; i < entityIDs.length; ++i) {
|
|
print("Editing...", entityIDs[i]);
|
|
if (isFunction(propertiesOrFn)) {
|
|
Entities.editEntity(entityIDs[i], propertiesOrFn(entityIDs[i]));
|
|
} else {
|
|
Entities.editEntity(entityIDs[i], propertiesOrFn);
|
|
}
|
|
}
|
|
}
|
|
|
|
function findEntitiesWithTag(tag) {
|
|
return findEntities({ userData: "" }, 10000, function(properties, key, value) {
|
|
data = parseJSON(value);
|
|
return data.tag == tag;
|
|
});
|
|
}
|
|
|
|
// From http://stackoverflow.com/questions/5999998/how-can-i-check-if-a-javascript-variable-is-function-type
|
|
function isFunction(functionToCheck) {
|
|
var getType = {};
|
|
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// STEP: DISABLE CONTROLLERS //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
var stepDisableControllers = function(name) {
|
|
this.tag = name;
|
|
}
|
|
stepDisableControllers.prototype = {
|
|
start: function(onFinish) {
|
|
Menu.setIsOptionChecked("Overlays", false);
|
|
Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'both');
|
|
Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
|
|
nearGrabEnabled: true,
|
|
holdEnabled: false,
|
|
farGrabEnabled: false,
|
|
}));
|
|
onFinish();
|
|
},
|
|
cleanup: function() {
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// STEP: Raise hands above head //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
var stepRaiseAboveHead = function(name) {
|
|
this.tag = name;
|
|
this.tempTag = name + "-temporary";
|
|
}
|
|
stepRaiseAboveHead.prototype = {
|
|
start: function(onFinish) {
|
|
var tag = this.tag;
|
|
|
|
var defaultTransform = {
|
|
position: {
|
|
x: 0.2459,
|
|
y: 0.9011,
|
|
z: 0.7266
|
|
},
|
|
rotation: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
w: 1
|
|
}
|
|
};
|
|
|
|
// Spawn content set
|
|
//spawnWithTag(HandsAboveHeadData, defaultTransform, tag);
|
|
print("raise hands...", this.tag);
|
|
editEntitiesWithTag(this.tag, { visible: true });
|
|
|
|
|
|
this.checkIntervalID = null;
|
|
function checkForHandsAboveHead() {
|
|
print("Checking...");
|
|
if (MyAvatar.getLeftPalmPosition().y > (MyAvatar.getHeadPosition().y + 0.1)) {
|
|
Script.clearInterval(this.checkIntervalID);
|
|
this.checkIntervalID = null;
|
|
this.soundInjector = Audio.playSound(successSound, {
|
|
position: defaultTransform.position,
|
|
volume: 0.7,
|
|
loop: false
|
|
});
|
|
onFinish();
|
|
}
|
|
}
|
|
this.checkIntervalID = Script.setInterval(checkForHandsAboveHead.bind(this), 500);
|
|
},
|
|
cleanup: function() {
|
|
if (this.checkIntervalID != null) {
|
|
Script.clearInterval(this.checkIntervalID);
|
|
}
|
|
editEntitiesWithTag(this.tag, { visible: false, collisionless: 1 });
|
|
deleteEntitiesWithTag(this.tempTag);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// STEP: Near Grab //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
var stepNearGrab = function(name) {
|
|
this.tag = name;
|
|
this.tempTag = name + "-temporary";
|
|
}
|
|
stepNearGrab.prototype = {
|
|
start: function(onFinish) {
|
|
var tag = this.tag;
|
|
|
|
// Spawn content set
|
|
//spawnWithTag(Step1EntityData, null, tag);
|
|
editEntitiesWithTag(this.tag, { visible: true });
|
|
|
|
var basketColliderID = findEntity({ name: NEAR_BASKET_COLLIDER_NAME }, 10000);
|
|
var basketPosition = Entities.getEntityProperties(basketColliderID, 'position').position;
|
|
|
|
function createBlock() {
|
|
var boxSpawnID = findEntity({ name: NEAR_BOX_SPAWN_NAME }, 10000);
|
|
if (!boxSpawnID) {
|
|
print("Error creating block, cannot find spawn");
|
|
return null;
|
|
}
|
|
|
|
Step1BlockData.position = Entities.getEntityProperties(boxSpawnID, 'position').position;
|
|
return spawnWithTag([Step1BlockData], null, this.tempTag)[0];
|
|
}
|
|
|
|
// Enabled grab
|
|
// Create table ?
|
|
// Create blocks and basket
|
|
var boxID = createBlock.bind(this)();
|
|
print("Created", boxID);
|
|
|
|
function onHit() {
|
|
onFinish();
|
|
}
|
|
|
|
// When block collides with basket start step 2
|
|
var checkCollidesTimer = null;
|
|
function checkCollides() {
|
|
print(this.tag, "CHECKING...");
|
|
if (Vec3.distance(basketPosition, Entities.getEntityProperties(boxID, 'position').position) < 0.1) {
|
|
Script.clearInterval(checkCollidesTimer);
|
|
this.soundInjector = Audio.playSound(successSound, {
|
|
position: basketPosition,
|
|
volume: 0.7,
|
|
loop: false
|
|
});
|
|
Script.setTimeout(onHit.bind(this), 1000);
|
|
}
|
|
}
|
|
checkCollidesTimer = Script.setInterval(checkCollides.bind(this), 500);
|
|
|
|
// If block gets too far away or hasn't been touched for X seconds, create a new block and destroy the old block
|
|
},
|
|
cleanup: function() {
|
|
editEntitiesWithTag(this.tag, { visible: false});
|
|
deleteEntitiesWithTag(this.tempTag);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// STEP: Far Grab //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
var stepFarGrab = function(name) {
|
|
this.tag = name;
|
|
this.tempTag = name + "-temporary";
|
|
}
|
|
stepFarGrab.prototype = {
|
|
start: function(onFinish) {
|
|
Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
|
|
farGrabEnabled: true,
|
|
}));
|
|
var tag = this.tag;
|
|
var transform = {
|
|
position: { x: 3, y: 0, z: 0 },
|
|
rotation: { x: 0, y: 0, z: 0, w: 1 }
|
|
}
|
|
|
|
// Spawn content set
|
|
//spawnWithTag(Step1EntityData, transform, tag);
|
|
editEntitiesWithTag(this.tag, { visible: true});
|
|
|
|
var basketColliderID = findEntity({ name: FAR_BASKET_COLLIDER_NAME }, 10000);
|
|
var basketPosition = Entities.getEntityProperties(basketColliderID, 'position').position;
|
|
|
|
function createBlock() {
|
|
var boxSpawnID = findEntity({ name: FAR_BOX_SPAWN_NAME }, 10000);
|
|
if (!boxSpawnID) {
|
|
print("Error creating block, cannot find spawn");
|
|
return null;
|
|
}
|
|
|
|
Step1BlockData.position = Entities.getEntityProperties(boxSpawnID, 'position').position;
|
|
return spawnWithTag([Step1BlockData], null, this.tempTag)[0];
|
|
}
|
|
|
|
// Enabled grab
|
|
// Create table ?
|
|
// Create blocks and basket
|
|
var boxID = createBlock.bind(this)();
|
|
print("Created", boxID);
|
|
|
|
function onHit() {
|
|
onFinish();
|
|
}
|
|
|
|
// When block collides with basket start step 2
|
|
var checkCollidesTimer = null;
|
|
function checkCollides() {
|
|
print("CHECKING...");
|
|
if (Vec3.distance(basketPosition, Entities.getEntityProperties(boxID, 'position').position) < 0.1) {
|
|
Script.clearInterval(checkCollidesTimer);
|
|
this.soundInjector = Audio.playSound(successSound, {
|
|
position: basketPosition,
|
|
volume: 0.7,
|
|
loop: false
|
|
});
|
|
Script.setTimeout(onHit.bind(this), 1000);
|
|
}
|
|
}
|
|
checkCollidesTimer = Script.setInterval(checkCollides.bind(this), 500);
|
|
|
|
// If block gets too far away or hasn't been touched for X seconds, create a new block and destroy the old block
|
|
},
|
|
cleanup: function() {
|
|
editEntitiesWithTag(this.tag, { visible: false});
|
|
deleteEntitiesWithTag(this.tempTag);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// STEP: Equip //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
var stepEquip = function(name) {
|
|
this.tag = name;
|
|
this.tempTag = name + "-temporary";
|
|
}
|
|
stepEquip.prototype = {
|
|
start: function(onFinish) {
|
|
Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
|
|
holdEnabled: true,
|
|
}));
|
|
|
|
var tag = this.tag;
|
|
|
|
var defaultTransform = {
|
|
position: {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.75
|
|
},
|
|
rotation: {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
w: 1
|
|
}
|
|
};
|
|
|
|
// Spawn content set
|
|
//spawnWithTag(StepGunData, defaultTransform, tag);
|
|
editEntitiesWithTag(this.tag, { visible: true});
|
|
|
|
var basketColliderID = findEntity({ name: GUN_BASKET_COLLIDER_NAME }, 10000);
|
|
var basketPosition = Entities.getEntityProperties(basketColliderID, 'position').position;
|
|
|
|
function createGun() {
|
|
var boxSpawnID = findEntity({ name: GUN_SPAWN_NAME }, 10000);
|
|
if (!boxSpawnID) {
|
|
print("Error creating block, cannot find spawn");
|
|
return null;
|
|
}
|
|
|
|
GunData.position = Entities.getEntityProperties(boxSpawnID, 'position').position;
|
|
Vec3.print("spawn", GunData.position);
|
|
print("Adding: ", JSON.stringify(GunData));
|
|
return spawnWithTag([GunData], null, this.tempTag)[0];
|
|
}
|
|
|
|
// Enabled grab
|
|
// Create table ?
|
|
// Create blocks and basket
|
|
var gunID = createGun.bind(this)();
|
|
print("Created", gunID);
|
|
|
|
function onHit() {
|
|
onFinish();
|
|
}
|
|
|
|
// When block collides with basket start step 2
|
|
var checkCollidesTimer = null;
|
|
function checkCollides() {
|
|
print("CHECKING...");
|
|
var ammoIDs = findEntities({ name: GUN_AMMO_NAME }, 15);
|
|
for (var i = 0; i < ammoIDs.length; ++i) {
|
|
if (Vec3.distance(basketPosition, Entities.getEntityProperties(ammoIDs[i], 'position').position) < 0.2) {
|
|
Script.clearInterval(checkCollidesTimer);
|
|
this.soundInjector = Audio.playSound(successSound, {
|
|
position: basketPosition,
|
|
volume: 0.7,
|
|
loop: false
|
|
});
|
|
Script.setTimeout(onHit.bind(this), 1000);
|
|
}
|
|
}
|
|
}
|
|
checkCollidesTimer = Script.setInterval(checkCollides.bind(this), 500);
|
|
|
|
// If block gets too far away or hasn't been touched for X seconds, create a new block and destroy the old block
|
|
},
|
|
cleanup: function() {
|
|
editEntitiesWithTag(this.tag, { visible: false});
|
|
deleteEntitiesWithTag(this.tempTag);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// //
|
|
// STEP: Teleport //
|
|
// //
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
var stepTeleport = function(name) {
|
|
this.tag = name;
|
|
this.tempTag = name + "-temporary";
|
|
}
|
|
stepTeleport.prototype = {
|
|
start: function(onFinish) {
|
|
Messages.sendLocalMessage('Hifi-Teleport-Disabler', 'none');
|
|
Menu.setIsOptionChecked("Overlays", false);
|
|
|
|
editEntitiesWithTag(this.tag, { visible: true });
|
|
},
|
|
cleanup: function() {
|
|
editEntitiesWithTag(this.tag, { visible: false });
|
|
deleteEntitiesWithTag(this.tempTag);
|
|
}
|
|
};
|
|
|
|
function showEntitiesWithTag(tag) {
|
|
editEntitiesWithTag(tag, function(entityID) {
|
|
var userData = Entities.getEntityProperties(entityID, "userData").userData;
|
|
var data = parseJSON(userData);
|
|
var newProperties = {
|
|
visible: data.visible == false ? false : true,
|
|
collisionless: data.collisionless == false ? false : true,
|
|
};
|
|
Entities.editEntity(entityID, newProperties);
|
|
});
|
|
}
|
|
function hideEntitiesWithTag(tag) {
|
|
editEntitiesWithTag(tag, function(entityID) {
|
|
var userData = Entities.getEntityProperties(entityID, "userData").userData;
|
|
var data = parseJSON(userData);
|
|
var newProperties = {
|
|
visible: false,
|
|
collisionless: 1,
|
|
ignoreForCollisions: 1,
|
|
};
|
|
Entities.editEntity(entityID, newProperties);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var STEPS;
|
|
|
|
var currentStepNum = -1;
|
|
var currentStep = null;
|
|
function startTutorial() {
|
|
currentStepNum = -1;
|
|
currentStep = null;
|
|
STEPS = [
|
|
//new stepDisableControllers("step0"),
|
|
new stepRaiseAboveHead("raiseHands"),
|
|
new stepNearGrab("nearGrab"),
|
|
new stepFarGrab("farGrab"),
|
|
new stepEquip("equip"),
|
|
new stepTeleport("teleport"),
|
|
]
|
|
startNextStep();
|
|
}
|
|
|
|
function startNextStep() {
|
|
if (currentStep) {
|
|
currentStep.cleanup();
|
|
}
|
|
|
|
++currentStepNum;
|
|
|
|
if (currentStepNum >= STEPS.length) {
|
|
// Done
|
|
print("DONE WITH TUTORIAL");
|
|
} else {
|
|
print("Starting step", currentStepNum);
|
|
currentStep = STEPS[currentStepNum];
|
|
currentStep.start(startNextStep);
|
|
}
|
|
}
|
|
|
|
function skipTutorial() {
|
|
}
|
|
|
|
function stopTutorial() {
|
|
if (currentStep) {
|
|
currentStep.cleanup();
|
|
}
|
|
currentStepNum = -1;
|
|
currentStep = null;
|
|
}
|
|
|
|
location = "/tutorial";
|
|
startTutorial();
|
|
|
|
Script.scriptEnding.connect(stopTutorial);
|
|
|
|
|
|
|
|
Controller.keyReleaseEvent.connect(function (event) {
|
|
if (event.text == ",") {
|
|
startNextStep();
|
|
} else if (event.text == ".") {
|
|
stopTutorial();
|
|
} else if (event.text == "r") {
|
|
stopTutorial();
|
|
startTutorial();
|
|
}
|
|
});
|