Template system to create behaviours attached to game objects.
properties are numbers such as:
- health
- resistance (and affiliates, resistance to poison, resistance to cold...)
- skills (ranged weapons, lockpicking)
affinities defines how much a gameObject loves or hates something. That something can be:
- any property of a game object, for example:
type
- ex: the gameObject hates orcshealth
- ex: the gameObject loves weak people- ``
provides are what a gameObject can provide. Provides are dynamic and vary according to state.
- A cow provides milk, mount, and if dead, meat
- A human provides the items he has, the information he has, and if subdued, a slave.
- A rock provides a weapon, and if destroyed, construction material
Some provides can occur by just modifying a number (ex: a human can lose money and remain a human), other convert the gameObject (a dead human is not considered a human anymore, it becomes another game object type).
Needs define what a gameObject...needs. Needs can be:
- Time-based and volatile, they go away if unfulfilled (ex: want a drink)
- Time-based and incremental if unfulfilled (ex: sleep, hunger)
- State-based (ex: poisoned and needs remedy)
- Affinity-based (ex: hates a gameObject and wants to attack it, loves reading and wants to sit and read a bit)
Decisions are parsed in an array particular to each gameObject. The first decision that matches gets used, and the others discarded. Not all decisions get parsed for all gameObjects; For example, a rock would have no decisions at all, and a cow would not get to decide if it wants to read.
function eat(gameObject,target,callback){
var hunger = gameObject.hunger;
if(hunger > 5){
if(!target){
target = gameObject.surroundings();
}
var possibleTargets = new GameObject();
target.each(function(target,next,done){
if(target.provides('hunger')){
var targetPosition = target.currentPosition();
var distance = character.currentPosition().distance(targetPosition);
var animated = target.is('alive') || target.is('undead');
target.distance = distance;
target.animated = animated;
possibleTargets.push(target);
}
},function(){
if(possibleTargets.length){
possibleTargets.orderBy('animated','distance');
possibleTargets.each(function(target,next,done){
if(target.is('alive')){
if(hunger < 10){return next();}
decisions.attack(gameObject,target,function(behavior){
if(behavior){
behavior(function(){
if(target.state.dead){
character.hunger.consume(target);
return done();
}else{
return next();
}
});
}else{
return next();
}
});
}else if(target.is('undead')){
return next();
}
else{
character.go(target.currentPosition())
.then(function(){
character.hunger.consume(target);
return done();
})
.fail(function(){
// handle failure state here
});
}
},function(){
callback();
});
}
});
}
}
function attack(gameObject,target,callback){
if(!target){
target = gameObject.surroundings();
}
var possibleTargets = new GameObject();
target.each(function(target,next,done){
var affinity = gameObject.affinity(target);
if(affinity > 10){return next();}
var needs = gameObject.needs();
var needsFullfilled = target.provides(needs);
var health = gameObject.health;
var courage = gameObject.courage;
var willpower = gameObject.willpower;
if(health < 10 && courage < 10){
return next();
}
var hate = roll(-affinity,courage,willpower,needsFullfilled);
if(hate > 5){
target.hate = hate;
possibleTargets.push(target);
return next();
}
return next();
},function(){
if(possibleTargets.length){
possibleTargets.orderBy('hate');
return callback(behaviors.attack.curry(gameObject,possibleTargets));
}else{
return callback();
}
});
}
Behaviors define what a gameObject does. Examples:
function attack(gameObject,target,callback){
gameObject.canSee(target)
.then(function(c,o){
var weapon,destination;
if(gameObject.intelligence>10){
weapon = gameObject.getBestWeaponForTarget(target);
}else{
weapon = gameObject.getBestWeapon();
}
if(weapon.type == 'longdistance'){
destination = target.currentPosition().substract(weapon.range);
}else{
destination = target.currentPosition();
}
gameObject.go(destination)
.then(function(c,o){
var hitPoints = roll(weapon.hitPoints,c.strength);
o.receiveDamage(hitPoints,weapon);
// passing the weapon for any status processing
// or particular resistance to type
if(o.state.dead){
return callback(o);
}
// at next turn the whole behaviour tree is parsed again to decide if the gameObject attacks again
})
.fail(function(c,o){
// handle failure, maybe the target has moved,
// maybe there' s an obstacle...
});
})
.fail(function(c,o){
});
}
function find(gameObject,target,callback){
gameObject.canSee(target)
.then(function(c,o){
gameObject.canReach(target)
.then(function(c,o){
callback(gameObject.go(target));
})
.fail(function(c,o){
// make gameObject go around the obstacle or whatever
});
})
.fail(function(c,o){
var point = c.currentPosition();
var destinations = [];
var test = function(c,o){
var currentDestination = c.getRandomDestination();
while(destinations.indexOf(currentDestination)>=0){
currentDestination = c.getRandomDestination();
}
gameObject.go(currentDestination)
.then(function(c,o){
c.canSee(o)
.then(function(c,o){find(c,o,callback)});
.fail(test);
});
.fail(test)
}
});
}