mirror of
https://github.com/logsol/chuck.js.git
synced 2026-05-11 10:37:34 +00:00
Combines GLOBALS and Chuck namespace into App. Also increases number of stack trace steps to be printed when an error occurs. I experimented with the "replace" plugin from require.js, which works but it would mean that our context switchable "import-statements" would look like the example below, which I decided against at least for now, just because of the looks. The downside of not using the plugin is that we cannot use the "use strict" statement in the channel.js entry script and also cannot put a "var" in front of App variable there. For example: "replace!Game/:AppContext/Physics/Engine", Instead of: "Game/" + App.context + "/Physics/Engine", Fixes #86
489 lines
No EOL
15 KiB
JavaScript
Executable file
489 lines
No EOL
15 KiB
JavaScript
Executable file
define([
|
|
"Game/" + App.context + "/GameObjects/GameObject",
|
|
"Lib/Utilities/Exception",
|
|
"Lib/Vendor/Box2D",
|
|
"Game/Config/Settings",
|
|
"Game/" + App.context + "/Collision/Detector",
|
|
"Game/" + App.context + "/GameObjects/Item",
|
|
"Lib/Utilities/NotificationCenter",
|
|
"Lib/Utilities/Assert"
|
|
],
|
|
|
|
function (Parent, Exception, Box2D, Settings, CollisionDetector, Item, nc, Assert) {
|
|
|
|
"use strict";
|
|
|
|
function Doll (physicsEngine, uid, player) {
|
|
|
|
this.characterName = "Chuck";
|
|
this.player = player;
|
|
this.height = 37;
|
|
this.width = 9;
|
|
this.headHeight = 12;
|
|
this.reachDistance = 20;
|
|
this.areaSize = 35;
|
|
|
|
Parent.call(this, physicsEngine, uid);
|
|
|
|
this.standing = false;
|
|
this.moveDirection = 0;
|
|
this.lookDirection = 0;
|
|
this.legs = null;
|
|
this.footSensor = null;
|
|
this.actionState = null;
|
|
this.lookAtXY = { x:0, y:0 };
|
|
this.reachableItems = {
|
|
left: [],
|
|
right: []
|
|
};
|
|
this.nearbyDolls = [];
|
|
this.holdingJoint = null;
|
|
this.holdingItem = null;
|
|
|
|
this.ragDoll = {head: null, body: null}; // FIXME: wtf is this? can we remove it?
|
|
|
|
this.createFixtures();
|
|
this.body.SetActive(false);
|
|
|
|
nc.trigger(nc.ns.core.game.worldUpdateObjects.add, this);
|
|
}
|
|
|
|
Doll.prototype = Object.create(Parent.prototype);
|
|
|
|
Doll.prototype.getBodyDef = function() {
|
|
var bodyDef = new Box2D.Dynamics.b2BodyDef();
|
|
bodyDef.position.x = 0;
|
|
bodyDef.position.y = 0;
|
|
bodyDef.fixedRotation = true;
|
|
bodyDef.linearDamping = Settings.PLAYER_LINEAR_DAMPING;
|
|
bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;
|
|
return bodyDef;
|
|
};
|
|
|
|
Doll.prototype.createFixtures = function () {
|
|
Assert.number(this.width, this.height);
|
|
Assert.number(this.reachDistance);
|
|
Assert.number(this.areaSize);
|
|
|
|
var self = this;
|
|
|
|
var fixtureDef = new Box2D.Dynamics.b2FixtureDef();
|
|
fixtureDef.density = Settings.PLAYER_DENSITY;
|
|
fixtureDef.friction = 0;
|
|
fixtureDef.restitution = Settings.PLAYER_RESTITUTION;
|
|
|
|
var headShape = new Box2D.Collision.Shapes.b2CircleShape();
|
|
var radius = this.width / 2 / Settings.RATIO;
|
|
headShape.SetRadius(radius);
|
|
|
|
headShape.SetLocalPosition(new Box2D.Common.Math.b2Vec2(0, -(this.height - (this.width / 2)) / Settings.RATIO));
|
|
fixtureDef.shape = headShape;
|
|
fixtureDef.isSensor = false;
|
|
fixtureDef.userData = {
|
|
onCollisionChange: this.onImpact.bind(this)
|
|
};
|
|
|
|
this.body.CreateFixture(fixtureDef);
|
|
|
|
var bodyShape = new Box2D.Collision.Shapes.b2PolygonShape();
|
|
bodyShape.SetAsOrientedBox(
|
|
this.width / 2 / Settings.RATIO,
|
|
(this.height - this.width) / 2 / Settings.RATIO,
|
|
new Box2D.Common.Math.b2Vec2(0, -this.height / 2 / Settings.RATIO)
|
|
);
|
|
fixtureDef.shape = bodyShape;
|
|
fixtureDef.isSensor = false;
|
|
this.body.CreateFixture(fixtureDef);
|
|
|
|
var legsShape = new Box2D.Collision.Shapes.b2CircleShape();
|
|
legsShape.SetRadius(this.width / 2 / Settings.RATIO);
|
|
legsShape.SetLocalPosition(new Box2D.Common.Math.b2Vec2(0, -this.width / 2 / Settings.RATIO));
|
|
fixtureDef.shape = legsShape;
|
|
fixtureDef.friction = Settings.PLAYER_FRICTION;
|
|
fixtureDef.isSensor = false;
|
|
|
|
this.legs = this.body.CreateFixture(fixtureDef);
|
|
|
|
fixtureDef.density = 0;
|
|
|
|
var feetShape = new Box2D.Collision.Shapes.b2CircleShape();
|
|
feetShape.SetRadius((this.width - 1) / 2 / Settings.RATIO); // the -1 one prevents collisions with walls
|
|
feetShape.SetLocalPosition(new Box2D.Common.Math.b2Vec2(0, 2 / Settings.RATIO)); // 2 is offset into ground
|
|
fixtureDef.shape = feetShape;
|
|
fixtureDef.isSensor = true;
|
|
|
|
fixtureDef.userData = {
|
|
onCollisionChange: this.onFootSensorDetection.bind(this)
|
|
};
|
|
|
|
this.footSensor = this.body.CreateFixture(fixtureDef);
|
|
|
|
var grabSensorLeftShape = new Box2D.Collision.Shapes.b2PolygonShape();
|
|
grabSensorLeftShape.SetAsOrientedBox(
|
|
this.reachDistance / 2 / Settings.RATIO,
|
|
((this.height / 2) + this.reachDistance / 4) / Settings.RATIO,
|
|
new Box2D.Common.Math.b2Vec2(
|
|
-this.reachDistance / 2 / Settings.RATIO,
|
|
-this.height / 2 / Settings.RATIO
|
|
)
|
|
);
|
|
fixtureDef.shape = grabSensorLeftShape;
|
|
fixtureDef.isSensor = true;
|
|
fixtureDef.userData = {
|
|
onCollisionChange: function(isColliding, fixture) {
|
|
self.onFixtureWithinReach(isColliding, "left", fixture);
|
|
}
|
|
};
|
|
this.body.CreateFixture(fixtureDef);
|
|
|
|
var grabSensorRightShape = new Box2D.Collision.Shapes.b2PolygonShape();
|
|
grabSensorRightShape.SetAsOrientedBox(
|
|
this.reachDistance / 2 / Settings.RATIO,
|
|
((this.height / 2) + this.reachDistance / 4) / Settings.RATIO,
|
|
new Box2D.Common.Math.b2Vec2(
|
|
this.reachDistance / 2 / Settings.RATIO,
|
|
-this.height / 2 / Settings.RATIO
|
|
)
|
|
);
|
|
fixtureDef.shape = grabSensorRightShape;
|
|
fixtureDef.isSensor = true;
|
|
|
|
fixtureDef.userData = {
|
|
onCollisionChange: function(isColliding, fixture) {
|
|
self.onFixtureWithinReach(isColliding, "right", fixture);
|
|
}
|
|
};
|
|
|
|
this.body.CreateFixture(fixtureDef);
|
|
|
|
// Area Sensor
|
|
var areaSensorShape = new Box2D.Collision.Shapes.b2PolygonShape();
|
|
areaSensorShape.SetAsOrientedBox(
|
|
(this.width + this.areaSize) / 2 / Settings.RATIO,
|
|
(this.height + this.areaSize) / 2 / Settings.RATIO,
|
|
new Box2D.Common.Math.b2Vec2(
|
|
0,
|
|
-this.height / 2 / Settings.RATIO
|
|
)
|
|
);
|
|
fixtureDef.shape = areaSensorShape;
|
|
fixtureDef.isSensor = true;
|
|
|
|
fixtureDef.userData = {
|
|
onCollisionChange: function(isColliding, fixture) {
|
|
var userData = fixture.GetBody().GetUserData();
|
|
if(userData instanceof Doll) {
|
|
var doll = userData;
|
|
var i = self.nearbyDolls.indexOf(doll);
|
|
if(isColliding) {
|
|
if(i === -1) {
|
|
self.nearbyDolls.push(doll);
|
|
}
|
|
} else {
|
|
if(i !== -1) {
|
|
self.nearbyDolls.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.body.CreateFixture(fixtureDef);
|
|
};
|
|
|
|
Doll.prototype.setActionState = function(state) {
|
|
this.actionState = state;
|
|
};
|
|
|
|
Doll.prototype.getActionState = function() {
|
|
return this.actionState;
|
|
};
|
|
|
|
Doll.prototype.isWalking = function() {
|
|
return ["walk", "walkback", "run"].indexOf(this.actionState) >= 0;
|
|
};
|
|
|
|
Doll.prototype.spawn = function (x, y) {
|
|
Assert.number(x, y);
|
|
this.body.SetPosition(new Box2D.Common.Math.b2Vec2(x / Settings.RATIO, y / Settings.RATIO));
|
|
this.body.SetActive(true);
|
|
this.setActionState("fall");
|
|
};
|
|
|
|
Doll.prototype.getHeadPosition = function() {
|
|
var pos = this.body.GetPosition();
|
|
return {
|
|
x: pos.x,
|
|
y: pos.y - (this.height - this.headHeight / 2) / Settings.RATIO
|
|
};
|
|
};
|
|
|
|
Doll.prototype.setFriction = function (friction) {
|
|
if(!friction) friction = -1;
|
|
|
|
Assert.number(friction);
|
|
|
|
if (this.legs.GetFriction() != friction) {
|
|
this.legs.SetFriction(friction);
|
|
}
|
|
};
|
|
|
|
Doll.prototype.move = function (direction, modifierActivated) {
|
|
|
|
this.moveDirection = direction;
|
|
var speed;
|
|
var isHoldingHeavyItem = this.holdingItem && this.holdingItem.options.weight > Settings.MAX_RUNNING_WEIGHT;
|
|
|
|
switch(true) {
|
|
case direction == this.lookDirection && this.isStanding() && !isHoldingHeavyItem && !modifierActivated:
|
|
speed = Settings.RUN_SPEED;
|
|
break;
|
|
|
|
case !this.isStanding():
|
|
speed = Settings.FLY_SPEED;
|
|
|
|
if(isHoldingHeavyItem) {
|
|
if(Settings.FLY_SPEED > Settings.WALK_SPEED) {
|
|
speed = Settings.WALK_SPEED;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
speed = Settings.WALK_SPEED;
|
|
break;
|
|
}
|
|
|
|
this.setFriction(Settings.PLAYER_MOTION_FRICTION);
|
|
this.body.SetAwake(true);
|
|
|
|
Assert.number(speed, direction);
|
|
var vector = new Box2D.Common.Math.b2Vec2(speed * direction, this.body.GetLinearVelocity().y);
|
|
this.body.SetLinearVelocity(vector);
|
|
|
|
if(this.isStanding()) {
|
|
if(this.moveDirection == this.lookDirection) {
|
|
|
|
if(isHoldingHeavyItem || modifierActivated) {
|
|
this.setActionState("walk");
|
|
} else {
|
|
this.setActionState("run");
|
|
}
|
|
|
|
} else {
|
|
this.setActionState("walkback");
|
|
}
|
|
}
|
|
};
|
|
|
|
Doll.prototype.stop = function () {
|
|
this.moveDirection = 0;
|
|
this.setFriction(Settings.PLAYER_FRICTION);
|
|
if(this.isStanding()) {
|
|
this.setActionState("stand");
|
|
} else {
|
|
var vector = this.body.GetLinearVelocity().Copy();
|
|
vector.x *= Settings.JUMP_STOP_DAMPING_FACTOR;
|
|
this.body.SetLinearVelocity(vector);
|
|
}
|
|
};
|
|
|
|
Doll.prototype.jump = function () {
|
|
if (this.isStanding()) {
|
|
|
|
this.body.SetAwake(true);
|
|
|
|
var vector = new Box2D.Common.Math.b2Vec2(0, -Settings.JUMP_SPEED);
|
|
this.body.SetLinearVelocity(vector);
|
|
|
|
this.setStanding(false);
|
|
this.setActionState("jump");
|
|
}
|
|
};
|
|
|
|
Doll.prototype.jumpStop = function () {
|
|
if (!this.isStanding() ) {
|
|
this.body.SetAwake(true);
|
|
var vector = this.body.GetLinearVelocity().Copy();
|
|
if(vector.y < 0) {
|
|
vector.y *= Settings.JUMP_STOP_DAMPING_FACTOR;
|
|
this.body.SetLinearVelocity(vector);
|
|
}
|
|
}
|
|
};
|
|
|
|
Doll.prototype.setStanding = function (isStanding) {
|
|
if (this.standing == isStanding) return;
|
|
this.standing = isStanding;
|
|
if(isStanding) this.setActionState("stand");
|
|
};
|
|
|
|
Doll.prototype.isStanding = function () {
|
|
return this.standing;
|
|
};
|
|
|
|
Doll.prototype.lookAt = function(x, y) {
|
|
var oldLookDirection = this.lookDirection;
|
|
|
|
this.body.SetAwake(true);
|
|
if(x < 0) {
|
|
this.lookDirection = -1;
|
|
} else {
|
|
this.lookDirection = 1;
|
|
}
|
|
|
|
this.lookAtXY.x = x;
|
|
this.lookAtXY.y = y;
|
|
|
|
if(oldLookDirection != this.lookDirection) {
|
|
this.positionHoldingItem();
|
|
}
|
|
};
|
|
|
|
Doll.prototype.grab = function(item) {
|
|
this.holdingItem = item;
|
|
this.positionHoldingItem();
|
|
};
|
|
|
|
Doll.prototype.positionHoldingItem = function() {
|
|
if(this.holdingItem) {
|
|
|
|
if(this.holdingJoint) {
|
|
this.body.GetWorld().DestroyJoint(this.holdingJoint);
|
|
this.holdingJoint = null;
|
|
}
|
|
|
|
var bodyPosition = this.body.GetPosition();
|
|
|
|
Assert.number(this.width, this.height);
|
|
Assert.number(this.lookDirection);
|
|
var handPosition = new Box2D.Common.Math.b2Vec2(
|
|
bodyPosition.x + ((this.width / 2 / Settings.RATIO) * this.lookDirection),
|
|
bodyPosition.y - this.height / 4 * 2 / Settings.RATIO // 2/3 of the body height
|
|
);
|
|
|
|
this.holdingItem.reposition(handPosition, this.lookDirection);
|
|
|
|
var jointDef = new Box2D.Dynamics.Joints.b2WeldJointDef();
|
|
jointDef.Initialize(this.body, this.holdingItem.body, this.holdingItem.getGrabPoint());
|
|
|
|
this.holdingJoint = this.body.GetWorld().CreateJoint(jointDef);
|
|
}
|
|
};
|
|
|
|
Doll.prototype.throw = function(item, options) {
|
|
if(this.holdingJoint) {
|
|
this.body.GetWorld().DestroyJoint(this.holdingJoint);
|
|
} else {
|
|
// log stack if we called throw without a holdingJoint
|
|
var w = new Error("Throwing without a holdingJoint");
|
|
console.error(w.message + "\n" + w.stack);
|
|
}
|
|
|
|
this.holdingJoint = null;
|
|
this.holdingItem = null;
|
|
|
|
var dollVelocity = {
|
|
x: this.body.GetLinearVelocity().x,
|
|
y: this.body.GetLinearVelocity().y
|
|
};
|
|
|
|
item.throw(options, dollVelocity);
|
|
};
|
|
|
|
Doll.prototype.isAnotherPlayerNearby = function() {
|
|
return this.nearbyDolls.length > 0;
|
|
};
|
|
|
|
Doll.prototype.onFootSensorDetection = function(isColliding, fixture) { // jshint unused:false
|
|
|
|
var self = this;
|
|
|
|
var hasJumpStartVelocity = this.body.GetLinearVelocity().y < -Settings.JUMP_SPEED;
|
|
|
|
if(isColliding) {
|
|
if(!hasJumpStartVelocity) {
|
|
this.setStanding(true);
|
|
}
|
|
} else {
|
|
|
|
var contactCount = 0;
|
|
|
|
var edge = self.body.GetContactList();
|
|
while (edge) {
|
|
var contact = edge.contact;
|
|
if(!contact.IsTouching()) {
|
|
edge = edge.next;
|
|
continue;
|
|
}
|
|
|
|
if(contact.GetFixtureA() === self.footSensor) {
|
|
contactCount++;
|
|
}
|
|
|
|
if(contact.GetFixtureB() === self.footSensor) {
|
|
contactCount++;
|
|
}
|
|
|
|
edge = edge.next;
|
|
}
|
|
|
|
if (contactCount === 0) {
|
|
self.setStanding(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
Doll.prototype.onImpact = function(isColliding, fixture) { // jshint unused:false
|
|
// overwrite if necessary
|
|
};
|
|
|
|
Doll.prototype.onFixtureWithinReach = function(isColliding, side, fixture) {
|
|
var item = fixture.GetBody().GetUserData();
|
|
if (!(item instanceof Item)) return;
|
|
|
|
if(isColliding) {
|
|
|
|
this.reachableItems[side].push(item);
|
|
|
|
} else {
|
|
var i = this.reachableItems[side].indexOf(item);
|
|
if (i >= 0) {
|
|
this.reachableItems[side].splice(i, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
Doll.prototype.getVelocities = function() {
|
|
return {
|
|
linearVelocity: this.body.GetLinearVelocity(),
|
|
angularVelocity: this.body.GetAngularVelocity()
|
|
};
|
|
};
|
|
|
|
Doll.prototype.update = function() {
|
|
|
|
if (this.body.GetLinearVelocity().x === 0 && this.isWalking()) {
|
|
this.stop();
|
|
}
|
|
|
|
if (!this.body.IsAwake() && !this.isStanding()) {
|
|
this.setStanding(true);
|
|
}
|
|
};
|
|
|
|
Doll.prototype.setUpdateData = function(update) {
|
|
|
|
Parent.prototype.setUpdateData.call(this, update);
|
|
|
|
this.setActionState(update.as);
|
|
this.lookAt(update.laxy.x, update.laxy.y);
|
|
};
|
|
|
|
Doll.prototype.destroy = function() {
|
|
nc.trigger(nc.ns.core.game.worldUpdateObjects.remove, this);
|
|
Parent.prototype.destroy.call(this);
|
|
};
|
|
|
|
return Doll;
|
|
}); |