Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but 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
16 Scenes/ClientSideAvatar/Readme.txt
View
@@ -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.
BIN  Scenes/ClientSideAvatar/WoodPallet.mesh
View
Binary file not shown
35 Scenes/ClientSideAvatar/av_common.js
View
@@ -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", "");
+ }
+}
158 Scenes/ClientSideAvatar/avatar.txml
View
@@ -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>
877 Scenes/ClientSideAvatar/avatar_entity.js
View
@@ -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);
+ }
+}
+
137 Scenes/ClientSideAvatar/avatarapplication.js
View
@@ -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);
+ }
+ }
+}
69 Scenes/ClientSideAvatar/avatarmenu.js
View
@@ -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);
+ }
+ }
+ }
+}
110 Scenes/ClientSideAvatar/crosshair.js
View
@@ -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);
+ }
+}
73 Scenes/ClientSideAvatar/exampleavataraddon.js
View
@@ -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);
+}
BIN  Scenes/ClientSideAvatar/firstpersonmouseicon.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  Scenes/ClientSideAvatar/fish.mesh
View
Binary file not shown
31 Scenes/ClientSideAvatar/instructions.js
View
@@ -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;
+}
BIN  Scenes/FlyingAvatar/WoodPallet.mesh
View
Binary file not shown
145 Scenes/FlyingAvatar/avatar.txml
View
@@ -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>
278 Scenes/FlyingAvatar/default_avatar.avatar
View
@@ -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>
BIN  Scenes/FlyingAvatar/fish.mesh
View
Binary file not shown
121 Scenes/FlyingAvatar/flyerapplication.js
View
@@ -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);
+ }
+ }
+}
446 Scenes/FlyingAvatar/flyingavatar.js
View
@@ -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;
+
+ var stabilizationWaitout = attrs.GetAttribute("stabilizationWaitout");
+
+ this.timer.stop();
+ this.timer.start(stabilizationWaitout);
+ this.stabilize = false;
+}
+
+FlyingAvatar.prototype.ClientHandleMouseLookX = function(param)
+{
+ var attrs = this.me.dynamiccomponent;
+ if(!attrs)
+ return;
+
+ var rollSensitivity = attrs.GetAttribute("rollSensitivity");
+ var rotateSensitivity = attrs.GetAttribute("rotateSensitivity");
+ var maxRoll = attrs.GetAttribute("maxRollDegree");
+
+ // Stop stabilizing while moving
+ this.timer.stop();
+ this.stabilize = false;
+
+ var move = parseInt(param);
+ var degrees = param * rollSensitivity;
+
+ // Clamp roll between -maxRoll..maxRoll
+ if(!((this.currentRoll >= maxRoll && degrees > 0) || (this.currentRoll <= -maxRoll && degrees < 0)))
+ this.currentRoll += degrees;
+ this.currentYaw -= rotateSensitivity * move;
+}
+
+FlyingAvatar.prototype.ClientHandleMouseLookY = function(param)
+{
+ var attrs = this.me.dynamiccomponent;
+ if(!attrs)
+ return;
+
+ var rotateSensitivity = attrs.GetAttribute("rotateSensitivity");
+ var invertMouseY = attrs.GetAttribute("invertMouseY");
+
+ // Stop stabilizing while moving
+ this.timer.stop();
+ this.stabilize = false;
+
+ if(invertMouseY)
+ param *= -1;
+
+ // Clamp pitch between -90..90 to keep the ground beneath us
+ if(this.currentPitch < 90 && this.currentPitch > -90)
+ this.currentPitch -= rotateSensitivity * param;
+}
+
+FlyingAvatar.prototype.ClientUpdateOrientation = function()
+{
+ var placeable = this.me.GetComponent("EC_Placeable")
+ var rigidbody = this.me.rigidbody;
+
+ var rollWorld = new Quat(float3(0,0,-1), DegToRad(this.currentRoll));
+ var yawWorld = new Quat(float3(0,1,0), DegToRad(this.currentYaw));
+ var pitchWorld = new Quat(float3(1,0,0), DegToRad(this.currentPitch));
+
+ var orientation = yawWorld.Mul(rollWorld);
+ orientation = orientation.Mul(pitchWorld);
+
+ rigidbody.SetOrientation(orientation);
+}
+
+FlyingAvatar.prototype.ClientUpdateCamera = function()
+{
+}
+
+FlyingAvatar.prototype.ClientHandleMove = function(param)
+{
+ if(!this.IsCameraActive())
+ return;
+
+ 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;
+ }
+}
+
+FlyingAvatar.prototype.ClientHandleStop = function(param)
+{
+ if(!this.IsCameraActive())
+ return;
+
+ 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;
+ }
+}
+
+FlyingAvatar.prototype.ClientUpdate = function(frametime)
+{
+ if (!this.IsCameraActive())
+ {
+ motion_x = 0;
+ motion_y = 0;
+ motion_z = 0;
+ return;
+ }
+
+ this.ClientStabilizeAvatar()
+ this.ClientUpdateCamera();
+ this.ClientUpdateOrientation();
+}
+
+FlyingAvatar.prototype.ClientUpdatePhysics = function(frametime)
+{
+ //print("ClientUpdatePhysics");
+
+ var attrs = this.me.dynamiccomponent;
+ if(!attrs)
+ return;
+
+ var moveSpeed = attrs.GetAttribute("moveSpeed");
+ var dampingForce = attrs.GetAttribute("dampingForce");
+
+ var placeable = this.me.placeable;
+ var rigidbody = this.me.rigidbody;
+
+ // Apply motion force
+ // If diagonal motion, normalize
+ if ((this.motionX != 0) || (this.motionY != 0) || (this.motionZ != 0)) {
+ var impulseVec = new float3(this.motionX, this.motionY, this.motionZ).Normalized().Mul(moveSpeed);
+ var tm = placeable.LocalToWorld();
+ impulseVec = tm.MulDir(impulseVec);
+
+ rigidbody.ApplyImpulse(impulseVec);
+ }
+
+ // 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 = dampingVec.Mul(-dampingForce);
+ dampingVec.x = -dampingForce * dampingVec.x;
+ dampingVec.y = -dampingForce * dampingVec.y;
+ dampingVec.z = -dampingForce * dampingVec.z;
+ rigidbody.ApplyImpulse(dampingVec);
+ }
+}
+
+FlyingAvatar.prototype.ClientHandleCollision = function(ent, pos, normal, distance, impulse, newCollision)
+{
+ var attrs = this.me.dynamiccomponent;
+ if(!attrs)
+ return;
+
+ print("ClientHandleCollision");
+
+ var stabilizationWaitout = attrs.GetAttribute("stabilizationWaitout");
+