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 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::use_move(GameState* gs, const GameAction& action) { perf_timer_begin(FUNCNAME); int dx = action.action_x; int dy = action.action_y; float mag = effective_stats().movespeed; float ddx = dx * mag; float ddy = dy * mag; EnemyInst* target = NULL; //Enemy hitting test for melee gs->object_radius_test(this, (GameInst**)&target, 1, &enemy_colfilter, x + ddx * 2, y + ddy * 2); //Smaller radius enemy pushing test, can intercept enemy radius but not too far EnemyInst* alreadyhitting[5] = { 0, 0, 0, 0, 0 }; gs->object_radius_test(this, (GameInst**)alreadyhitting, 5, &enemy_colfilter, x, y, radius); bool already = false; for (int i = 0; i < 5; i++) { if (alreadyhitting[i]) { if (ddx < 0 == ((alreadyhitting[i]->x - x + ddx * 2) < 0)) { ddx = 0; } if (ddy < 0 == ((alreadyhitting[i]->y - y + ddy * 2) < 0)) { ddy = 0; } already = true; } } Pos newpos(round(rx + ddx), round(ry + ddy)); if (!gs->tile_radius_test(newpos.x, newpos.y, radius)) { vx = ddx; vy = ddy; } else if (!gs->tile_radius_test(newpos.x, y, radius)) { vx = ddx; } else if (!gs->tile_radius_test(x, newpos.y, radius)) { vy = ddy; } else if (ddx != 0 && ddy != 0) { //Alternatives in opposite directions for x & y Pos newpos_alt1(round(vx + ddx), round(vy - ddy)); Pos newpos_alt2(round(vx - ddx), round(vy + ddy)); if (!gs->tile_radius_test(newpos_alt1.x, newpos_alt1.y, radius)) { vx += ddx; vy -= ddy; } else if (!gs->tile_radius_test(newpos_alt2.x, newpos_alt2.y, radius)) { vx -= ddx; vy += ddy; } } event_log("Player id: %d using move for turn %d, vx=%f, vy=%f\n", id, gs->frame(), vx, vy); perf_timer_end(FUNCNAME); }
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::damage(GameState* gs, const EffectiveAttackStats& attack) { int dmg = damage_formula(attack, effective_stats()); if (gs->game_settings().verbose_output) { char buff[100]; snprintf(buff, 100, "Attack: [dmg %d pow %d mag %d%%] -> Damage: %d", attack.damage, attack.power, int(attack.magic_percentage * 100), dmg); gs->game_chat().add_message(buff); } char dmgstr[32]; snprintf(dmgstr, 32, "%d", dmg); gs->add_instance( new AnimatedInst(Pos(), -1, 25, Posf(), Posf(), AnimatedInst::DEPTH, dmgstr, Colour(255, 148, 120))); return damage(gs, dmg); }
EffectiveAttackStats CombatGameInst::effective_atk_stats(MTwist& mt, const AttackStats& attack) { return effective_stats().with_attack(mt, attack); }
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::use_item(GameState* gs, const GameAction& action) { if (!effective_stats().allowed_actions.can_use_items) { return; } itemslot_t slot = action.use_id; ItemSlot& itemslot = inventory().get(slot); Item& item = itemslot.item; ItemEntry& type = itemslot.item_entry(); lua_State* L = gs->luastate(); if (item.amount > 0) { if (item.is_equipment()) { if (itemslot.is_equipped()) { inventory().deequip(slot); } else { if (item.is_projectile()) { // Best-effort to equip, may not be possible: projectile_smart_equip(inventory(), slot); } else if (item.is_weapon()) { const Projectile& p = equipment().projectile(); if (!p.empty()) { if (!p.projectile_entry().is_standalone()) { inventory().deequip_type( EquipmentEntry::AMMO); } } equipment().equip(slot); // Try and equip a projectile WeaponEntry& wentry = item.weapon_entry(); if (wentry.uses_projectile) { const Projectile& p = equipment().projectile(); if (p.empty()) { projectile_smart_equip(inventory(), wentry.weapon_class); } } } else { equipment().equip(slot); } if (item.is_weapon() || item.is_projectile()) { last_chosen_weaponclass = weapon().weapon_entry().weapon_class; } } } else if (equipment().valid_to_use(item) && item_check_lua_prereq(L, type, this)) { item_do_lua_action(L, type, this, Pos(action.action_x, action.action_y), item.amount); if (is_local_player() && !type.inventory_use_message().empty()) { gs->game_chat().add_message(type.inventory_use_message(), Colour(100, 100, 255)); } if (item.is_projectile()) itemslot.clear(); else item.remove_copies(1); reset_rest_cooldown(); } } }