void player::perform_special_attacks(game *g, monster *z, player *p, int &bash_dam, int &cut_dam, int &stab_dam) { bool can_poison = false; int bash_armor = (z == NULL ? 0 : z->armor_bash()); int cut_armor = (z == NULL ? 0 : z->armor_cut()); std::vector<special_attack> special_attacks = mutation_attacks(z, p); for (int i = 0; i < special_attacks.size(); i++) { bool did_damage = false; if (special_attacks[i].bash > bash_armor) { bash_dam += special_attacks[i].bash; did_damage = true; } if (special_attacks[i].cut > cut_armor) { cut_dam += special_attacks[i].cut - cut_armor; did_damage = true; } if (special_attacks[i].stab > cut_armor * .8) { stab_dam += special_attacks[i].stab - cut_armor * .8; did_damage = true; } if (!can_poison && one_in(2) && (special_attacks[i].cut > cut_armor || special_attacks[i].stab > cut_armor * .8)) can_poison = true; if (did_damage) g->add_msg( special_attacks[i].text.c_str() ); } if (can_poison && has_trait(PF_POISONOUS)) { if (z != NULL) { if (!is_npc() && !z->has_effect(ME_POISONED)) g->add_msg("You poison the %s!", z->name().c_str()); z->add_effect(ME_POISONED, 6); } else if (p != NULL) { if (!is_npc() && !p->has_disease(DI_POISON)) g->add_msg("You poison %s!", p->name.c_str()); p->add_disease(DI_POISON, 6, g); } } }
void Character::spell_gas_breath (int sn, int lvl, void *vo) { Character *vch; CharIter rch, next; for (rch = in_room->people.begin(); rch != in_room->people.end(); rch = next) { vch = *rch; next = ++rch; if (is_npc () ? !vch->is_npc () : vch->is_npc ()) { int hpch = std::max (10, hit); int dam = number_range (hpch / 16 + 1, hpch / 8); if (vch->saves_spell (lvl)) dam /= 2; damage (this, vch, dam, sn); } } return; }
void player::stumble(game *g) { int stumble_pen = 2 * weapon.volume() + weapon.weight(); if (has_trait(PF_DEFT)) stumble_pen = int(stumble_pen * .3) - 10; if (stumble_pen < 0) stumble_pen = 0; if (stumble_pen > 0 && (str_cur >= 15 || dex_cur >= 21 || one_in(16 - str_cur) || one_in(22 - dex_cur))) stumble_pen = rng(0, stumble_pen); if (!is_npc()) { // Only display messages if this is the player if (stumble_pen >= 60) g->add_msg("You miss and stumble with the momentum."); else if (stumble_pen >= 10) g->add_msg("You swing wildly and miss."); else g->add_msg("You miss."); } moves -= stumble_pen; }
void Character::spell_call_lightning (int sn, int lvl, void *vo) { Character *vch; if (!is_outside()) { send_to_char ("You must be out of doors.\r\n"); return; } if (!g_world->is_raining()) { send_to_char ("You need bad weather.\r\n"); return; } int dam = dice (lvl / 2, 8); send_to_char ("God's lightning strikes your foes!\r\n"); act ("$n calls God's lightning to strike $s foes!", NULL, NULL, TO_ROOM); CharIter c, next; for (c = char_list.begin(); c != char_list.end(); c = next) { vch = *c; next = ++c; if (vch->in_room == NULL) continue; if (vch->in_room == in_room) { if (vch != this && (is_npc () ? !vch->is_npc () : vch->is_npc ())) damage (this, vch, vch->saves_spell (lvl) ? dam / 2 : dam, sn); continue; } if (vch->in_room->area == in_room->area && vch->is_outside() && vch->is_awake ()) vch->send_to_char ("Lightning flashes in the sky.\r\n"); } return; }
void Character::spell_earthquake (int sn, int lvl, void *vo) { send_to_char ("The earth trembles beneath your feet!\r\n"); act ("$n makes the earth tremble and shiver.", NULL, NULL, TO_ROOM); CharIter c, next; for (c = char_list.begin(); c != char_list.end(); c = next) { Character* vch = *c; next = ++c; if (vch->in_room == NULL) continue; if (vch->in_room == in_room) { if (vch != this && (is_npc () ? !vch->is_npc () : vch->is_npc ())) damage (this, vch, lvl + dice (2, 8), sn); continue; } if (vch->in_room->area == in_room->area) vch->send_to_char ("The earth trembles and shivers.\r\n"); } return; }
void Character::spell_dispel_evil (int sn, int lvl, void *vo) { Character *victim = (Character *) vo; if (!is_npc () && is_evil ()) victim = this; if (victim->is_good ()) { act ("God protects $N.", NULL, victim, TO_ROOM); return; } if (victim->is_neutral ()) { act ("$N does not seem to be affected.", NULL, victim, TO_CHAR); return; } int dam = dice (lvl, 4); if (victim->saves_spell (lvl)) dam /= 2; damage (this, victim, dam, sn); return; }
bool player::hit_player(player &p, body_part &bp, int &hitdam, int &hitcut) { // TODO: Add bionics and other bonus (e.g. heat drain, shock, etc) if (!is_npc() && p.is_npc()) { npc *foe = dynamic_cast<npc*>(&p); foe->make_angry(); } bool unarmed = unarmed_attack(), bashing = weapon.is_bashing_weapon(), cutting = weapon.is_cutting_weapon(); int hitit = hit_roll() - p.dodge_roll(); if (hitit < 0) { // They dodged practice(sk_melee, rng(2, 4)); if (unarmed) practice(sk_unarmed, 3); if (bashing) practice(sk_bashing, 1); if (cutting) practice(sk_cutting, 2); return false; } if (hitit >= 15) bp = bp_eyes; else if (hitit >= 12) bp = bp_mouth; else if (hitit >= 10) bp = bp_head; else if (hitit >= 6) bp = bp_torso; else if (hitit >= 2) bp = bp_arms; else bp = bp_legs; hitdam = base_damage(); if (unarmed) {// Unarmed bonuses hitdam += rng(0, sklevel[sk_unarmed]); if (sklevel[sk_unarmed] >= 5) hitdam += rng(sklevel[sk_unarmed], 3 * sklevel[sk_unarmed]); if (has_trait(PF_TALONS)) hitcut += 10; if (sklevel[sk_unarmed] >= 8 && (one_in(3) || rng(5, 20) < sklevel[sk_unarmed])) hitdam *= rng(2, 3); } // Weapon adds (melee_dam / 4) to (melee_dam) hitdam += rng(weapon.type->melee_dam / 4, weapon.type->melee_dam); if (bashing) hitdam += rng(0, sklevel[sk_bashing]) * sqrt(str_cur); hitdam += int(pow(1.5, sklevel[sk_melee])); hitcut = weapon.type->melee_cut; if (hitcut > 0) hitcut += int(sklevel[sk_cutting] / 3); if (hitdam < 0) hitdam = 0; if (hitdam > 0 || hitcut > 0) { // Practicing practice(sk_melee, rng(5, 10)); if (unarmed) practice(sk_unarmed, rng(5, 10)); if (bashing) practice(sk_bashing, rng(5, 10)); if (cutting) practice(sk_cutting, rng(5, 10)); } else { // Less practice if we missed practice(sk_melee, rng(2, 5)); if (unarmed) practice(sk_unarmed, 2); if (bashing) practice(sk_bashing, 2); if (cutting) practice(sk_cutting, 3); } return true; }
void player::perform_defensive_technique( technique_id technique, game *g, monster *z, player *p, body_part &bp_hit, int &side, int &bash_dam, int &cut_dam, int &stab_dam) { int junk; bool mon = (z != NULL); std::string You = (is_npc() ? name : "You"); std::string your = (is_npc() ? (male ? "his" : "her") : "your"); std::string target = (mon ? "the " + z->name() : p->name); bool u_see = (!is_npc() || g->u_see(posx, posy, junk)); switch (technique) { case TEC_BLOCK: case TEC_BLOCK_LEGS: { if (technique == TEC_BLOCK) { bp_hit = bp_arms; if (hp_cur[hp_arm_l] >= hp_cur[hp_arm_r]) side = 0; else side = 1; } else { // Blocking with our legs bp_hit = bp_legs; if (hp_cur[hp_leg_l] >= hp_cur[hp_leg_r]) side = 0; else side = 1; } if (u_see) g->add_msg("%s block%s with %s %s.", You.c_str(), (is_npc() ? "s" : ""), your.c_str(), body_part_name(bp_hit, side).c_str()); bash_dam *= .5; double reduction = 1.0; // Special reductions for certain styles if (weapon.typeId() == "style_tai_chi") reduction -= double(0.08 * double(per_cur - 6)); if (weapon.typeId() == "style_taekwondo") reduction -= double(0.08 * double(str_cur - 6)); if (reduction > 1.0) reduction = 1.0; if (reduction < 0.3) reduction = 0.3; bash_dam *= reduction; } break; case TEC_WBLOCK_1: case TEC_WBLOCK_2: case TEC_WBLOCK_3: // TODO: Cause weapon damage bash_dam = 0; cut_dam = 0; stab_dam = 0; if (u_see) g->add_msg("%s block%s with %s %s.", You.c_str(), (is_npc() ? "s" : ""), your.c_str(), weapon.tname().c_str()); case TEC_COUNTER: break; // Handled elsewhere case TEC_DEF_THROW: if (u_see) g->add_msg("%s throw%s %s!", You.c_str(), (is_npc() ? "s" : ""), target.c_str()); bash_dam = 0; cut_dam = 0; stab_dam = 0; if (mon) { z->add_effect(ME_DOWNED, rng(1, 2)); z->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); } else { p->add_disease(DI_DOWNED, rng(1, 2), g); p->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); } break; case TEC_DEF_DISARM: g->m.add_item(p->posx, p->posy, p->remove_weapon()); // Re-roll damage, without our weapon bash_dam = p->roll_bash_damage(NULL, false); cut_dam = p->roll_cut_damage(NULL, false); stab_dam = p->roll_stab_damage(NULL, false); if (u_see) g->add_msg("%s disarm%s %s!", You.c_str(), (is_npc() ? "s" : ""), target.c_str()); break; } // switch (technique) }
void player::perform_technique(technique_id technique, game *g, monster *z, player *p, int &bash_dam, int &cut_dam, int &stab_dam, int &pain) { bool mon = (z != NULL); std::string You = (is_npc() ? name : "You"); std::string target = (mon ? "the " + z->name() : (p->is_npc() ? p->name : "you")); std::string s = (is_npc() ? "s" : ""); int tarx = (mon ? z->posx : p->posx), tary = (mon ? z->posy : p->posy); int junk; bool u_see = (!is_npc() || g->u_see(posx, posy, junk)); if (technique == TEC_RAPID) { moves += int( attack_speed(*this, false) / 2); return; } if (technique == TEC_BLOCK) { bash_dam *= .7; return; } // The rest affect our target, and thus depend on z vs. p switch (technique) { case TEC_SWEEP: if (z != NULL && !z->has_flag(MF_FLIES)) { z->add_effect(ME_DOWNED, rng(1, 2)); bash_dam += z->fall_damage(); } else if (p != NULL && p->weapon.typeId() != "style_judo") { p->add_disease(DI_DOWNED, rng(1, 2), g); bash_dam += 3; } break; case TEC_PRECISE: if (z != NULL) z->add_effect(ME_STUNNED, rng(1, 4)); else if (p != NULL) p->add_disease(DI_STUNNED, rng(1, 2), g); pain += rng(5, 8); break; case TEC_BRUTAL: if (z != NULL) { z->add_effect(ME_STUNNED, 1); z->knock_back_from(g, posx, posy); } else if (p != NULL) { p->add_disease(DI_STUNNED, 1, g); p->knock_back_from(g, posy, posy); } break; case TEC_THROW: // Throws are less predictable than brutal strikes. // We knock them back from a tile adjacent to us! if (z != NULL) { z->add_effect(ME_DOWNED, rng(1, 2)); z->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); } else if (p != NULL) { p->knock_back_from(g, posx + rng(-1, 1), posy + rng(-1, 1)); if (p->weapon.typeId() != "style_judo") p->add_disease(DI_DOWNED, rng(1, 2), g); } break; case TEC_WIDE: { int count_hit = 0; for (int x = posx - 1; x <= posx + 1; x++) { for (int y = posy - 1; y <= posy + 1; y++) { if (x != tarx || y != tary) { // Don't double-hit our target int mondex = g->mon_at(x, y); if (mondex != -1 && hit_roll() >= rng(0, 5) + g->z[mondex].dodge_roll()) { count_hit++; int dam = roll_bash_damage(&(g->z[mondex]), false) + roll_cut_damage (&(g->z[mondex]), false); g->z[mondex].hurt(dam); if (u_see) g->add_msg("%s hit%s %s for %d damage!", You.c_str(), s.c_str(), target.c_str(), dam); } int npcdex = g->npc_at(x, y); if (npcdex != -1 && hit_roll() >= rng(0, 5) + g->active_npc[npcdex].dodge_roll(g)) { count_hit++; int dam = roll_bash_damage(NULL, false); int cut = roll_cut_damage (NULL, false); g->active_npc[npcdex].hit(g, bp_legs, 3, dam, cut); if (u_see) g->add_msg("%s hit%s %s for %d damage!", You.c_str(), s.c_str(), g->active_npc[npcdex].name.c_str(), dam + cut); } } } } if (!is_npc()) g->add_msg("%d enemies hit!", count_hit); } break; case TEC_DISARM: g->m.add_item(p->posx, p->posy, p->remove_weapon()); if (u_see) g->add_msg("%s disarm%s %s!", You.c_str(), s.c_str(), target.c_str()); break; } // switch (tech) }
std::vector<special_attack> player::mutation_attacks(monster *z, player *p) { std::vector<special_attack> ret; if (z == NULL && p == NULL) return ret; bool mon = (z != NULL); bool is_u = (!is_npc());// Affects how we'll display messages std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : (male ? "his" : "her")); std::string target = (mon ? "the " + z->name() : p->name); std::stringstream text; if (has_trait(PF_FANGS) && !wearing_something_on(bp_mouth) && one_in(20 - dex_cur - skillLevel("unarmed"))) { special_attack tmp; text << You << " sink" << (is_u ? " " : "s ") << your << " fangs into " << target << "!"; tmp.text = text.str(); tmp.stab = 20; ret.push_back(tmp); } if (has_trait(PF_MANDIBLES) && one_in(22 - dex_cur - skillLevel("unarmed"))) { special_attack tmp; text << You << " slice" << (is_u ? " " : "s ") << target << " with " << your << " mandibles!"; tmp.text = text.str(); tmp.cut = 12; ret.push_back(tmp); } if (has_trait(PF_BEAK) && one_in(15 - dex_cur - skillLevel("unarmed"))) { special_attack tmp; text << You << " peck" << (is_u ? " " : "s ") << target << "!"; tmp.text = text.str(); tmp.stab = 15; ret.push_back(tmp); } if (has_trait(PF_HOOVES) && one_in(25 - dex_cur - 2 * skillLevel("unarmed"))) { special_attack tmp; text << You << " kick" << (is_u ? " " : "s ") << target << " with " << your << " hooves!"; tmp.text = text.str(); tmp.bash = str_cur * 3; if (tmp.bash > 40) tmp.bash = 40; ret.push_back(tmp); } if (has_trait(PF_HORNS) && one_in(20 - dex_cur - skillLevel("unarmed"))) { special_attack tmp; text << You << " headbutt" << (is_u ? " " : "s ") << target << " with " << your << " horns!"; tmp.text = text.str(); tmp.bash = 3; tmp.stab = 3; ret.push_back(tmp); } if (has_trait(PF_HORNS_CURLED) && one_in(20 - dex_cur - skillLevel("unarmed"))) { special_attack tmp; text << You << " headbutt" << (is_u ? " " : "s ") << target << " with " << your << " curled horns!"; tmp.text = text.str(); tmp.bash = 14; ret.push_back(tmp); } if (has_trait(PF_HORNS_POINTED) && one_in(22 - dex_cur - skillLevel("unarmed"))){ special_attack tmp; text << You << " stab" << (is_u ? " " : "s ") << target << " with " << your << " pointed horns!"; tmp.text = text.str(); tmp.stab = 24; ret.push_back(tmp); } if (has_trait(PF_ANTLERS) && one_in(20 - dex_cur - skillLevel("unarmed"))) { special_attack tmp; text << You << " butt" << (is_u ? " " : "s ") << target << " with " << your << " antlers!"; tmp.text = text.str(); tmp.bash = 4; ret.push_back(tmp); } if (has_trait(PF_TAIL_STING) && one_in(3) && one_in(10 - dex_cur)) { special_attack tmp; text << You << " sting" << (is_u ? " " : "s ") << target << " with " << your << " tail!"; tmp.text = text.str(); tmp.stab = 20; ret.push_back(tmp); } if (has_trait(PF_TAIL_CLUB) && one_in(3) && one_in(10 - dex_cur)) { special_attack tmp; text << You << " hit" << (is_u ? " " : "s ") << target << " with " << your << " tail!"; tmp.text = text.str(); tmp.bash = 18; ret.push_back(tmp); } if (has_trait(PF_ARM_TENTACLES) || has_trait(PF_ARM_TENTACLES_4) || has_trait(PF_ARM_TENTACLES_8)) { int num_attacks = 1; if (has_trait(PF_ARM_TENTACLES_4)) num_attacks = 3; if (has_trait(PF_ARM_TENTACLES_8)) num_attacks = 7; if (weapon.is_two_handed(this)) num_attacks--; for (int i = 0; i < num_attacks; i++) { if (one_in(18 - dex_cur - skillLevel("unarmed"))) { special_attack tmp; text.str(""); text << You << " slap" << (is_u ? " " : "s ") << target << " with " << your << " tentacle!"; tmp.text = text.str(); tmp.bash = str_cur / 2; ret.push_back(tmp); } } } return ret; }
void player::melee_special_effects(game *g, monster *z, player *p, bool crit, int &bash_dam, int &cut_dam, int &stab_dam) { if (z == NULL && p == NULL) return; bool mon = (z != NULL); int junk; bool is_u = (!is_npc()); bool can_see = (is_u || g->u_see(posx, posy, junk)); std::string You = (is_u ? "You" : name); std::string Your = (is_u ? "Your" : name + "'s"); std::string your = (is_u ? "your" : name + "'s"); std::string target = (mon ? "the " + z->name() : (p->is_npc() ? p->name : "you")); std::string target_possessive = (mon ? "the " + z->name() + "'s" : (p->is_npc() ? p->name + "'s" : your)); int tarposx = (mon ? z->posx : p->posx), tarposy = (mon ? z->posy : p->posy); // Bashing effecs if (mon) z->moves -= rng(0, bash_dam * 2); else p->moves -= rng(0, bash_dam * 2); // Bashing crit if (crit && !unarmed_attack()) { int turns_stunned = int(bash_dam / 20) + rng(0, int(skillLevel("bashing") / 2)); if (turns_stunned > 6) turns_stunned = 6; if (turns_stunned > 0) { if (mon) z->add_effect(ME_STUNNED, turns_stunned); else p->add_disease(DI_STUNNED, 1 + turns_stunned / 2, g); } } // Stabbing effects int stab_moves = rng(stab_dam / 2, stab_dam * 1.5); if (crit) stab_moves *= 1.5; if (stab_moves >= 150) { if (can_see) g->add_msg("%s force%s the %s to the ground!", You.c_str(), (is_u ? "" : "s"), target.c_str()); if (mon) { z->add_effect(ME_DOWNED, 1); z->moves -= stab_moves / 2; } else { p->add_disease(DI_DOWNED, 1, g); p->moves -= stab_moves / 2; } } else if (mon) z->moves -= stab_moves; else p->moves -= stab_moves; // Bonus attacks! bool shock_them = (has_bionic("bio_shock") && power_level >= 2 && unarmed_attack() && (!mon || !z->has_flag(MF_ELECTRIC)) && one_in(3)); bool drain_them = (has_bionic("bio_heat_absorb") && power_level >= 1 && !is_armed() && (!mon || z->has_flag(MF_WARM))); if (drain_them) power_level--; drain_them &= one_in(2); // Only works half the time if (shock_them) { power_level -= 2; int shock = rng(2, 5); if (mon) { z->hurt( shock * rng(1, 3) ); z->moves -= shock * 180; if (can_see) g->add_msg("%s shock%s %s!", You.c_str(), (is_u ? "" : "s"), target.c_str()); } else { p->hurt(g, bp_torso, 0, shock * rng(1, 3)); p->moves -= shock * 80; } } if (drain_them) { charge_power(rng(0, 4)); if (can_see) g->add_msg("%s drain%s %s body heat!", You.c_str(), (is_u ? "" : "s"), target_possessive.c_str()); if (mon) { z->moves -= rng(80, 120); z->speed -= rng(4, 6); } else p->moves -= rng(80, 120); } bool conductive = !wearing_something_on(bp_hands) && weapon.conductive(); if (mon && z->has_flag(MF_ELECTRIC) && conductive) { hurtall(rng(0, 1)); moves -= rng(0, 50); if (is_u) g->add_msg("Contact with the %s shocks you!", z->name().c_str()); } // Glass weapons shatter sometimes if (weapon.made_of(GLASS) && rng(0, weapon.volume() + 8) < weapon.volume() + str_cur) { if (can_see) g->add_msg("%s %s shatters!", Your.c_str(), weapon.tname(g).c_str()); g->sound(posx, posy, 16, ""); // Dump its contents on the ground for (int i = 0; i < weapon.contents.size(); i++) g->m.add_item(posx, posy, weapon.contents[i]); hit(g, bp_arms, 1, 0, rng(0, weapon.volume() * 2));// Take damage if (weapon.is_two_handed(this))// Hurt left arm too, if it was big hit(g, bp_arms, 0, 0, rng(0, weapon.volume())); cut_dam += rng(0, 5 + int(weapon.volume() * 1.5));// Hurt the monster extra remove_weapon(); } // Getting your weapon stuck int cutting_penalty = roll_stuck_penalty(z, stab_dam > cut_dam); if (weapon.has_flag(IF_MESSY)) { // e.g. chainsaws cutting_penalty /= 6; // Harder to get stuck for (int x = tarposx - 1; x <= tarposx + 1; x++) { for (int y = tarposy - 1; y <= tarposy + 1; y++) { if (!one_in(3)) { if (g->m.field_at(x, y).type == fd_blood && g->m.field_at(x, y).density < 3) g->m.field_at(x, y).density++; else g->m.add_field(g, x, y, fd_blood, 1); } } } } if (!unarmed_attack() && cutting_penalty > dice(str_cur * 2, 20)) { if (is_u) g->add_msg("Your %s gets stuck in %s, pulling it out of your hands!", weapon.tname().c_str(), target.c_str()); if (mon) { if (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB)) z->speed *= .7; else z->speed *= .85; z->add_item(remove_weapon()); } else g->m.add_item(posx, posy, remove_weapon()); } else { if (mon && (cut_dam >= z->hp || stab_dam >= z->hp)) { cutting_penalty /= 2; cutting_penalty -= rng(skillLevel("cutting"), skillLevel("cutting") * 2 + 2); } if (cutting_penalty > 0) moves -= cutting_penalty; if (cutting_penalty >= 50 && is_u) g->add_msg("Your %s gets stuck in %s, but you yank it free.", weapon.tname().c_str(), target.c_str()); if (mon && (weapon.has_flag(IF_SPEAR) || weapon.has_flag(IF_STAB))) z->speed *= .9; } // Finally, some special effects for martial arts if(weapon.typeId() == "style_karate"){ dodges_left++; blocks_left += 2; } else if(weapon.typeId() == "style_aikido"){ bash_dam /= 2; } else if(weapon.typeId() == "style_capoeira"){ add_disease(DI_DODGE_BOOST, 2, g, 2); } else if(weapon.typeId() == "style_muay_thai"){ if ((mon && z->type->size >= MS_LARGE) || (!mon && p->str_max >= 12)) bash_dam += rng((mon ? z->type->size : (p->str_max - 8) / 4), 3 * (mon ? z->type->size : (p->str_max - 8) / 4)); } else if(weapon.typeId() == "style_tiger"){ add_disease(DI_DAMAGE_BOOST, 2, g, 2, 10); } else if(weapon.typeId() == "style_centipede"){ add_disease(DI_SPEED_BOOST, 2, g, 4, 40); } else if(weapon.typeId() == "style_venom_snake"){ if (has_disease(DI_VIPER_COMBO)) { if (disease_intensity(DI_VIPER_COMBO) == 1) { if (is_u) g->add_msg("Snakebite!"); int dambuf = bash_dam; bash_dam = stab_dam; stab_dam = dambuf; add_disease(DI_VIPER_COMBO, 2, g, 1, 2); // Upgrade to Viper Strike } else if (disease_intensity(DI_VIPER_COMBO) == 2) { if (hp_cur[hp_arm_l] >= hp_max[hp_arm_l] * .75 && hp_cur[hp_arm_r] >= hp_max[hp_arm_r] * .75 ) { if (is_u) g->add_msg("Viper STRIKE!"); bash_dam *= 3; } else if (is_u) g->add_msg("Your injured arms prevent a viper strike!"); rem_disease(DI_VIPER_COMBO); } } else if (crit) { if (is_u) g->add_msg("Tail whip! Viper Combo Intiated!"); bash_dam += 5; add_disease(DI_VIPER_COMBO, 2, g, 1, 2); } } else if(weapon.typeId() == "style_scorpion"){ if (crit) { if (!is_npc()) g->add_msg("Stinger Strike!"); if (mon) { z->add_effect(ME_STUNNED, 3); int zposx = z->posx, zposy = z->posy; z->knock_back_from(g, posx, posy); if (z->posx != zposx || z->posy != zposy) z->knock_back_from(g, posx, posy); // Knock a 2nd time if the first worked } else { p->add_disease(DI_STUNNED, 2, g); int pposx = p->posx, pposy = p->posy; p->knock_back_from(g, posx, posy); if (p->posx != pposx || p->posy != pposy) p->knock_back_from(g, posx, posy); // Knock a 2nd time if the first worked } } } else if(weapon.typeId() == "style_zui_quan"){ dodges_left = 50; // Basically, unlimited. } }
void player::consume_effects( const item &food ) { if( !food.is_comestible() ) { debugmsg( "called player::consume_effects with non-comestible" ); return; } const auto &comest = *food.type->comestible; const int capacity = stomach_capacity(); if( has_trait( trait_id( "THRESH_PLANT" ) ) && food.type->can_use( "PLANTBLECH" ) ) { // Just keep nutrition capped, to prevent vomiting cap_nutrition_thirst( *this, capacity, true, true ); return; } if( ( has_trait( trait_id( "HERBIVORE" ) ) || has_trait( trait_id( "RUMINANT" ) ) ) && food.has_any_flag( herbivore_blacklist ) ) { // No good can come of this. return; } // Rotten food causes health loss const float relative_rot = food.get_relative_rot(); if( relative_rot > 1.0f && !has_trait( trait_id( "SAPROPHAGE" ) ) && !has_trait( trait_id( "SAPROVORE" ) ) && !has_bionic( bio_digestion ) ) { const float rottedness = clamp( 2 * relative_rot - 2.0f, 0.1f, 1.0f ); // ~-1 health per 1 nutrition at halfway-rotten-away, ~0 at "just got rotten" // But always round down int h_loss = -rottedness * comest.nutr; mod_healthy_mod( h_loss, -200 ); add_msg( m_debug, "%d health from %0.2f%% rotten food", h_loss, rottedness ); } const auto nutr = nutrition_for( food ); mod_hunger( -nutr ); mod_thirst( -comest.quench ); mod_stomach_food( nutr ); mod_stomach_water( comest.quench ); if( comest.healthy != 0 ) { // Effectively no cap on health modifiers from food mod_healthy_mod( comest.healthy, ( comest.healthy >= 0 ) ? 200 : -200 ); } if( comest.stim != 0 && ( abs( stim ) < ( abs( comest.stim ) * 3 ) || sgn( stim ) != sgn( comest.stim ) ) ) { if( comest.stim < 0 ) { stim = std::max( comest.stim * 3, stim + comest.stim ); } else { stim = std::min( comest.stim * 3, stim + comest.stim ); } } add_addiction( comest.add, comest.addict ); if( addiction_craving( comest.add ) != MORALE_NULL ) { rem_morale( addiction_craving( comest.add ) ); } // Morale is in minutes int morale_time = HOURS( 2 ) / MINUTES( 1 ); if( food.has_flag( "HOT" ) && food.has_flag( "EATEN_HOT" ) ) { morale_time = HOURS( 3 ) / MINUTES( 1 ); int clamped_nutr = std::max( 5, std::min( 20, nutr / 10 ) ); add_morale( MORALE_FOOD_HOT, clamped_nutr, 20, morale_time, morale_time / 2 ); } std::pair<int, int> fun = fun_for( food ); if( fun.first < 0 ) { add_morale( MORALE_FOOD_BAD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } else if( fun.first > 0 ) { add_morale( MORALE_FOOD_GOOD, fun.first, fun.second, morale_time, morale_time / 2, false, food.type ); } const bool hibernate = has_active_mutation( trait_id( "HIBERNATE" ) ); if( hibernate ) { if( ( nutr > 0 && get_hunger() < -60 ) || ( comest.quench > 0 && get_thirst() < -60 ) ) { //Tell the player what's going on add_msg_if_player( _( "You gorge yourself, preparing to hibernate." ) ); if( one_in( 2 ) ) { //50% chance of the food tiring you mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -200 ) || ( comest.quench > 0 && get_thirst() < -200 ) ) { //Hibernation should cut burn to 60/day add_msg_if_player( _( "You feel stocked for a day or two. Got your bed all ready and secured?" ) ); if( one_in( 2 ) ) { //And another 50%, intended cumulative mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -400 ) || ( comest.quench > 0 && get_thirst() < -400 ) ) { add_msg_if_player( _( "Mmm. You can still fit some more in...but maybe you should get comfortable and sleep." ) ); if( !one_in( 3 ) ) { //Third check, this one at 66% mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -600 ) || ( comest.quench > 0 && get_thirst() < -600 ) ) { add_msg_if_player( _( "That filled a hole! Time for bed..." ) ); // At this point, you're done. Schlaf gut. mod_fatigue( nutr ); } } // Moved here and changed a bit - it was too complex // Incredibly minor stuff like this shouldn't require complexity if( !is_npc() && has_trait( trait_id( "SLIMESPAWNER" ) ) && ( get_hunger() < capacity + 40 || get_thirst() < capacity + 40 ) ) { add_msg_if_player( m_mixed, _( "You feel as though you're going to split open! In a good way?" ) ); mod_pain( 5 ); std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) ) { valid.push_back( dest ); } } int numslime = 1; for( int i = 0; i < numslime && !valid.empty(); i++ ) { const tripoint target = random_entry_removed( valid ); if( monster *const slime = g->summon_mon( mon_player_blob, target ) ) { slime->friendly = -1; } } mod_hunger( 40 ); mod_thirst( 40 ); //~slimespawns have *small voices* which may be the Nice equivalent //~of the Rat King's ALL CAPS invective. Probably shared-brain telepathy. add_msg_if_player( m_good, _( "hey, you look like me! let's work together!" ) ); } // Last thing that happens before capping hunger if( get_hunger() < capacity && has_trait( trait_id( "EATHEALTH" ) ) ) { int excess_food = capacity - get_hunger(); add_msg_player_or_npc( _( "You feel the %s filling you out." ), _( "<npcname> looks better after eating the %s." ), food.tname().c_str() ); // Guaranteed 1 HP healing, no matter what. You're welcome. ;-) if( excess_food <= 5 ) { healall( 1 ); } else { // Straight conversion, except it's divided amongst all your body parts. healall( excess_food /= 5 ); } // Note: We want this here to prevent "you can't finish this" messages set_hunger( capacity ); } cap_nutrition_thirst( *this, capacity, nutr > 0, comest.quench > 0 ); }
void player::consume_effects( item &food, bool rotten ) { if( !food.is_food() ) { debugmsg( "called player::consume_effects with non-comestible" ); return; } const auto comest = food.type->comestible.get(); const int capacity = stomach_capacity(); if( has_trait( "THRESH_PLANT" ) && food.type->can_use( "PLANTBLECH" ) ) { // Just keep nutrition capped, to prevent vomiting cap_nutrition_thirst( *this, capacity, true, true ); return; } if( ( has_trait( "HERBIVORE" ) || has_trait( "RUMINANT" ) ) && food.has_any_flag( herbivore_blacklist ) ) { // No good can come of this. return; } float factor = 1.0f; float hunger_factor = 1.0f; bool unhealthy_allowed = true; if( has_trait( "GIZZARD" ) ) { factor *= 0.6f; } if( has_trait( "CARNIVORE" ) && food.has_flag( "CARNIVORE_OK" ) ) { // At least partially edible if( food.has_any_flag( carnivore_blacklist ) ) { // Other things are in it, we only get partial benefits add_msg_if_player( _( "You pick out the edible parts and throw away the rest." ) ); factor *= 0.5f; } else { // Carnivores don't get unhealthy off pure meat diets unhealthy_allowed = false; } } // Saprophages get full nutrition from rotting food if( rotten && !has_trait( "SAPROPHAGE" ) ) { // everyone else only gets a portion of the nutrition hunger_factor *= rng_float( 0, 1 ); // and takes a health penalty if they aren't adapted if( !has_trait( "SAPROVORE" ) && !has_bionic( "bio_digestion" ) ) { mod_healthy_mod( -30, -200 ); } } // Bio-digestion gives extra nutrition if( has_bionic( "bio_digestion" ) ) { hunger_factor += rng_float( 0, 1 ); } const auto nutr = nutrition_for( food.type ); mod_hunger( -nutr * factor * hunger_factor ); mod_thirst( -comest->quench * factor ); mod_stomach_food( nutr * factor * hunger_factor ); mod_stomach_water( comest->quench * factor ); if( unhealthy_allowed || comest->healthy > 0 ) { // Effectively no cap on health modifiers from food mod_healthy_mod( comest->healthy, ( comest->healthy >= 0 ) ? 200 : -200 ); } if( comest->stim != 0 && ( abs( stim ) < ( abs( comest->stim ) * 3 ) || sgn( stim ) != sgn( comest->stim ) ) ) { if( comest->stim < 0 ) { stim = std::max( comest->stim * 3, stim + comest->stim ); } else { stim = std::min( comest->stim * 3, stim + comest->stim ); } } add_addiction( comest->add, comest->addict ); if( addiction_craving( comest->add ) != MORALE_NULL ) { rem_morale( addiction_craving( comest->add ) ); } if( food.has_flag( "HOT" ) && food.has_flag( "EATEN_HOT" ) ) { add_morale( MORALE_FOOD_HOT, 5, 10 ); } auto fun = comest->fun; if( food.has_flag( "COLD" ) && food.has_flag( "EATEN_COLD" ) && fun > 0 ) { if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun * 3, fun * 3, 60, 30, false, food.type ); } else { fun = 1; } } const bool gourmand = has_trait( "GOURMAND" ); const bool hibernate = has_active_mutation( "HIBERNATE" ); if( gourmand ) { if( fun < -2 ) { add_morale( MORALE_FOOD_BAD, fun * 0.5, fun, 60, 30, false, food.type ); } else if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun * 3, fun * 6, 60, 30, false, food.type ); } } else if( fun < 0 ) { add_morale( MORALE_FOOD_BAD, fun, fun * 6, 60, 30, false, food.type ); } else if( fun > 0 ) { add_morale( MORALE_FOOD_GOOD, fun, fun * 4, 60, 30, false, food.type ); } if( hibernate ) { if( ( nutr > 0 && get_hunger() < -60 ) || ( comest->quench > 0 && get_thirst() < -60 ) ) { //Tell the player what's going on add_msg_if_player( _( "You gorge yourself, preparing to hibernate." ) ); if( one_in( 2 ) ) { //50% chance of the food tiring you mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -200 ) || ( comest->quench > 0 && get_thirst() < -200 ) ) { //Hibernation should cut burn to 60/day add_msg_if_player( _( "You feel stocked for a day or two. Got your bed all ready and secured?" ) ); if( one_in( 2 ) ) { //And another 50%, intended cumulative mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -400 ) || ( comest->quench > 0 && get_thirst() < -400 ) ) { add_msg_if_player( _( "Mmm. You can still fit some more in...but maybe you should get comfortable and sleep." ) ); if( !one_in( 3 ) ) { //Third check, this one at 66% mod_fatigue( nutr ); } } if( ( nutr > 0 && get_hunger() < -600 ) || ( comest->quench > 0 && get_thirst() < -600 ) ) { add_msg_if_player( _( "That filled a hole! Time for bed..." ) ); // At this point, you're done. Schlaf gut. mod_fatigue( nutr ); } } // Moved here and changed a bit - it was too complex // Incredibly minor stuff like this shouldn't require complexity if( !is_npc() && has_trait( "SLIMESPAWNER" ) && ( get_hunger() < capacity + 40 || get_thirst() < capacity + 40 ) ) { add_msg_if_player( m_mixed, _( "You feel as though you're going to split open! In a good way?" ) ); mod_pain( 5 ); std::vector<tripoint> valid; for( const tripoint &dest : g->m.points_in_radius( pos(), 1 ) ) { if( g->is_empty( dest ) ) { valid.push_back( dest ); } } int numslime = 1; for( int i = 0; i < numslime && !valid.empty(); i++ ) { const tripoint target = random_entry_removed( valid ); if( g->summon_mon( mon_player_blob, target ) ) { monster *slime = g->monster_at( target ); slime->friendly = -1; } } mod_hunger( 40 ); mod_thirst( 40 ); //~slimespawns have *small voices* which may be the Nice equivalent //~of the Rat King's ALL CAPS invective. Probably shared-brain telepathy. add_msg_if_player( m_good, _( "hey, you look like me! let's work together!" ) ); } // Last thing that happens before capping hunger if( get_hunger() < capacity && has_trait( "EATHEALTH" ) ) { int excess_food = capacity - get_hunger(); add_msg_player_or_npc( _( "You feel the %s filling you out." ), _( "<npcname> looks better after eating the %s." ), food.tname().c_str() ); // Guaranteed 1 HP healing, no matter what. You're welcome. ;-) if( excess_food <= 5 ) { healall( 1 ); } else { // Straight conversion, except it's divided amongst all your body parts. healall( excess_food /= 5 ); } // Note: We want this here to prevent "you can't finish this" messages set_hunger( capacity ); } cap_nutrition_thirst( *this, capacity, nutr > 0, comest->quench > 0 ); }
edible_rating player::can_eat( const item &food, bool interactive, bool force ) const { if( is_npc() || force ) { // Just to be sure interactive = false; } const std::string &itname = food.tname(); // Helper to avoid ton of `if( interactive )` // Prints if interactive is true, does nothing otherwise const auto maybe_print = [interactive, &itname] ( game_message_type type, const char *str ) { if( interactive ) { add_msg( type, str, itname.c_str() ); } }; // As above, but for queries // Asks if interactive and not force // Always true if force // Never true otherwise const auto maybe_query = [force, interactive, &itname, this]( const char *str ) { if( force ) { return true; } else if( !interactive ) { return false; } return query_yn( str, itname.c_str() ); }; const auto comest = food.type->comestible.get(); if( comest == nullptr ) { maybe_print( m_info, _( "That doesn't look edible." ) ); return INEDIBLE; } if( comest->tool != "null" ) { bool has = has_amount( comest->tool, 1 ); if( item::count_by_charges( comest->tool ) ) { has = has_charges( comest->tool, 1 ); } if( !has ) { if( interactive ) { add_msg_if_player( m_info, _( "You need a %s to consume that!" ), item::nname( comest->tool ).c_str() ); } return NO_TOOL; } } if( is_underwater() ) { maybe_print( m_info, _( "You can't do that while underwater." ) ); return INEDIBLE; } // For all those folks who loved eating marloss berries. D:< mwuhahaha if( has_trait( "M_DEPENDENT" ) && food.type->id != "mycus_fruit" ) { maybe_print( m_info, _( "We can't eat that. It's not right for us." ) ); return INEDIBLE_MUTATION; } const bool drinkable = comest->comesttype == "DRINK" && !food.has_flag( "USE_EAT_VERB" ); // Here's why PROBOSCIS is such a negative trait. if( has_trait( "PROBOSCIS" ) && !drinkable ) { maybe_print( m_info, _( "Ugh, you can't drink that!" ) ); return INEDIBLE_MUTATION; } int capacity = stomach_capacity(); // TODO: Move this cache to a structure and pass it around // to speed up checking entire inventory for edibles const bool gourmand = has_trait( "GOURMAND" ); const bool hibernate = has_active_mutation( "HIBERNATE" ); const bool eathealth = has_trait( "EATHEALTH" ); const bool slimespawner = has_trait( "SLIMESPAWNER" ); const int nutr = nutrition_for( food.type ); const int quench = comest->quench; bool spoiled = food.rotten(); const int temp_hunger = get_hunger() - nutr; const int temp_thirst = get_thirst() - quench; const bool overeating = get_hunger() < 0 && nutr >= 5 && !gourmand && !eathealth && !slimespawner && !hibernate; if( interactive && hibernate && ( get_hunger() >= -60 && get_thirst() >= -60 ) && ( temp_hunger < -60 || temp_thirst < -60 ) ) { if( !maybe_query( _( "You're adequately fueled. Prepare for hibernation?" ) ) ) { return TOO_FULL; } } const bool carnivore = has_trait( "CARNIVORE" ); if( carnivore && nutr > 0 && food.has_any_flag( carnivore_blacklist ) && !food.has_flag( "CARNIVORE_OK" ) ) { maybe_print( m_info, _( "Eww. Inedible plant stuff!" ) ); return INEDIBLE_MUTATION; } if( ( has_trait( "HERBIVORE" ) || has_trait( "RUMINANT" ) ) && food.has_any_flag( herbivore_blacklist ) ) { // Like non-cannibal, but more strict! maybe_print( m_info, _( "The thought of eating that makes you feel sick. You decide not to." ) ); return INEDIBLE_MUTATION; } if( food.has_flag( "CANNIBALISM" ) ) { if( !has_trait_flag( "CANNIBAL" ) && !maybe_query( _( "The thought of eating that makes you feel sick. Really do it?" ) ) ) { return CANNIBALISM; } } if( is_allergic( food ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } if( carnivore && food.has_flag( "ALLERGEN_JUNK" ) && !food.has_flag( "CARNIVORE_OK" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { return ALLERGY; } const bool saprophage = has_trait( "SAPROPHAGE" ); // The item is solid food const bool chew = comest->comesttype == "FOOD" || food.has_flag( "USE_EAT_VERB" ); if( spoiled ) { if( !saprophage && !has_trait( "SAPROVORE" ) && !maybe_query( _( "This %s smells awful! Eat it?" ) ) ) { return ROTTEN; } } else if( saprophage && chew && !food.has_flag( "FERTILIZER" ) && !maybe_query( _( "Really eat that %s? Your stomach won't be happy." ) ) ) { // Note: We're allowing all non-solid "food". This includes drugs // Hardcoding fertilizer for now - should be a separate flag later //~ No, we don't eat "rotten" food. We eat properly aged food, like a normal person. //~ Semantic difference, but greatly facilitates people being proud of their character. maybe_print( m_info, _( "It's too fresh, let it age a little first." ) ); return ROTTEN; } // Print at most one of those bool overfull = false; if( overeating ) { overfull = !maybe_query( _( "You're full. Force yourself to eat?" ) ); } else if( ( ( nutr > 0 && temp_hunger < capacity ) || ( comest->quench > 0 && temp_thirst < capacity ) ) && !eathealth && !slimespawner ) { overfull = !maybe_query( _( "You will not be able to finish it all. Consume it?" ) ); } if( overfull ) { return TOO_FULL; } // All checks ended, it's edible (or we're pretending it is) return EDIBLE; }