// PATH FINDING: ----------------------------------------------------------------------------------------- pathToFollow = function(map, x, y, character, targetx, targety, targetmap, radius, canSwim) { return pathToFollowMultipleTargets(map, x, y, character, [{x:targetx, y:targety, map:targetmap}], radius, canSwim); } pathToFleeMultipleTargets = function(map, x, y, character, targetList, radius, canSwim) { var buffer = boundedPathFinding(map, x, y, character, radius, canSwim); var bw = radius*2+1; var destoffs = 0; var destination = null; var d = 0; var offs = 0; var bestCost = 0; for(var i = 0;id || (minimumDistance==d && buffer[offs].cost0) { var current = stack[0]; var offs = (current.x + radius) + (current.y + radius)*bw; stack.splice(0,1); // check to see if there is a bridge: characters cannot cross bridges. The only exception to this is if they // start ON a bridge, in which case they should be able to move, otherwise, they would be stuck if (map.getBridge(x+current.x, y+current.y)==null || (current.x==0 && current.y==0)) { for(var i = 0;i<4;i++) { var nextx = current.x + direction_offx[i]; var nexty = current.y + direction_offy[i]; if (nextx*nextx + nexty*nexty <= sqr) { var nextoffs = (nextx + radius) + (nexty + radius)*bw; if (buffer[nextoffs]==null && ((canSwim==false && map.walkable(x+nextx,y+nexty,character)) || canSwim && map.sailable(x+nextx,y+nexty,character))) { buffer[nextoffs] = {cost:buffer[offs].cost+1, move:i}; stack.push({x:nextx, y:nexty}); } } } } } return buffer; } // BASIC BEHAVIORS: ----------------------------------------------------------------------------------------- function Behavior() { this.state = null; this.update = function(character, map, time, game) { return null; } this.generateSaveData = function(data) { data.state = this.state; return data; } } BRUnfriendlyIfAttackingFriendly.prototype = new Behavior(); function BRUnfriendlyIfAttackingFriendly() { this.update = function(character, map, time, game) { var objectwmes = character.AI.getAllWMEs("object"); for(var i = 0;i0 && ((character.canSwim==false && map.walkable(character.x-1,character.y,character)) || (character.canSwim==true && map.sailable(character.x-1,character.y,character)))) { return {type:ACTION_MOVE, direction:DIRECTION_LEFT}; } else { this.state = 1; } } else { if (character.xcharacter.sightRadius || dy>character.sightRadius) return returnAction; } var directions = []; for(var i = 0;i<4;i++) { if ((character.canSwim==false && map.walkable(character.x + direction_offx[i], character.y + direction_offy[i], character)) || (character.canSwim==true && map.sailable(character.x + direction_offx[i], character.y + direction_offy[i], character))) { directions.push(i); // if the last one is possible, add it more than once, for having more chances of going in a straight line: if (i==this.state.state) { for(var j = 0;j0) { this.state.state = directions[Math.floor(Math.random() * directions.length)]; if (this.state.state==-1) return {type:ACTION_NONE}; return {type:ACTION_MOVE, direction:this.state.state}; } else { return null; } } } BRWanderStopping.prototype = new Behavior(); function BRWanderStopping(radiusX, radiusY, stopTime) { this.state = {}; this.state.radiusX = radiusX; this.state.radiusY = radiusY; this.state.stopTime = stopTime; this.state.timer = 0; this.state.homeX = null; this.state.homeY = null; this.state.homeMap = null; this.subBehavior = null; this.update = function(character, map, time, game) { if (this.state.homeX==null) { this.state.homeX = character.x; this.state.homeY = character.y; this.state.homeMap = character.map; } // don't move if it's talking: if (character.conversationEngines.length>0) return null; if (this.state.timer<=0) { // choose a new target destination: if (this.subBehavior==null) this.subBehavior = new BRGoTo(null,null,null); this.subBehavior.state.targetmap = character.map; do { this.subBehavior.state.targetx = this.state.homeX + Math.floor(Math.random()*(this.state.radiusX*2+1)) - this.state.radiusX; this.subBehavior.state.targety = this.state.homeY + Math.floor(Math.random()*(this.state.radiusY*2+1)) - this.state.radiusY; }while(!map.walkable(this.subBehavior.state.targetx, this.subBehavior.state.targety, character)); this.state.timer = this.state.stopTime; } else { if (this.subBehavior==null) { this.subBehavior = new BRGoTo(null,null,null); this.subBehavior.state.targetmap = character.map; } var tmp = this.subBehavior.update(character, map, time, game); if (tmp == null && character.state==STATE_READY) this.state.timer--; return tmp; } return null; } } BRWanderInCircles.prototype = new Behavior(); function BRWanderInCircles() { this.state = DIRECTION_RIGHT; // stores the last direction this.update = function(character, map, time, game) { var directions = []; for(var i = 0;i<4;i++) { if (map.sailable(character.x + direction_offx[i], character.y + direction_offy[i])) { directions.push(i); // if the last one is possible, add it more than once, for having more chances of going in a straight line: if (i==this.state) { directions.push(i); directions.push(i); directions.push(i); directions.push(i); directions.push(i); } // if it's the next direction in the circle, also add it more than once, to tend to move in circles: if (i==((this.state+1)%4)) { directions.push(i); directions.push(i); directions.push(i); } } } if (directions.length>0) { this.state = directions[Math.floor(Math.random() * directions.length)]; return {type:ACTION_MOVE, direction:this.state}; } else { return null; } } } BRGoTo.prototype = new Behavior(); function BRGoTo(targetx, targety, targetmap) { this.state = {}; this.state.targetx = targetx; this.state.targety = targety; this.state.targetmap = targetmap; this.update = function(character, map, time, game) { if (this.state.targetmap==map.ID) { if (character.x!=this.state.targetx || character.y!=this.state.targety) { var path = pathToFollow(map, character.x, character.y, character, this.state.targetx, this.state.targety, this.state.targetmap, character.sightRadius, character.canSwim); if (path!=null && path.length>0) { return {type:ACTION_MOVE, direction:path[0]}; } } } else { // see if there is a bridge in sight: var bridges = map.bridgesInRadius(character.x, character.y, character.sightRadius); for(var i = 0;i0) { return {type:ACTION_MOVE, direction:path[0]}; } } } } return null; } } BRGoToWithWander.prototype = new Behavior(); function BRGoToWithWander(targetx, targety, targetmap) { this.state = {}; this.state.targetx = targetx; this.state.targety = targety; this.state.targetmap = targetmap; this.wanderBehavior = null; this.update = function(character, map, time, game) { if (this.state.targetmap==map.ID) { if (character.x!=this.state.targetx || character.y!=this.state.targety) { var path = pathToFollow(map, character.x, character.y, character, this.state.targetx, this.state.targety, this.state.targetmap, character.sightRadius, character.canSwim); if (path!=null && path.length>0) { return {type:ACTION_MOVE, direction:path[0]}; } } else { return null; } } else { // see if there is a bridge in sight: var bridges = map.bridgesInRadius(character.x, character.y, character.sightRadius); for(var i = 0;i0) { return {type:ACTION_MOVE, direction:path[0]}; } } } } character.AI.addShortTermWME({type:"lost", params: null}, time, 25); if (this.wanderBehavior==null) this.wanderBehavior = new BRWander(2,false); return this.wanderBehavior.update(character,map,time);; } } BRReturnHome.prototype = new Behavior(); function BRReturnHome() { this.subBehavior = new BRGoToWithWander(null,null,null); this.update = function(character, map, time, game) { if (this.subBehavior.state.targetx == null) { this.subBehavior.state.targetx = character.x; this.subBehavior.state.targety = character.y; this.subBehavior.state.targetmap = character.map; } var action = this.subBehavior.update(character,map,time,game); return action; } this.getHomeX = function() { return this.subBehavior.state.targetx; } this.getHomeY = function() { return this.subBehavior.state.targety; } this.getHomeMap = function() { return this.subBehavior.state.targetmap; } } BREnemyReturnHome.prototype = new Behavior(); function BREnemyReturnHome() { this.state = {}; this.state.timer = Math.round(Math.random()*200); this.state.target_offset_x = 0; this.state.target_offset_y = 0; this.wanderBehavior = null; this.update = function(character, map, time, game) { if (this.state.targetx == null) { this.state.targetx = character.x; this.state.targety = character.y; this.state.targetmap = character.map; } this.state.timer += 1; if ((this.state.timer%200)==0) { this.state.target_offset_x = Math.round(Math.random()*2)-1; this.state.target_offset_y = Math.round(Math.random()*2)-1; if (!map.walkable(this.state.targetx+this.state.target_offset_x, this.state.targety+this.state.target_offset_y, character)) { this.state.target_offset_x = 0; this.state.target_offset_y = 0; } } if (this.state.targetmap==map.ID) { if (character.x!=this.state.targetx+this.state.target_offset_x || character.y!=this.state.targety+this.state.target_offset_y) { var path = pathToFollow(map, character.x, character.y, character, this.state.targetx+this.state.target_offset_x, this.state.targety+this.state.target_offset_y, this.state.targetmap, character.sightRadius, character.canSwim); if (path!=null && path.length>0) { return {type:ACTION_MOVE, direction:path[0]}; } } else { return null; } } else { // see if there is a bridge in sight: var bridges = map.bridgesInRadius(character.x, character.y, character.sightRadius); for(var i = 0;i0) { return {type:ACTION_MOVE, direction:path[0]}; } } } } character.AI.addShortTermWME({type:"lost", params: null}, time, 25); if (this.wanderBehavior==null) this.wanderBehavior = new BRWander(2,false); return this.wanderBehavior.update(character,map,time);; } } BRFollowAtDistance.prototype = new Behavior(); function BRFollowAtDistance(target, mindistance, maxdistance) { this.state = {}; this.state.target = target; this.state.mindistance = mindistance; this.state.maxdistance = maxdistance; this.BRGoto = null; this.FleeBehavior = null; this.update = function(character, map, time, game) { if (this.state.target!=null) { if (this.BRGoTo==null) this.BRGoTo = new BRGoTo(null); this.BRGoTo.state.targetx = this.state.target.x; this.BRGoTo.state.targety = this.state.target.y; this.BRGoTo.state.targetmap = this.state.target.map; var dx = character.x - this.state.target.x; var dy = character.y - this.state.target.y; var d = Math.sqrt(dx*dx + dy*dy); if (d>maxdistance || this.state.target.map != character.map) { if (this.BRGoTo==null) this.BRGoTo = new BRGoTo(null); return this.BRGoTo.update(character, map, time); } else if (d0) { return {type:ACTION_MOVE, direction:path[0]}; } return null; } } BRAttackMultiple.prototype = new Behavior(); function BRAttackMultiple(targetList) { this.state = {}; this.state.target = targetList; this.state.timeSinceLastSpell = 0; this.update = function(character, map, time, game) { // spells: var tmp = [SPELL_MAGIC_MISSILE,SPELL_FIREBALL,SPELL_INCINERATE]; for(var i = 0;i= 50 && character.mp>=spell_cost[tmp[i]] && character.spells.indexOf(tmp[i])!=-1) { for(var ix = 0;ix0) { offsx = 1; direction = DIRECTION_RIGHT; } if (offsx<0) { offsx = -1; direction = DIRECTION_LEFT; } if (offsy>0) { offsy = 1; direction = DIRECTION_DOWN; } if (offsy<0) { offsy = -1; direction = DIRECTION_UP; } if (offsx!=0 || offsy!=0) { // see if there is anything in between: var x = character.x+ix + offsx; var y = character.y+iy + offsy; var path = true; while(x!=target.x && y!=target.y && path) { if ((character.canSwim && !map.walkable(x,y,character)) || (!character.canSwim && !map.sailable(x,y,character))) path = false; x = x + offsx; y = y + offsy; } if (path) { // can use spell! this.state.timeSinceLastSpell = time; return {type:ACTION_SPELL, spell:tmp[i], direction:direction, offsx:ix, offsy:iy}; } } } } } } } } // console.log("BRAttackMultiple..."); // check if we can attack: for(var ix = 0;ix0) { return {type:ACTION_MOVE, direction:path[0]}; } return null; } } BRPendingTalk.prototype = new Behavior(); function BRPendingTalk() { this.update = function(character, map, time, game) { // console.log("BRPendingTalk for " + character.name); if (character.talk_state!=STATE_READY) { // the character is doing something, hold on return; } for(var idx = 0;idx0) { var objectwmes = character.AI.getAllWMEs("object"); for(var i = 0;i0) { var objectwmes = character.AI.getAllWMEs("object"); for(var i = 0;i0) { var objectwmes = character.AI.getAllWMEs("object"); var warpedobjectwmes = character.AI.getAllWMEs("warpedobject"); objectwmes = objectwmes.concat(warpedobjectwmes); for(var i = 0;i0) return null; if (this.state.timer<=0) { var message = this.state.messages[Math.floor(Math.random()*this.state.messages.length)]; this.state.timer = this.state.period; if (this.state.angry) { return {type:ACTION_TALK_ANGRY, performative:{type:message, target:null}} } else { return {type:ACTION_TALK, performative:{type:message, target:null}} } } else { this.state.timer--; } return null; } } BRCurious.prototype = new Behavior(); function BRCurious() { this.followBehavior = new BRFollowAtDistance(null,0,0); this.update = function(character, map, time, game) { var wmes = character.AI.getAllWMEs("object"); for(var i = 0;i= this.state.timeWalking) { character.AI.addShortTermWME({type:"tired", params:null}, time, this.state.restTime); this.state.startWalkingTime = null; } return null; } } BRYellToUnfriendly.prototype = new Behavior(); function BRYellToUnfriendly(message) { this.state = {}; this.state.message = message; this.update = function(character, map, time, game) { var objectwmes = character.AI.getAllWMEs("object"); var warpedobjectwmes = character.AI.getAllWMEs("warpedobject"); objectwmes = objectwmes.concat(warpedobjectwmes); var unfriendlywmes = character.AI.getAllWMEs("unfriendly"); var friendlywmes = character.AI.getAllWMEs("friendly"); var dangerous = []; for(var i = 0;i350) { // if it's recent: var wme2 = character.AI.getWME("angry"); if (wme2==null) { // character.AI.addShortTermWME({type:"angry", params:null}, time, 400); // follow: this.subBehavior.state.targetx = friendlyobject.x; this.subBehavior.state.targety = friendlyobject.y; this.subBehavior.state.targetmap = friendlyobject.map; return this.subBehavior.update(character, map, time, game); } } } } } } }