Character.prototype = new Object(); function Character() { this.animations = {}; this.hp = this.max_hp = 10; this.mp = this.max_mp = 10; this.inventory = new Array(); this.spells = new Array(); this.equippedItems = [null,null,null]; this.activeSpells = new Array(); this.player_character = false; // this variable is only true in Player Characters, so the user has feedback of the actions this.walk_speed = 16; this.animation_speed = 4; this.attack_speed = 32; this.animations = {}; this.currentAnimation = null; this.attack = 4; this.defense = 0; this.attack_modifier = 1; this.defense_modifier = 1; this.magicImmune = false; this.target = null; // this will store the target of an attack, if any this.direction = DIRECTION_RIGHT; this.state = STATE_READY; this.state_timer = 0; this.talk_state = STATE_READY; this.talk_state_timer = 0; this.talk_message = null; this.just_attacked_by = null; // holds if another character attacked "this" in the last frame (used by the AI modules) this.receivedDamageTimmer = 0; // this is used to draw a graphic effect when a character is hit this.vehicle = null; this.generateSaveData = function(objects, data) { data = Character.prototype.generateSaveData.call(this,objects,data); // character data: if (this.level!=undefined) data.level = this.level; if (this.experience!=undefined) data.experience = this.experience; if (this.hp!=undefined) { data.hp = this.hp; data.mp = this.mp; data.max_hp = this.max_hp; data.max_mp = this.max_mp; } if (this.inventory!=undefined) { data.inventory = []; for(var i = 0;i0) { map.pushTextOverlay("" + damage, this.x, this.y, 255,0,0); this.receivedDamageTimmer = 25; } else { map.pushTextOverlay("miss", this.x, this.y, 255,255,0); } if (attacker!=null) this.just_attacked_by = {attacker:attacker, timer:25}; } this.sendAction = function(action,map,game) { if (this.state == STATE_READY) { if (action.type == ACTION_INTERACT) { var oldx = this.x; var oldy = this.y; var nextx = this.x + direction_offx[action.direction]; var nexty = this.y + direction_offy[action.direction]; if (map.walkable(nextx,nexty,this) || (this.canSwim == true && map.sailable(nextx,nexty,this))) { // change action to move: action.type = ACTION_MOVE; } else { // check for trees to he chopped: var weapon = this.equippedItems[ITEM_WEAPON]; if (weapon!=null && weapon.canChop) { if (map.choppeable(nextx,nexty)) { // chop the tree!! if (this.player_character) game.pushMessage("You chop the tree with the " + weapon.name + "!"); map.chopTree(nextx,nexty); this.state = STATE_INTERACTING; // chop a tree is like an attack this.state_timer = 0; return true; } else if (map.nonchoppeabletree(nextx,nexty)) { // chop the tree!! if (this.player_character) game.pushMessage("You try to chop the tree with the axe, but you fail..."); return true; } } // check for doors: var door = map.getDoor(nextx,nexty); if (door!=null && door.state) { // console.log("bumped onto door: " + door.name); // check if we have the key: for(i = 0;i0) { if (!this.player_character || this.inventory.length=this.inventory.length) { if (this.player_character) game.pushMessage("You don't have that item!"); return false; } else { var item = this.inventory[action.item]; this.inventory.splice(action.item,1); game.warpObject(item, this.x, this.y, this.map, 1); return true; } } else if (action.type == ACTION_DROP_GOLD) { // console.log("ACTION_DROP_GOLD " + action.gold); if (this.gold=this.inventory.length) { if (this.player_character) game.pushMessage("You don't have that item!"); return false; } else { var item = this.inventory[action.item]; if (item.equipable==true) { // Equip item: this.inventory.splice(action.item,1); previousItem = this.equippedItems[item.itemType]; this.equippedItems[item.itemType] = item; if (previousItem!=null) this.inventory.push(previousItem); return true; } else if (item.useable==true) { item.effect(this,map,game); } else { if (this.player_character) game.pushMessage("You cannot use that item!"); return false; } } } else if (action.type == ACTION_UNEQUIP) { // console.log("ACTION_UNEQUIP " + action.item); if (action.item<0 || this.equippedItems[action.item]==null) { if (this.player_character) game.pushMessage("You don't have that item!"); return false; } else { var item = this.equippedItems[action.item]; if (this.inventory.length>=INVENTORY_SIZE) { if (this.player_character) game.pushMessage("Inventory full!"); return false; } else { this.equippedItems[item.itemType] = null; this.inventory.push(item); return true; } } } else if (action.type == ACTION_ATTACK) { var nextx = this.x; var nexty = this.y; if (action.direction == DIRECTION_UP) nexty--; else if (action.direction == DIRECTION_DOWN) nexty++; else if (action.direction == DIRECTION_LEFT) nextx--; else if (action.direction == DIRECTION_RIGHT) nextx++; if (map.walkable(nextx,nexty,this) || (this.canSwim == true && map.sailable(nextx,nexty,this))) { // change action to move: action.type = ACTION_MOVE; } else { for(var ix = 0;ix=target.max_hp) target.hp = target.max_hp; this.state = STATE_CASTING; this.state_timer = 0; } } else if (action.spell == SPELL_SHIELD) { var targetx = this.x; var targety = this.y; if (action.direction==DIRECTION_LEFT) targetx--; if (action.direction==DIRECTION_UP) targety--; if (action.direction==DIRECTION_RIGHT) targetx+=this.width; if (action.direction==DIRECTION_DOWN) targety+=this.height; var target = map.getCharacter(targetx,targety); if (target!=null) { this.mp-=spell_cost[action.spell]; var found = false; for(var i = 0;i=target.max_hp) target.hp = target.max_hp; this.mp-=spell_cost[action.spell]; this.state = STATE_CASTING; this.state_timer = 0; } } else if (action.spell == SPELL_INCINERATE) { if (action.direction==DIRECTION_LEFT || action.direction==DIRECTION_UP || action.direction==DIRECTION_RIGHT || action.direction==DIRECTION_DOWN) { this.mp-=spell_cost[action.spell]; var anim = game.defaultAnimations[SPELL_INCINERATE + ANIM_DIRECTIONS[action.direction]]; if (anim==null) anim = game.defaultAnimations[SPELL_INCINERATE] game.warpObject(new Spell(action.direction, 24, Math.floor(this.getMagicMultiplier()*32), 0, this, anim), this.x+action.offsx,this.y+action.offsy, this.map, 1); this.state = STATE_CASTING; this.state_timer = 0; } } } if (action.type == ACTION_MOVE) { var oldx = this.x; var oldy = this.y; var nextx = this.x + direction_offx[action.direction]; var nexty = this.y + direction_offy[action.direction]; if (map.walkable(nextx,nexty,this) || (this.canSwim == true && map.sailable(nextx,nexty,this))) { this.x = nextx; this.y = nexty; this.state = STATE_MOVING; this.state_timer = 0; this.direction = action.direction; return true; } else { return false; } } } else if (this.state == STATE_VEHICLE_READY) { // controlling a vehicle: if (action.type == ACTION_TAKE) { this.state = STATE_READY; this.vehicle = null; } else if (action.type == ACTION_MOVE) { var oldx = this.x; var oldy = this.y; var nextx = this.x + direction_offx[action.direction]; var nexty = this.y + direction_offy[action.direction]; if ((this.vehicle.canSwim && map.sailable(nextx,nexty,this)) || (this.vehicle.canWalk && map.walkable(nextx,nexty,this))) { this.x = nextx; this.y = nexty; this.state = STATE_VEHICLE_MOVING; this.state_timer = 0; this.direction = action.direction; this.vehicle.x = nextx; this.vehicle.y = nexty; this.vehicle.state = STATE_MOVING; this.vehicle.state_timer = 0; this.vehicle.direction = action.direction; return true; } else { return false; } } } if (this.talk_state==STATE_READY) { if (action.type == ACTION_TALK || action.type == ACTION_TALK_ANGRY) { var text = action.performative.type; if (text==TALK_HI) text = "Good day!"; else if (text==TALK_BYE) text = "Farewell!"; else if (text==TALK_TRADE) { text = "Would you like to trade with me?"; } else if (text==TALK_ASK) { text = action.performative.text; } var time = 50 + text.length*4; if (action.type == ACTION_TALK_ANGRY) { map.pushTextBubbleColor(text,this,time,'#000000','#ffff3f'); } else { map.pushTextBubble(text,this,time); } if (this.player_character) { game.pushMessage("You say: " + text); } else { var currentCharacter = game.characters[game.currentCharacter] if (this.map == currentCharacter.map) game.pushMessage(this.name + " says: " + text); } // Send the performative: if (action.performative.target instanceof AICharacter && this.map == action.performative.target.map) { action.performative.target.receiveDelayedMessage(action.performative, this, time); } this.talk_state = STATE_TALKING; this.talk_state_timer = time; this.talk_message = action.performative; } } return false; } this.update = function(character, map, game) { if (this.hp<=0) { if (this.state == STATE_DEATH && (this.animations[this.currentAnimation]==null || this.animations[this.currentAnimation].isCompleted())) { // drop items and gold: for(var i = 0;i0) { item = new ItemCoinPurse(this.gold, game.defaultAnimations[COINPURSE_ANIMATION]); game.warpObject(item, this.x, this.y, this.map, 1); } // pop all the input menus from the game stack (otherwise, the other character will receive the input) if (this instanceof PlayerCharacter) game.menuStack = []; return false; } else { this.state = STATE_DEATH; } } if (this.currentAnimation!=null && this.animations[this.currentAnimation]!=null) { this.animations[this.currentAnimation].update(); this.width = this.animations[this.currentAnimation].getWidth(); this.height = this.animations[this.currentAnimation].getHeight(); } switch(this.state) { case STATE_READY: if (this.state_timer==0) this.currentAnimation = ANIM_IDLE + ANIM_DIRECTIONS[this.direction]; this.state_timer++; break; case STATE_MOVING: if (this.state_timer==0) { this.currentAnimation = ANIM_MOVING + ANIM_DIRECTIONS[this.direction]; if (this.animations[this.currentAnimation]==null) this.currentAnimation = ANIM_IDLE + ANIM_DIRECTIONS[this.direction]; } this.state_timer++; if (this.state_timer>=this.walk_speed) { this.target = null; this.state = STATE_READY; this.state_timer = 0; // check for bridges: var bridge = map.getBridge(this.x,this.y); if (bridge != null) { var targetMap = game.maps[bridge.mapTarget]; for(tx = bridge.xTarget;tx=this.walk_speed) { this.target = null; this.state = STATE_VEHICLE_READY; this.state_timer = 0; // check for bridges: var bridge = map.getBridge(this.x,this.y); if (bridge != null) { var targetMap = game.maps[bridge.mapTarget]; for(tx = bridge.xTarget;tx=this.attack_speed) { this.state = STATE_READY; this.state_timer = 0; } break; case STATE_INTERACTING: if (this.state_timer==0) { this.currentAnimation = ANIM_INTERACTING + ANIM_DIRECTIONS[this.direction]; if (this.animations[this.currentAnimation]==null) this.currentAnimation = ANIM_IDLE + ANIM_DIRECTIONS[this.direction]; } this.state_timer++; if (this.state_timer>=this.attack_speed) { this.state = STATE_READY; this.state_timer = 0; } break; case STATE_CASTING: if (this.state_timer==0) { this.currentAnimation = ANIM_TALKING + ANIM_DIRECTIONS[this.direction]; if (this.animations[this.currentAnimation]==null) this.currentAnimation = ANIM_IDLE + ANIM_DIRECTIONS[this.direction]; } this.state_timer++; if (this.state_timer>=this.attack_speed) { this.state = STATE_READY; this.state_timer = 0; } break; case STATE_DEATH: this.currentAnimation = ANIM_DEATH; this.state_timer++; break; default: break; } switch(this.talk_state) { case STATE_READY: break; case STATE_TALKING: this.talk_state_timer--; if (this.talk_state_timer<=0) { this.talk_state = STATE_READY; this.talk_state_timer = 0; this.talk_message = null; } break; default: break; } if (this.receivedDamageTimmer>0) this.receivedDamageTimmer--; for(var i = 0;i0) { var f = this.receivedDamageTimmer/25; if (f>1) f = 1; if (f<0) f = 0; drawRedTintedTile(tile,x,y,alpha*f); } } }