bool CombatGameInst::projectile_attack(GameState* gs, CombatGameInst* inst, const Weapon& weapon, const Projectile& projectile) { if (!cooldowns().can_doaction()) return false; MTwist& mt = gs->rng(); WeaponEntry& wentry = weapon.weapon_entry(); ProjectileEntry& pentry = projectile.projectile_entry(); AttackStats attack; if (!pentry.is_unarmed()) { attack.weapon = weapon; } attack.projectile = projectile; EffectiveAttackStats atkstats = effective_atk_stats(mt, AttackStats(weapon, projectile)); Pos p(inst->x, inst->y); p.x += gs->rng().rand(-12, +13); p.y += gs->rng().rand(-12, +13); if (gs->tile_radius_test(p.x, p.y, 10)) { p.x = inst->x; p.y = inst->y; } GameInst* bullet = new ProjectileInst(projectile, atkstats, id, Pos(x, y), p, pentry.speed, pentry.range); gs->add_instance(bullet); cooldowns().reset_action_cooldown(pentry.cooldown); cooldowns().action_cooldown += gs->rng().rand(-4, 5); return false; }
void CombatGameInst::draw(GameState *gs) { GameView& view = gs->view(); SpriteEntry& spr = game_sprite_data[sprite]; Colour draw_colour = effects().effected_colour(); if (cooldowns().is_hurting()) { float s = 1 - hurt_alpha_value(cooldowns().hurt_cooldown); draw_colour = draw_colour.multiply(Colour(255, 255 * s, 255 * s)); } int sx = x - spr.width() / 2, sy = y - spr.height() / 2; gl_draw_sprite(view, sprite, sx, sy, vx, vy, gs->frame(), draw_colour); effects().draw_effect_sprites(gs, Pos(sx, sy)); if (is_resting) { GLimage& restimg = game_sprite_data[get_sprite_by_name("resting")].img(); gl_draw_image(view, restimg, x - spr.width() / 2, y - spr.height() / 2); } CoreStats& ecore = effective_stats().core; //Draw health bar int healthbar_offsety = 20; if (target_radius > 16) healthbar_offsety = target_radius + 8; if (ecore.hp < ecore.max_hp) { const BBox statbox(x - 10, y - healthbar_offsety, x + 10, y - healthbar_offsety + 5); gl_draw_statbar(view, statbox, ecore.hp, ecore.max_hp); } }
void PlayerInst::use_dngn_portal(GameState* gs, const GameAction& action) { if (!effective_stats().allowed_actions.can_use_stairs) { if (is_local_player()) { gs->game_chat().add_message( "You cannot use the exit in this state!"); } return; } cooldowns().reset_stopaction_timeout(50); FeatureInst* portal = find_usable_portal(gs, this); portal->player_interact(gs, this); reset_rest_cooldown(); std::string subject_and_verb = "You travel"; if (!is_local_player()) { subject_and_verb = player_entry(gs).player_name + " travels"; } const std::string& map_label = gs->game_world().get_level(current_floor)->label(); bool label_has_digit = false; // Does it have a number in the label? for (int i = 0; i < map_label.size(); i++) { if (isdigit(map_label[i])) { label_has_digit = true; break; } } gs->game_chat().add_message( format("%s to %s%s", subject_and_verb.c_str(), label_has_digit ? "" : "the ", map_label.c_str()), is_local_player() ? COL_WHITE : COL_YELLOW); }
void PlayerInst::pickup_item(GameState* gs, const GameAction& action) { const int PICKUP_RATE = 10; GameInst* inst = gs->get_instance(action.use_id); if (!inst) { return; } ItemInst* iteminst = dynamic_cast<ItemInst*>(inst); LANARTS_ASSERT(iteminst); const Item& type = iteminst->item_type(); int amnt = iteminst->item_quantity(); bool inventory_full = false; if (type.id == get_item_by_name("Gold")) { gold() += amnt; } else { itemslot_t slot = inventory().add(type); if (slot == -1) { inventory_full = true; } else if (projectile_should_autowield(equipment(), type, this->last_chosen_weaponclass)) { projectile_smart_equip(inventory(), slot); } } if (!inventory_full) { cooldowns().reset_pickup_cooldown(PICKUP_RATE); gs->remove_instance(iteminst); } }
bool CombatGameInst::damage(GameState* gs, int dmg) { if (core_stats().hurt(dmg)) { die(gs); return true; } signal_was_damaged(); cooldowns().reset_hurt_cooldown(HURT_COOLDOWN); return false; }
void PlayerInst::use_spell(GameState* gs, const GameAction& action) { if (!effective_stats().allowed_actions.can_use_spells) { return; } MTwist& mt = gs->rng(); EffectiveStats& estats = effective_stats(); spell_id spell = spells_known().get(action.use_id); SpellEntry& spl_entry = game_spell_data.at(spell); if (spl_entry.mp_cost > core_stats().mp || (!spl_entry.can_cast_with_cooldown && !cooldowns().can_doaction())) { return; } Pos target = Pos(action.action_x, action.action_y); player_use_spell(gs, this, spl_entry, target); cooldowns().action_cooldown *= estats.cooldown_mult; cooldowns().reset_rest_cooldown(REST_COOLDOWN); }
bool CombatGameInst::melee_attack(GameState* gs, CombatGameInst* inst, const Weapon& weapon) { bool isdead = false; if (!cooldowns().can_doaction()) return false; MTwist& mt = gs->rng(); AttackStats attack(weapon); EffectiveAttackStats atkstats = effective_atk_stats(mt, AttackStats(weapon)); int damage = damage_formula(atkstats, inst->effective_stats()); if (dynamic_cast<PlayerInst*>(this) || !gs->game_settings().invincible) { isdead = inst->damage(gs, damage); } char dmgstr[32]; snprintf(dmgstr, 32, "%d", damage); float rx, ry; direction_towards(Pos(x, y), Pos(inst->x, inst->y), rx, ry, 0.5); gs->add_instance( new AnimatedInst(Pos(inst->x - 5 + rx * 5, inst->y + ry * 5), -1, 25, Posf(rx, ry), Posf(), AnimatedInst::DEPTH, dmgstr, Colour(255, 148, 120))); cooldowns().reset_action_cooldown(atkstats.cooldown); cooldowns().action_cooldown += gs->rng().rand(-4, 5); WeaponEntry wentry = weapon.weapon_entry(); if (wentry.name != "none") { gs->add_instance( new AnimatedInst(inst->pos(), wentry.attack_sprite, 25)); } signal_attacked_successfully(); return isdead; }
void PlayerInst::use_rest(GameState* gs, const GameAction& action) { if (!effective_stats().allowed_actions.can_use_rest) { return; } CoreStats& ecore = effective_stats().core; int emax_hp = ecore.max_hp, emax_mp = ecore.max_mp; bool atfull = core_stats().hp >= emax_hp && core_stats().mp >= emax_mp; if (cooldowns().can_rest() && !atfull && effects().can_rest()) { core_stats().heal_hp(ecore.hpregen * 8, emax_hp); core_stats().heal_mp(ecore.mpregen * 8, emax_mp); ecore.hp = core_stats().hp; ecore.mp = core_stats().mp; is_resting = true; } }
void PlayerInst::use_weapon(GameState* gs, const GameAction& action) { WeaponEntry& wentry = weapon().weapon_entry(); MTwist& mt = gs->rng(); const int MAX_MELEE_HITS = 10; EffectiveStats& estats = effective_stats(); if (!cooldowns().can_doaction()) { return; } Pos start(x, y); Pos actpos(action.action_x, action.action_y); if (wentry.uses_projectile && !equipment().has_projectile()) { return; } int cooldown = 0; if (equipment().has_projectile()) { const Projectile& projectile = equipment().projectile; ProjectileEntry& pentry = projectile.projectile_entry(); item_id item = get_item_by_name(pentry.name.c_str()); int weaprange = std::max(wentry.range, pentry.range); AttackStats weaponattack(weapon()); bool wallbounce = false; int nbounces = 0; float movespeed = pentry.speed; cooldown = std::max(wentry.cooldown, pentry.cooldown); //XXX: Horrible hack REMOVE THIS LATER if (class_stats().class_type().name == "Archer" && pentry.weapon_class == "bows") { int xplevel = class_stats().xplevel; float movebonus = class_stats().xplevel / 4.0f; if (movebonus > 2) { movebonus = 2; } float cooldown_mult = 1.0f - (class_stats().xplevel - 1) / 20.0f; if (cooldown_mult <= 0.85) { cooldown_mult = 0.85; } cooldown *= cooldown_mult; movespeed += movebonus; if (xplevel >= 3 && core_stats().mp >= 5) { nbounces = 2; core_stats().mp -= 5; } } GameInst* bullet = new ProjectileInst(projectile, effective_atk_stats(mt, weaponattack), id, start, actpos, movespeed, weaprange, NONE, wallbounce, nbounces); gs->add_instance(bullet); equipment().use_ammo(); } else { int weaprange = wentry.range + this->radius + TILE_SIZE / 2; float mag = distance_between(actpos, Pos(x, y)); if (mag > weaprange) { float dx = actpos.x - x, dy = actpos.y - y; actpos = Pos(x + dx / mag * weaprange, y + dy / mag * weaprange); } GameInst* enemies[MAX_MELEE_HITS]; int max_targets = std::min(MAX_MELEE_HITS, wentry.max_targets); int numhit = get_targets(gs, this, actpos.x, actpos.y, wentry.dmgradius, enemies, max_targets); if (numhit == 0) { return; } for (int i = 0; i < numhit; i++) { EnemyInst* e = (EnemyInst*)enemies[i]; lua_hit_callback(gs->get_luastate(), wentry.on_hit_func, this, e); if (attack(gs, e, AttackStats(equipment().weapon))) { PlayerData& pc = gs->player_data(); signal_killed_enemy(); char buffstr[32]; int amnt = round( double(e->xpworth()) / pc.all_players().size()); players_gain_xp(gs, amnt); snprintf(buffstr, 32, "%d XP", amnt); gs->add_instance( new AnimatedInst(Pos(e->x - 5, e->y - 5), -1, 25, Posf(), Posf(), AnimatedInst::DEPTH, buffstr, Colour(255, 215, 11))); } } cooldown = wentry.cooldown; } cooldowns().reset_action_cooldown(cooldown * estats.cooldown_mult); reset_rest_cooldown(); }
void PlayerInst::enqueue_io_actions(GameState* gs) { LANARTS_ASSERT(is_local_player() && gs->local_player() == this); if (actions_set_for_turn) { return; } bool single_player = (gs->player_data().all_players().size() <= 1); actions_set_for_turn = true; GameSettings& settings = gs->game_settings(); GameView& view = gs->view(); PlayerDataEntry& pde = gs->player_data().local_player_data(); if (pde.action_queue.has_actions_for_frame(gs->frame())) { pde.action_queue.extract_actions_for_frame(queued_actions, gs->frame()); event_log("Player %d has %d actions", player_entry(gs).net_id, (int) queued_actions.size()); return; } if (!single_player) { gs->set_repeat_actions_counter(settings.frame_action_repeat); } int dx = 0, dy = 0; bool mouse_within = gs->mouse_x() < gs->view().width; int rmx = view.x + gs->mouse_x(), rmy = view.y + gs->mouse_y(); bool was_moving = moving, do_stopaction = false; IOController& io = gs->io_controller(); if (!settings.loadreplay_file.empty()) { load_actions(gs, queued_actions); } enqueue_io_movement_actions(gs, dx, dy); if (was_moving && !moving && cooldowns().can_do_stopaction()) { do_stopaction = true; } //Shifting target if (gs->key_press_state(SDLK_k)) { shift_autotarget(gs); } if (gs->key_press_state(SDLK_m)) spellselect = -1; bool attack_used = false; if (!gs->game_hud().handle_io(gs, queued_actions)) { attack_used = enqueue_io_spell_and_attack_actions(gs, dx, dy); enqueue_io_equipment_actions(gs, do_stopaction); } bool action_usage = io.query_event(IOEvent::ACTIVATE_SPELL_N) || io.query_event(IOEvent::USE_WEAPON) || io.query_event(IOEvent::AUTOTARGET_CURRENT_ACTION) || io.query_event(IOEvent::MOUSETARGET_CURRENT_ACTION); if ((do_stopaction && !action_usage) || gs->key_down_state(SDLK_PERIOD) || gs->mouse_downwheel()) { queue_portal_use(gs, this, queued_actions); } // If we haven't done anything, rest if (queued_actions.empty()) { queued_actions.push_back(game_action(gs, this, GameAction::USE_REST)); } ActionQueue only_passive_actions; for (int i = 0; i < queued_actions.size(); i++) { GameAction::action_t act = queued_actions[i].act; if (act == GameAction::MOVE || act == GameAction::USE_REST) { only_passive_actions.push_back(queued_actions[i]); } } GameNetConnection& net = gs->net_connection(); if (net.is_connected()) { net_send_player_actions(net, gs->frame(), player_get_playernumber(gs, this), queued_actions); } int repeat = single_player ? 0 : settings.frame_action_repeat; for (int i = 1; i <= repeat; i++) { for (int j = 0; j < only_passive_actions.size(); j++) { only_passive_actions[j].frame = gs->frame() + i; } pde.action_queue.queue_actions_for_frame(only_passive_actions, gs->frame() + i); if (net.is_connected()) { net_send_player_actions(net, gs->frame() + i, player_get_playernumber(gs, this), only_passive_actions); } } }
void PlayerInst::enqueue_io_equipment_actions(GameState* gs, bool do_stopaction) { GameView& view = gs->view(); bool mouse_within = gs->mouse_x() < gs->view().width; int rmx = view.x + gs->mouse_x(), rmy = view.y + gs->mouse_y(); int level = gs->get_level()->id(); int frame = gs->frame(); bool item_used = false; IOController& io = gs->io_controller(); Pos p(gs->mouse_x() + view.x, gs->mouse_y() + view.y); obj_id target = this->current_target; GameInst* targetted = gs->get_instance(target); if (targetted) p = Pos(targetted->x, targetted->y); // We may have already used an item eg due to auto-use of items bool used_item = false; //Item use for (int i = 0; i < 9 && !used_item; i++) { if (io.query_event(IOEvent(IOEvent::USE_ITEM_N, i))) { if (inventory().get(i).amount() > 0) { item_used = true; queued_actions.push_back( GameAction(id, GameAction::USE_ITEM, frame, level, i, p.x, p.y)); } } } if (!used_item && gs->game_settings().autouse_health_potions && core_stats().hp < AUTOUSE_HEALTH_POTION_THRESHOLD) { int item_slot = inventory().find_slot( get_item_by_name("Health Potion")); if (item_slot > -1) { queued_actions.push_back( game_action(gs, this, GameAction::USE_ITEM, item_slot)); used_item = true; } } //Item pickup GameInst* inst = NULL; if (cooldowns().can_pickup() && gs->object_radius_test(this, &inst, 1, &item_colfilter)) { ItemInst* iteminst = (ItemInst*)inst; Item& item = iteminst->item_type(); bool was_dropper = iteminst->last_held_by() == id; bool dropper_autopickup = iteminst->autopickup_held(); bool autopickup = (item.is_normal_item() && !was_dropper && !dropper_autopickup) || (was_dropper && dropper_autopickup); bool wieldable_projectile = projectile_should_autowield(equipment(), item, this->last_chosen_weaponclass); bool pickup_io = gs->key_down_state(SDLK_LSHIFT) || gs->key_down_state(SDLK_RSHIFT); if (do_stopaction || wieldable_projectile || pickup_io || autopickup) queued_actions.push_back( GameAction(id, GameAction::PICKUP_ITEM, frame, level, iteminst->id)); } }