Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: Chiru/ChiruAddons
base: 6b825ba691
...
head fork: Chiru/ChiruAddons
compare: bb2f044c45
  • 9 commits
  • 28 files changed
  • 0 commit comments
  • 3 contributors
Showing with 25,092 additions and 0 deletions.
  1. +16 −0 Scenes/ClientSideAvatar/Readme.txt
  2. BIN  Scenes/ClientSideAvatar/WoodPallet.mesh
  3. +35 −0 Scenes/ClientSideAvatar/av_common.js
  4. +158 −0 Scenes/ClientSideAvatar/avatar.txml
  5. +877 −0 Scenes/ClientSideAvatar/avatar_entity.js
  6. +137 −0 Scenes/ClientSideAvatar/avatarapplication.js
  7. +69 −0 Scenes/ClientSideAvatar/avatarmenu.js
  8. +110 −0 Scenes/ClientSideAvatar/crosshair.js
  9. +73 −0 Scenes/ClientSideAvatar/exampleavataraddon.js
  10. BIN  Scenes/ClientSideAvatar/firstpersonmouseicon.png
  11. BIN  Scenes/ClientSideAvatar/fish.mesh
  12. +31 −0 Scenes/ClientSideAvatar/instructions.js
  13. BIN  Scenes/FlyingAvatar/WoodPallet.mesh
  14. +145 −0 Scenes/FlyingAvatar/avatar.txml
  15. +278 −0 Scenes/FlyingAvatar/default_avatar.avatar
  16. BIN  Scenes/FlyingAvatar/fish.mesh
  17. +121 −0 Scenes/FlyingAvatar/flyerapplication.js
  18. +446 −0 Scenes/FlyingAvatar/flyingavatar.js
  19. +25 −0 Scenes/FlyingAvatar/instructions.js
  20. BIN  Scenes/ObjectGrab/WoodPallet.mesh
  21. +24 −0 Scenes/ObjectGrab/firefox.Material.0.material
  22. +24 −0 Scenes/ObjectGrab/firefox.Material.1.material
  23. +27 −0 Scenes/ObjectGrab/firefox.Material.2.material
  24. BIN  Scenes/ObjectGrab/firefox.mesh
  25. +22,192 −0 Scenes/ObjectGrab/firefox.mesh.xml
  26. BIN  Scenes/ObjectGrab/firefox_logo_smalll.png
  27. +191 −0 Scenes/ObjectGrab/objectgrab.js
  28. +113 −0 Scenes/ObjectGrab/objectgrab.txml
