/** * logic() * Handle a single frame. This includes: * - move the avatar based on buttons pressed * - calculate the next frame of animation * - calculate camera position based on avatar position * * @param power_index The actionbar power activated. -1 means no power. */ void Avatar::logic(int actionbar_power, bool restrictPowerUse) { Point target; int stepfx; stats.logic(); if (stats.stun_duration > 0) return; bool allowed_to_move; bool allowed_to_use_power; // check level up if (stats.level < 17 && stats.xp >= stats.xp_table[stats.level]) { stats.level++; stringstream ss; ss << "Congratulations, you have reached level " << stats.level << "! You may increase one attribute through the Character Menu."; log_msg = ss.str(); stats.recalc(); Mix_PlayChannel(-1, level_up, 0); } // check for bleeding spurt if (stats.bleed_duration % 30 == 1) { powers->activate(POWER_SPARK_BLOOD, &stats, stats.pos); } // check for bleeding to death if (stats.hp == 0 && !(stats.cur_state == AVATAR_DEAD)) { stats.cur_state = AVATAR_DEAD; } // assist mouse movement if (!inp->pressing[MAIN1]) drag_walking = false; // handle animation activeAnimation->advanceFrame(); switch(stats.cur_state) { case AVATAR_STANCE: setAnimation("stance"); // allowed to move or use powers? if (MOUSE_MOVE) { allowed_to_move = restrictPowerUse && (!inp->lock[MAIN1] || drag_walking); allowed_to_use_power = !allowed_to_move; } else { allowed_to_move = true; allowed_to_use_power = true; } // handle transitions to RUN if (allowed_to_move) set_direction(); if (pressing_move() && allowed_to_move) { if (MOUSE_MOVE && inp->pressing[MAIN1]) { inp->lock[MAIN1] = true; drag_walking = true; } if (move()) { // no collision stats.cur_state = AVATAR_RUN; } } // handle power usage if (allowed_to_use_power && actionbar_power != -1 && stats.cooldown_ticks == 0) { target = screen_to_map(inp->mouse.x, inp->mouse.y + powers->powers[actionbar_power].aim_assist, stats.pos.x, stats.pos.y); // check requirements if (powers->powers[actionbar_power].requires_mp > stats.mp) break; if (powers->powers[actionbar_power].requires_physical_weapon && !stats.wielding_physical) break; if (powers->powers[actionbar_power].requires_mental_weapon && !stats.wielding_mental) break; if (powers->powers[actionbar_power].requires_offense_weapon && !stats.wielding_offense) break; if (powers->powers[actionbar_power].requires_los && !map->collider.line_of_sight(stats.pos.x, stats.pos.y, target.x, target.y)) break; if (powers->powers[actionbar_power].requires_empty_target && !map->collider.is_empty(target.x, target.y)) break; current_power = actionbar_power; act_target.x = target.x; act_target.y = target.y; // is this a power that requires changing direction? if (powers->powers[current_power].face) { stats.direction = face(target.x, target.y); } // handle melee powers if (powers->powers[current_power].new_state == POWSTATE_SWING) { stats.cur_state = AVATAR_MELEE; break; } // handle ranged powers if (powers->powers[current_power].new_state == POWSTATE_SHOOT) { stats.cur_state = AVATAR_SHOOT; break; } // handle ment powers if (powers->powers[current_power].new_state == POWSTATE_CAST) { stats.cur_state = AVATAR_CAST; break; } if (powers->powers[current_power].new_state == POWSTATE_BLOCK) { stats.cur_state = AVATAR_BLOCK; stats.blocking = true; break; } } break; case AVATAR_RUN: setAnimation("run"); stepfx = rand() % 4; if (activeAnimation->getCurFrame() == 1 || activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { Mix_PlayChannel(-1, sound_steps[stepfx], 0); } // allowed to move or use powers? if (MOUSE_MOVE) { allowed_to_use_power = !(restrictPowerUse && !inp->lock[MAIN1]); } else { allowed_to_use_power = true; } // handle direction changes set_direction(); // handle transition to STANCE if (!pressing_move()) { stats.cur_state = AVATAR_STANCE; break; } else if (!move()) { // collide with wall stats.cur_state = AVATAR_STANCE; break; } // handle power usage if (allowed_to_use_power && actionbar_power != -1 && stats.cooldown_ticks == 0) { target = screen_to_map(inp->mouse.x, inp->mouse.y + powers->powers[actionbar_power].aim_assist, stats.pos.x, stats.pos.y); // check requirements if (powers->powers[actionbar_power].requires_mp > stats.mp) break; if (powers->powers[actionbar_power].requires_physical_weapon && !stats.wielding_physical) break; if (powers->powers[actionbar_power].requires_mental_weapon && !stats.wielding_mental) break; if (powers->powers[actionbar_power].requires_offense_weapon && !stats.wielding_offense) break; if (powers->powers[actionbar_power].requires_los && !map->collider.line_of_sight(stats.pos.x, stats.pos.y, target.x, target.y)) break; if (powers->powers[actionbar_power].requires_empty_target && !map->collider.is_empty(target.x, target.y)) break; current_power = actionbar_power; act_target.x = target.x; act_target.y = target.y; // is this a power that requires changing direction? if (powers->powers[current_power].face) { stats.direction = face(target.x, target.y); } // handle melee powers if (powers->powers[current_power].new_state == POWSTATE_SWING) { stats.cur_state = AVATAR_MELEE; break; } // handle ranged powers if (powers->powers[current_power].new_state == POWSTATE_SHOOT) { stats.cur_state = AVATAR_SHOOT; break; } // handle ment powers if (powers->powers[current_power].new_state == POWSTATE_CAST) { stats.cur_state = AVATAR_CAST; break; } if (powers->powers[current_power].new_state == POWSTATE_BLOCK) { stats.cur_state = AVATAR_BLOCK; stats.blocking = true; break; } } break; case AVATAR_MELEE: setAnimation("melee"); if (activeAnimation->getCurFrame() == 1) { Mix_PlayChannel(-1, sound_melee, 0); } // do power if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { powers->activate(current_power, &stats, act_target); } if (activeAnimation->getTimesPlayed() >= 1) { stats.cur_state = AVATAR_STANCE; if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown; } break; case AVATAR_CAST: setAnimation("ment"); // do power if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { powers->activate(current_power, &stats, act_target); } if (activeAnimation->getTimesPlayed() >= 1) { stats.cur_state = AVATAR_STANCE; if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown; } break; case AVATAR_SHOOT: setAnimation("ranged"); // do power if (activeAnimation->getCurFrame() == activeAnimation->getMaxFrame()/2) { powers->activate(current_power, &stats, act_target); } if (activeAnimation->getTimesPlayed() >= 1) { stats.cur_state = AVATAR_STANCE; if (stats.haste_duration == 0) stats.cooldown_ticks += stats.cooldown; } break; case AVATAR_BLOCK: setAnimation("block"); if (powers->powers[actionbar_power].new_state != POWSTATE_BLOCK) { stats.cur_state = AVATAR_STANCE; stats.blocking = false; } break; case AVATAR_HIT: setAnimation("hit"); if (activeAnimation->getTimesPlayed() >= 1) { stats.cur_state = AVATAR_STANCE; } break; case AVATAR_DEAD: setAnimation("die"); if (activeAnimation->getCurFrame() == 1 && activeAnimation->getTimesPlayed() < 1) { Mix_PlayChannel(-1, sound_die, 0); log_msg = "You are defeated. You lose half your gold. Press Enter to continue."; } if (activeAnimation->getTimesPlayed() >= 1) { stats.corpse = true; } // allow respawn with Accept if (inp->pressing[ACCEPT]) { stats.hp = stats.maxhp; stats.mp = stats.maxmp; stats.alive = true; stats.corpse = false; stats.cur_state = AVATAR_STANCE; // remove temporary effects stats.clearEffects(); // set teleportation variables. GameEngine acts on these. map->teleportation = true; map->teleport_mapname = map->respawn_map; map->teleport_destination.x = map->respawn_point.x; map->teleport_destination.y = map->respawn_point.y; } break; default: break; } // calc new cam position from player position // cam is focused at player position map->cam.x = stats.pos.x; map->cam.y = stats.pos.y; map->hero_tile.x = stats.pos.x / 32; map->hero_tile.y = stats.pos.y / 32; // check for map events map->checkEvents(stats.pos); }
/** * logic() * Handle a single frame. This includes: * - move the avatar based on buttons pressed * - calculate the next frame of animation * - calculate camera position based on avatar position * * @param action The actionbar power activated and the target. action.power == 0 means no power. * @param restrict_power_use Whether or not to allow power usage on mouse1 * @param npc True if the player is talking to an NPC. Can limit ability to move/attack in certain conditions */ void Avatar::logic(std::vector<ActionData> &action_queue, bool restrict_power_use, bool npc) { // hazards are processed after Avatar and Enemy[] // so process and clear sound effects from previous frames // check sound effects if (AUDIO) { if (play_sfx_phys) snd->play(sound_melee, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_ment) snd->play(sound_mental, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_hit) snd->play(sound_hit, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_die) snd->play(sound_die, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if (play_sfx_critdie) snd->play(sound_die, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); if(play_sfx_block) snd->play(sound_block, GLOBAL_VIRTUAL_CHANNEL, stats.pos, false); // clear sound flags play_sfx_hit = false; play_sfx_phys = false; play_sfx_ment = false; play_sfx_die = false; play_sfx_critdie = false; play_sfx_block = false; } // clear current space to allow correct movement mapr->collider.unblock(stats.pos.x, stats.pos.y); // turn on all passive powers if ((stats.hp > 0 || stats.effects.triggered_death) && !respawn && !transform_triggered) powers->activatePassives(&stats); if (transform_triggered) transform_triggered = false; // handle when the player stops blocking if (stats.effects.triggered_block && !stats.blocking) { stats.cur_state = AVATAR_STANCE; stats.effects.triggered_block = false; stats.effects.clearTriggerEffects(TRIGGER_BLOCK); stats.refresh_stats = true; } stats.logic(); bool allowed_to_move; bool allowed_to_use_power = true; #ifdef __ANDROID__ const bool click_to_respawn = true; #else const bool click_to_respawn = false; #endif // check for revive if (stats.hp <= 0 && stats.effects.revive) { stats.hp = stats.get(STAT_HP_MAX); stats.alive = true; stats.corpse = false; stats.cur_state = AVATAR_STANCE; } // check level up if (stats.level < static_cast<int>(stats.xp_table.size()) && stats.xp >= stats.xp_table[stats.level]) { stats.level_up = true; stats.level++; std::stringstream ss; ss << msg->get("Congratulations, you have reached level %d!", stats.level); if (stats.level < stats.max_spendable_stat_points) { ss << " " << msg->get("You may increase one attribute through the Character Menu."); newLevelNotification = true; } log_msg = ss.str(); stats.recalc(); snd->play(sound_levelup); // if the player managed to level up while dead (e.g. via a bleeding creature), restore to life if (stats.cur_state == AVATAR_DEAD) { stats.cur_state = AVATAR_STANCE; } } // check for bleeding to death if (stats.hp == 0 && !(stats.cur_state == AVATAR_DEAD)) { stats.effects.triggered_death = true; stats.cur_state = AVATAR_DEAD; } // assist mouse movement if (!inpt->pressing[MAIN1]) { drag_walking = false; } // block some interactions when attacking if (!inpt->pressing[MAIN1] && !inpt->pressing[MAIN2]) { stats.attacking = false; } else if((inpt->pressing[MAIN1] && !inpt->lock[MAIN1]) || (inpt->pressing[MAIN2] && !inpt->lock[MAIN2])) { stats.attacking = true; } // handle animation if (!stats.effects.stun) { activeAnimation->advanceFrame(); for (unsigned i=0; i < anims.size(); i++) { if (anims[i] != NULL) anims[i]->advanceFrame(); } } if (target_anim && target_anim->getTimesPlayed() >= 1) { target_visible = false; target_anim->reset(); } if (target_anim && target_visible) target_anim->advanceFrame(); // change the cursor if we're attacking if (action_queue.empty()) { lock_cursor = false; } else if (lock_cursor) { curs->setCursor(CURSOR_ATTACK); } // save a valid tile position in the event that we untransform on an invalid tile if (stats.transformed && mapr->collider.is_valid_position(stats.pos.x,stats.pos.y,MOVEMENT_NORMAL, true)) { transform_pos = stats.pos; transform_map = mapr->getFilename(); } switch(stats.cur_state) { case AVATAR_STANCE: setAnimation("stance"); // allowed to move or use powers? if (MOUSE_MOVE) { allowed_to_move = restrict_power_use && (!inpt->lock[MAIN1] || drag_walking) && !lockAttack && !npc; allowed_to_use_power = !allowed_to_move; } else { allowed_to_move = true; allowed_to_use_power = true; } // handle transitions to RUN if (allowed_to_move) set_direction(); if (pressing_move() && allowed_to_move) { if (MOUSE_MOVE && inpt->pressing[MAIN1]) { inpt->lock[MAIN1] = true; drag_walking = true; } if (move()) { // no collision stats.cur_state = AVATAR_RUN; } } if (MOUSE_MOVE && !inpt->pressing[MAIN1]) { inpt->lock[MAIN1] = false; lockAttack = false; } break; case AVATAR_RUN: setAnimation("run"); if (!sound_steps.empty()) { int stepfx = rand() % static_cast<int>(sound_steps.size()); if (activeAnimation->isFirstFrame() || activeAnimation->isActiveFrame()) snd->play(sound_steps[stepfx]); } // allowed to move or use powers? if (MOUSE_MOVE) { allowed_to_use_power = !(restrict_power_use && !inpt->lock[MAIN1]); } else { allowed_to_use_power = true; } // handle direction changes set_direction(); // handle transition to STANCE if (!pressing_move()) { stats.cur_state = AVATAR_STANCE; break; } else if (!move()) { // collide with wall stats.cur_state = AVATAR_STANCE; break; } if (activeAnimation->getName() != "run") stats.cur_state = AVATAR_STANCE; break; case AVATAR_ATTACK: setAnimation(attack_anim); if (MOUSE_MOVE) lockAttack = true; if (activeAnimation->isFirstFrame() && attack_anim == "swing") snd->play(sound_melee); if (activeAnimation->isFirstFrame() && attack_anim == "cast") snd->play(sound_mental); // do power if (activeAnimation->isActiveFrame()) { // some powers check if the caster is blocking a tile // so we block the player tile prematurely here mapr->collider.block(stats.pos.x, stats.pos.y, false); powers->activate(current_power, &stats, act_target); hero_cooldown[current_power] = powers->getPower(current_power).cooldown; } if (activeAnimation->getTimesPlayed() >= 1 || activeAnimation->getName() != attack_anim) { stats.cur_state = AVATAR_STANCE; stats.cooldown_ticks += stats.cooldown; } break; case AVATAR_BLOCK: setAnimation("block"); stats.blocking = false; break; case AVATAR_HIT: setAnimation("hit"); if (activeAnimation->isFirstFrame()) { stats.effects.triggered_hit = true; } if (activeAnimation->getTimesPlayed() >= 1 || activeAnimation->getName() != "hit") { stats.cur_state = AVATAR_STANCE; } break; case AVATAR_DEAD: allowed_to_use_power = false; if (stats.effects.triggered_death) break; if (stats.transformed) { stats.transform_duration = 0; untransform(); } setAnimation("die"); if (!stats.corpse && activeAnimation->isFirstFrame() && activeAnimation->getTimesPlayed() < 1) { stats.effects.clearEffects(); // raise the death penalty flag. Another module will read this and reset. stats.death_penalty = true; // close menus in GameStatePlay close_menus = true; snd->play(sound_die); if (stats.permadeath) { log_msg = msg->get("You are defeated. Game over! Press Enter to exit to Title."); } else { log_msg = msg->get("You are defeated. Press Enter to continue."); } // if the player is attacking, we need to block further input if (inpt->pressing[MAIN1]) inpt->lock[MAIN1] = true; } if (activeAnimation->getTimesPlayed() >= 1 || activeAnimation->getName() != "die") { stats.corpse = true; } // allow respawn with Accept if not permadeath if (inpt->pressing[ACCEPT] || (click_to_respawn && inpt->pressing[MAIN1] && !inpt->lock[MAIN1])) { if (inpt->pressing[ACCEPT]) inpt->lock[ACCEPT] = true; if (click_to_respawn && inpt->pressing[MAIN1]) inpt->lock[MAIN1] = true; mapr->teleportation = true; mapr->teleport_mapname = mapr->respawn_map; if (stats.permadeath) { // set these positions so it doesn't flash before jumping to Title mapr->teleport_destination.x = stats.pos.x; mapr->teleport_destination.y = stats.pos.y; } else { respawn = true; // set teleportation variables. GameEngine acts on these. mapr->teleport_destination.x = mapr->respawn_point.x; mapr->teleport_destination.y = mapr->respawn_point.y; } } break; default: break; } // handle power usage if (allowed_to_use_power) handlePower(action_queue); // calc new cam position from player position // cam is focused at player position mapr->cam.x = stats.pos.x; mapr->cam.y = stats.pos.y; // check for map events mapr->checkEvents(stats.pos); // decrement all cooldowns for (unsigned i = 0; i < hero_cooldown.size(); i++) { hero_cooldown[i]--; if (hero_cooldown[i] < 0) hero_cooldown[i] = 0; } // make the current square solid mapr->collider.block(stats.pos.x, stats.pos.y, false); }