View
16 Scenes/ClientSideAvatar/Readme.txt
@@ -0,0 +1,16 @@
+This test scene implements avatars. It uses two script files: jsmodules/avatar/avatarapplication.js, which hooks
+to connects/disconnects of users and creates/deletes avatars for them, as well as handles the client's camera switching
+(ctrl+tab), and jsmodules/avatar/simpleavatar.js, which implements movement & animation of a single avatar.
+
+To test, copy default_avatar.xml, fish.mesh & WoodPallet.mesh to your tundra bin/data/assets directory, and avatar.xml
+to your bin directory.
+
+Then, run the server and load the scene on it by drag-and-dropping avatar.txml to the main window, or by using console
+command loadscene(avatar.txml)
+
+Next, start one or more clients and connect to the server. Each client should get an avatar that can be controlled
+with WASD + arrows + mouse. F toggles fly mode, space flies up and C flies down. Space jumps when you are not in fly mode.
+In addition there is a example how to make addons to the default functionality of simpleavatar.js. exampleavataraddon.js
+adds Q to make a wave gesture and R to toggle sitting on the ground. Mouse scroll and +/- zooms in/out.
+
+Only third person camera is currently implemented. Also note that collisions are disabled when flying for now.
View
BIN  Scenes/ClientSideAvatar/WoodPallet.mesh
Binary file not shown
View
35 Scenes/ClientSideAvatar/av_common.js
@@ -0,0 +1,35 @@
+function log(msg) {
+ print("[Avatar app] " + msg);
+}
+
+function dc_set(ent, key, val) {
+ var dc = ent.GetOrCreateComponent("EC_DynamicComponent");
+ if (!dc.ContainsAttribute(key)) {
+ // log("creating attr");
+ dc.CreateAttribute("qvariant", key);
+ if (!dc.ContainsAttribute(key)) {
+ log("CreateAttribute didn't work!");
+ throw new Error("Couldn't create DynamicComponent key " + key);
+ }
+ }
+ dc.SetAttribute(key, val);
+ // log("set attribute: " + key + ": " + val);
+ // log("read back attribute: " + key + ": " + dc.GetAttribute(key));
+}
+
+function dc_get(ent, key) {
+ var dc = ent.GetOrCreateComponent("EC_DynamicComponent");
+ // log("read back (3) attribute: " + dc.GetAttribute("connectionID"));
+ var val = dc.GetAttribute(key);
+ // log("get attribute: " + key + ": " + val);
+ // log("contains: " + dc.ContainsAttribute(key));
+ return val;
+}
+
+function clear_av_connectionids(scene) {
+ var av_ents = scene.GetEntitiesWithComponent("EC_Avatar");
+ for (var i = 0; i < av_ents.length; i++) {
+ var ent = av_ents[i];
+ dc_set(ent, "connectionID", "");
+ }
+}
View
158 Scenes/ClientSideAvatar/avatar.txml
@@ -0,0 +1,158 @@
+<!DOCTYPE Scene>
+<scene>
+ <entity id="1" sync="1">
+ <component type="EC_Script" sync="1">
+ <attribute value="avatarmenu.js" name="Script ref"/>
+ <attribute value="true" name="Run on load"/>
+ <attribute value="0" name="Run mode"/>
+ <attribute value="" name="Script application name"/>
+ <attribute value="" name="Script class name"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="AvatarMenu" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ </entity>
+ <entity id="2" sync="1">
+ <component type="EC_Script" sync="1">
+ <attribute value="avatarapplication.js;avatar_entity.js;exampleavataraddon.js" name="Script ref"/>
+ <attribute value="true" name="Run on load"/>
+ <attribute value="0" name="Run mode"/>
+ <attribute value="AvatarApp" name="Script application name"/>
+ <attribute value="" name="Script class name"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="AvatarApp" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ </entity>
+ <entity id="3" sync="1">
+ <component type="EC_Mesh" sync="1">
+ <attribute value="0,0,0,0,0,0,1,1,1" name="Transform"/>
+ <attribute value="fish.mesh" name="Mesh ref"/>
+ <attribute value="" name="Skeleton ref"/>
+ <attribute value="" name="Mesh materials"/>
+ <attribute value="0" name="Draw distance"/>
+ <attribute value="true" name="Cast shadows"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="Fish" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_Placeable" sync="1">
+ <attribute value="1.45201,-4.65185,5.40487,-47.8323,42.1262,-145.378,1,1,1" name="Transform"/>
+ <attribute value="false" name="Show bounding box"/>
+ <attribute value="true" name="Visible"/>
+ <attribute value="1" name="Selection layer"/>
+ <attribute value="" name="Parent entity ref"/>
+ <attribute value="" name="Parent bone name"/>
+ </component>
+ <component type="EC_RigidBody" sync="1">
+ <attribute value="10" name="Mass"/>
+ <attribute value="6" name="Shape type"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Size"/>
+ <attribute value="fish.mesh" name="Collision mesh ref"/>
+ <attribute value="0.5" name="Friction"/>
+ <attribute value="0" name="Restitution"/>
+ <attribute value="0" name="Linear damping"/>
+ <attribute value="0" name="Angular damping"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Linear factor"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Angular factor"/>
+ <attribute value="false" name="Kinematic"/>
+ <attribute value="false" name="Phantom"/>
+ <attribute value="false" name="Draw Debug"/>
+ <attribute value="-0.001488 0.007011 -0.005740" name="Linear velocity"/>
+ <attribute value="-0.981553 -0.320282 -0.493557" name="Angular velocity"/>
+ <attribute value="-1" name="Collision Layer"/>
+ <attribute value="-1" name="Collision Mask"/>
+ </component>
+ </entity>
+ <entity id="4" sync="1">
+ <component type="EC_Mesh" sync="1">
+ <attribute value="0,0,0,0,0,0,0.14,0.2,0.14" name="Transform"/>
+ <attribute value="WoodPallet.mesh" name="Mesh ref"/>
+ <attribute value="" name="Skeleton ref"/>
+ <attribute value="" name="Mesh materials"/>
+ <attribute value="0" name="Draw distance"/>
+ <attribute value="true" name="Cast shadows"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="Floor" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_Placeable" sync="1">
+ <attribute value="0,-5,0,0,0,0,100,1,100" name="Transform"/>
+ <attribute value="false" name="Show bounding box"/>
+ <attribute value="true" name="Visible"/>
+ <attribute value="1" name="Selection layer"/>
+ <attribute value="" name="Parent entity ref"/>
+ <attribute value="" name="Parent bone name"/>
+ </component>
+ <component type="EC_RigidBody" sync="1">
+ <attribute value="0" name="Mass"/>
+ <attribute value="0" name="Shape type"/>
+ <attribute value="1.000000 0.200000 1.000000" name="Size"/>
+ <attribute value="" name="Collision mesh ref"/>
+ <attribute value="0.5" name="Friction"/>
+ <attribute value="0" name="Restitution"/>
+ <attribute value="0" name="Linear damping"/>
+ <attribute value="0" name="Angular damping"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Linear factor"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Angular factor"/>
+ <attribute value="false" name="Kinematic"/>
+ <attribute value="false" name="Phantom"/>
+ <attribute value="false" name="Draw Debug"/>
+ <attribute value="0.000000 0.000000 0.000000" name="Linear velocity"/>
+ <attribute value="0.000000 0.000000 0.000000" name="Angular velocity"/>
+ <attribute value="-1" name="Collision Layer"/>
+ <attribute value="-1" name="Collision Mask"/>
+ </component>
+ </entity>
+ <entity id="5" sync="1">
+ <component type="EC_Name" sync="1">
+ <attribute value="Instructions" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_Script" sync="1">
+ <attribute value="instructions.js" name="Script ref"/>
+ <attribute value="true" name="Run on load"/>
+ <attribute value="0" name="Run mode"/>
+ <attribute value="" name="Script application name"/>
+ <attribute value="" name="Script class name"/>
+ </component>
+ </entity>
+ <entity id="6" sync="1">
+ <component type="EC_Name" sync="1">
+ <attribute value="Environment" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_EnvironmentLight" sync="1">
+ <attribute value="0.638999999 0.638999999 0.638999999 1" name="Sunlight color"/>
+ <attribute value="0.363999993 0.363999993 0.363999993 1" name="Ambient light color"/>
+ <attribute value="0.930000007 0.930000007 0.930000007 1" name="Sunlight diffuse color"/>
+ <attribute value="-1.000000 -1.000000 -1.000000" name="Sunlight direction vector"/>
+ <attribute value="true" name="Sunlight cast shadows"/>
+ </component>
+ <component type="EC_Sky" sync="1">
+ <attribute value="RexSkyBox" name="Material"/>
+ <attribute value="rex_sky_front.dds;rex_sky_back.dds;rex_sky_left.dds;rex_sky_right.dds;rex_sky_top.dds;rex_sky_bot.dds" name="Texture"/>
+ <attribute value="50" name="Distance"/>
+ <attribute value="0.000000 0.000000 0.000000 1.000000" name="Orientation"/>
+ <attribute value="true" name="Draw first"/>
+ </component>
+ </entity>
+ <entity id="7" sync="1">
+ <component type="EC_Placeable" sync="1">
+ <attribute value="1.07871,16.6713,35.9556,-33.9,-0.60001,0,1,1,1" name="Transform"/>
+ <attribute value="false" name="Show bounding box"/>
+ <attribute value="true" name="Visible"/>
+ <attribute value="1" name="Selection layer"/>
+ <attribute value="" name="Parent entity ref"/>
+ <attribute value="" name="Parent bone name"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="FreeLookCameraSpawnPos" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ </entity>
+</scene>
View
877 Scenes/ClientSideAvatar/avatar_entity.js
@@ -0,0 +1,877 @@
+// !ref: default_avatar.avatar
+
+// A simple walking avatar running on client side
+
+engine.IncludeFile("av_common.js");
+
+if (!server.IsRunning() && !framework.IsHeadless())
+{
+ engine.ImportExtension("qt.core");
+ engine.ImportExtension("qt.gui");
+}
+
+// A simple walking avatar with physics & 1st/3rd person camera
+function SimpleAvatar(entity, comp)
+{
+ // Store the entity reference
+ this.me = entity;
+
+ this.rotateSpeed = 100.0;
+ this.mouseRotateSensitivity = 0.2;
+ this.moveForce = 15.0;
+ this.flySpeedFactor = 0.25;
+ this.dampingForce = 3.0;
+ this.walkAnimSpeed = 0.5;
+ this.avatarCameraHeight = 1.0;
+ this.avatarMass = 10;
+
+ // Tracking motion with entity actions
+ this.motionX = 0;
+ this.motionY = 0;
+ this.motionZ = 0;
+
+ // Clientside yaw, pitch & rotation state
+ this.yaw = 0;
+ this.pitch = 0;
+ this.rotate = 0;
+
+ // Needed bools for logic
+ this.isServer = server.IsRunning() || server.IsAboutToStart();
+ this.ownAvatar = false;
+ this.flying = false;
+ this.falling = false;
+ this.isMouseLookLockedOnX = true;
+
+ // Animation detection
+ this.standAnimName = "Stand";
+ this.walkAnimName = "Walk";
+ this.flyAnimName = "Fly";
+ this.hoverAnimName = "Hover";
+ this.animList = [this.standAnimName, this.walkAnimName, this.flyAnimName, this.hoverAnimName];
+
+ this.animsDetected = false;
+ this.listenGesture = false;
+
+ if (this.isServer)
+ this.ServerInitialize();
+ else
+ this.ClientInitialize();
+}
+
+SimpleAvatar.prototype.OnScriptObjectDestroyed = function() {
+ // Must remember to manually disconnect subsystem signals, otherwise they'll continue to get signalled
+ if (!this.isServer) {
+ scene.physics.Updated.disconnect(this, this.ClientUpdatePhysics);
+ frame.Updated.disconnect(this, this.AnimationUpdate);
+ frame.Updated.disconnect(this, this.ClientUpdate);
+ frame.Updated.disconnect(this, this.ClientUpdate2);
+ }
+}
+
+SimpleAvatar.prototype.ServerInitialize = function() {
+ var rigidbody = this.me.GetOrCreateComponent("EC_RigidBody");
+ rigidbody.AssertAuthority(false);
+}
+
+SimpleAvatar.prototype.ClientInitialize2 = function() {
+ // This is ServerInitialize in the standard avatar app.
+
+ // Create the avatar component & set the avatar appearance. The
+ // avatar component will create the mesh & animationcontroller,
+ // once the avatar asset has loaded
+
+ log("ClientInitialize2 called");
+
+ var avatar = this.me.GetOrCreateComponent("EC_Avatar");
+
+ var me_connid = dc_get(this.me, "connectionID");
+ var thisclient_connid = client.GetConnectionID();
+
+ // connection id is set in avatar app UserConnected handler and cleared in UserDisconnected handler
+ if (me_connid != thisclient_connid) {
+ log(".. nope.");
+ return;
+ }
+ // Try to dig login param avatar. This seemed like the easiest way to do it.
+ // Parse the connection id from the entity name and get a connection for him,
+ // check if a custom av url was passed when this client logged in.
+
+ // var entName = this.me.name;
+ // var indexNum = entName.substring(6); // 'Avatar' == 6, we want the number after that
+ // var clientPtr = server.GetUserConnection(parseInt(indexNum, 10));
+
+ // Default avatar ref
+ var avatarurl = "default_avatar.avatar";
+
+ var avatarurlProp = client.GetLoginProperty("avatarurl");
+ if (avatarurlProp && avatarurlProp.length > 0) {
+ debug.Log("Avatar from login parameters enabled: " + avatarurlProp);
+ avatarurl = avatarurlProp;
+ }
+
+ var r = avatar.appearanceRef;
+ r.ref = avatarurl;
+ avatar.appearanceRef = r;
+
+ // Get rigid body component and set physics properties
+ var rigidbody = this.me.GetOrCreateComponent("EC_RigidBody");
+ rigidbody.AssertAuthority(true);
+ var sizeVec = new float3(0.5, 2.4, 0.5);
+ rigidbody.mass = this.avatarMass;
+ rigidbody.shapeType = 3; // Capsule
+ rigidbody.size = sizeVec;
+ rigidbody.angularFactor = float3.zero; // Set zero angular factor so that body stays upright
+
+ // Create dynamic component attributes for disabling/enabling functionality, and for camera distance / 1st/3rd mode
+ var attrs = this.me.dynamiccomponent;
+ attrs.CreateAttribute("bool", "enableWalk");
+ attrs.CreateAttribute("bool", "enableJump");
+ attrs.CreateAttribute("bool", "enableFly");
+ attrs.CreateAttribute("bool", "enableRotate");
+ attrs.CreateAttribute("bool", "enableAnimation");
+ attrs.CreateAttribute("bool", "enableZoom");
+ attrs.CreateAttribute("real", "cameraDistance");
+ attrs.SetAttribute("enableWalk", true);
+ attrs.SetAttribute("enableJump", true);
+ attrs.SetAttribute("enableFly", true);
+ attrs.SetAttribute("enableRotate", true);
+ attrs.SetAttribute("enableAnimation", true);
+ attrs.SetAttribute("enableZoom", true);
+ attrs.SetAttribute("cameraDistance", 7.0);
+
+ // Create an inactive proximitytrigger, so that other proximitytriggers can detect the avatar
+ // var proxtrigger = me.GetOrCreateComponent("EC_ProximityTrigger");
+ // proxtrigger.active = false;
+
+ // Hook to physics update
+ scene.physics.Updated.connect(this, this.ClientUpdatePhysics);
+
+ // Hook to tick update for animation update
+ frame.Updated.connect(this, this.AnimationUpdate);
+
+ // Connect actions. These come from the client side inputmapper
+ this.me.Action("Move").Triggered.connect(this, this.ClientHandleMove);
+ this.me.Action("Stop").Triggered.connect(this, this.ClientHandleStop);
+ this.me.Action("ToggleFly").Triggered.connect(this, this.ClientHandleToggleFly);
+ this.me.Action("SetRotation").Triggered.connect(this, this.ClientHandleSetRotation);
+
+ rigidbody.PhysicsCollision.connect(this, this.ClientHandleCollision);
+}
+
+SimpleAvatar.prototype.AnimationUpdate = function(frametime) {
+ var attrs = this.me.dynamiccomponent;
+
+ if (!this.animsDetected) {
+ this.CommonFindAnimations();
+ }
+
+ // If walk enable was toggled off, make sure the motion state is cleared
+ if (!attrs.GetAttribute("enableWalk"))
+ {
+ this.motionX = 0;
+ this.motionZ = 0;
+ }
+
+ // If flying enable was toggled off, but we are still flying, disable now
+ if ((this.flying) && (!attrs.GetAttribute("enableFly")))
+ this.ClientSetFlying(false);
+
+ this.CommonUpdateAnimation(frametime);
+}
+
+SimpleAvatar.prototype.ClientHandleCollision = function(ent, pos, normal, distance, impulse, newCollision) {
+ //log("collision handler called");
+ if (this.falling && newCollision) {
+ this.falling = false;
+ this.SetAnimationState();
+ }
+}
+
+SimpleAvatar.prototype.ClientUpdatePhysics = function(frametime) {
+ var placeable = this.me.placeable;
+ var rigidbody = this.me.rigidbody;
+ var attrs = this.me.dynamiccomponent;
+ var transform = placeable.transform;
+
+ // don't fall to china
+ if (transform.pos.z < -50) {
+ transform.pos.z = 20;
+ placeable.transform = transform;
+ ServerHandleToggleFly();
+ }
+
+ if (!this.flying) {
+ // Apply motion force
+ // If diagonal motion, normalize
+ if (this.motionX != 0 || this.motionZ != 0) {
+ var impulse = new float3(this.motionX, 0, -this.motionZ).Normalized().Mul(this.moveForce);
+ var tm = placeable.LocalToWorld();
+ impulse = tm.MulDir(impulse);
+ rigidbody.ApplyImpulse(impulse);
+ }
+
+ // Apply jump
+ if (this.motionY == 1 && !this.falling) {
+ if (attrs.GetAttribute("enableJump")) {
+ var jumpVec = new float3(0, 75, 0);
+ this.motionY = 0;
+ this.falling = true;
+ rigidbody.ApplyImpulse(jumpVec);
+ }
+ }
+
+ // Apply damping. Only do this if the body is active, because otherwise applying forces
+ // to a resting object wakes it up
+ if (rigidbody.IsActive()) {
+ var dampingVec = rigidbody.GetLinearVelocity();
+ dampingVec.x = -this.dampingForce * dampingVec.x;
+ dampingVec.y = 0;
+ dampingVec.z = -this.dampingForce * dampingVec.z;
+ rigidbody.ApplyImpulse(dampingVec);
+ }
+ } else {
+ // Manually move the avatar placeable when flying
+ // this has the downside of no collisions.
+ // Feel free to reimplement properly with mass enabled.
+ var avTransform = placeable.transform;
+
+ // Make a vector where we have moved
+ var moveVec = new float3(this.motionX * this.flySpeedFactor, this.motionY * this.flySpeedFactor, -this.motionZ * this.flySpeedFactor);
+
+ // Apply that with av looking direction to the current position
+ var offsetVec = placeable.LocalToWorld().MulDir(moveVec);
+ avTransform.pos.x = avTransform.pos.x + offsetVec.x;
+ avTransform.pos.y = avTransform.pos.y + offsetVec.y;
+ avTransform.pos.z = avTransform.pos.z + offsetVec.z;
+
+ placeable.transform = avTransform;
+ }
+}
+
+SimpleAvatar.prototype.ClientHandleMove = function(param) {
+ //log("ClientHandleMove " + param);
+ if (dc_get(this.me, "enableWalk")) {
+ if (param == "forward") {
+ this.motionZ = 1;
+ }
+ if (param == "back") {
+ this.motionZ = -1;
+ }
+ if (param == "right") {
+ this.motionX = 1;
+ }
+ if (param == "left") {
+ this.motionX = -1;
+ }
+ }
+
+ if (param == "up") {
+ this.motionY = 1;
+ }
+ if (param == "down") {
+ this.motionY = -1;
+ }
+
+ this.SetAnimationState();
+}
+
+SimpleAvatar.prototype.ClientHandleStop = function(param) {
+ // log("ClientHandleStop " + param);
+ if ((param == "forward") && (this.motionZ == 1)) {
+ this.motionZ = 0;
+ }
+ if ((param == "back") && (this.motionZ == -1)) {
+ this.motionZ = 0;
+ }
+ if ((param == "right") && (this.motionX == 1)) {
+ this.motionX = 0;
+ }
+ if ((param == "left") && (this.motionX == -1)) {
+ this.motionX = 0;
+ }
+ if ((param == "up") && (this.motionY == 1)) {
+ this.motionY = 0;
+ }
+ if ((param == "down") && (this.motionY == -1)) {
+ this.motionY = 0;
+ }
+
+ this.SetAnimationState();
+}
+
+SimpleAvatar.prototype.ClientHandleToggleFly = function() {
+ this.ClientSetFlying(!this.flying);
+}
+
+SimpleAvatar.prototype.ClientSetFlying = function(newFlying) {
+ var attrs = this.me.dynamiccomponent;
+ if (!attrs.GetAttribute("enableFly"))
+ newFlying = false;
+
+ if (this.flying == newFlying)
+ return;
+
+ var rigidbody = this.me.rigidbody;
+ this.flying = newFlying;
+ if (this.flying) {
+ rigidbody.mass = 0;
+ } else {
+ var placeable = this.me.placeable;
+ // Set mass back for collisions
+ rigidbody.mass = this.avatarMass;
+ // Push avatar a bit to the fly direction
+ // so the motion does not just stop to a wall
+ var moveVec = new float3(this.motionX * 120, this.motionY * 120, -this.motionZ * 120);
+ var pushVec = placeable.LocalToWorld().MulDir(moveVec);
+ rigidbody.ApplyImpulse(pushVec);
+ }
+ this.SetAnimationState();
+}
+
+SimpleAvatar.prototype.ClientHandleSetRotation = function(param) {
+ if (dc_get(this.me, "enableRotate")) {
+ var rot = new float3(0, parseFloat(param), 0);
+ this.me.rigidbody.SetRotation(rot);
+ }
+}
+
+SimpleAvatar.prototype.SetAnimationState = function() {
+ // Not flying: Stand, Walk or Crouch
+ var animName = this.standAnimName;
+ if ((this.motionX != 0) || (this.motionZ != 0)) {
+ animName = this.walkAnimName;
+ }
+
+ // Flying: Fly if moving forward or back, otherwise hover
+ if (this.flying || this.falling) {
+ animName = this.flyAnimName;
+ if (this.motionZ == 0)
+ animName = this.hoverAnimName;
+ }
+
+ if (animName == "") {
+ return;
+ }
+
+ // Update the variable to sync to client if changed
+ var animcontroller = this.me.animationcontroller;
+ if (animcontroller != null) {
+ if (animcontroller.animationState != animName) {
+ animcontroller.animationState = animName;
+ }
+ }
+}
+
+SimpleAvatar.prototype.ClientInitialize = function() {
+ // Set all avatar entities as temprary also on the clients.
+ // This is already done in the server but the info seems to not travel to the clients!
+ this.me.SetTemporary(true);
+
+ // TODO t1 ver connected Move action to ClientHandleMove here unconditionally, check
+
+ // Check if this is our own avatar
+ // Note: bad security. For now there's no checking who is allowed to invoke actions
+ // on an entity, and we could theoretically control anyone's avatar
+
+ var thisclient_connid = client.GetConnectionID();
+
+ log("me = " + me);
+ // log("this.me = " + this.me); // heh, this blows up
+
+ log("ent id: " + this.me.id);
+ // log("dc keys: ");
+ // for (var i = 0; i < this.me.dynamiccomponent.GetNumAttributes(); i++)
+ // log(" " + this.me.dynamiccomponent.GetAttributeName(i));
+ if (dc_get(this.me, "foo") != "x")
+ log("canary dc check failed");
+
+ // scene.AttributeChanged.connect(function(comp, attr, changetype) {
+ // log("attribute changed: " + attr.Owner() + "type: " + changetype);
+ // });
+ this.me.dynamiccomponent.AttributeChanged.connect(
+ function (key, type) {
+ log("dc attribute " + key + " change, time=" + new Date().getTime());
+ });
+
+ dc_set(this.me, "testing", "fooo");
+ // connection id is set in avatar app UserConnected handler and cleared in UserDisconnected handler
+ var me_connid = dc_get(this.me, "connectionID");
+ log("get avatar connid time=" +new Date().getTime());
+ log("checking if own avatar, client has " + thisclient_connid + ", me ent has " + me_connid);
+ if (me_connid == undefined) {
+ log("XXXXXXXXXXXXX me.connid is undefined, bug!");
+ return;
+ }
+ if (me_connid == thisclient_connid) {
+ this.ownAvatar = true;
+ this.ClientCreateInputMapper();
+ this.ClientCreateAvatarCamera();
+ var soundlistener = this.me.GetOrCreateComponent("EC_SoundListener", 2, false);
+ soundlistener.active = true;
+
+ this.me.Action("MouseScroll").Triggered.connect(this, this.ClientHandleMouseScroll);
+ this.me.Action("Zoom").Triggered.connect(this, this.ClientHandleKeyboardZoom);
+ this.me.Action("Rotate").Triggered.connect(this, this.ClientHandleRotate);
+ this.me.Action("StopRotate").Triggered.connect(this, this.ClientHandleStopRotate);
+ this.ClientInitialize2();
+ }
+ else
+ {
+ // Make hovering name tag for other clients
+ var clientName = this.me.GetComponent("EC_Name");
+ if (clientName != null) {
+ // Description holds the actual login name
+ if (clientName.description != "") {
+ var nameTag = this.me.GetOrCreateComponent("EC_HoveringText", 2, false);
+ if (nameTag != null) {
+ nameTag.SetTemporary(true);
+ nameTag.text = clientName.description;
+ var pos = nameTag.position;
+ pos.y = 1.3;
+ nameTag.position = pos;
+ nameTag.fontSize = 90;
+ var color = new Color(0.2, 0.2, 0.2, 1.0);
+ nameTag.backgroundColor = color;
+ var font_color = new Color(1.0, 1.0, 1.0, 1.0);
+ nameTag.fontColor = font_color;
+ }
+ }
+ }
+ }
+
+ // Hook to tick update to update visual effects (both own and others' avatars)
+ log("connect ClientUpdate")
+ frame.Updated.connect(this, this.ClientUpdate);
+}
+
+SimpleAvatar.prototype.IsCameraActive = function() {
+ var cameraentity = scene.GetEntityByName("AvatarCamera");
+ if (cameraentity == null)
+ return false;
+ var camera = cameraentity.camera;
+ return camera.IsActive();
+}
+
+SimpleAvatar.prototype.ClientUpdate2 = function(frametime) {
+ // TODO hook this up, dead code now
+
+ // former ServerUpdate
+ var attrs = this.me.dynamiccomponent;
+
+ // already done in ClientUpdate
+ // if (!this.animsDetected) {
+ // this.CommonFindAnimations();
+ // }
+
+ // If walk enable was toggled off, make sure the motion state is cleared
+ if (!attrs.GetAttribute("enableWalk"))
+ {
+ this.motionX = 0;
+ this.motionZ = 0;
+ }
+
+ // If flying enable was toggled off, but we are still flying, disable now
+ if ((this.flying) && (!attrs.GetAttribute("enableFly")))
+ this.ServerSetFlying(false);
+
+ // already done in ClientUpdate
+ // this.CommonUpdateAnimation(frametime);
+
+ //print("dc foo: " + this.me.dynamiccomponent.GetAttribute("foo"));
+}
+
+SimpleAvatar.prototype.ClientUpdate = function(frametime) {
+ // Tie enabled state of inputmapper to the enabled state of avatar camera
+ if (this.ownAvatar) {
+ var avatarcameraentity = scene.GetEntityByName("AvatarCamera");
+ var inputmapper = this.me.inputmapper;
+ if ((avatarcameraentity != null) && (inputmapper != null)) {
+ var active = avatarcameraentity.camera.IsActive();
+ if (inputmapper.enabled != active) {
+ inputmapper.enabled = active;
+ }
+ }
+ this.ClientUpdateRotation(frametime);
+ this.ClientUpdateAvatarCamera(frametime);
+ }
+
+ if (!this.animsDetected) {
+ this.CommonFindAnimations();
+ }
+ this.CommonUpdateAnimation(frametime);
+
+ //print("dc foo: " + this.me.dynamiccomponent.GetAttribute("foo"));
+}
+
+SimpleAvatar.prototype.ClientCreateInputMapper = function() {
+ // Create a nonsynced inputmapper
+ var inputmapper = this.me.GetOrCreateComponent("EC_InputMapper", 2, false);
+ inputmapper.contextPriority = 101;
+ inputmapper.takeMouseEventsOverQt = false;
+ inputmapper.takeKeyboardEventsOverQt = false;
+ inputmapper.modifiersEnabled = false;
+ inputmapper.keyrepeatTrigger = false; // Disable repeat keyevent sending over network, not needed and will flood network
+ inputmapper.executionType = 1; // Execute actions on client
+
+ // Key pressed -actions
+ inputmapper.RegisterMapping("W", "Move(forward)", 1); // 1 = keypress
+ inputmapper.RegisterMapping("S", "Move(back)", 1);
+ inputmapper.RegisterMapping("A", "Move(left)", 1);
+ inputmapper.RegisterMapping("D", "Move(right))", 1);
+ inputmapper.RegisterMapping("Up", "Move(forward)", 1);
+ inputmapper.RegisterMapping("Down", "Move(back)", 1);
+ inputmapper.RegisterMapping("F", "ToggleFly()", 1);
+ inputmapper.RegisterMapping("Space", "Move(up)", 1);
+ inputmapper.RegisterMapping("C", "Move(down)", 1);
+
+ // Key released -actions
+ inputmapper.RegisterMapping("W", "Stop(forward)", 3); // 3 = keyrelease
+ inputmapper.RegisterMapping("S", "Stop(back)", 3);
+ inputmapper.RegisterMapping("A", "Stop(left)", 3);
+ inputmapper.RegisterMapping("D", "Stop(right)", 3);
+ inputmapper.RegisterMapping("Up", "Stop(forward)", 3);
+ inputmapper.RegisterMapping("Down", "Stop(back)", 3);
+ inputmapper.RegisterMapping("Space", "Stop(up)", 3);
+ inputmapper.RegisterMapping("C", "Stop(down)", 3);
+
+ // Connect mouse gestures
+ var inputContext = inputmapper.GetInputContext();
+ inputContext.GestureStarted.connect(this, this.GestureStarted);
+ inputContext.GestureUpdated.connect(this, this.GestureUpdated);
+ inputContext.MouseMove.connect(this, this.ClientHandleMouseMove);
+
+ // Local mapper for mouse scroll and rotate
+ var inputmapper = this.me.GetOrCreateComponent("EC_InputMapper", "CameraMapper", 2, false);
+ inputmapper.contextPriority = 100;
+ inputmapper.takeMouseEventsOverQt = true;
+ inputmapper.modifiersEnabled = false;
+ inputmapper.executionType = 1; // Execute actions locally
+ inputmapper.RegisterMapping("+", "Zoom(in)", 1);
+ inputmapper.RegisterMapping("-", "Zoom(out)", 1);
+ inputmapper.RegisterMapping("Left", "Rotate(left)", 1);
+ inputmapper.RegisterMapping("Right", "Rotate(right))", 1);
+ inputmapper.RegisterMapping("Left", "StopRotate(left)", 3);
+ inputmapper.RegisterMapping("Right", "StopRotate(right))", 3);
+}
+
+SimpleAvatar.prototype.ClientCreateAvatarCamera = function() {
+ var cameraentity = scene.GetEntityByName("AvatarCamera");
+ if (cameraentity == null)
+ {
+ cameraentity = scene.CreateLocalEntity();
+ cameraentity.SetName("AvatarCamera");
+ cameraentity.SetTemporary(true);
+ }
+
+ var camera = cameraentity.GetOrCreateComponent("EC_Camera");
+ var placeable = cameraentity.GetOrCreateComponent("EC_Placeable");
+
+ camera.SetActive();
+
+ var parentRef = placeable.parentRef;
+ parentRef.ref = this.me; // Parent camera to avatar, always
+ placeable.parentRef = parentRef;
+
+ // Set initial position
+ this.ClientUpdateAvatarCamera();
+}
+
+SimpleAvatar.prototype.GestureStarted = function(gestureEvent) {
+ if (!this.IsCameraActive())
+ return;
+ if (gestureEvent.GestureType() == Qt.PanGesture)
+ {
+ this.listenGesture = true;
+
+ var attrs = this.me.dynamiccomponent;
+ if (attrs.GetAttribute("enableRotate")) {
+ var x = new Number(gestureEvent.Gesture().offset.toPoint().x());
+ this.yaw += x;
+ this.me.Exec(1, "SetRotation", this.yaw.toString());
+ }
+
+ gestureEvent.Accept();
+ }
+ else if (gestureEvent.GestureType() == Qt.PinchGesture)
+ gestureEvent.Accept();
+}
+
+SimpleAvatar.prototype.GestureUpdated = function(gestureEvent) {
+ if (!IsCameraActive())
+ return;
+
+ if (gestureEvent.GestureType() == Qt.PanGesture && this.listenGesture == true)
+ {
+ // Rotate avatar with X pan gesture
+ delta = gestureEvent.Gesture().delta.toPoint();
+ this.yaw += delta.x;
+ this.me.Exec(1, "SetRotation", this.yaw.toString());
+
+ // Start walking or stop if total Y len of pan gesture is 100
+ var walking = false;
+ if (this.me.animationcontroller.animationState == this.walkAnimName)
+ walking = true;
+ var totalOffset = gestureEvent.Gesture().offset.toPoint();
+ if (totalOffset.y() < -100)
+ {
+ if (walking) {
+ this.me.Exec(1, "Stop", "forward");
+ this.me.Exec(1, "Stop", "back");
+ } else
+ this.me.Exec(1, "Move", "forward");
+ listenGesture = false;
+ }
+ else if (totalOffset.y() > 100)
+ {
+ if (walking) {
+ this.me.Exec(1, "Stop", "forward");
+ this.me.Exec(1, "Stop", "back");
+ } else
+ this.me.Exec(1, "Move", "back");
+ this.listenGesture = false;
+ }
+ gestureEvent.Accept();
+ }
+ else if (gestureEvent.GestureType() == Qt.PinchGesture)
+ {
+ var scaleChange = gestureEvent.Gesture().scaleFactor - gestureEvent.Gesture().lastScaleFactor;
+ if (scaleChange > 0.1 || scaleChange < -0.1)
+ this.ClientHandleMouseScroll(scaleChange * 100);
+ gestureEvent.Accept();
+ }
+}
+
+SimpleAvatar.prototype.ClientHandleKeyboardZoom = function(direction) {
+ if (direction == "in") {
+ this.ClientHandleMouseScroll(10);
+ } else if (direction == "out") {
+ this.ClientHandleMouseScroll(-10);
+ }
+}
+
+SimpleAvatar.prototype.ClientHandleMouseScroll = function(relativeScroll) {
+ var attrs = this.me.dynamiccomponent;
+ // Check that zoom is allowed
+ if (!attrs.GetAttribute("enableZoom"))
+ return;
+
+ var avatarCameraDistance = attrs.GetAttribute("cameraDistance");
+
+ if (!this.IsCameraActive())
+ return;
+
+ var moveAmount = 0;
+ if (relativeScroll < 0 && avatarCameraDistance < 500) {
+ if (relativeScroll < -50)
+ moveAmount = 2;
+ else
+ moveAmount = 1;
+ } else if (relativeScroll > 0 && avatarCameraDistance > 0) {
+ if (relativeScroll > 50)
+ moveAmount = -2
+ else
+ moveAmount = -1;
+ }
+ if (moveAmount != 0)
+ {
+ // Add movement
+ avatarCameraDistance = avatarCameraDistance + moveAmount;
+ // Clamp distance to be between -0.5 and 500
+ if (avatarCameraDistance < -0.5)
+ avatarCameraDistance = -0.5;
+ else if (avatarCameraDistance > 500)
+ avatarCameraDistance = 500;
+
+ attrs.SetAttribute("cameraDistance", avatarCameraDistance);
+ }
+}
+
+SimpleAvatar.prototype.ClientHandleRotate = function(param) {
+ log("rotate " + param);
+ if (param == "left") {
+ this.rotate = -1;
+ }
+ if (param == "right") {
+ this.rotate = 1;
+ }
+}
+
+SimpleAvatar.prototype.ClientHandleStopRotate = function(param) {
+ if ((param == "left") && (this.rotate == -1)) {
+ this.rotate = 0;
+ }
+ if ((param == "right") && (this.rotate == 1)) {
+ this.rotate = 0;
+ }
+}
+
+SimpleAvatar.prototype.ClientUpdateRotation = function(frametime) {
+ var attrs = this.me.dynamiccomponent;
+ // Check that rotation is allowed
+ if (!attrs.GetAttribute("enableRotate"))
+ return;
+
+ if (this.rotate != 0) {
+ this.yaw -= this.rotateSpeed * this.rotate * frametime;
+ this.me.Exec(1, "SetRotation", this.yaw.toString());
+ }
+}
+
+SimpleAvatar.prototype.ClientUpdateAvatarCamera = function() {
+ var attrs = this.me.dynamiccomponent;
+ if (attrs == null)
+ return;
+ var avatarCameraDistance = attrs.GetAttribute("cameraDistance");
+ if (avatarCameraDistance == undefined) {
+ log("skipping camera update, distance undefined");
+ return;
+ }
+ var cameraentity = scene.GetEntityByName("AvatarCamera");
+ if (cameraentity == null)
+ return;
+ var cameraplaceable = cameraentity.placeable;
+
+ var cameratransform = cameraplaceable.transform;
+ //log("paa");
+ cameratransform.rot = new float3(this.pitch, 0, 0);
+ //log("apa " + this.avatarCameraHeight+ " " + avatarCameraDistance);
+ cameratransform.pos = new float3(0, this.avatarCameraHeight, avatarCameraDistance);
+ //log("aap");
+ cameraplaceable.transform = cameratransform;
+}
+
+SimpleAvatar.prototype.ClientHandleMouseMove = function(mouseevent) {
+ var attrs = this.me.dynamiccomponent;
+
+ // Do not rotate if not allowed
+ if (!attrs.GetAttribute("enableRotate"))
+ return;
+
+ // Do not rotate in third person if right mousebutton not held down
+ if (input.IsMouseCursorVisible())
+ return;
+
+ if (mouseevent.IsRightButtonDown())
+ this.LockMouseMove(mouseevent.relativeX, mouseevent.relativeY);
+ else
+ this.isMouseLookLockedOnX = true;
+
+ var cameraentity = scene.GetEntityByName("AvatarCamera");
+ if (cameraentity == null)
+ return;
+
+ // Dont move av rotation if we are not the active cam
+ if (!cameraentity.camera.IsActive())
+ return;
+
+ if (mouseevent.relativeX != 0)
+ {
+ // Rotate avatar or camera
+ this.yaw -= this.mouseRotateSensitivity * parseInt(mouseevent.relativeX);
+ this.me.Exec(1, "SetRotation", this.yaw.toString());
+ }
+
+ if (mouseevent.relativeY != 0 && !this.isMouseLookLockedOnX)
+ {
+ // Look up/down
+ var attrs = this.me.dynamiccomponent;
+ this.pitch -= this.mouseRotateSensitivity * parseInt(mouseevent.relativeY);
+
+ // Dont let the 1st person flip vertically, 180 deg view angle
+ if (this.pitch < -90)
+ this.pitch = -90;
+ if (this.pitch > 90)
+ this.pitch = 90;
+ }
+}
+
+SimpleAvatar.prototype.LockMouseMove = function(x,y) {
+ if (Math.abs(y) > Math.abs(x))
+ this.isMouseLookLockedOnX = false;
+ else
+ this.isMouseLookLockedOnX = true;
+}
+
+SimpleAvatar.prototype.CommonFindAnimations = function() {
+ var animcontrol = this.me.animationcontroller;
+ if (animcontrol == null)
+ return;
+ var availableAnimations = animcontrol.GetAvailableAnimations();
+ if (availableAnimations.length > 0) {
+ // Detect animation names
+ for(var i=0; i<this.animList.length; i++) {
+ var animName = this.animList[i];
+ if (availableAnimations.indexOf(animName) == -1) {
+ // Disable this animation by setting it to a empty string
+ print("Could not find animation for:", animName, " - disabling animation");
+ this.animList[i] = "";
+ }
+ }
+
+ // Assign the possible empty strings for
+ // not found anims back to the variables
+ this.standAnimName = this.animList[0];
+ this.walkAnimName = this.animList[1];
+ this.flyAnimName = this.animList[2];
+ this.hoverAnimName = this.animList[3];
+
+ this.animsDetected = true;
+ }
+}
+
+SimpleAvatar.prototype.CommonUpdateAnimation = function(frametime) {
+ // This function controls the known move animations, such as walk, fly, hover and stand,
+ // which are replicated through the animationState attribute of the AnimationController.
+ // Only one such move animation can be active at a time.
+ // Other animations, such as for gestures, can be freely enabled/disabled by other scripts.
+
+ var attrs = this.me.dynamiccomponent;
+
+ if (!this.animsDetected) {
+ return;
+ }
+
+ var animcontroller = this.me.animationcontroller;
+ var rigidbody = this.me.rigidbody;
+ if ((animcontroller == null) || (rigidbody == null)) {
+ return;
+ }
+
+ if (!attrs.GetAttribute("enableAnimation"))
+ {
+ // When animations disabled, forcibly disable all running move animations
+ // Todo: what if custom scripts want to run the move anims as well?
+ for (var i = 0; i < this.animList.length; ++i) {
+ if (this.animList[i] != "")
+ animcontroller.DisableAnimation(this.animList[i], 0.25);
+ }
+ return;
+ }
+
+ var animName = animcontroller.animationState;
+
+ // Enable animation, skip with headless server
+ if (animName != "" && !framework.IsHeadless()) {
+ // Do custom speeds for certain anims
+ if (animName == this.hoverAnimName) {
+ animcontroller.SetAnimationSpeed(animName, 0.25);
+ }
+ // Enable animation
+ if (!animcontroller.IsAnimationActive(animName)) {
+ animcontroller.EnableAnimation(animName, true, 0.25, false);
+ }
+ // Disable other move animations
+ for (var i = 0; i < this.animList.length; ++i) {
+ if ((this.animList[i] != animName) && (this.animList[i] != "") && (animcontroller.IsAnimationActive(this.animList[i])))
+ animcontroller.DisableAnimation(this.animList[i], 0.25);
+ }
+ }
+
+ // If walk animation is playing, adjust its speed according to the avatar rigidbody velocity
+ if (animName != "" && animcontroller.IsAnimationActive(this.walkAnimName)) {
+ var velocity = rigidbody.linearVelocity;
+ var walkspeed = Math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z) * this.walkAnimSpeed;
+ animcontroller.SetAnimationSpeed(this.walkAnimName, walkspeed);
+ }
+}
+
View
137 Scenes/ClientSideAvatar/avatarapplication.js
@@ -0,0 +1,137 @@
+// Client-side avatar application, based on the bundled Avatar app.
+
+// This runs in a single EC on the server, and gives each connection
+// an avatar entity of their own (and an along with an avatar entity
+// script, avatar_entity.js)
+
+engine.IncludeFile("av_common.js");
+
+var avatar_area_size = 10;
+var avatar_area_x = 0;
+var avatar_area_y = 5;
+var avatar_area_z = 0;
+
+is_server = server.IsRunning() || server.IsAboutToStart();
+is_client = !is_server;
+
+if (is_client) {
+ client.Disconnected.connect(HandleClientDisconnected);
+} else if (is_server) {
+ server.UserAboutToConnect.connect(ServerHandleUserAboutToConnect);
+ server.UserConnected.connect(ServerHandleUserConnected);
+ server.UserDisconnected.connect(ServerHandleUserDisconnected);
+
+ // If there are connected users when this script was added, add av for all of them
+ // TODO new, check
+
+ var userIdList = server.GetConnectionIDs();
+ if (userIdList.length > 0)
+ log("Application started. Creating avatars for logged in clients.");
+
+ for (var i=0; i < userIdList.length; i++)
+ {
+ var userId = userIdList[i];
+ var userConnection = server.GetUserConnection(userId);
+ if (userConnection != null)
+ ServerHandleUserConnected(userId, userConnection);
+ }
+}
+
+function HandleClientDisconnected() {
+ // clear up stuff that might be stale after disconnection
+
+ clear_av_connectionids(scene);
+}
+
+function ServerHandleUserAboutToConnect(connectionID, user) {
+ // Uncomment to test access control
+ //if (user.GetProperty("password") != "xxx")
+ // user.DenyConnection();
+}
+
+function ServerHandleUserConnected(connectionID, user) {
+ if (!user) {
+ log("got UserConnected but no user");
+ }
+ var username = user.GetProperty("username");
+ var avatarEntityName = "Avatar_" + username;
+ var avatarEntity = scene.GetEntityByName(avatarEntityName);
+
+ // avatar entity state might be out of sync
+ if (!avatarEntity) {
+ log("no existing av ent found by username" + username + ", creating new");
+ avatarEntity = CreateAvatarEntity(username, connectionID, avatarEntityName);
+ } else {
+ log("skip setting appearance");
+ //SetAvatarAppearance(avatarEntity, "default");
+ }
+ dc_set(avatarEntity, "connectionID", connectionID);
+ // log("set avatar connid time=" + new Date().getTime());
+ // var dc = avatarEntity.GetOrCreateComponent("EC_DynamicComponent");
+ // log("read back (2) attribute: " + dc.GetAttribute("connectionID"));
+ // log("value: " + dc_get(avatarEntity, "connectionID"));
+}
+
+function CreateAvatarEntity(username, connectionID, avatarEntityName) {
+ // Create necessary components to the avatar entity:
+ // - Script for the main avatar script simpleavatar.js
+ // - Placeable for position
+ // - AnimationController for skeletal animation control
+ // - DynamicComponent for holding disabled/enabled avatar features
+
+ var avatarEntity = scene.CreateEntity(scene.NextFreeId() /*scene.NextFreeIdPersistent()*/, ["EC_Script", "EC_Placeable", "EC_AnimationController", "EC_DynamicComponent"]);
+ //avatarEntity.SetKeepOverDisconnect(true);
+
+ dc_set(avatarEntity, "foo", "x");
+
+ avatarEntity.SetTemporary(true); // We never want to save the avatar entities to disk.
+ avatarEntity.SetName(avatarEntityName);
+
+ avatarEntity.SetDescription(username);
+
+ var script = avatarEntity.script;
+ script.className = "AvatarApp.SimpleAvatar";
+
+ // Simpleavatar.js implements the basic avatar movement and animation.
+ // Also load an additional script object to the same entity (ExampleAvatarAddon.js) to demonstrate adding features to the avatar.
+ var script2 = avatarEntity.GetOrCreateComponent("EC_Script", "Addon", 0, true);
+ script2.className = "AvatarApp.ExampleAvatarAddon";
+
+ // Set random starting position for avatar
+ var placeable = avatarEntity.placeable;
+ var transform = placeable.transform;
+ transform.pos.x = (Math.random() - 0.5) * avatar_area_size + avatar_area_x;
+ transform.pos.y = avatar_area_y;
+ transform.pos.z = (Math.random() - 0.5) * avatar_area_size + avatar_area_z;
+ placeable.transform = transform;
+
+ if (username)
+ log("Created avatar for " + username);
+ return avatarEntity;
+}
+
+function ServerHandleUserDisconnected(connectionID, user) {
+ username = user.GetProperty("username");
+ if (!username) {
+ log("no username!");
+ return;
+ }
+ var avatarEntityName = "Avatar_" + username;
+ var avatarEntity = scene.GetEntityByName(avatarEntityName);
+ if (avatarEntity != null) {
+ // not removing avatar entity. will stick around
+ log("clearing connectionid from " + avatarEntityName);
+ log("dc 2");
+ dc_set(avatarEntity, "connectionID", "");
+ log("connectionID now: '" + dc_get(avatarEntity, "connectionID") + "'");
+
+ var av_transform = avatarEntity.placeable.transform;
+ var entityID = avatarEntity.id;
+
+ if (user != null) {
+ log("User " + username + " disconnected, destroyed avatar entity.");
+ log("dc 3");
+ dc_set(me, username, av_transform);
+ }
+ }
+}
View
69 Scenes/ClientSideAvatar/avatarmenu.js
@@ -0,0 +1,69 @@
+var avatarMenu = null;
+
+function MenuActionHandler(assetRef, index)
+{
+ this.assetName = assetRef;
+}
+
+MenuActionHandler.prototype.triggered = function()
+{
+ var avatarEntity = scene.GetEntity("Avatar" + client.GetConnectionID());
+ if (avatarEntity == null)
+ return;
+ var r = avatarEntity.avatar.appearanceRef;
+ r.ref = this.assetName;
+ avatarEntity.avatar.appearanceRef = r;
+}
+
+if (!framework.IsHeadless())
+{
+ engine.ImportExtension("qt.core");
+ engine.ImportExtension("qt.gui");
+
+ // Connect to asset refs ready signal of existing storages (+ refresh them now)
+ var assetStorages = asset.GetAssetStorages();
+ for (var i = 0; i < assetStorages.length; ++i)
+ {
+ assetStorages[i].AssetRefsChanged.connect(PopulateAvatarUiMenu);
+ assetStorages[i].RefreshAssetRefs();
+ }
+ // Connect to adding new storages
+ asset.AssetStorageAdded.connect(OnAssetStorageAdded);
+}
+
+function OnAssetStorageAdded(storage)
+{
+ storage.AssetRefsChanged.connect(PopulateAvatarUiMenu);
+ storage.RefreshAssetRefs();
+}
+
+function EndsWith(str, suffix)
+{
+ return str.indexOf(suffix) == (str.length - suffix.length);
+}
+
+function PopulateAvatarUiMenu()
+{
+ var menu = ui.MainWindow().menuBar();
+ if (avatarMenu == null)
+ avatarMenu = menu.addMenu("&Avatar");
+
+ avatarMenu.clear();
+
+ var assetStorages = asset.GetAssetStorages();
+ for (var i = 0; i < assetStorages.length; ++i)
+ {
+ var assetList = assetStorages[i].GetAllAssetRefs();
+ for (var j = 0; j < assetList.length; ++j)
+ {
+ var assetNameLower = assetList[j].toLowerCase();
+ // Can not check the actual asset type from the ref only. But for now assume xml = avatar xml
+ if ((EndsWith(assetNameLower, ".xml")) || (EndsWith(assetNameLower, ".avatar")))
+ {
+ var assetName = assetList[j];
+ var handler = new MenuActionHandler(assetName);
+ avatarMenu.addAction(assetName).triggered.connect(handler, handler.triggered);
+ }
+ }
+ }
+}
View
110 Scenes/ClientSideAvatar/crosshair.js
@@ -0,0 +1,110 @@
+// !ref: firstpersonmouseicon.png
+
+if (!server.IsRunning() && !framework.IsHeadless())
+{
+ function Crosshair(useLabel)
+ {
+ // Init
+ this.is_active = false;
+ this.is_ready = false;
+ this.isUsingLabel = useLabel;
+ this.sideLength = 8;
+
+ var iconAsset = asset.GetAsset("local://firstpersonmouseicon.png");
+ if (iconAsset == null)
+ {
+ print("Could not find icon asset, cannot use first person mouse icon!");
+ return;
+ }
+
+ var iconDiskSource = iconAsset.DiskSource();
+ if (iconDiskSource == null || iconDiskSource == "")
+ {
+ print("Could not find icon asset disk source, cannot use first person mouse icon!");
+ return;
+ }
+
+ var image = new QPixmap(this.sideLength, this.sideLength);
+ image.load(iconDiskSource);
+
+ if (!this.isUsingLabel)
+ {
+ this.cursor = new QCursor(image);
+ }
+ else
+ {
+ var label = new QLabel();
+ label.resize(this.sideLength, this.sideLength);
+ label.setPixmap(image);
+ label.setStyleSheet("QLabel { background-color: transparent; }");
+ this.proxy = new UiProxyWidget(label);
+ this.proxy.focus = false;
+ this.proxy.focusPolicy = Qt.NoFocus;
+ //\todo: It would be nice to find a way in js to ignore mouse clicks on crosshair label.
+ ui.AddProxyWidgetToScene(this.proxy);
+ this.proxy.x = ui.GraphicsView().scene().width()/2 - this.sideLength/2;
+ this.proxy.y = ui.GraphicsView().scene().height()/2 - this.sideLength/2;
+ this.proxy.windowFlags = 0;
+ this.proxy.visible = false;
+ this.cursor = new QCursor(Qt.BlankCursor);
+ ui.GraphicsView().scene().sceneRectChanged.connect(this, this.rectChanged);
+ }
+ // Mark as ready
+ this.is_ready = true;
+ }
+
+ Crosshair.prototype.isActive = function()
+ {
+ return this.is_active;
+ }
+
+ Crosshair.prototype.show = function()
+ {
+ // Never put more than one of our icon to the stack
+ if (this.is_active)
+ return;
+
+ if (this.is_ready)
+ {
+ if (this.isUsingLabel)
+ {
+ this.proxy.visible = true;
+ input.SetMouseCursorVisible(false);
+ }
+ else
+ {
+ //\ note: SetMouseCursorVisible makes the nessesary mouse centering without producing double move events.
+ //\ However, we need to show the cursor only, so additional checks are added in simpleavatar.js as well.
+ input.SetMouseCursorVisible(false);
+ QApplication.setOverrideCursor(this.cursor);
+ }
+ this.is_active = true;
+ }
+ }
+
+ Crosshair.prototype.hide = function()
+ {
+ if (!this.is_active)
+ return;
+
+ if (this.is_ready)
+ {
+ if (this.isUsingLabel)
+ {
+ this.proxy.visible = false;
+ input.SetMouseCursorVisible(true);
+ }
+ else
+ {
+ QApplication.restoreOverrideCursor();
+ input.SetMouseCursorVisible(true);
+ }
+ this.is_active = false;
+ }
+ }
+
+ Crosshair.prototype.rectChanged = function(rect)
+ {
+ this.proxy.pos = new QPointF(rect.width()/2 - this.sideLength/2, rect.height()/2 - this.sideLength/2);
+ }
+}
View
73 Scenes/ClientSideAvatar/exampleavataraddon.js
@@ -0,0 +1,73 @@
+// Avatar addon script. Adds wave gesture & sitting -features
+
+// A simple walking avatar with physics & 1st/3rd person camera
+function ExampleAvatarAddon(entity, comp)
+{
+ // Store the entity reference
+ this.me = entity;
+
+ // Sitting flag. Used only on the server
+ this.sitting = false;
+ // Own avatar flag. Checked on the client
+ this.ownAvatar = false;
+
+ // Check whether runs on server or client, and do different initialization based on that
+ if (server.IsRunning()) {
+ this.ServerInitialize();
+ } else {
+ this.ClientInitialize();
+ }
+}
+
+ExampleAvatarAddon.prototype.ServerInitialize = function()
+{
+ // Connect actions. These come from the client inputmapper
+ // Note: use "Addon" prefix in the actions, so that we don't confuse with other actions
+ this.me.Action("AddonWave").Triggered.connect(this, this.ServerHandleWave);
+ this.me.Action("AddonSit").Triggered.connect(this, this.ServerHandleSit);
+}
+
+ExampleAvatarAddon.prototype.ClientInitialize = function()
+{
+ // Initialization is only necessary for own avatar. Do nothing with others' avatars
+ if (this.me.name == "Avatar" + client.GetConnectionID()) {
+ ownAvatar = true;
+
+ // Connect keys to the inputmapper actions
+ // If simpleavatar.js has not yet run, create the inputmapper here
+ var inputmapper = this.me.GetOrCreateComponent("EC_InputMapper", 2, false);
+ inputmapper.RegisterMapping("Q", "AddonWave()", 1); // 1 = Keypress
+ inputmapper.RegisterMapping("R", "AddonSit()", 1); // 1 = Keypress
+ }
+}
+
+ExampleAvatarAddon.prototype.ServerHandleWave = function()
+{
+ // Play the wave animation by using the AnimationController component's action PlayAnimAutoStop
+ // (parameters: animation name, fade-in time)
+ // Executiontype 7 (client, server & peers) means it gets replicated to all
+ // AutoStop means that the animation is faded out automatically when finished, so that it doesn't disturb other animations
+ this.me.Exec(7, "PlayAnimAutoStop", "Wave", 0.25);
+}
+
+ExampleAvatarAddon.prototype.ServerHandleSit = function()
+{
+ // Toggle sitting state
+ this.sitting = !this.sitting;
+
+ // Use the attributes in the avatar's dynamic component to disable simpleavatar.js features when we're sitting:
+ // - walking
+ // - flying
+ // - default animations
+ var attrs = this.me.dynamiccomponent;
+ attrs.SetAttribute("enableWalk", !this.sitting);
+ attrs.SetAttribute("enableFly", !this.sitting);
+ attrs.SetAttribute("enableAnimation", !this.sitting);
+
+ // Then either play or stop the sitting animation, depending on the state
+ // Like with the Wave gesture animation, use an AnimationController action that is replicated to all
+ if (this.sitting)
+ this.me.Exec(7, "PlayAnim", "SitOnGround", 0.25);
+ else
+ this.me.Exec(7, "StopAnim", "SitOnGround", 0.25);
+}
View
BIN  Scenes/ClientSideAvatar/firstpersonmouseicon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  Scenes/ClientSideAvatar/fish.mesh
Binary file not shown
View
31 Scenes/ClientSideAvatar/instructions.js
@@ -0,0 +1,31 @@
+var showUi = true;
+if (server.IsRunning() && framework.IsHeadless())
+ showUi = false;
+
+if (showUi)
+{
+ engine.ImportExtension("qt.core");
+ engine.ImportExtension("qt.gui");
+
+ var label = new QLabel();
+ label.objectName = "InfoLabel";
+ label.setStyleSheet("QLabel#InfoLabel { padding: 10px; background-color: rgba(230,230,230,175); border: 1px solid black; font-size: 16px; }");
+ label.text = "This scene implements an \"avatar application\". In this world, each client who connects\nto the scene, \
+gets an avatar presence to control. The avatars' visual appearance is\nimplemented via the Avatar and AnimationController \
+components. The logic of adding\navatars is implemented in avatarapplication.js, and the movement is controlled with\nan \
+InputMapper component.\n\nYou can embed AvatarApplication into your scenes by copying all the entities\nand assets from \
+this scene to yours.\n\nWASD \t\t= Move avatar\nF \t\t= Toggle fly mode\nSpace \t\t= Jump/Fly up\nC \t\t= Fly down\nQ \t\t= Wave gesture\nR \t\t= \
+Toggle sit\nMouse Scroll and +/- \t= Zoom in/out";
+
+ var proxy = ui.AddWidgetToScene(label);
+
+ // Check if the browser ui is present (or anything else on top left corner)
+ proxy.x = 20;
+ if (ui.GraphicsView().GetVisibleItemAtCoords(20,40) == null)
+ proxy.y = 40;
+ else
+ proxy.y = 100;
+ proxy.windowFlags = 0;
+ proxy.visible = true;
+ proxy.focusPolicy = Qt.NoFocus;
+}
View
BIN  Scenes/FlyingAvatar/WoodPallet.mesh
Binary file not shown
View
145 Scenes/FlyingAvatar/avatar.txml
@@ -0,0 +1,145 @@
+<!DOCTYPE Scene>
+<scene>
+ <entity id="2" sync="1">
+ <component type="EC_Script" sync="1">
+ <attribute value="flyerapplication.js;flyingavatar.js" name="Script ref"/>
+ <attribute value="true" name="Run on load"/>
+ <attribute value="0" name="Run mode"/>
+ <attribute value="FlyerApp" name="Script application name"/>
+ <attribute value="" name="Script class name"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="FlyerApp" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ </entity>
+ <entity id="3" sync="1">
+ <component type="EC_Mesh" sync="1">
+ <attribute value="0,0,0,0,0,0,1,1,1" name="Transform"/>
+ <attribute value="fish.mesh" name="Mesh ref"/>
+ <attribute value="" name="Skeleton ref"/>
+ <attribute value="" name="Mesh materials"/>
+ <attribute value="0" name="Draw distance"/>
+ <attribute value="true" name="Cast shadows"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="Fish" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_Placeable" sync="1">
+ <attribute value="1.45201,-4.65185,5.40487,-47.8323,42.1262,-145.378,1,1,1" name="Transform"/>
+ <attribute value="false" name="Show bounding box"/>
+ <attribute value="true" name="Visible"/>
+ <attribute value="1" name="Selection layer"/>
+ <attribute value="" name="Parent entity ref"/>
+ <attribute value="" name="Parent bone name"/>
+ </component>
+ <component type="EC_RigidBody" sync="1">
+ <attribute value="10" name="Mass"/>
+ <attribute value="6" name="Shape type"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Size"/>
+ <attribute value="fish.mesh" name="Collision mesh ref"/>
+ <attribute value="0.5" name="Friction"/>
+ <attribute value="0" name="Restitution"/>
+ <attribute value="0" name="Linear damping"/>
+ <attribute value="0" name="Angular damping"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Linear factor"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Angular factor"/>
+ <attribute value="false" name="Kinematic"/>
+ <attribute value="false" name="Phantom"/>
+ <attribute value="false" name="Draw Debug"/>
+ <attribute value="-0.001488 0.007011 -0.005740" name="Linear velocity"/>
+ <attribute value="-0.981553 -0.320282 -0.493557" name="Angular velocity"/>
+ <attribute value="-1" name="Collision Layer"/>
+ <attribute value="-1" name="Collision Mask"/>
+ </component>
+ </entity>
+ <entity id="4" sync="1">
+ <component type="EC_Mesh" sync="1">
+ <attribute value="0,0,0,0,0,0,0.14,0.2,0.14" name="Transform"/>
+ <attribute value="WoodPallet.mesh" name="Mesh ref"/>
+ <attribute value="" name="Skeleton ref"/>
+ <attribute value="" name="Mesh materials"/>
+ <attribute value="0" name="Draw distance"/>
+ <attribute value="true" name="Cast shadows"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="Floor" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_Placeable" sync="1">
+ <attribute value="0,-5,0,0,0,0,100,1,100" name="Transform"/>
+ <attribute value="false" name="Show bounding box"/>
+ <attribute value="true" name="Visible"/>
+ <attribute value="1" name="Selection layer"/>
+ <attribute value="" name="Parent entity ref"/>
+ <attribute value="" name="Parent bone name"/>
+ </component>
+ <component type="EC_RigidBody" sync="1">
+ <attribute value="0" name="Mass"/>
+ <attribute value="0" name="Shape type"/>
+ <attribute value="1.000000 0.200000 1.000000" name="Size"/>
+ <attribute value="" name="Collision mesh ref"/>
+ <attribute value="0.5" name="Friction"/>
+ <attribute value="0" name="Restitution"/>
+ <attribute value="0" name="Linear damping"/>
+ <attribute value="0" name="Angular damping"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Linear factor"/>
+ <attribute value="1.000000 1.000000 1.000000" name="Angular factor"/>
+ <attribute value="false" name="Kinematic"/>
+ <attribute value="false" name="Phantom"/>
+ <attribute value="false" name="Draw Debug"/>
+ <attribute value="0.000000 0.000000 0.000000" name="Linear velocity"/>
+ <attribute value="0.000000 0.000000 0.000000" name="Angular velocity"/>
+ <attribute value="-1" name="Collision Layer"/>
+ <attribute value="-1" name="Collision Mask"/>
+ </component>
+ </entity>
+ <entity id="5" sync="1">
+ <component type="EC_Name" sync="1">
+ <attribute value="Instructions" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_Script" sync="1">
+ <attribute value="instructions.js" name="Script ref"/>
+ <attribute value="true" name="Run on load"/>
+ <attribute value="0" name="Run mode"/>
+ <attribute value="" name="Script application name"/>
+ <attribute value="" name="Script class name"/>
+ </component>
+ </entity>
+ <entity id="6" sync="1">
+ <component type="EC_Name" sync="1">
+ <attribute value="Environment" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ <component type="EC_EnvironmentLight" sync="1">
+ <attribute value="0.638999999 0.638999999 0.638999999 1" name="Sunlight color"/>
+ <attribute value="0.363999993 0.363999993 0.363999993 1" name="Ambient light color"/>
+ <attribute value="0.930000007 0.930000007 0.930000007 1" name="Sunlight diffuse color"/>
+ <attribute value="-1.000000 -1.000000 -1.000000" name="Sunlight direction vector"/>
+ <attribute value="true" name="Sunlight cast shadows"/>
+ </component>
+ <component type="EC_Sky" sync="1">
+ <attribute value="RexSkyBox" name="Material"/>
+ <attribute value="rex_sky_front.dds;rex_sky_back.dds;rex_sky_left.dds;rex_sky_right.dds;rex_sky_top.dds;rex_sky_bot.dds" name="Texture"/>
+ <attribute value="50" name="Distance"/>
+ <attribute value="0.000000 0.000000 0.000000 1.000000" name="Orientation"/>
+ <attribute value="true" name="Draw first"/>
+ </component>
+ </entity>
+ <entity id="7" sync="1">
+ <component type="EC_Placeable" sync="1">
+ <attribute value="1.07871,16.6713,35.9556,-33.9,-0.60001,0,1,1,1" name="Transform"/>
+ <attribute value="false" name="Show bounding box"/>
+ <attribute value="true" name="Visible"/>
+ <attribute value="1" name="Selection layer"/>
+ <attribute value="" name="Parent entity ref"/>
+ <attribute value="" name="Parent bone name"/>
+ </component>
+ <component type="EC_Name" sync="1">
+ <attribute value="FreeLookCameraSpawnPos" name="name"/>
+ <attribute value="" name="description"/>
+ </component>
+ </entity>
+</scene>
View
278 Scenes/FlyingAvatar/default_avatar.avatar
@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Template XML file for avatar parameters -->
+<avatar>
+ <!-- version string for this file -->
+ <version>0.2</version>
+ <base name="default_female" mesh="Jack.mesh" />
+ <skeleton name="Jack.skeleton" />
+ <material name="Jack_Body.material" />
+ <material name="Jack_Face.material" />
+ <texture_face name="" />
+ <texture_body name="" />
+ <appearance height="1.800000" weight="1" />
+ <transformation position="0 0 0" rotation="1 0 0 0" scale="1 1 1" />
+ <dynamic_animation_parameter name="Height_Adjust" position="0.5" />
+ <dynamic_animation name="Height_Adjust">
+ <base_animations />
+ <bones>
+ <bone name="Bip01_Pelvis">
+ <rotation start="0 0 0" end="0 0 0" mode="absolute" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="0.93 0.93 0.93" end="1.07 1.07 1.07" />
+ </bone>
+ <bone name="Bip01_Spine1">
+ <rotation start="0 0 0" end="0 0 0" mode="absolute" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="0.97 0.97 0.97" end="1.03 1.03 1.03" />
+ </bone>
+ <bone name="Bip01_L_Thigh">
+ <rotation start="0 0 0" end="0 0 0" mode="absolute" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="0.93 0.93 0.93" end="1.07 1.07 1.07" />
+ </bone>
+ <bone name="Bip01_R_Thigh">
+ <rotation start="0 0 0" end="0 0 0" mode="absolute" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="0.93 0.93 0.93" end="1.07 1.07 1.07" />
+ </bone>
+ </bones>
+ </dynamic_animation>
+ <dynamic_animation_parameter name="Widen_Clavicles" position="0" />
+ <dynamic_animation name="Widen_Clavicles">
+ <base_animations />
+ <bones>
+ <bone name="Bip01_L_Clavicle">
+ <rotation start="0 0 0" end="0 0 0" mode="absolute" />
+ <translation start="0 0 0" end="0 0 0.05" mode="relative" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_R_Clavicle">
+ <rotation start="0 0 0" end="0 0 0" mode="absolute" />
+ <translation start="0 0 0" end="0 0 -0.05" mode="relative" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ </bones>
+ </dynamic_animation>
+ <dynamic_animation_parameter name="Spine_Posture" position="0.5" />
+ <dynamic_animation name="Spine_Posture">
+ <base_animations />
+ <bones>
+ <bone name="Bip01_Spine1">
+ <rotation start="0 0 -20" end="0 0 20" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_Head">
+ <rotation start="0 0 15" end="0 0 -15" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_R_UpperArm">
+ <rotation start="0 0 20" end="0 0 -20" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_L_UpperArm">
+ <rotation start="0 0 20" end="0 0 -20" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ </bones>
+ </dynamic_animation>
+ <dynamic_animation_parameter name="Leg_Twist" position="0" />
+ <dynamic_animation name="Leg_Twist">
+ <base_animations />
+ <bones>
+ <bone name="Bip01_L_Thigh">
+ <rotation start="0 0 0" end="0 -10 0" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_L_Calf">
+ <rotation start="0 0 0" end="0 -20 0" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_R_Thigh">
+ <rotation start="0 0 0" end="0 10 0" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_R_Calf">
+ <rotation start="0 0 0" end="0 20 0" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ </bones>
+ </dynamic_animation>
+ <dynamic_animation_parameter name="Arm_Twist" position="0" />
+ <dynamic_animation name="Arm_Twist">
+ <base_animations />
+ <bones>
+ <bone name="Bip01_L_UpperArm">
+ <rotation start="0 0 0" end="0 -20 0" mode="cumulative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_L_Forearm">
+ <rotation start="0 0 0" end="0 10 0" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_R_UpperArm">
+ <rotation start="0 0 0" end="0 20 0" mode="cumulative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ <bone name="Bip01_R_Forearm">
+ <rotation start="0 0 0" end="0 -10 0" mode="relative" />
+ <translation start="0 0 0" end="0 0 0" mode="absolute" />
+ <scale start="1 1 1" end="1 1 1" />
+ </bone>
+ </bones>
+ </dynamic_animation>
+ <morph_modifier name="fat-arms-lower" internal_name="Morph_fat-arms-lower" influence="0.000000" />
+ <morph_modifier name="fat-arms-upper" internal_name="Morph_fat-arms-upper" influence="0.000000" />
+ <morph_modifier name="fat-body-lower" internal_name="Morph_fat-body-lower" influence="0.000000" />
+ <morph_modifier name="fat-body-upper" internal_name="Morph_fat-body-upper" influence="0.000000" />
+ <morph_modifier name="fat-legs-lower" internal_name="Morph_fat-legs-lower" influence="0.000000" />
+ <morph_modifier name="fat-legs-upper" internal_name="Morph_fat-legs-upper" influence="0.000000" />
+ <morph_modifier name="muscular-arms-lower" internal_name="Morph_muscular-arms-lower" influence="0.000000" />
+ <morph_modifier name="muscular-arms-upper" internal_name="Morph_muscular-arms-upper" influence="0.000000" />
+ <morph_modifier name="muscular-body-lower" internal_name="Morph_muscular-body-lower" influence="0.000000" />
+ <morph_modifier name="muscular-body-upper" internal_name="Morph_muscular-body-upper" influence="0.000000" />
+ <morph_modifier name="muscular-legs-lower" internal_name="Morph_muscular-legs-lower" influence="0.000000" />
+ <morph_modifier name="muscular-legs-upper" internal_name="Morph_muscular-legs-upper" influence="0.000000" />
+ <morph_modifier name="thin-arms-lower" internal_name="Morph_thin-arms-lower" influence="0.000000" />
+ <morph_modifier name="thin-arms-upper" internal_name="Morph_thin-arms-upper" influence="0.000000" />
+ <morph_modifier name="thin-body-lower" internal_name="Morph_thin-body-lower" influence="0.000000" />
+ <morph_modifier name="thin-body-upper" internal_name="Morph_thin-body-upper" influence="0.000000" />
+ <morph_modifier name="thin-legs-lower" internal_name="Morph_thin-legs-lower" influence="0.000000" />
+ <morph_modifier name="thin-legs-upper" internal_name="Morph_thin-legs-upper" influence="0.000000" />
+ <animation name="CrouchWalk" id="47f5f6fb-22e5-ae44-f871-73aaaf4a6022" internal_name="Crouch" looped="1" speedfactor="1.4" usevelocity="1" fadein="0.25" fadeout="0.25" />
+ <animation name="Fly" id="aec4610c-757f-bc4e-c092-c6e9caf18daf" internal_name="Fly" looped="1" speedfactor="1.1" fadein="0.25" fadeout="0.25" />
+ <animation name="Hover" id="4ae8016b-31b9-03bb-c401-b1ea941db41d" internal_name="Hover" speedfactor="0.35" looped="1" fadein="0.25" fadeout="0.25" />
+ <animation name="HoverDown" id="20f063ea-8306-2562-0b07-5c853b37b31e" internal_name="Hover" speedfactor="0.35" looped="1" fadein="0.25" fadeout="0.25" />
+ <animation name="HoverUp" id="62c5de58-cb33-5743-3d07-9e4cd4352864" internal_name="Hover" speedfactor="0.35" looped="1" fadein="0.25" fadeout="0.25" />
+ <animation name="Run" id="05ddbff8-aaa9-92a1-2b74-8fe77a29b445" internal_name="Run" looped="1" speedfactor="0.6" usevelocity="1" fadein="0.25" fadeout="0.25" />
+ <animation name="Sit" id="1a5fe8ac-a804-8a5d-7cbd-56bd83184568" internal_name="SitOnObject" looped="0" fadein="0.4" fadeout="0.5" />
+ <animation name="SitGround" id="1c7600d6-661f-b87b-efe2-d7421eb93c86" internal_name="SitOnGround1" looped="0" fadein="0.4" fadeout="0.5" />
+ <animation name="Stand" id="2408fe9e-df1d-1d7d-f4ff-1384fa7b350f" internal_name="Stand" looped="1" fadein="0.25" fadeout="0.25" />
+ <animation name="Walk" id="6ed24bd8-91aa-4b12-ccc7-c97c857ab4e0" internal_name="Walk" looped="1" speedfactor="1.0" usevelocity="1" fadein="0.25" fadeout="0.25" />
+ <master_modifier name="Arms twist" position="0.000000" category="Posture">
+ <target_modifier type="dynamic_animation" name="Arm_Twist" mode="cumulative" />
+ </master_modifier>
+ <master_modifier name="Body mass" position="0.500000" category="Body">
+ <target_modifier type="morph" name="thin-legs-lower" mode="cumulative">
+ <position_mapping master="0.000000" target="1.000000" />
+ <position_mapping master="0.500000" target="0.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="thin-legs-upper" mode="cumulative">
+ <position_mapping master="0.000000" target="1.000000" />
+ <position_mapping master="0.500000" target="0.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="thin-arms-lower" mode="cumulative">
+ <position_mapping master="0.000000" target="1.000000" />
+ <position_mapping master="0.500000" target="0.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="thin-arms-upper" mode="cumulative">
+ <position_mapping master="0.000000" target="1.000000" />
+ <position_mapping master="0.500000" target="0.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="thin-body-lower" mode="cumulative">
+ <position_mapping master="0.000000" target="1.000000" />
+ <position_mapping master="0.500000" target="0.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="thin-body-upper" mode="cumulative">
+ <position_mapping master="0.000000" target="1.000000" />
+ <position_mapping master="0.500000" target="0.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="fat-arms-lower" mode="cumulative">
+ <position_mapping master="0.500000" target="0.000000" />
+ <position_mapping master="1.000000" target="1.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="fat-arms-upper" mode="cumulative">
+ <position_mapping master="0.500000" target="0.000000" />
+ <position_mapping master="1.000000" target="1.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="fat-legs-lower" mode="cumulative">
+ <position_mapping master="0.500000" target="0.000000" />
+ <position_mapping master="1.000000" target="1.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="fat-legs-upper" mode="cumulative">
+ <position_mapping master="0.500000" target="0.000000" />
+ <position_mapping master="1.000000" target="1.000000" />
+ </target_modifier>
+ <target_modifier type="morph" name="fat-body-lower" mode="cumulative">
+ <position_mapping master="0.500000" target="0.000000" />
+ <position_mapping master="1.000000" target="0.800000" />
+ </target_modifier>
+ <target_modifier type="morph" name="fat-body-upper" mode="cumulative">
+ <position_mapping master="0.500000" target="0.000000" />
+ <position_mapping master="1.000000" target="1.000000" />
+ </target_modifier>
+ <target_modifier type="dynamic_animation" name="Widen_Clavicles2" mode="cumulative">
+ <position_mapping master="0.500000" target="0.000000" />
+ <position_mapping master="1.000000" target="0.800000" />
+ </target_modifier>
+ </master_modifier>
+ <master_modifier name="Body muscles" position="0.000000" category="Body">
+ <target_modifier type="morph" name="muscular-arms-lower" mode="cumulative" />
+ <target_modifier type="morph" name="muscular-arms-upper" mode="cumulative" />
+ <target_modifier type="morph" name="muscular-legs-lower" mode="cumulative" />
+ <target_modifier type="morph" name="muscular-legs-upper" mode="cumulative" />
+ <target_modifier type="morph" name="muscular-body-lower" mode="cumulative" />
+ <target_modifier type="morph" name="muscular-body-upper" mode="cumulative" />
+ <target_modifier type="dynamic_animation" name="Widen_Clavicles2" mode="cumulative">
+ <position_mapping master="0.300000" target="0.000000" />
+ <position_mapping master="1.000000" target="0.500000" />
+ </target_modifier>
+ </master_modifier>
+ <master_modifier name="Height" position="0.500000" category="Body">
+ <target_modifier type="dynamic_animation" name="Height_Adjust" mode="cumulative" />
+ </master_modifier>
+ <master_modifier name="Leg twist" position="0.000000" category="Posture">
+ <target_modifier type="dynamic_animation" name="Leg_Twist" mode="cumulative" />
+ </master_modifier>
+ <master_modifier name="Spine back/forward" position="0.500000" category="Posture">
+ <target_modifier type="dynamic_animation" name="Spine_Posture" mode="cumulative" />
+ </master_modifier>
+ <property name="MovementSpeed" value="1" />
+ <property name="attach_Chest" value="Bip01_Spine1" />
+ <property name="attach_Chin" value="Bip01_Head" />
+ <property name="attach_L Forearm" value="Bip01_L_Forearm" />
+ <property name="attach_L Lower Leg" value="Bip01_L_Calf" />
+ <property name="attach_L Upper Arm" value="Bip01_L_UpperArm" />
+ <property name="attach_L Upper Leg" value="Bip01_L_Thigh" />
+ <property name="attach_Left Ear" value="Bip01_Head" />
+ <property name="attach_Left Eyeball" value="Bip01_Head" />
+ <property name="attach_Left Foot" value="Bip01_L_Foot" />
+ <property name="attach_Left Hand" value="Bip01_L_Hand" />
+ <property name="attach_Left Hip" value="Bip01_L_Thigh" />
+ <property name="attach_Left Pec" value="Bip01_Spine1" />
+ <property name="attach_Left Shoulder" value="Bip01_L_Clavicle" />
+ <property name="attach_Mouth" value="Bip01_Head" />
+ <property name="attach_Nose" value="Bip01_Head" />
+ <property name="attach_Pelvis" value="Bip01_Pelvis" />
+ <property name="attach_R Forearm" value="Bip01_R_Forearm" />
+ <property name="attach_R Lower Leg" value="Bip01_R_Calf" />
+ <property name="attach_R Upper Arm" value="Bip01_R_UpperArm" />
+ <property name="attach_R Upper Leg" value="Bip01_R_Thigh" />
+ <property name="attach_Right Ear" value="Bip01_Head" />
+ <property name="attach_Right Eyeball" value="Bip01_Head" />
+ <property name="attach_Right Foot" value="Bip01_R_Foot" />
+ <property name="attach_Right Hand" value="Bip01_R_Hand" />
+ <property name="attach_Right Hip" value="Bip01_R_Thigh" />
+ <property name="attach_Right Pec" value="Bip01_Spine1" />
+ <property name="attach_Right Shoulder" value="Bip01_R_Clavicle" />
+ <property name="attach_Skull" value="Bip01_Head" />
+ <property name="attach_Spine" value="Bip01_Spine" />
+ <property name="attach_Stomach" value="Bip01_Pelvis" />
+ <property name="basebone" value="Bip01_R_Toe0" />
+ <property name="basemesh" value="Jack.mesh" />
+ <property name="baseoffset" value="0 -0.03 0" />
+ <property name="headbone" value="Bip01_Head" />
+ <property name="neckbone" value="Bip01_Spine2" />
+ <property name="rootbone" value="Bip01" />
+ <property name="torsobone" value="Bip01_Spine1" />
+</avatar>
View
BIN  Scenes/FlyingAvatar/fish.mesh
Binary file not shown
View
121 Scenes/FlyingAvatar/flyerapplication.js
@@ -0,0 +1,121 @@
+// Avatar application. Will handle switching logic between avatar & freelook camera (clientside), and
+// spawning avatars for clients (serverside). Note: this is not a startup script, but is meant to be
+// placed in an entity in a scene that wishes to implement avatar functionality.
+
+var avatar_area_size = 10;
+var avatar_area_x = 0;
+var avatar_area_y = 0;
+var avatar_area_z = 20;
+
+var isserver = server.IsRunning();
+
+if (isserver == false) {
+ var inputmapper = me.GetOrCreateComponent("EC_InputMapper");
+ inputmapper.SetTemporary(true);
+ inputmapper.contextPriority = 102;
+ inputmapper.RegisterMapping("Ctrl+Tab", "ToggleCamera", 1);
+
+ engine.ImportExtension("qt.core");
+ engine.ImportExtension("qt.gui");
+
+ me.Action("ToggleCamera").Triggered.connect(ClientHandleToggleCamera);
+} else {
+ server.UserAboutToConnect.connect(ServerHandleUserAboutToConnect);
+ server.UserConnected.connect(ServerHandleUserConnected);
+ server.UserDisconnected.connect(ServerHandleUserDisconnected);
+
+ // If there are connected users when this script was added, add av for all of them
+ var userIdList = server.GetConnectionIDs();
+ if (userIdList.length > 0)
+ print("[Flyer Application] Application started. Creating avatars for logged in clients.");
+
+ for (var i=0; i < userIdList.length; i++)
+ {
+ var userId = userIdList[i];
+ var userConnection = server.GetUserConnection(userId);
+ if (userConnection != null)
+ ServerHandleUserConnected(userId, userConnection);
+ }
+}
+
+function ClientHandleToggleCamera() {
+ // For camera switching to work, must have both the freelookcamera & avatarcamera in the scene
+ var freelookcameraentity = scene.GetEntityByName("FreeLookCamera");
+ var avatarcameraentity = scene.GetEntityByName("FlyingCamera");
+ var avatarent = scene.GetEntityByName("Flyer" + client.GetConnectionID());
+ if ((freelookcameraentity == null) || (avatarcameraentity == null))
+ return;
+ var freelookcamera = freelookcameraentity.camera;
+ var avatarcamera = avatarcameraentity.camera;
+
+ if (avatarcamera.IsActive()) {
+ freelookcameraentity.placeable.transform = avatarcameraentity.placeable.transform;
+ freelookcamera.SetActive();
+ } else {
+ avatarcamera.SetActive();
+ }
+
+ // Ask entity to check his camera state
+ avatarent.Exec(1, "CheckState");
+}
+
+function ServerHandleUserAboutToConnect(connectionID, user) {
+ // Uncomment to test access control
+ //if (user.GetProperty("password") != "xxx")
+ // user.DenyConnection();
+}
+
+function ServerHandleUserConnected(connectionID, user) {
+ var flyerEntityName = "Flyer" + connectionID;
+ var flyerEntity = scene.CreateEntity(scene.NextFreeId(), ["EC_Script", "EC_Placeable", "EC_AnimationController", "EC_DynamicComponent"]);
+ flyerEntity.SetTemporary(true); // We never want to save the flyer entities to disk.
+ flyerEntity.SetName(flyerEntityName);
+
+ var username = user.GetProperty("username");
+ if (user != null) {
+ flyerEntity.SetDescription(username);
+ }
+
+ var script = flyerEntity.script;
+ script.className = "FlyerApp.FlyingAvatar";
+
+ // Set random starting position for flyer
+ var placeable = flyerEntity.placeable;
+ var transform = placeable.transform;
+ var sticky = me.GetOrCreateComponent("EC_DynamicComponent");
+ var prev_transform = sticky.GetAttribute(username);
+ if (prev_transform) {
+ transform = prev_transform;
+ } else {
+ transform.pos.x = (Math.random() - 0.5) * avatar_area_size + avatar_area_x;
+ transform.pos.y = avatar_area_y;
+ transform.pos.z = (Math.random() - 0.5) * avatar_area_size + avatar_area_z;
+ }
+ placeable.transform = transform;
+
+ scene.EmitEntityCreated(flyerEntity);
+
+ if (user != null) {
+ print("[Flyer Application] Created flyer for " + user.GetProperty("username"));
+ }
+}
+
+function ServerHandleUserDisconnected(connectionID, user) {
+ var flyerEntityName = "Flyer" + connectionID;
+ var flyerEntity = scene.GetEntityByName(flyerEntityName);
+ var sticky = me.GetOrCreateComponent("EC_DynamicComponent");
+ var flyer_transform = flyerEntity.placeable.transform;
+ if (flyerEntity != null) {
+ var entityID = flyerEntity.id;
+ scene.RemoveEntity(entityID);
+
+ if (user != null) {
+ var username = user.GetProperty("username");
+ print("[Flyer Application] User " + username + " disconnected, destroyed flyer entity.");
+ if (!sticky.GetAttribute(username))
+ sticky.AddQVariantAttribute(username);
+
+ sticky.SetAttribute(username, flyer_transform);
+ }
+ }
+}
View
446 Scenes/FlyingAvatar/flyingavatar.js
@@ -0,0 +1,446 @@
+// !ref: http://chiru.cie.fi/lvm-assets/Osprey.mesh
+// !ref: http://chiru.cie.fi/lvm-assets/Osprey.skeleton
+// !ref: http://chiru.cie.fi/lvm-assets/leathers.002.material
+// !ref: http://chiru.cie.fi/lvm-assets/body.001.material
+
+/*if (!server.IsRunning() && !framework.IsHeadless())
+{
+ engine.ImportExtension("qt.core");
+ engine.ImportExtension("qt.gui");
+}*/
+
+function FlyingAvatar(entity, comp)
+{
+ this.me = entity;
+ this.isServer = server.IsRunning() || server.IsAboutToStart();
+
+ if (this.isServer)
+ this.ServerInitialize();
+ else
+ this.ClientInitialize();
+}
+
+FlyingAvatar.prototype.OnScriptObjectDestroyed = function() {
+ // Must remember to manually disconnect subsystem signals, otherwise they'll continue to get signalled
+ if (this.isServer)
+ scene.physics.Updated.disconnect(this, this.ClientUpdatePhysics);
+ else
+ frame.Updated.disconnect(this, this.ClientUpdate);
+}
+
+FlyingAvatar.prototype.ServerInitialize = function ()
+{
+ var mesh = this.me.GetOrCreateComponent("EC_Mesh");
+ var meshRef = mesh.meshRef;
+ meshRef.ref = "http://chiru.cie.fi/lvm-assets/Osprey.mesh";
+ mesh.meshRef = meshRef;
+
+ var skeletonRef = mesh.skeletonRef;
+ skeletonRef.ref = "http://chiru.cie.fi/lvm-assets/Osprey.skeleton";
+ mesh.skeletonRef = skeletonRef;
+
+ var materials = mesh.meshMaterial;
+ materials = ["http://chiru.cie.fi/lvm-assets/leathers.002.material", "http://chiru.cie.fi/lvm-assets/body.001.material"];
+ mesh.meshMaterial = materials;
+
+ var trans = mesh.nodeTransformation;
+ trans.rot.y = 180;
+ mesh.nodeTransformation = trans;
+
+ var rigidbody = this.me.GetOrCreateComponent("EC_RigidBody");
+ rigidbody.AssertAuthority(false);
+ print("Gave rigidbody authority to client");
+}
+
+FlyingAvatar.prototype.ClientInitialize = function()
+{
+ engine.ImportExtension("qt.core");
+ engine.ImportExtension("qt.gui");
+
+ this.timer = new QTimer();
+ this.currentRoll = 0;
+ this.currentYaw = 0;
+ this.currentPitch = 0;
+ this.stabilize = false;
+ this.motionX = 0;
+ this.motionY = 0;
+ this.motionZ = 0;
+
+ this.me.SetTemporary(true);
+
+ if (this.me.name == "Flyer" + client.GetConnectionID()) {
+ this.ownAvatar = true;
+
+ var placeable = this.me.GetOrCreateComponent("EC_Placeable");
+
+ var rigidbody = this.me.GetOrCreateComponent("EC_RigidBody");
+ rigidbody.AssertAuthority(true);
+
+ rigidbody.mass = 10;
+ rigidbody.shapeType = 3; // Capsule
+ rigidbody.size = float3(0.5, 0.5, 2.4);
+ rigidbody.gravityEnabled = false;
+
+ this.me.Action("Move").Triggered.connect(this, this.ClientHandleMove);
+ this.me.Action("Stop").Triggered.connect(this, this.ClientHandleStop);
+ this.me.Action("MouseLookX").Triggered.connect(this, this.ClientHandleMouseLookX);
+ this.me.Action("MouseLookY").Triggered.connect(this, this.ClientHandleMouseLookY);
+
+ var attrs = this.me.dynamiccomponent;
+ attrs.CreateAttribute("real", "rollSensitivity");
+ attrs.CreateAttribute("real", "rotateSensitivity");
+ attrs.CreateAttribute("real", "stabilizationSpeed");
+ attrs.CreateAttribute("real", "stabilizationWaitout");
+ attrs.CreateAttribute("bool", "invertMouseY");
+ attrs.CreateAttribute("real", "maxRollDegree");
+ attrs.CreateAttribute("real", "moveSpeed");
+ attrs.CreateAttribute("real", "dampingForce");
+ attrs.SetAttribute("rollSensitivity", 0.3);
+ attrs.SetAttribute("rotateSensitivity", 0.2);
+ attrs.SetAttribute("stabilizationSpeed", 5.0);
+ attrs.SetAttribute("stabilizationWaitout", 400);
+ attrs.SetAttribute("invertMouseY", true);
+ attrs.SetAttribute("maxRollDegree", 30.0);
+ attrs.SetAttribute("moveSpeed", 30.0);
+ attrs.SetAttribute("dampingForce", 1.0);
+
+ // Hook to update ticks
+ frame.Updated.connect(this, this.ClientUpdate);
+ this.timer.timeout.connect(this, this.ClientStartStabilization);
+ scene.physics.Updated.connect(this, this.ClientUpdatePhysics);
+ rigidbody.PhysicsCollision.connect(this, this.ClientHandleCollision);
+
+ this.ClientCreateCamera();
+ this.ClientCreateInputMapper();
+ }
+}
+
+FlyingAvatar.prototype.ClientCreateCamera = function()
+{
+ var cameraentity = scene.GetEntityByName("FlyingCamera");
+ if (cameraentity == null)
+ {
+ cameraentity = scene.CreateLocalEntity();
+ cameraentity.SetName("FlyingCamera");
+ cameraentity.SetTemporary(true);
+ }
+
+ var camera = cameraentity.GetOrCreateComponent("EC_Camera");
+ var placeable = cameraentity.GetOrCreateComponent("EC_Placeable");
+
+ camera.SetActive();
+
+ var parentRef = placeable.parentRef;
+ parentRef.ref = this.me;
+ placeable.parentRef = parentRef;
+}
+
+FlyingAvatar.prototype.ClientCreateInputMapper = function()
+{
+ var inputmapper = this.me.GetOrCreateComponent("EC_InputMapper");
+
+ inputmapper.contextPriority = 101;
+ inputmapper.SetNetworkSyncEnabled(false);
+ inputmapper.takeMouseEventsOverQt = true;
+ inputmapper.modifiersEnabled = false;
+ inputmapper.keyrepeatTrigger = false;
+ inputmapper.executionType = 1;
+ inputmapper.RegisterMapping("W", "Move(forward)", 1);
+ inputmapper.RegisterMapping("S", "Move(back)", 1);
+ inputmapper.RegisterMapping("A", "Move(left)", 1);
+ inputmapper.RegisterMapping("D", "Move(right)", 1);
+ inputmapper.RegisterMapping("Space", "Move(up)", 1);
+ inputmapper.RegisterMapping("C", "Move(down)", 1);
+ inputmapper.RegisterMapping("W", "Stop(forward)", 3);
+ inputmapper.RegisterMapping("S", "Stop(back)", 3);
+ inputmapper.RegisterMapping("A", "Stop(left)", 3);
+ inputmapper.RegisterMapping("D", "Stop(right)", 3);
+ inputmapper.RegisterMapping("Space", "Stop(up)", 3);
+ inputmapper.RegisterMapping("C", "Stop(down)", 3);
+
+ var mousemapper = this.me.GetOrCreateComponent("EC_InputMapper", "MouseMapper", 2, false);
+ mousemapper.contextPriority = 100;
+ mousemapper.SetNetworkSyncEnabled(false);
+ mousemapper.takeMouseEventsOverQt = true;
+ var inputContext = mousemapper.GetInputContext();
+ inputContext.MouseRightReleased.connect(this, this.HandleMouseRightReleased);
+}
+
+FlyingAvatar.prototype.ClientStartStabilization = function()
+{
+ this.stabilize = true;
+ this.timer.stop();
+}
+
+FlyingAvatar.prototype.ClientStabilizeAvatar = function()
+{
+ if(!this.stabilize)
+ return;
+
+ var attrs = this.me.dynamiccomponent;
+ if(!attrs)
+ return;
+
+ var stabilizationSpeed = attrs.GetAttribute("stabilizationSpeed");
+
+ // Adjust roll towards
+ if(this.currentRoll > 0)
+ this.currentRoll -= this.currentRoll / (100 / stabilizationSpeed);
+ else if(this.currentRoll < 0)
+ this.currentRoll -= this.currentRoll / (100 / stabilizationSpeed);
+
+ // Adjust pitch towards zero
+ if(this.currentPitch > 0)
+ this.currentPitch -= this.currentPitch / (50 / stabilizationSpeed);
+ else if(this.currentPitch < 0)
+ this.currentPitch -= this.currentPitch / (50 / stabilizationSpeed);
+
+ // Clamp roll
+ if(Math.abs(this.currentRoll) < 1)
+ this.currentRoll = 0;
+ // Clamp pitch
+ if(Math.abs(this.currentPitch) < 1)
+ this.currentPitch = 0;
+
+ if(this.currentPitch == 0 && this.currentRoll == 0)
+ this.stabilize = false;
+}
+
+FlyingAvatar.prototype.HandleMouseRightReleased = function()
+{
+ var attrs = this.me.dynamiccomponent;
+ if(!attrs)
+ return;
+