Anonymous | Login | 2025-04-19 11:16 CEST |
Main | My View | View Issues | Change Log | Wiki | Tavern | News |
Viewing Issue Simple Details [ Jump to Notes ] [ Wiki ] | [ View Advanced ] [ Issue History ] [ Print ] | ||||||
ID | Category | Severity | Reproducibility | Date Submitted | Last Update | ||
0007188 | [DCSS] Patches | feature | N/A | 2013-06-09 07:31 | 2013-10-04 04:14 | ||
Reporter | gammafunk | View Status | public | ||||
Assigned To | neil | ||||||
Priority | normal | Resolution | done | ||||
Status | closed | Product Branch | 0.13 ancient branch | ||||
Summary | 0007188: Initial implementation of a jump attack ability. | ||||||
Description |
I've created a page on the dev wiki for discussion that goes into the details: https://crawl.develz.org/wiki/doku.php?id=dcss:brainstorm:combat:jump_attack [^] Short summary: A new ability where the player can jump or perform a jumping attack that can hit one to a few targets. The user can target a monster to perform a jump attack dealing additional melee damage and secondary damage to hostiles along the jump path. A jump with no primary attack can be performed by targeting an empty square, although hostile monsters along the jump path will be hit by the secondary attack Tested on tiles and ascii builds on OS X and a webtiles server on linux. The jump evocation ability comes from an armour ego (either boots or hat). There's also a player mutation, which Felids now start with at level one, increasing to levels 2 and 3 at XL 6 and XL 12. It costs 2mp to cast, uses hunger similar to a breath attack, and has a 'jump power' mechanic to limit consecutive jumps. There's a Dragoon class that starts with boots of jumping or a hat of jumping, appropriate skills in evocations and 2mp. This will need lots of tweaking for balance, and I'm open to taking the 'translocations attack' idea in other direction(s). |
||||||
Additional Information | |||||||
Tags | No tags attached. | ||||||
Attached Files |
![]() ![]() ![]() ![]() From 54afd568956b04179a166886389577d507ede6f7 Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Wed, 12 Jun 2013 20:38:31 -0500 Subject: [PATCH] Reworked Jump attack (version 2) Changes based on feedback from dev. team. * You can jump attack an enemy or invisible monster marker only; no standalone 'cblink' jump. * The landing site is randomly chosen from the valid sites near the monster, so no more double-targetting. * Damage increase is greatly reduced, from 200% to 20%. * Species that can't wear boots are restricted from Dragoon, get an unbranded helmet/cap if they still choose the class. * Starting attack range increased from 2 to 3, up to a max of 5 instead of 4. The movement range is still one less than the attack range. * No more landing cursor, or alternate landing site tiles, since we don't need them. --- crawl-ref/source/abl-show.cc | 103 +++++++- crawl-ref/source/actor.cc | 10 + crawl-ref/source/actor.h | 2 + crawl-ref/source/beam.cc | 1 + crawl-ref/source/dat/descript/ability.txt | 17 ++- crawl-ref/source/dat/descript/backgrounds.txt | 5 + crawl-ref/source/describe.cc | 6 + crawl-ref/source/directn.cc | 264 +++++++++++++------- crawl-ref/source/directn.h | 7 +- crawl-ref/source/enum.h | 33 ++- crawl-ref/source/fight.cc | 47 ++++- crawl-ref/source/fight.h | 5 +- crawl-ref/source/itemname.cc | 2 + crawl-ref/source/itemprop-enum.h | 2 +- crawl-ref/source/itemprop.cc | 3 +- crawl-ref/source/jobs.cc | 4 +- crawl-ref/source/main.cc | 1 - crawl-ref/source/makeitem.cc | 1 + crawl-ref/source/mapdef.cc | 1 + crawl-ref/source/melee_attack.cc | 24 ++- crawl-ref/source/melee_attack.h | 5 +- crawl-ref/source/monster.cc | 18 ++ crawl-ref/source/monster.h | 1 + crawl-ref/source/mutation-data.h | 18 ++ crawl-ref/source/mutation.cc | 1 - crawl-ref/source/newgame.cc | 3 +- crawl-ref/source/ng-restr.cc | 16 ++- crawl-ref/source/ng-setup.cc | 24 ++ crawl-ref/source/player-act.cc | 64 +++++- crawl-ref/source/player-equip.cc | 16 ++ crawl-ref/source/player.cc | 183 +++++++++----- crawl-ref/source/player.h | 5 +- crawl-ref/source/rltiles/dc-abilities.txt | 2 + crawl-ref/source/rltiles/dc-feat.txt | 1 + crawl-ref/source/rltiles/dc-item.txt | 1 + crawl-ref/source/rltiles/gui/abilities/jump.png | Bin 0 -> 957 bytes .../rltiles/item/armour/brands/i-jumping.png | Bin 0 -> 161 bytes crawl-ref/source/rltiles/misc/landing.png | Bin 0 -> 215 bytes crawl-ref/source/shopping.cc | 4 + crawl-ref/source/target.cc | 233 +++++++++++++++++ crawl-ref/source/target.h | 37 +++ crawl-ref/source/tiledgnbuf.cc | 2 + crawl-ref/source/tilepick.cc | 4 + crawl-ref/source/tileview.cc | 10 +- .../webserver/game_data/static/cell_renderer.js | 3 + .../source/webserver/game_data/static/enums.js | 6 +- crawl-ref/source/wiz-item.cc | 3 + crawl-ref/source/wiz-you.cc | 1 + 48 files changed, 1000 insertions(+), 199 deletions(-) create mode 100644 crawl-ref/source/rltiles/gui/abilities/jump.png create mode 100644 crawl-ref/source/rltiles/item/armour/brands/i-jumping.png create mode 100644 crawl-ref/source/rltiles/misc/landing.png diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc index 62cee08..3ffe2c6 100644 --- a/crawl-ref/source/abl-show.cc +++ b/crawl-ref/source/abl-show.cc @@ -40,6 +40,7 @@ #include "evoke.h" #include "macro.h" #include "maps.h" +#include "melee_attack.h" #include "message.h" #include "menu.h" #include "misc.h" @@ -109,6 +110,7 @@ static void _pay_ability_costs(const ability_def& abil, int zpcost); static int _scale_piety_cost(ability_type abil, int original_cost); static string _zd_mons_description_for_ability(const ability_def &abil); static monster_type _monster_for_ability(const ability_def& abil); +static bool _jump_player(int jump_range); /** * This all needs to be split into data/util/show files @@ -223,6 +225,7 @@ static const ability_def Ability_List[] = { ABIL_FLY, "Fly", 3, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_STOP_FLYING, "Stop Flying", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_JUMP, "Jump Attack", 0, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_HELLFIRE, "Hellfire", 0, 150, 200, 0, 0, ABFLAG_NONE}, { ABIL_DELAYED_FIREBALL, "Release Delayed Fireball", @@ -248,6 +251,7 @@ static const ability_def Ability_List[] = { ABIL_EVOKE_TURN_INVISIBLE, "Evoke Invisibility", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TURN_VISIBLE, "Turn Visible", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_EVOKE_JUMP, "Evoke Jump Attack", 2, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_EVOKE_FLIGHT, "Evoke Flight", 1, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_FOG, "Evoke Fog", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TELEPORT_CONTROL, "Evoke Teleport Control", 4, 0, 200, 0, 0, ABFLAG_NONE}, @@ -921,7 +925,6 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - case ABIL_BREATHE_FROST: case ABIL_BREATHE_POISON: case ABIL_SPIT_ACID: @@ -941,7 +944,12 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - + // copied from spit poison + case ABIL_JUMP: + failure = ((you.species == SP_FELID) ? 20 : 40) + - 10 * player_mutation_level(MUT_JUMP) + - you.experience_level; + break; case ABIL_FLY: failure = 45 - (3 * you.experience_level); break; @@ -995,7 +1003,9 @@ talent get_talent(ability_type ability, bool check_confused) case ABIL_EVOKE_BLINK: failure = 40 - you.skill(SK_EVOCATIONS, 2); break; - + case ABIL_EVOKE_JUMP: + failure = 30 - you.skill(SK_EVOCATIONS, 2); + break; case ABIL_EVOKE_BERSERK: case ABIL_EVOKE_FOG: case ABIL_EVOKE_TELEPORT_CONTROL: @@ -1240,6 +1250,10 @@ void no_ability_msg() if (you.flight_mode()) mpr("You're already flying!"); } + else if (you.species == SP_FELID && you.airborne()) + { + mpr("You can't jump while flying."); + } else if (silenced(you.pos()) && you.religion != GOD_NO_GOD) { // At the very least the player has "Renounce Religion", but @@ -1340,6 +1354,7 @@ static bool _check_ability_possible(const ability_def& abil, bool hungerCheck = true, bool quiet = false) { + if (silenced(you.pos()) && you.religion != GOD_NEMELEX_XOBEH) { talent tal = get_talent(abil.ability, false); @@ -1573,6 +1588,13 @@ bool activate_talent(const talent& tal) return false; } + if ((tal.which == ABIL_EVOKE_JUMP || tal.which == ABIL_JUMP) + && !you.can_jump()) + { + crawl_state.zero_turns_taken(); + return false; + } + if ((tal.which == ABIL_EVOKE_FLIGHT || tal.which == ABIL_TRAN_BAT) && you.liquefied_ground()) { @@ -1883,7 +1905,15 @@ static bool _do_ability(const ability_def& abil) break; } - + case ABIL_JUMP: + { + int jump_level = player_jump_level(false); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.experience_level)); + break; + } case ABIL_RECHARGING: if (recharge_wand() <= 0) return false; // fail message is already given @@ -2138,7 +2168,15 @@ static bool _do_ability(const ability_def& abil) else fly_player(you.skill(SK_EVOCATIONS, 2) + 30); break; - + case ABIL_EVOKE_JUMP: + { + int jump_level = player_jump_level(true); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.skill(SK_EVOCATIONS, 1))); + break; + } case ABIL_EVOKE_FOG: // cloak of the Thief mpr("With a swish of your cloak, you release a cloud of fog."); big_cloud(random_smoke_type(), &you, you.pos(), 50, 8 + random2(8)); @@ -3080,7 +3118,11 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) if (you.species == SP_DEEP_DWARF) _add_talent(talents, ABIL_RECHARGING, check_confused); - + if (player_mutation_level(MUT_JUMP) + && !you.evokable_jump() && !you.airborne()) + { + _add_talent(talents, ABIL_JUMP, check_confused); + } // Spit Poison. Nontransformed nagas can upgrade to Breathe Poison. // Transformed nagas, or non-nagas, can only get Spit Poison. if (you.species == SP_NAGA @@ -3092,8 +3134,7 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } else if (player_mutation_level(MUT_SPIT_POISON) || you.species == SP_NAGA) - { - _add_talent(talents, ABIL_SPIT_POISON, check_confused); + { _add_talent(talents, ABIL_SPIT_POISON, check_confused); } if (player_genus(GENPC_DRACONIAN)) @@ -3229,6 +3270,9 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } } + if (you.evokable_jump() && !you.airborne()) + _add_talent(talents, ABIL_EVOKE_JUMP, check_confused); + if (you.wearing(EQ_RINGS, RING_TELEPORTATION) && !crawl_state.game_is_sprint()) { @@ -3497,3 +3541,46 @@ int scaling_cost::cost(int max) const { return (value < 0) ? (-value) : ((value * max + 500) / 1000); } + + +bool _jump_player(int jump_range) +{ + coord_def landing; + direction_chooser_args args; + targetter_jump tgt(&you, jump_range); + dist jdirect; + + args.restricts = DIR_JUMP; + args.mode = TARG_HOSTILE; + args.just_looking = false; + args.needs_path = true; + args.may_target_monster = true; + args.may_target_self = false; + args.target_prefix = NULL; + args.top_prompt = "Aiming: <white>Jump Attack</white>"; + args.behaviour = NULL; + args.cancel_at_self = true; + args.hitfunc = &tgt; + args.range = jump_range; + direction(jdirect, args); + if (!jdirect.isValid) + { + // Check for user cancel. + canned_msg(MSG_OK); + return false; + } + + if (!check_moveto(jdirect.target, "jump")) + return false; + // Our jump path was blocked by an invisible monster, so waste a turn. + if (tgt.jump_is_blocked) + { + mpr("You rebound off of something unseen!"); + } + else if (!fight_jump(&you, actor_at(jdirect.target), + tgt.landing_site)) + { + return false; + } + return true; +} diff --git a/crawl-ref/source/actor.cc b/crawl-ref/source/actor.cc index a612e2a..ec82979 100644 --- a/crawl-ref/source/actor.cc +++ b/crawl-ref/source/actor.cc @@ -386,6 +386,16 @@ int actor::evokable_flight(bool calc_unid) const + scan_artefacts(ARTP_FLY, calc_unid); } +// Return an int so we know whether an item is the sole source. +int actor::evokable_jump(bool calc_unid) const +{ + if (suppressed() + || (is_player() && player_jump_level(true) <= player_jump_level(false))) + return 0; + return wearing_ego(EQ_ALL_ARMOUR, SPARM_JUMPING, calc_unid) + + scan_artefacts(ARTP_JUMP, calc_unid); +} + int actor::spirit_shield(bool calc_unid, bool items) const { int ss = 0; diff --git a/crawl-ref/source/actor.h b/crawl-ref/source/actor.h index 59b0973..3bd977b 100644 --- a/crawl-ref/source/actor.h +++ b/crawl-ref/source/actor.h @@ -171,6 +171,7 @@ public: virtual bool can_see_invisible() const = 0; virtual bool invisible() const = 0; virtual bool nightvision() const = 0; + virtual bool can_jump() const = 0; // Would looker be able to see the actor when in LOS? virtual bool visible_to(const actor *looker) const = 0; @@ -316,6 +317,7 @@ public: // Return an int so we know whether an item is the sole source. virtual int evokable_flight(bool calc_unid = true) const; + virtual int evokable_jump(bool calc_unid = true) const; virtual int spirit_shield(bool calc_unid = true, bool items = true) const; virtual flight_type flight_mode() const = 0; diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc index 026d809..a7a9c93 100644 --- a/crawl-ref/source/beam.cc +++ b/crawl-ref/source/beam.cc @@ -2979,6 +2979,7 @@ bool bolt::is_harmless(const monster* mon) const { case BEAM_VISUAL: case BEAM_DIGGING: + case BEAM_NONE: return true; case BEAM_HOLY: diff --git a/crawl-ref/source/dat/descript/ability.txt b/crawl-ref/source/dat/descript/ability.txt index bfa125d..75d1484 100644 --- a/crawl-ref/source/dat/descript/ability.txt +++ b/crawl-ref/source/dat/descript/ability.txt @@ -74,7 +74,17 @@ Start flying. During flight you can safely cross water and similar obstacles. Be warned, though, that flight may time out at inopportune moments and cause you to fall to your death. %%%% -Hellfire +Jump Attack + +Jump attack at limited distance, covering ground with great speed. You may +target an enemy to perform a jumping attack with increased damage and landing in +some position next to your target. You cannot jump over flying enemies nor +giant enemies not submerged in deep water or lava. Flying, standing or swimming +in liquids, or the use of stasis will also prevent you from using this ability. +Felids have an innate ability to jump attack that increases in range at +experience levels 6 and 12. + +%%%% Hellfire Blast your enemies with hellfire. %%%% @@ -123,6 +133,11 @@ Evoke Flight <Fly> %%%% +Evoke Jump Attack + +<Jump Attack> +%%%% + Stop Flying Stop flying. diff --git a/crawl-ref/source/dat/descript/backgrounds.txt b/crawl-ref/source/dat/descript/backgrounds.txt index 446e640..cc1f17a 100644 --- a/crawl-ref/source/dat/descript/backgrounds.txt +++ b/crawl-ref/source/dat/descript/backgrounds.txt @@ -43,6 +43,11 @@ Death Knight Death Knights are melee fighters who command the undead in the name of Yredelemnul the Dark. %%%% +Dragoon + +Dragoons harness the power of magical armour that allows them to +launch a devestating jumping attack on multiple foes. +%%%% Earth Elementalist Earth Elementalists know the Sandblast spell, and carry stones to increase the diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index 01dfa25..5b2db64 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -224,6 +224,7 @@ static vector<string> _randart_propnames(const item_def& item, { "+Inv", ARTP_INVISIBLE, 2 }, { "+Fly", ARTP_FLY, 2 }, { "+Fog", ARTP_FOG, 2 }, + { "+Jump", ARTP_JUMP, 2 }, // Resists, also really important { "rElec", ARTP_ELECTRICITY, 2 }, @@ -418,6 +419,7 @@ static string _randart_descrip(const item_def &item) { ARTP_EYESIGHT, "It enhances your eyesight.", false}, { ARTP_INVISIBLE, "It lets you turn invisible.", false}, { ARTP_FLY, "It lets you fly.", false}, + { ARTP_JUMP, "It lets perform a jumping attack.", false}, { ARTP_BLINK, "It lets you blink.", false}, { ARTP_BERSERK, "It lets you go berserk.", false}, { ARTP_NOISES, "It makes noises.", false}, @@ -1324,6 +1326,10 @@ static string _describe_armour(const item_def &item, bool verbose) description += "It can be activated to allow its wearer to " "fly indefinitely."; break; + case SPARM_JUMPING: + description += "It can be activated to allow its wearer to " + "perform a jumping attack."; + break; case SPARM_MAGIC_RESISTANCE: description += "It increases its wearer's resistance " "to enchantments."; diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index 4e2713b..57b6b15 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -109,6 +109,8 @@ static bool _find_feature(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); static bool _find_fprop_unoccupied(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc); #ifndef USE_TILE_LOCAL static bool _find_mlist(const coord_def& where, int mode, bool need_path, @@ -175,8 +177,8 @@ static void _wizard_make_friendly(monster* m) #endif dist::dist() - : isValid(false), isTarget(false), isEndpoint(false), - isCancel(true), choseRay(false), target(), delta(), ray() + : isValid(false), isTarget(false), isEndpoint(false), isCancel(true), + choseRay(false), target(), delta(), ray() { } @@ -379,6 +381,9 @@ void direction_chooser::print_key_hints() const case DIR_DIR: case DIR_TARGET_OBJECT: break; + case DIR_JUMP: + prompt += hint_string; + break; } } @@ -513,6 +518,7 @@ direction_chooser::direction_chooser(dist& moves_, show_items_once = false; target_unshifted = Options.target_unshifted_dirs; + } class view_desc_proc @@ -1013,89 +1019,107 @@ static void _update_mlist(bool enable) } #endif -// Find a good square to start targetting from. -coord_def direction_chooser::find_default_target() const +// Try to find an enemy monster to target +bool direction_chooser::find_default_monster_target(coord_def& result) const { - coord_def result = you.pos(); bool success = false; - - if (restricts == DIR_TARGET_OBJECT) + int search_range = range; + bool (*find_targ)(const coord_def&, int, bool, int, targetter*); + + // First try to pick our previous target. + const monster* mons_target = get_current_target(); + if (mons_target != NULL + && (mode != TARG_EVOLVABLE_PLANTS + && mons_attitude(mons_target) == ATT_HOSTILE + || mode == TARG_ENEMY && !mons_target->friendly() + || mode == TARG_EVOLVABLE_PLANTS + && mons_is_evolvable(mons_target) + || mode == TARG_HOSTILE_UNDEAD && !mons_target->friendly() + && mons_target->holiness() == MH_UNDEAD + || mode == TARG_INJURED_FRIEND + && (mons_target->friendly() && mons_get_damage_level(mons_target) > MDAM_OKAY + || (!mons_target->wont_attack() + && !mons_target->neutral() + && is_pacifiable(mons_target) >= 0))) + && in_range(mons_target->pos())) { - // Try to find an object. - success = _find_square_wrapper(result, 1, _find_object, - needs_path, TARG_ANY, range, hitfunc, - true, LS_FLIPVH); + success = true; + result = mons_target->pos(); } - else if (mode == TARG_ENEMY || mode == TARG_HOSTILE - || mode == TARG_HOSTILE_SUBMERGED - || mode == TARG_EVOLVABLE_PLANTS - || mode == TARG_HOSTILE_UNDEAD - || mode == TARG_INJURED_FRIEND) + if (!success) { - // Try to find an enemy monster. - - // First try to pick our previous target. - const monster* mon_target = get_current_target(); - if (mon_target != NULL - && (mode != TARG_EVOLVABLE_PLANTS - && mons_attitude(mon_target) == ATT_HOSTILE - || mode == TARG_ENEMY && !mon_target->friendly() - || mode == TARG_EVOLVABLE_PLANTS - && mons_is_evolvable(mon_target) - || mode == TARG_HOSTILE_UNDEAD && !mon_target->friendly() - && mon_target->holiness() == MH_UNDEAD - || mode == TARG_INJURED_FRIEND - && (mon_target->friendly() && mons_get_damage_level(mon_target) > MDAM_OKAY - || (!mon_target->wont_attack() - && !mon_target->neutral() - && is_pacifiable(mon_target) >= 0))) - && in_range(mon_target->pos())) + // Have to increase search_range by one so monsters out of range but + // with landing sites in-range are found. + if (restricts == DIR_JUMP) { - result = mon_target->pos(); - success = true; + find_targ = _find_jump_attack_mons; + search_range += 1; } else { - // The previous target is no good. Try to find one from scratch. - success = _find_square_wrapper(result, 1, _find_monster, + find_targ = _find_monster; + } + // The previous target is no good. Try to find one from scratch. + success = _find_square_wrapper(result, 1, find_targ, needs_path, mode, + search_range, hitfunc, true); + + // We might be able to hit monsters in LOS that are outside of + // normal range, but inside explosion/cloud range + if (!success && hitfunc && restricts != DIR_JUMP + && (you.current_vision > range || hitfunc->can_affect_walls())) + { + success = _find_square_wrapper(result, 1, _find_monster_expl, needs_path, mode, range, hitfunc, true); + } - // We might be able to hit monsters in LOS that are outside of - // normal range, but inside explosion/cloud range - if (!success - && hitfunc && hitfunc->can_affect_outside_range() - && (you.current_vision > range || hitfunc->can_affect_walls())) - { - success = _find_square_wrapper(result, 1, _find_monster_expl, - needs_path, mode, range, hitfunc, - true); - } - // If we couldn't, maybe it was because of line-of-fire issues. - // Check if that's happening, and inform the user (because it's - // pretty confusing.) - if (!success - && needs_path - && _find_square_wrapper(result, 1, _find_monster, - false, mode, range, hitfunc, true)) + // If we couldn't, maybe it was because of line-of-fire issues. + // Check if that's happening, and inform the user (because it's + // pretty confusing.) + if (!success && needs_path + && _find_square_wrapper(result, 1, find_targ, false, mode, + search_range, hitfunc, true)) + { + // Special colouring in tutorial or hints mode. + const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; + mpr("All monsters which could be auto-targeted are covered by " + "a wall or statue which interrupts your line of fire, even " + "though it doesn't interrupt your line of sight.", + need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); + + if (need_hint) { - // Special colouring in tutorial or hints mode. - const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; - mpr("All monsters which could be auto-targeted are covered by " - "a wall or statue which interrupts your line of fire, even " - "though it doesn't interrupt your line of sight.", - need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); - - if (need_hint) - { mpr("To return to the main mode, press <w>Escape</w>.", MSGCH_TUTORIAL); Hints.hints_events[HINT_TARGET_NO_FOE] = false; - } } } } + return success; +} + +// Find a good square to start targetting from. +void direction_chooser::set_default_target() +{ + coord_def result = you.pos(); + bool success = false; + + if (restricts == DIR_TARGET_OBJECT) + { + // Try to find an object. + success = _find_square_wrapper(result, 1, _find_object, + needs_path, TARG_ANY, range, hitfunc, + true, LS_FLIPVH); + } + else if (mode == TARG_ENEMY || mode == TARG_HOSTILE + || mode == TARG_HOSTILE_SUBMERGED + || mode == TARG_EVOLVABLE_PLANTS + || mode == TARG_HOSTILE_UNDEAD + || mode == TARG_INJURED_FRIEND) + { + success = find_default_monster_target(result); + } // Evolution can also auto-target mold squares (but shouldn't if // there are any monsters to evolve), so try _find_square_wrapper // again @@ -1108,8 +1132,7 @@ coord_def direction_chooser::find_default_target() const if (!success) result = you.pos(); - - return result; + set_target(result); } const coord_def& direction_chooser::target() const @@ -1119,6 +1142,20 @@ const coord_def& direction_chooser::target() const void direction_chooser::set_target(const coord_def& new_target) { + coord_def jump_pos; + set<coord_def>::const_iterator site; + monster *mons; + + if (restricts == DIR_JUMP) + { + mons = monster_at(new_target); + if (((mons && you.can_see(mons)) + || env.map_knowledge(new_target).invisible_monster()) + && hitfunc->has_additional_sites(new_target, true)) + valid_jump = true; + else + valid_jump = false; + } moves.target = new_target; } @@ -1161,11 +1198,22 @@ void direction_chooser::draw_beam_if_needed() #ifndef USE_TILE_LOCAL int bcol = BLACK; if (aff < 0) + { bcol = DARKGREY; + } else if (aff < AFF_YES) + { bcol = (*ri == target()) ? RED : MAGENTA; - else + } + else if (aff == AFF_YES) + { bcol = (*ri == target()) ? LIGHTRED : LIGHTMAGENTA; + } + // Jump attack landing sites + else + { + bcol = (*ri == target()) ? LIGHTGREEN : GREEN; + } _draw_ray_glyph(*ri, bcol, '*', bcol | COLFLAG_REVERSE); #endif } @@ -1283,15 +1331,23 @@ void direction_chooser::update_previous_target() const bool direction_chooser::select(bool allow_out_of_range, bool endpoint) { + const monster* mons = monster_at(target()); + + if (restricts == DIR_JUMP && !valid_jump) + { + if (mons && you.can_see(mons)) + mpr("The monster cannot be jump-attacked there."); + else + mpr("There is no monster to jump-attack there!"); + return false; + } if (!allow_out_of_range && !in_range(target())) { mpr(hitfunc? hitfunc->why_not : "That is beyond the maximum range.", MSGCH_EXAMINE_FILTER); return false; } - - const monster* m = monster_at(target()); - moves.isEndpoint = endpoint || (m && _mon_exposed(m)); + moves.isEndpoint = endpoint || (mons && _mon_exposed(mons)); moves.isValid = true; moves.isTarget = true; update_previous_target(); @@ -1627,9 +1683,12 @@ void direction_chooser::handle_movement_key(command_type key_command, const coord_def& delta = Compass[compass_idx]; const bool unshifted = (shift_direction(key_command) != key_command); if (unshifted) + { set_target(target() + delta); - else + } else + { *loop_done = select_compass_direction(delta); + } } } @@ -1773,13 +1832,13 @@ void direction_chooser::do_redraws() if (need_cursor_redraw || Options.use_fake_cursor) { - cursorxy(crawl_view.grid2screen(target())); #ifdef USE_TILE_WEB // cursorxy doesn't place the cursor in Webtiles, we do it manually here // This is by design, since we don't want to use the mouse cursor for // the overview map. tiles.place_cursor(CURSOR_MOUSE, target()); #endif + cursorxy(crawl_view.grid2screen(target())); need_cursor_redraw = false; } } @@ -1958,7 +2017,6 @@ bool direction_chooser::do_main_loop() case CMD_TARGET_DESCRIBE: describe_target(); break; case CMD_TARGET_HELP: show_help(); break; - default: // Some blocks of keys with similar handling. handle_movement_key(key_command, &loop_done); @@ -1983,7 +2041,7 @@ bool direction_chooser::do_main_loop() } // Redraw whatever is necessary. - if (old_target != target()) + if (restricts == DIR_JUMP || old_target != target()) { have_beam = show_beam && find_ray(you.pos(), target(), beam, opc_solid_see, BDS_DEFAULT); @@ -2035,8 +2093,11 @@ bool direction_chooser::choose_direction() // init moves.delta.reset(); - // Find a default target. - set_target(Options.default_target ? find_default_target() : you.pos()); + // Set a default target. + if (Options.default_target) + set_default_target(); + else + set_target(you.pos()); objfind_pos = monsfind_pos = target(); // If requested, show the beam on startup. @@ -2410,14 +2471,14 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, if ((mode == TARG_FRIEND || mode == TARG_ANY) && where == you.pos()) return true; - // Don't target out of range. - if (!_is_target_in_range(where, range, hitfunc)) + // Don't target out of range + if (hitfunc && !_is_target_in_range(where, range, hitfunc)) return false; const monster* mon = monster_at(where); // No monster or outside LOS. - if (mon == NULL || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) + if (!mon || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) return false; // Monster in LOS but only via glass walls, so no direct path. @@ -2433,9 +2494,39 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, return _want_target_monster(mon, mode); } +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc) +{ +#ifdef CLUA_BINDINGS + { + coord_def dp = grid2player(where); + // We could pass more info here. + maybe_bool x = clua.callmbooleanfn("ch_target_jump", "dd", + dp.x, dp.y); + if (x != MB_MAYBE) + return tobool(x); + } +#endif + + // Need a monster to attack; this checks that the monster is a valid target. + if (!_find_monster(where, mode, need_path, range, hitfunc)) + return false; + // Can't jump on yourself + if (where == you.pos()) + return false; + + return hitfunc->has_additional_sites(where, false); +} + + + + static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc) { + const monster* mons; + coord_def jump_pos; + ASSERT(hitfunc); #ifdef CLUA_BINDINGS @@ -2466,16 +2557,17 @@ static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, return false; if (hitfunc->set_aim(where)) + { for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri) { - if (hitfunc->is_affected(*ri) >= AFF_YES) + mons = monster_at(*ri); + if (mons && hitfunc->is_affected(*ri) == AFF_YES) { - const monster* mon = monster_at(*ri); - if (mon && _mons_is_valid_target(mon, mode, range)) - return _want_target_monster(mon, mode); + if (_mons_is_valid_target(mons, mode, range)) + return _want_target_monster(mons, mode); } } - + } return false; } diff --git a/crawl-ref/source/directn.h b/crawl-ref/source/directn.h index e5e5a0c..412fa27 100644 --- a/crawl-ref/source/directn.h +++ b/crawl-ref/source/directn.h @@ -66,6 +66,7 @@ public: bool isEndpoint; // Does the player want the attack to stop at target? bool isCancel; // user cancelled (usually <ESC> key) bool choseRay; // user wants a specific beam + coord_def target; // target x,y or logical extension of beam to map edge coord_def delta; // delta x and y if direction - always -1,0,1 ray_def ray; // ray chosen if necessary @@ -116,7 +117,7 @@ private: bool do_main_loop(); // Return the location where targetting should start. - coord_def find_default_target() const; + void set_default_target(); void handle_mlist_cycle_command(command_type key_command); void handle_wizard_command(command_type key_command, bool* loop_done); @@ -162,6 +163,7 @@ private: actor* targeted_actor() const; monster* targeted_monster() const; + bool find_default_monster_target(coord_def& result) const; // Functions which print things to the user. // Each one is commented with a sample output. @@ -247,6 +249,8 @@ private: bool show_beam; // Does the user want the beam displayed? bool have_beam; // Is the currently stored beam valid? coord_def objfind_pos, monsfind_pos; // Cycling memory + bool valid_jump; // If jumping, do we currently have a monster + // target with a valid landing position? // What we need to redraw. bool need_beam_redraw; @@ -256,7 +260,6 @@ private: bool show_items_once; // Should we show items this time? bool target_unshifted; // Do unshifted direction keys fire? - // Default behaviour, saved across instances. static targetting_behaviour stock_behaviour; diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 9b3079f..e6e9acb 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -54,7 +54,7 @@ enum ability_type ABIL_BREATHE_MEPHITIC, ABIL_SPIT_ACID, ABIL_BLINK, - + ABIL_JUMP, // Others ABIL_DELAYED_FIREBALL, ABIL_END_TRANSFORMATION, @@ -82,6 +82,7 @@ enum ability_type ABIL_EVOKE_BLINK, ABIL_EVOKE_TURN_INVISIBLE, ABIL_EVOKE_TURN_VISIBLE, + ABIL_EVOKE_JUMP, ABIL_EVOKE_FLIGHT, #if TAG_MAJOR_VERSION == 34 ABIL_EVOKE_STOP_LEVITATING, @@ -1875,6 +1876,7 @@ enum job_type JOB_DEATH_KNIGHT, JOB_ABYSSAL_KNIGHT, JOB_JESTER, + JOB_DRAGOON, NUM_JOBS, // always after the last job JOB_UNKNOWN = 100, @@ -2793,6 +2795,7 @@ enum mutation_type MUT_HEAT_RESISTANCE, MUT_HERBIVOROUS, MUT_HURL_HELLFIRE, + MUT_JUMP, MUT_FAST, MUT_FAST_METABOLISM, MUT_FLEXIBLE_WEAK, @@ -2997,6 +3000,7 @@ enum artefact_prop_type #if TAG_MAJOR_VERSION != 34 ARTP_FOG, #endif + ARTP_JUMP, ARTP_BLINK, ARTP_BERSERK, ARTP_NOISES, @@ -3511,6 +3515,7 @@ enum targetting_type DIR_TARGET, // smite targetting DIR_DIR, // needs a clear line to target DIR_TARGET_OBJECT, // targets items + DIR_JUMP, // a jump target }; enum torment_source_type @@ -3863,9 +3868,10 @@ enum tile_flags ENUM_INT64 //// Background flags - TILE_FLAG_RAY = 0x00010000ULL, - TILE_FLAG_MM_UNSEEN = 0x00020000ULL, - TILE_FLAG_UNSEEN = 0x00040000ULL, + TILE_FLAG_RAY = 0x00010000ULL, + TILE_FLAG_MM_UNSEEN = 0x00020000ULL, + TILE_FLAG_UNSEEN = 0x00040000ULL, + // 3 mutually exclusive flags for cursors. TILE_FLAG_CURSOR1 = 0x00180000ULL, @@ -3873,13 +3879,14 @@ enum tile_flags ENUM_INT64 TILE_FLAG_CURSOR3 = 0x00100000ULL, TILE_FLAG_CURSOR = 0x00180000ULL, - TILE_FLAG_TUT_CURSOR = 0x00200000ULL, - TILE_FLAG_TRAV_EXCL = 0x00400000ULL, - TILE_FLAG_EXCL_CTR = 0x00800000ULL, - TILE_FLAG_RAY_OOR = 0x01000000ULL, - TILE_FLAG_OOR = 0x02000000ULL, - TILE_FLAG_WATER = 0x04000000ULL, - TILE_FLAG_NEW_STAIR = 0x08000000ULL, + TILE_FLAG_TUT_CURSOR = 0x00200000ULL, + TILE_FLAG_TRAV_EXCL = 0x00400000ULL, + TILE_FLAG_EXCL_CTR = 0x00800000ULL, + TILE_FLAG_RAY_OOR = 0x01000000ULL, + TILE_FLAG_OOR = 0x02000000ULL, + TILE_FLAG_WATER = 0x04000000ULL, + TILE_FLAG_NEW_STAIR = 0x08000000ULL, + // Kraken tentacle overlays. TILE_FLAG_KRAKEN_NW = 0x020000000ULL, @@ -3901,6 +3908,10 @@ enum tile_flags ENUM_INT64 //// General + // Should go up with RAY/RAY_OOR, but they need to be exclusive for those + // flags and there's no room. + TILE_FLAG_LANDING = 0x20000000000ULL, + // Mask for the tile index itself. TILE_FLAG_MASK = 0x0000FFFFULL, }; diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index b2a94b7..04e7cee 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -183,6 +183,45 @@ bool fight_melee(actor *attacker, actor *defender, bool *did_hit, bool simu) return true; } +/* Handles jump attack between player and defender. If defender is + * null, we are only doing secondary attacks to additional_targets + */ +bool fight_jump(actor *attacker, actor *defender, coord_def landing_pos, + bool *did_hit) +{ + coord_def current_pos = attacker->pos(); + + ASSERT(!crawl_state.game_is_arena()); + // Can't damage orbs this way. + if (mons_is_projectile(defender->type) && !you.confused()) + { + you.turn_is_over = false; + return false; + } + melee_attack first_attk(attacker, defender, -1, -1, false, true); + + // Check if the player is fighting with something unsuitable, + // or someone unsuitable. + if (you.can_see(defender) + && !wielded_weapon_check(first_attk.weapon)) + { + you.turn_is_over = false; + return false; + } + + move_player_to_grid(landing_pos, false, true); + // Attack was cancelled, so reset position... + if (!first_attk.attack() && first_attk.cancel_attack) + { + you.turn_is_over = false; + move_player_to_grid(current_pos, false, true); + return false; + } + if (did_hit) + *did_hit = first_attk.did_hit; + return true; +} + unchivalric_attack_type is_unchivalric_attack(const actor *attacker, const actor *defender) { @@ -394,7 +433,9 @@ bool wielded_weapon_check(item_def *weapon, bool no_message) return true; } -static bool _cleave_dont_harm(const actor* attacker, const actor* defender) +// Used by cleave and jump attack to determine if multi-hit targets will be +// attacked. +bool dont_harm(const actor* attacker, const actor* defender) { return (mons_aligned(attacker, defender) || attacker == &you && defender->wont_attack() @@ -420,7 +461,7 @@ void get_cleave_targets(const actor* attacker, const coord_def& def, int dir, break; actor * target = actor_at(atk + atk_vector); - if (target && !_cleave_dont_harm(attacker, target)) + if (target && !dont_harm(attacker, target)) targets.push_back(target); } } @@ -446,7 +487,7 @@ void attack_cleave_targets(actor* attacker, list<actor*> &targets, { actor* def = targets.front(); if (attacker->alive() && def && def->alive() - && !_cleave_dont_harm(attacker, def)) + && !dont_harm(attacker, def)) { melee_attack attck(attacker, def, attack_number, ++effective_attack_number, true); diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h index 6c026e9..d789463 100644 --- a/crawl-ref/source/fight.h +++ b/crawl-ref/source/fight.h @@ -29,6 +29,9 @@ enum unchivalric_attack_type bool fight_melee(actor *attacker, actor *defender, bool *did_hit = NULL, bool simu = false); +bool fight_jump(actor *attacker, actor *defender, coord_def landing_pos, + bool *did_hit = NULL); + int resist_adjust_damage(actor *defender, beam_type flavour, int res, int rawdamage, bool ranged = false); @@ -47,5 +50,5 @@ void get_all_cleave_targets(const actor* attacker, const coord_def& def, void attack_cleave_targets(actor* attacker, list<actor*> &targets, int attack_number = 0, int effective_attack_number = 0); - +bool dont_harm(const actor* attacker, const actor* defender); #endif diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc index 19bb80e..489b9f6 100644 --- a/crawl-ref/source/itemname.cc +++ b/crawl-ref/source/itemname.cc @@ -517,6 +517,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return "intelligence"; case SPARM_PONDEROUSNESS: return "ponderousness"; case SPARM_FLYING: return "flying"; + case SPARM_JUMPING: return "jumping"; case SPARM_MAGIC_RESISTANCE: return "magic resistance"; case SPARM_PROTECTION: return "protection"; case SPARM_STEALTH: return "stealth"; @@ -546,6 +547,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return " {Int+3}"; case SPARM_PONDEROUSNESS: return " {ponderous}"; case SPARM_FLYING: return " {Fly}"; + case SPARM_JUMPING: return " {Jump}"; case SPARM_MAGIC_RESISTANCE: return " {MR+}"; case SPARM_PROTECTION: return " {AC+3}"; case SPARM_STEALTH: return " {Stlth+}"; diff --git a/crawl-ref/source/itemprop-enum.h b/crawl-ref/source/itemprop-enum.h index 6a9289c..2a46943 100644 --- a/crawl-ref/source/itemprop-enum.h +++ b/crawl-ref/source/itemprop-enum.h @@ -179,7 +179,6 @@ enum jewellery_type RING_FIRE, RING_ICE, RING_TELEPORT_CONTROL, - NUM_RINGS, // keep as last ring; should not overlap // with amulets! // RINGS after num_rings are for unique types for artefacts @@ -345,6 +344,7 @@ enum special_armour_type SPARM_INTELLIGENCE, SPARM_PONDEROUSNESS, SPARM_FLYING, + SPARM_JUMPING, SPARM_MAGIC_RESISTANCE, SPARM_PROTECTION, SPARM_STEALTH, diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc index ab007ef..02e7d2e 100644 --- a/crawl-ref/source/itemprop.cc +++ b/crawl-ref/source/itemprop.cc @@ -2573,7 +2573,8 @@ bool gives_ability(const item_def &item) return false; const special_armour_type ego = get_armour_ego_type(item); - if (ego == SPARM_DARKNESS || ego == SPARM_FLYING) + if (ego == SPARM_DARKNESS || ego == SPARM_FLYING + || ego == SPARM_JUMPING) return true; break; } diff --git a/crawl-ref/source/jobs.cc b/crawl-ref/source/jobs.cc index 564f396..d9cd6ce 100644 --- a/crawl-ref/source/jobs.cc +++ b/crawl-ref/source/jobs.cc @@ -15,7 +15,7 @@ static const char * Job_Abbrev_List[ NUM_JOBS ] = "St", #endif "Mo", "Wr", "Wn", "Ar", "AM", - "DK", "AK", "Jr" }; + "DK", "AK", "Jr", "Dn" }; static const char * Job_Name_List[ NUM_JOBS ] = { "Fighter", "Wizard", "Priest", @@ -29,7 +29,7 @@ static const char * Job_Name_List[ NUM_JOBS ] = "Stalker", #endif "Monk", "Warper", "Wanderer", "Artificer", "Arcane Marksman", - "Death Knight", "Abyssal Knight", "Jester" }; + "Death Knight", "Abyssal Knight", "Jester", "Dragoon" }; const char *get_job_abbrev(int which_job) { diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc index 7342073..d5f440f 100644 --- a/crawl-ref/source/main.cc +++ b/crawl-ref/source/main.cc @@ -3153,7 +3153,6 @@ static void _player_reacts() } _regenerate_hp_and_mp(capped_time); - recharge_rods(you.time_taken, false); // Reveal adjacent mimics. diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index 128a5de..b54409e 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -2293,6 +2293,7 @@ bool is_armour_brand_ok(int type, int brand, bool strict) // deliberate fall-through case SPARM_RUNNING: case SPARM_STEALTH: + case SPARM_JUMPING: return (slot == EQ_BOOTS); case SPARM_ARCHMAGI: diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index 9cb253f..808a6e3 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -4553,6 +4553,7 @@ static int _str_to_ego(item_spec &spec, string ego_str) "intelligence", "ponderousness", "flying", + "jumping", "magic_resistance", "protection", "stealth", diff --git a/crawl-ref/source/melee_attack.cc b/crawl-ref/source/melee_attack.cc index 542d9df..41c7171 100644 --- a/crawl-ref/source/melee_attack.cc +++ b/crawl-ref/source/melee_attack.cc @@ -106,7 +106,7 @@ static bool _form_uses_xl() */ melee_attack::melee_attack(actor *attk, actor *defn, int attack_num, int effective_attack_num, - bool is_cleaving) + bool is_cleaving, bool is_jump_attack) : // Call attack's constructor ::attack(attk, defn), @@ -114,7 +114,8 @@ melee_attack::melee_attack(actor *attk, actor *defn, effective_attack_number(effective_attack_num), skip_chaos_message(false), special_damage_flavour(BEAM_NONE), stab_attempt(false), stab_bonus(0), cleaving(is_cleaving), - miscast_level(-1), miscast_type(SPTYP_NONE), miscast_target(NULL), + jumping_attack(is_jump_attack), miscast_level(-1), miscast_type(SPTYP_NONE), + miscast_target(NULL), simu(false) { attack_occurred = false; @@ -124,8 +125,8 @@ melee_attack::melee_attack(actor *attk, actor *defn, if (_form_uses_xl()) wpn_skill = SK_FIGHTING; // for stabbing, mostly to_hit = calc_to_hit(); - can_cleave = wpn_skill == SK_AXES && attacker != defender - && !attacker->confused(); + can_cleave = !jumping_attack && wpn_skill == SK_AXES && attacker != defender + && !attacker->confused(); attacker_armour_tohit_penalty = div_rand_round(attacker->armour_tohit_penalty(true, 20), 20); @@ -828,9 +829,10 @@ bool melee_attack::handle_phase_aux() if (attacker->is_player()) { // returns whether an aux attack successfully took place + // additional attacks from cleave don't get aux if (!defender->as_monster()->friendly() && adjacent(defender->pos(), attacker->pos()) - && !cleaving) // additional attacks from cleave don't get aux + && !cleaving) { player_aux_unarmed(); } @@ -1802,7 +1804,8 @@ int melee_attack::player_apply_final_multipliers(int damage) //cleave damage modifier if (cleaving) damage = cleave_damage_mod(damage); - + else if (jumping_attack) + damage = jump_damage_mod(damage); // not additive, statues are supposed to be bad with tiny toothpicks but // deal crushing blows with big weapons if (you.form == TRAN_STATUE) @@ -4047,6 +4050,9 @@ void melee_attack::player_stab_check() break; } + if (stab_bonus && jumping_attack) + stab_bonus = min(6, stab_bonus + 2); + // See if we need to roll against dexterity / stabbing. if (stab_attempt && roll_needed) { @@ -5333,6 +5339,12 @@ int melee_attack::cleave_damage_mod(int dam) return div_rand_round(dam * 3, 4); } +// jump attack modifier: 120% of base damage +int melee_attack::jump_damage_mod(int dam) +{ + return div_rand_round(dam * 6, 5); +} + void melee_attack::chaos_affect_actor(actor *victim) { melee_attack attk(victim, victim); diff --git a/crawl-ref/source/melee_attack.h b/crawl-ref/source/melee_attack.h index bd44533..a21e99f 100644 --- a/crawl-ref/source/melee_attack.h +++ b/crawl-ref/source/melee_attack.h @@ -45,6 +45,7 @@ public: bool can_cleave; list<actor*> cleave_targets; bool cleaving; // additional attack from cleaving + bool jumping_attack; // Miscast to cause after special damage is done. If miscast_level == 0 // the miscast is discarded if special_damage_message isn't empty. @@ -57,7 +58,7 @@ public: public: melee_attack(actor *attacker, actor *defender, int attack_num = -1, int effective_attack_num = -1, - bool is_cleaving = false); + bool is_cleaving = false, bool is_jump_attack = false); // Applies attack damage and other effects. bool attack(); @@ -106,6 +107,8 @@ private: /* Axe cleaving */ void cleave_setup(); int cleave_damage_mod(int dam); + int jump_damage_mod(int dam); + int jump_additional_damage_mod(int dam); /* Mutation Effects */ void do_spines(); diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc index ca223ed..2500f12 100644 --- a/crawl-ref/source/monster.cc +++ b/crawl-ref/source/monster.cc @@ -4775,6 +4775,24 @@ bool monster::can_go_berserk() const return true; } +bool monster::can_jump() const +{ + if (mons_intel(this) == I_PLANT) + return false; + + if (swimming() || airborne()) + return false; + + if (paralysed() || petrified() || petrifying() || asleep()) + return false; + + if (has_ench(ENCH_FATIGUE)) + return false; + + + return true; +} + bool monster::berserk() const { return (has_ench(ENCH_BERSERK) || has_ench(ENCH_INSANE)); diff --git a/crawl-ref/source/monster.h b/crawl-ref/source/monster.h index c2a4ba2..1dc6483 100644 --- a/crawl-ref/source/monster.h +++ b/crawl-ref/source/monster.h @@ -299,6 +299,7 @@ public: void attacking(actor *other); bool can_go_berserk() const; + bool can_jump() const; void go_berserk(bool intentional, bool potion = false); void go_frenzy(); bool berserk() const; diff --git a/crawl-ref/source/mutation-data.h b/crawl-ref/source/mutation-data.h index 622936c..5ddd1c8 100644 --- a/crawl-ref/source/mutation-data.h +++ b/crawl-ref/source/mutation-data.h @@ -414,6 +414,24 @@ "breathe flames" }, +{ MUT_JUMP, 4, 3, false, false, false, + "jump", + + {"You can jump attack at a short distance.", + "You can jump attack at a medium distance.", + "You can jump attack at a long distance."}, + + {"You feel sure on your feet.", + "You feel sure on your feet.", + "You feel sure on your feet."}, + + {"You feel less sure on your feet.", + "You feel less sure on your feet.", + "You feel less sure on your feet."}, + + "jump" +}, + { MUT_BLINK, 3, 3, false, false, false, "blink", diff --git a/crawl-ref/source/mutation.cc b/crawl-ref/source/mutation.cc index c77b937..bf99837 100644 --- a/crawl-ref/source/mutation.cc +++ b/crawl-ref/source/mutation.cc @@ -1656,7 +1656,6 @@ bool mutate(mutation_type which_mutation, const string &reason, bool failMsg, case MUT_HIGH_MAGIC: calc_mp(); break; - case MUT_PASSIVE_MAPPING: add_daction(DACT_REAUTOMAP); break; diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc index daefd44..415744a 100644 --- a/crawl-ref/source/newgame.cc +++ b/crawl-ref/source/newgame.cc @@ -1010,7 +1010,7 @@ static void _construct_backgrounds_menu(const newgame_def* ng, "Warrior", coord_def(0, 0), 15, {JOB_FIGHTER, JOB_GLADIATOR, JOB_MONK, JOB_HUNTER, JOB_ASSASSIN, - JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} + JOB_DRAGOON, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} }, { "Adventurer", @@ -1758,6 +1758,7 @@ static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice, case JOB_WARPER: case JOB_HUNTER: case JOB_ARCANE_MARKSMAN: + case JOB_DRAGOON: break; default: return true; diff --git a/crawl-ref/source/ng-restr.cc b/crawl-ref/source/ng-restr.cc index 930a60c..dd24161 100644 --- a/crawl-ref/source/ng-restr.cc +++ b/crawl-ref/source/ng-restr.cc @@ -553,7 +553,21 @@ char_choice_restriction job_allowed(species_type speci, job_type job) default: return CC_UNRESTRICTED; } - + case JOB_DRAGOON: + switch (speci) + { + case SP_CENTAUR: + case SP_DJINNI: + case SP_OCTOPODE: + case SP_OGRE: + case SP_NAGA: + case SP_SPRIGGAN: + case SP_TENGU: + case SP_TROLL: + return CC_RESTRICTED; + default: + return CC_UNRESTRICTED; + } case JOB_WANDERER: return CC_RESTRICTED; diff --git a/crawl-ref/source/ng-setup.cc b/crawl-ref/source/ng-setup.cc index ba978e4..de1bd73 100644 --- a/crawl-ref/source/ng-setup.cc +++ b/crawl-ref/source/ng-setup.cc @@ -162,6 +162,7 @@ static void _jobs_stat_init(job_type which_job) case JOB_FIGHTER: s = 8; i = 0; d = 4; hp = 15; mp = 0; break; case JOB_BERSERKER: s = 9; i = -1; d = 4; hp = 15; mp = 0; break; case JOB_GLADIATOR: s = 7; i = 0; d = 5; hp = 14; mp = 0; break; + case JOB_DRAGOON: s = 6; i = 0; d = 6; hp = 13; mp = 2; break; case JOB_SKALD: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; case JOB_CHAOS_KNIGHT: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; @@ -343,6 +344,7 @@ void give_basic_mutations(species_type speci) you.mutation[MUT_FAST] = 1; you.mutation[MUT_CARNIVOROUS] = 3; you.mutation[MUT_SLOW_METABOLISM] = 1; + you.mutation[MUT_JUMP] = 1; break; case SP_OCTOPODE: you.mutation[MUT_CAMOUFLAGE] = 1; @@ -566,6 +568,28 @@ static void _give_items_skills(const newgame_def& ng) you.skills[SK_DODGING] = 3; weap_skill = 3; break; + case JOB_DRAGOON: + // Equipment. + newgame_make_item(0, EQ_WEAPON, OBJ_WEAPONS, WPN_SHORT_SWORD); + _update_weapon(ng); + newgame_make_item(1, EQ_BODY_ARMOUR, OBJ_ARMOUR, ARM_LEATHER_ARMOUR, + ARM_ANIMAL_SKIN); + newgame_make_item(2, EQ_BOOTS, OBJ_ARMOUR, ARM_BOOTS); + + you.skills[SK_FIGHTING] = 2; + if (you_can_wear(EQ_BOOTS)) + { + you.inv[2].special = SPARM_JUMPING; + you.skills[SK_EVOCATIONS] = 2; + } + else + { + newgame_make_item(2, EQ_HELMET, OBJ_ARMOUR, ARM_HELMET, ARM_CAP); + you.skills[SK_STEALTH] = 2; + } + you.skills[SK_DODGING] = 3; + weap_skill = 3; + break; case JOB_MONK: you.equip[EQ_WEAPON] = -1; // Monks fight unarmed. diff --git a/crawl-ref/source/player-act.cc b/crawl-ref/source/player-act.cc index d5ea690..dced00d 100644 --- a/crawl-ref/source/player-act.cc +++ b/crawl-ref/source/player-act.cc @@ -159,7 +159,7 @@ bool player::is_habitable_feat(dungeon_feature_type actual_grid) const if (airborne() || species == SP_DJINNI) return true; - if (actual_grid == DNGN_LAVA + if (actual_grid == DNGN_LAVA && species != SP_LAVA_ORC || actual_grid == DNGN_DEEP_WATER && !can_swim()) { return false; @@ -720,6 +720,68 @@ bool player::can_go_berserk(bool intentional, bool potion, bool quiet) const return true; } +bool player::can_jump(bool quiet) const +{ + if (duration[DUR_EXHAUSTED]) + { + if (!quiet) + mpr("You're too exhausted to jump."); + // or else they won't notice -- no message here + return false; + } + + // Stasis, but only for identified amulets; unided amulets will + // trigger when the player attempts to activate jump, + // auto-iding at that point, but also killing the jump and + // wasting a turn. + if (you.stasis(false)) + { + if (!quiet) + { + const item_def *amulet = you.slot_item(EQ_AMULET, false); + mprf("You cannot jump with %s on.", + amulet? amulet->name(DESC_YOUR).c_str() : "your amulet"); + } + return false; + } + if (you.airborne()) + { + if (!quiet) + mpr("You can't jump while in the air."); + return false; + } + if (you.in_water()) + { + if (!quiet) + mpr("You can't jump while in water."); + return false; + } + if (feat_is_lava(grd(you.pos()))) + { + if (!quiet) + mpr("You can't jump while standing in lava."); + return false; + } + if (you.liquefied_ground()) + { + if (!quiet) + mpr("You can't jump while stuck in this mess."); + return false; + } + if (you.is_constricted()) + { + if (!quiet) + mpr("You can't jump while being constricted."); + return false; + } + + return true; +} + +bool player::can_jump() const +{ + return can_jump(false); +} bool player::berserk() const { return (duration[DUR_BERSERK]); diff --git a/crawl-ref/source/player-equip.cc b/crawl-ref/source/player-equip.cc index 7749b7a..ce99ca5 100644 --- a/crawl-ref/source/player-equip.cc +++ b/crawl-ref/source/player-equip.cc @@ -315,6 +315,17 @@ static void _equip_artefact_effect(item_def &item, bool *show_msgs, bool unmeld) mpr("You feel a build-up of mutagenic energy."); artefact_wpn_learn_prop(item, ARTP_MUTAGENIC); } + if (unknown_proprt(ARTP_JUMP)) + { + if (msg) + { + if (player_jump_level(true) > player_jump_level(false)) + mpr("You feel more sure on your feet."); + else + mpr("Your feet feel light for a moment."); + } + artefact_wpn_learn_prop(item, ARTP_JUMP); + } if (!unmeld && !item.cursed() && proprt[ARTP_CURSED] > 0 && one_chance_in(proprt[ARTP_CURSED])) @@ -929,6 +940,11 @@ static void _equip_armour_effect(item_def& arm, bool unmeld) mpr("You feel rather light."); break; + case SPARM_JUMPING: + if (you.evokable_jump()) + mpr("You feel more sure on your feet."); + break; + case SPARM_MAGIC_RESISTANCE: mpr("You feel resistant to hostile enchantments."); break; diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index c9cae77..be5f1fe 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -127,7 +127,8 @@ static void _moveto_maybe_repel_stairs() } } -static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) +static bool _check_moveto_cloud(const coord_def& p, const string &move_verb, + bool interactive = true) { const int cloud = env.cgrid(p); if (cloud != EMPTY_CLOUD && !you.confused()) @@ -151,22 +152,29 @@ static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) return true; } - string prompt = make_stringf("Really %s into that cloud of %s?", - move_verb.c_str(), - cloud_name_at_index(cloud).c_str()); - learned_something_new(HINT_CLOUD_WARNING); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { + string prompt = make_stringf("Really %s into that cloud of %s?", + move_verb.c_str(), + cloud_name_at_index(cloud).c_str()); + learned_something_new(HINT_CLOUD_WARNING); + + if (!yesno(prompt.c_str(), false, 'n')) + { canned_msg(MSG_OK); return false; + } + } else + { + return false; } } } return true; } -static bool _check_moveto_trap(const coord_def& p, const string &move_verb) + static bool _check_moveto_trap(const coord_def& p, const string &move_verb, + bool interactive = true) { // If there's no trap, let's go. trap_def* trap = find_trap(p); @@ -175,28 +183,40 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) if (trap->type == TRAP_ZOT && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Do you really want to %s into the Zot trap", - move_verb.c_str()); - - if (!yes_or_no("%s", prompt.c_str())) + if (interactive) + { + string prompt = make_stringf("Do you really want to %s into the Zot trap", + move_verb.c_str()); + if (!yes_or_no("%s", prompt.c_str())) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } else if (!trap->is_safe() && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Really %s %s that %s?", - move_verb.c_str(), - (trap->type == TRAP_ALARM || trap->type == TRAP_PLATE) ? "onto" - : "into", - feature_description_at(p, false, DESC_BASENAME, false).c_str()); - - if (!yesno(prompt.c_str(), true, 'n')) + if (interactive) + { + string prompt = make_stringf( + "Really %s %s that %s?", + move_verb.c_str(), + (trap->type == TRAP_ALARM + || trap->type == TRAP_PLATE) ? "onto" + : "into", + feature_description_at(p, false, + DESC_BASENAME, + false).c_str()); + if (!yesno(prompt.c_str(), true, 'n')) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } @@ -204,7 +224,7 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) } static bool _check_moveto_dangerous(const coord_def& p, const string& msg, - bool cling = true) + bool cling = true, bool interactive = true) { if (you.can_swim() && feat_is_water(env.grid(p)) || you.airborne() || cling && you.can_cling_to(p) @@ -213,28 +233,30 @@ static bool _check_moveto_dangerous(const coord_def& p, const string& msg, return true; } - if (msg != "") - mpr(msg.c_str()); - else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) - mpr("You cannot swim in your current form."); - else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) - && is_feat_dangerous(env.grid(p))) + if (interactive) { - mpr("You cannot enter lava in your current form."); + if (msg != "") + mpr(msg.c_str()); + else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) + mpr("You cannot swim in your current form."); + else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) + && is_feat_dangerous(env.grid(p))) + { + mpr("You cannot enter lava in your current form."); + } + else + canned_msg(MSG_UNTHINKING_ACT); } - else - canned_msg(MSG_UNTHINKING_ACT); - return false; } static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive = true) { if (you.is_wall_clinging() && (move_verb == "blink" || move_verb == "passwall")) { - return _check_moveto_dangerous(p, msg, false); + return _check_moveto_dangerous(p, msg, false, interactive); } if (!need_expiration_warning() && need_expiration_warning(p) @@ -243,60 +265,69 @@ static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, if (!_check_moveto_dangerous(p, msg)) return false; - string prompt; + if (interactive) + { + string prompt; - if (msg != "") - prompt = msg + " "; + if (msg != "") + prompt = msg + " "; - prompt += "Are you sure you want to " + move_verb; + prompt += "Are you sure you want to " + move_verb; - if (you.ground_level()) - prompt += " into "; - else - prompt += " over "; + if (you.ground_level()) + prompt += " into "; + else + prompt += " over "; - prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; + prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; - prompt += need_expiration_warning(DUR_FLIGHT, p) - ? " while you are losing your buoyancy?" - : " while your transformation is expiring?"; + prompt += need_expiration_warning(DUR_FLIGHT, p) + ? " while you are losing your buoyancy?" + : " while your transformation is expiring?"; - if (!yesno(prompt.c_str(), false, 'n')) - { - canned_msg(MSG_OK); - return false; + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } - return _check_moveto_dangerous(p, msg); + return _check_moveto_dangerous(p, msg, true, interactive); } -static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb) +static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb, + bool interactive = true) { + string prompt; + if (is_excluded(p) && !is_stair_exclusion(p) && !is_excluded(you.pos()) && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf("Really %s into a travel-excluded area?", - move_verb.c_str()); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { - canned_msg(MSG_OK); - return false; + prompt = make_stringf("Really %s into a travel-excluded area?", + move_verb.c_str()); + + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } return true; } bool check_moveto(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive) { - return (_check_moveto_terrain(p, move_verb, msg) - && _check_moveto_cloud(p, move_verb) - && _check_moveto_trap(p, move_verb) - && _check_moveto_exclusion(p, move_verb)); + return (_check_moveto_terrain(p, move_verb, msg, interactive) + && _check_moveto_cloud(p, move_verb, interactive) + && _check_moveto_trap(p, move_verb, interactive) + && _check_moveto_exclusion(p, move_verb, interactive)); } static void _splash() @@ -530,6 +561,22 @@ bool player_genus(genus_type which_genus, species_type species) return (species_genus(species) == which_genus); } +int player_jump_level(bool is_evoke) +{ + int level = 0; + + if (is_evoke) + { + level = 3 + (you.skill(SK_EVOCATIONS, 1) >= 5) + + (you.skill(SK_EVOCATIONS, 1) >= 10); + } + else + { + level = 2 + player_mutation_level(MUT_JUMP); + } + return(level); +} + // If transform is true, compare with current transformation instead // of (or in addition to) underlying species. // (See mon-data.h for species/genus use.) @@ -3595,8 +3642,10 @@ void level_change(int source, const char* aux, bool skip_attribute_increase) } if (you.experience_level == 6 || you.experience_level == 12) + { perma_mutate(MUT_SHAGGY_FUR, 1, "growing up"); - + perma_mutate(MUT_JUMP, 1, "growing up"); + } _felid_extra_life(); break; @@ -4673,6 +4722,7 @@ bool enough_mp(int minimum, bool suppress_msg, bool include_items) return true; } + bool enough_zp(int minimum, bool suppress_msg) { ASSERT(!crawl_state.game_is_arena()); @@ -4834,6 +4884,7 @@ void set_mp(int new_amount) you.redraw_magic_points = true; } + // If trans is true, being berserk and/or transformed is taken into account // here. Else, the base hp is calculated. If rotted is true, calculate the // real max hp you'd have if the rotting was cured. diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h index dddf445..a62d7f9 100644 --- a/crawl-ref/source/player.h +++ b/crawl-ref/source/player.h @@ -562,6 +562,8 @@ public: bool can_go_berserk() const; bool can_go_berserk(bool intentional, bool potion = false, bool quiet = false) const; + bool can_jump() const; + bool can_jump(bool quiet) const; void go_berserk(bool intentional, bool potion = false); bool berserk() const; bool has_lifeforce() const; @@ -791,7 +793,7 @@ void moveto_location_effects(dungeon_feature_type old_feat, const coord_def& old_pos=coord_def()); bool check_moveto(const coord_def& p, const string &move_verb = "step", - const string &msg = ""); + const string &msg = "", bool interactive = true); void move_player_to_grid(const coord_def& p, bool stepped, bool allow_shift); bool is_map_persistent(void); @@ -814,6 +816,7 @@ bool is_effectively_light_armour(const item_def *item); bool player_effectively_in_light_armour(); bool player_under_penance(void); +int player_jump_level(bool is_evoke); int burden_change(void); diff --git a/crawl-ref/source/rltiles/dc-abilities.txt b/crawl-ref/source/rltiles/dc-abilities.txt index fcbf3cc..1557f28 100644 --- a/crawl-ref/source/rltiles/dc-abilities.txt +++ b/crawl-ref/source/rltiles/dc-abilities.txt @@ -18,9 +18,11 @@ evoke_fog ABILITY_EVOKE_FOG evoke_invisibility_end ABILITY_EVOKE_INVISIBILITY_END evoke_invisibility ABILITY_EVOKE_INVISIBILITY flight ABILITY_EVOKE_FLIGHT +jump ABILITY_EVOKE_JUMP evoke_teleport ABILITY_EVOKE_TELEPORT flight_end ABILITY_FLIGHT_END flight ABILITY_FLIGHT +jump ABILITY_JUMP hellfire ABILITY_HELLFIRE mummy_restoration ABILITY_MUMMY_RESTORATION recharge ABILITY_RECHARGE diff --git a/crawl-ref/source/rltiles/dc-feat.txt b/crawl-ref/source/rltiles/dc-feat.txt index d8ee583..3a9f38a 100644 --- a/crawl-ref/source/rltiles/dc-feat.txt +++ b/crawl-ref/source/rltiles/dc-feat.txt @@ -721,6 +721,7 @@ effect/disjunct0 DISJUNCT effect/disjunct1 effect/disjunct2 effect/disjunct3 +landing LANDING effect/heataura0 HEAT_AURA effect/heataura1 effect/heataura2 diff --git a/crawl-ref/source/rltiles/dc-item.txt b/crawl-ref/source/rltiles/dc-item.txt index 1180806..e9b48d5 100644 --- a/crawl-ref/source/rltiles/dc-item.txt +++ b/crawl-ref/source/rltiles/dc-item.txt @@ -606,6 +606,7 @@ i-dexterity BRAND_ARM_DEXTERITY i-intelligence BRAND_ARM_INTELLIGENCE i-ponderous BRAND_ARM_PONDEROUSNESS i-flying BRAND_ARM_FLYING +i-jumping BRAND_ARM_JUMPING i-magic-res BRAND_ARM_MAGIC_RESISTANCE i-protection BRAND_ARM_PROTECTION %rim 0 diff --git a/crawl-ref/source/rltiles/gui/abilities/jump.png b/crawl-ref/source/rltiles/gui/abilities/jump.png new file mode 100644 index 0000000000000000000000000000000000000000..bb80d7233abf4dd4c155c8f598f9a6dfbd06c963 GIT binary patch literal 957 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VB8tt6XFWwQVZ18)v>a&BAaGv zYU=0b2NE&p;^pNP6ciK^5>j+IrlFyssi|pXWMpGwV{dQoarBP2w|7ubP;hW?czAeJ zR8(ACTtY%ZQc_Y%N=kZqdRA6ec6N4tetvOraaC1Sb#--3O%2ex`uh5YhK8o5rk0kL z*4Eaxwzl^6_U`WP-rnB6zP|qc{s|K%Oqw)l%9JTnr%s(QW5&FB^XAW=zhJ?FB}<kp zUAlDn^5v^mty;5Y&DynV*R5N(apT6#n>TOSvSsVmt=qP3+rEAKjvYI8?%cU+*RFm0 z_U+%l|G<F*2M-=Rbm-9G!-tO?Idb&q(PPJs9Y22j#EBCpPo6w=>eT7er_Y=@bN1}n z^XJcBxNzaprAwDDU%q<v>a}avu3x`?>(;H?w{PFMbLZ~eyZ7$hyMO=wg9i^DKYsk= z$&;r~pFVr`?D_NOFJ8QO_3G8@*RS8adGr4L`;Q+#e){z3>({T}zkmPv^XH#GfBydc z`|sbs|Ns9_`8hEd7_f>ZL4Lsuk~&7_>Gcg$X3Si;dH;!1r_Wxwe)Imb_n$uf`1Kp4 ze##S0b6|{5@^o<wskpUvVtDW&2NAdMK#^+Z>YZUBZ}x`2*}Hhcf+nE^U5&^iT@gy( z{v?UtV@j;}c*gGS&$Rv4<rnueZrsC{^dn04$On^&bEiq(eci-)*t0N1-b%r)Ny*DG zcSTCvpLO~ZI9s?h9_;uh^fy(I(d$Bxnas4i^Z6%aa&FusBrs>%=Xk-CXoYpBK9oKE zy!&2jP1Vvf><lj#2}w_n={woH{Hryu(=55?3Gvz<aSoMp?WX4zUi=)|Q?$L~chUwv z6=Q|ehdS@KewXi>ut0Tc%Nf@*lj1%maI%!y6$T0y&i|~u>O(_X(&xHkS&v-qoDu$> zs@b(*LF%8I&*uuczM840+U3Gotsgi2ddvB~n~b897C*W6_*+6w^7U1sMRsx89M`gY z3>WirYiyd-Jom#Z@jQd45&S{x@5;PlYzRFxKWXz^W8=4%7zE}8y!evFe!7t7a7lZu zb<a0H>CSdj*<8!DSMq@-&zLlYm;1??UFY8#PcfWu+o|%of%KJQGwWVG6_P5=EOl<* l$9qLXtLsY7CYdk&e;BiK3wkGX7}<d`iKnZd%Q~loCIG90%-a9} literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png b/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png new file mode 100644 index 0000000000000000000000000000000000000000..006c2a1b1ae0395eef6e8266ee3398fd6e7203b5 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^S0D`piY~`Ij^1(5J%0<x zVJr#q3ubV5b|VeQarSg^43W5;oFH-M0Hc?2T!Yb#?GBPf`3rkq-4}4}dH>x}WaIj1 z_P`_i>g}g|{QFznpxM;KgeB{ocnPy0&kT`l4rYemO~SlSy5p=sHhH@GxvX<aXaWEM C-Y|Or literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/misc/landing.png b/crawl-ref/source/rltiles/misc/landing.png new file mode 100644 index 0000000000000000000000000000000000000000..17d5001279d4e1bcfcb74b824f12a9be9f7265a9 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJd7dtgArYK!L!!A3DDVW|-1z-% zYdOPquBi_$l=46KPr1;QD-=AlKRV{_5mB|G^enCg%q0zc7aAYLelo5<-aVna<4Irs zg;%kg_AqL`66L$}tcyYQN3@4r<D_I4^Lst)g#z)PCo@+yeB@%uHr)PYCJV=khDe6c zhOOHynOIjTGhJVNaPzM9TlecSG|m%9+W+L4;FaF^x0*mJ7_A)G%TzoiFFxqs`3mS# N22WQ%mvv4FO#uI5RRRD2 literal 0 HcmV?d00001 diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc index 92c604a..cc22a8b 100644 --- a/crawl-ref/source/shopping.cc +++ b/crawl-ref/source/shopping.cc @@ -1140,6 +1140,9 @@ int artefact_value(const item_def &item) if (prop[ ARTP_BERSERK ]) ret += 5; + if (prop[ ARTP_JUMP ]) + ret += 5; + if (prop[ ARTP_INVISIBLE ]) ret += 20; @@ -1704,6 +1707,7 @@ unsigned int item_value(item_def item, bool ident) case SPARM_SEE_INVISIBLE: case SPARM_INTELLIGENCE: case SPARM_FLYING: + case SPARM_JUMPING: case SPARM_PRESERVATION: case SPARM_STEALTH: case SPARM_STRENGTH: diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc index c9f555b..c9b5236 100644 --- a/crawl-ref/source/target.cc +++ b/crawl-ref/source/target.cc @@ -11,6 +11,7 @@ #include "itemprop.h" #include "libutil.h" #include "losglobal.h" +#include "player.h" #include "spl-damage.h" #include "terrain.h" @@ -52,6 +53,11 @@ bool targetter::anyone_there(coord_def loc) return actor_at(loc); } +bool targetter::has_additional_sites(coord_def loc, bool allow_harmful) +{ + return false; +} + targetter_beam::targetter_beam(const actor *act, int range, zap_type zap, int pow, int min_ex_rad, int max_ex_rad) : min_expl_rad(min_ex_rad), @@ -875,3 +881,230 @@ aff_type targetter_spray::is_affected(coord_def loc) return affected; } + +targetter_jump::targetter_jump(const actor* act, int range) +{ + ASSERT(act); + agent = act; + origin = act->pos(); + range2 = dist_range(range); + jump_is_blocked = false; +} + +bool targetter_jump::valid_aim(coord_def a) +{ + actor *act; + bool is_valid = true; + coord_def c, jump_pos; + ray_def ray; + + act = actor_at(a); + if ((origin - a).abs() > range2) + { + is_valid = notify_fail("Out of range."); + } + // If there's an actor we can see, check that we have at least one valid + // landing site for a jump attack based on what the agent can see. + else if (act && agent->can_see(act)) + { + if(!has_additional_sites(a, true)) + { + if (no_landing_reason == BLOCKED_FLYING) + is_valid = notify_fail("A flying creature is in the way."); + else if (no_landing_reason == BLOCKED_GIANT) + is_valid = notify_fail("A giant creature is in the way."); + else if (no_landing_reason == BLOCKED_MOVE) + is_valid = notify_fail("There is no safe place to move near the" + " monster."); + else if (no_landing_reason == BLOCKED_PATH) + is_valid = notify_fail("A dungeon feature is in the way."); + } + return is_valid; + } + else if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS)) + { + if (agent->see_cell(a)) + is_valid = notify_fail("There's something in the way."); + else + is_valid = notify_fail("You cannot see that place."); + } + else if (feat_is_solid(grd(a))) + { + is_valid = notify_fail("There's something in the way."); + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + is_valid = notify_fail("A dungeon feature is in the way"); + return is_valid; + } + return is_valid; +} + +bool targetter_jump::valid_landing(coord_def a, bool check_invis) +{ + actor *act; + ray_def ray; + + if (grd(a) == DNGN_OPEN_SEA || grd(a) == DNGN_LAVA_SEA + || !agent->is_habitable(a) || (origin - a).abs() > range2 - 1) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + if (agent->is_player()) + { + monster* beholder = you.get_beholder(a); + if (beholder) + { + return false; + blocked_landing_reason = BLOCKED_MOVE; + } + + monster* fearmonger = you.get_fearmonger(a); + if (fearmonger) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + blocked_landing_reason = BLOCKED_PATH; + return false; + } + // Check if a landing site is invalid due to a visible monster obstructing + // the path. + ray.advance(); + while(map_bounds(ray.pos())) + { + act = actor_at(ray.pos()); + if (ray.pos() == a) + { + if (act && (!check_invis || agent->can_see(act))) + { + blocked_landing_reason = BLOCKED_OCCUPIED; + return false; + } + break; + } + const dungeon_feature_type grid = grd(ray.pos()); + if (act && (!check_invis || agent->can_see(act))) + { + + // Can't jump over airborn enemies nor gient enemies not in deep + // water or lava. + if (act->airborne()) + { + blocked_landing_reason = BLOCKED_FLYING; + return false; + } + else if(act->body_size() == SIZE_GIANT + && grid != DNGN_DEEP_WATER && grid != DNGN_LAVA) + { + blocked_landing_reason = BLOCKED_GIANT; + return false; + } + } + ray.advance(); + } + return true; +} + +aff_type targetter_jump::is_affected(coord_def loc) +{ + aff_type aff = AFF_NO; + + if (loc == aim) + aff = AFF_YES; + else if (additional_sites.count(loc)) + aff = AFF_LANDING; + return aff; +} + +// Handle setting the aim for jump, which is the landing position of the jump. +// If something unsee either occupies the aim positiion or blocks the jump path, +// indicate that with jump_is_blocked. +bool targetter_jump::set_aim(coord_def a) +{ + actor *act; + ray_def ray; + set<coord_def>::const_iterator site; + + if (a == origin) + return false; + if (!targetter::set_aim(a)) + return false; + + jump_is_blocked = false; + act = actor_at(aim); + // Can't set aim if we can't see anything at the location. + if (!env.map_knowledge(aim).invisible_monster() + && (!act || !agent->can_see(act))) + return false; + // Find our set of landing sites, choose one at random, and see if it's + // actually blocked. + set_additional_sites(aim); + if (additional_sites.size()) + { + int site_ind = random2(additional_sites.size()); + for (site = additional_sites.begin(); site_ind > 0; site++) + site_ind--; + landing_site = *site; + if (!valid_landing(landing_site, false)) + jump_is_blocked = true; + return true; + } + return false; +} + +void targetter_jump::set_additional_sites(coord_def a) +{ + get_additional_sites(a); + additional_sites = temp_sites; +} + +void targetter_jump::get_additional_sites(coord_def a) +{ + bool agent_adjacent = a.distance_from(agent->pos()) == 1; + temp_sites.clear(); + + no_landing_reason = BLOCKED_NONE; + for (adjacent_iterator ai(a, false); ai; ++ai) + { + // See if site is valid, record a putative reason for why no sites were + // found. A flying or giant monster blocking the landing site gets + // priority as an reason, since it's very jump-specific. + if (!agent_adjacent || agent->pos().distance_from(*ai) > 1) + { + if (valid_landing(*ai)) + { + temp_sites.insert(*ai); + no_landing_reason = BLOCKED_NONE; + } + else if (no_landing_reason != BLOCKED_FLYING + && no_landing_reason != BLOCKED_GIANT) + { + no_landing_reason = blocked_landing_reason; + } + } + } +} + +// See if we can find at least one valid landing position for the +// given monster. Possibly we also care to exclude sites harmful to +// the player. +bool targetter_jump::has_additional_sites(coord_def a, bool allow_harmful) +{ + set<coord_def>::const_iterator site; + get_additional_sites(a); + // Find a valid jump_attack position in the adjacent squares, chosing the + // valid position closest to the player. + for (site = temp_sites.begin(); + site != temp_sites.end(); site++) + { + if (allow_harmful || (agent->is_player() + && check_moveto(*site, "jump", "", false))) + return true; + } + return false; +} diff --git a/crawl-ref/source/target.h b/crawl-ref/source/target.h index 0ec6882..d2aa800 100644 --- a/crawl-ref/source/target.h +++ b/crawl-ref/source/target.h @@ -12,6 +12,7 @@ enum aff_type // sign and non-zeroness matters AFF_YES, // intended/likely to affect // If you want to extend this to pass the probability somehow, feel free to, // just keep AFF_YES the minimal "bright" value. + AFF_LANDING, // Valid jump attack landing site }; class targetter @@ -30,6 +31,7 @@ public: virtual bool can_affect_walls(); virtual aff_type is_affected(coord_def loc) = 0; + virtual bool has_additional_sites(coord_def a, bool allow_harmful); protected: bool anyone_there(coord_def loc); }; @@ -188,4 +190,39 @@ private: int range2; }; +enum jump_block_reason +{ + BLOCKED_NONE, + BLOCKED_OCCUPIED, + BLOCKED_FLYING, + BLOCKED_GIANT, + BLOCKED_MOVE, + BLOCKED_PATH, +}; + +class targetter_jump : public targetter +{ +public: + targetter_jump(const actor* act, int range); + + bool valid_aim(coord_def a); + bool set_aim(coord_def a); + bool jump_is_blocked; + aff_type is_affected(coord_def loc); + bool has_additional_sites(coord_def a, bool allow_harmful); + set<coord_def> additional_sites; + coord_def landing_site; +private: + void set_additional_sites(coord_def a); + void get_additional_sites(coord_def a); + bool valid_landing(coord_def a, bool check_invis = true); + jump_block_reason no_landing_reason; + jump_block_reason blocked_landing_reason; + set<coord_def> temp_sites; + int _range; + int range2; +}; + + + #endif diff --git a/crawl-ref/source/tiledgnbuf.cc b/crawl-ref/source/tiledgnbuf.cc index cf4483f..de57a9e 100644 --- a/crawl-ref/source/tiledgnbuf.cc +++ b/crawl-ref/source/tiledgnbuf.cc @@ -288,6 +288,8 @@ void DungeonCellBuffer::pack_background(int x, int y, const packed_cell &cell) m_buf_feat.add(TILE_RAY, x, y); else if (bg & TILE_FLAG_RAY_OOR) m_buf_feat.add(TILE_RAY_OUT_OF_RANGE, x, y); + else if (bg & TILE_FLAG_LANDING) + m_buf_feat.add(TILE_LANDING, x, y); } } diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index 2f9b867..764d543 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -5149,6 +5149,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_SPIT_ACID; case ABIL_BLINK: return TILEG_ABILITY_BLINK; + case ABIL_JUMP: + return TILEG_ABILITY_JUMP; // Others case ABIL_DELAYED_FIREBALL: @@ -5192,6 +5194,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_EVOKE_INVISIBILITY_END; case ABIL_EVOKE_FLIGHT: return TILEG_ABILITY_EVOKE_FLIGHT; + case ABIL_EVOKE_JUMP: + return TILEG_ABILITY_EVOKE_JUMP; case ABIL_EVOKE_FOG: return TILEG_ABILITY_EVOKE_FOG; diff --git a/crawl-ref/source/tileview.cc b/crawl-ref/source/tileview.cc index 5718aef..9c898e1 100644 --- a/crawl-ref/source/tileview.cc +++ b/crawl-ref/source/tileview.cc @@ -966,10 +966,16 @@ void tile_place_ray(const coord_def &gc, aff_type in_range) void tile_draw_rays(bool reset_count) { + tileidx_t flag; + for (unsigned int i = 0; i < num_tile_rays; i++) { - tileidx_t flag = tile_ray_vec[i].in_range > AFF_MAYBE ? TILE_FLAG_RAY - : TILE_FLAG_RAY_OOR; + if (tile_ray_vec[i].in_range < AFF_YES) + flag = TILE_FLAG_RAY_OOR; + else if (tile_ray_vec[i].in_range == AFF_YES) + flag = TILE_FLAG_RAY; + else if (tile_ray_vec[i].in_range == AFF_LANDING) + flag = TILE_FLAG_LANDING; env.tile_bg(tile_ray_vec[i].ep) |= flag; } diff --git a/crawl-ref/source/webserver/game_data/static/cell_renderer.js b/crawl-ref/source/webserver/game_data/static/cell_renderer.js index 73df042..c3a2b97 100644 --- a/crawl-ref/source/webserver/game_data/static/cell_renderer.js +++ b/crawl-ref/source/webserver/game_data/static/cell_renderer.js @@ -662,6 +662,9 @@ function ($, view_data, main, tileinfo_player, icons, dngn, enums, map_knowledge this.draw_dngn(dngn.RAY, x, y); else if (bg.RAY_OOR) this.draw_dngn(dngn.RAY_OUT_OF_RANGE, x, y); + else if (bg.LANDING) + this.draw_dngn(dngn.LANDING, x, y); + } }, diff --git a/crawl-ref/source/webserver/game_data/static/enums.js b/crawl-ref/source/webserver/game_data/static/enums.js index 18ec2d9..8164238 100644 --- a/crawl-ref/source/webserver/game_data/static/enums.js +++ b/crawl-ref/source/webserver/game_data/static/enums.js @@ -30,7 +30,7 @@ define(function () { exports.CURSOR_MOUSE = 0; exports.CURSOR_TUTORIAL = 1; exports.CURSOR_MAP = 2; - exports.CURSOR_MAX = 3; + exports.CURSOR_MAX = 4; // Halo flags exports.HALO_NONE = 0; @@ -214,6 +214,7 @@ define(function () { bg_flags.flags.RAY = 0x00010000; bg_flags.flags.MM_UNSEEN = 0x00020000; bg_flags.flags.UNSEEN = 0x00040000; + bg_flags.exclusive_flags.push({ mask : 0x00180000, CURSOR1 : 0x00180000, @@ -228,6 +229,7 @@ define(function () { bg_flags.flags.WATER = 0x04000000; bg_flags.flags.NEW_STAIR = 0x08000000; + // Kraken tentacle overlays. bg_flags.flags.KRAKEN_NW = 0x20000000; bg_flags.flags.KRAKEN_NE = 0x40000000; @@ -239,7 +241,7 @@ define(function () { bg_flags.flags.ELDRITCH_NE = [0, 0x04]; bg_flags.flags.ELDRITCH_SE = [0, 0x08]; bg_flags.flags.ELDRITCH_SW = [0, 0x10]; - + bg_flags.flags.LANDING = [0, 0x200]; bg_flags.mask = 0x0000FFFF; exports.prepare_fg_flags = function (tileidx) diff --git a/crawl-ref/source/wiz-item.cc b/crawl-ref/source/wiz-item.cc index ffef8ee..085d915 100644 --- a/crawl-ref/source/wiz-item.cc +++ b/crawl-ref/source/wiz-item.cc @@ -262,6 +262,7 @@ static const char* _prop_name[] = { "SInv", "Inv", "Fly", + "Jump", "Blnk", "Bers", "Nois", @@ -305,6 +306,7 @@ static int8_t _prop_type[] = { ARTP_VAL_BOOL, //EYESIGHT ARTP_VAL_BOOL, //INVISIBLE ARTP_VAL_BOOL, //FLIGHT + ARTP_VAL_BOOL, //JUMPING ARTP_VAL_BOOL, //BLINK ARTP_VAL_BOOL, //BERSERK ARTP_VAL_POS, //NOISES @@ -1166,6 +1168,7 @@ static void _debug_acquirement_stats(FILE *ostat) "intelligence", "ponderous", "flight", + "jumping", "magic reistance", "protection", "stealth", diff --git a/crawl-ref/source/wiz-you.cc b/crawl-ref/source/wiz-you.cc index 096893e..03b26f4 100644 --- a/crawl-ref/source/wiz-you.cc +++ b/crawl-ref/source/wiz-you.cc @@ -284,6 +284,7 @@ void wizard_heal(bool super_heal) you.duration[DUR_CONF] = 0; you.duration[DUR_MISLED] = 0; you.duration[DUR_POISONING] = 0; + you.duration[DUR_EXHAUSTED] = 0; set_hp(you.hp_max); set_mp(you.max_magic_points); set_hunger(10999, true); -- 1.7.4.4 ![]() From dea14c3a59c5ea75287535c83143a30fcd9f0702 Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Wed, 12 Jun 2013 21:08:07 -0500 Subject: [PATCH] Reworked Jump attack (version 2) Changes based on feedback from dev. team. * You can jump attack an enemy or invisible monster marker only; no standalone 'cblink' jump. * The landing site is randomly chosen from the valid sites near the monster, so no more double-targetting. * Damage increase is greatly reduced, from 200% to 20%. * Species that can't wear boots are restricted from Dragoon, get an unbranded helmet/cap if they still choose the class. * Starting attack range increased from 2 to 3, up to a max of 5 instead of 4. The movement range is still one less than the attack range. * No more landing cursor or alternate landing site tiles, since we don't need them. --- crawl-ref/source/abl-show.cc | 103 +++++++- crawl-ref/source/actor.cc | 10 + crawl-ref/source/actor.h | 2 + crawl-ref/source/dat/descript/ability.txt | 17 ++- crawl-ref/source/dat/descript/backgrounds.txt | 5 + crawl-ref/source/describe.cc | 6 + crawl-ref/source/directn.cc | 261 +++++++++++++------- crawl-ref/source/directn.h | 7 +- crawl-ref/source/enum.h | 11 +- crawl-ref/source/fight.cc | 47 ++++- crawl-ref/source/fight.h | 5 +- crawl-ref/source/itemname.cc | 2 + crawl-ref/source/itemprop-enum.h | 2 +- crawl-ref/source/itemprop.cc | 3 +- crawl-ref/source/jobs.cc | 4 +- crawl-ref/source/main.cc | 1 - crawl-ref/source/makeitem.cc | 1 + crawl-ref/source/mapdef.cc | 1 + crawl-ref/source/melee_attack.cc | 24 ++- crawl-ref/source/melee_attack.h | 5 +- crawl-ref/source/monster.cc | 18 ++ crawl-ref/source/monster.h | 1 + crawl-ref/source/mutation-data.h | 18 ++ crawl-ref/source/mutation.cc | 1 - crawl-ref/source/newgame.cc | 3 +- crawl-ref/source/ng-restr.cc | 16 ++- crawl-ref/source/ng-setup.cc | 24 ++ crawl-ref/source/player-act.cc | 64 +++++- crawl-ref/source/player-equip.cc | 16 ++ crawl-ref/source/player.cc | 183 +++++++++----- crawl-ref/source/player.h | 5 +- crawl-ref/source/rltiles/dc-abilities.txt | 2 + crawl-ref/source/rltiles/dc-feat.txt | 1 + crawl-ref/source/rltiles/dc-item.txt | 1 + crawl-ref/source/rltiles/gui/abilities/jump.png | Bin 0 -> 957 bytes .../rltiles/item/armour/brands/i-jumping.png | Bin 0 -> 161 bytes crawl-ref/source/rltiles/misc/landing.png | Bin 0 -> 215 bytes crawl-ref/source/shopping.cc | 4 + crawl-ref/source/target.cc | 233 +++++++++++++++++ crawl-ref/source/target.h | 37 +++ crawl-ref/source/tiledgnbuf.cc | 2 + crawl-ref/source/tilepick.cc | 4 + crawl-ref/source/tileview.cc | 10 +- .../webserver/game_data/static/cell_renderer.js | 3 + .../source/webserver/game_data/static/enums.js | 2 +- crawl-ref/source/wiz-item.cc | 3 + crawl-ref/source/wiz-you.cc | 1 + 47 files changed, 983 insertions(+), 186 deletions(-) create mode 100644 crawl-ref/source/rltiles/gui/abilities/jump.png create mode 100644 crawl-ref/source/rltiles/item/armour/brands/i-jumping.png create mode 100644 crawl-ref/source/rltiles/misc/landing.png diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc index 62cee08..a52e208 100644 --- a/crawl-ref/source/abl-show.cc +++ b/crawl-ref/source/abl-show.cc @@ -40,6 +40,7 @@ #include "evoke.h" #include "macro.h" #include "maps.h" +#include "melee_attack.h" #include "message.h" #include "menu.h" #include "misc.h" @@ -109,6 +110,7 @@ static void _pay_ability_costs(const ability_def& abil, int zpcost); static int _scale_piety_cost(ability_type abil, int original_cost); static string _zd_mons_description_for_ability(const ability_def &abil); static monster_type _monster_for_ability(const ability_def& abil); +static bool _jump_player(int jump_range); /** * This all needs to be split into data/util/show files @@ -223,6 +225,7 @@ static const ability_def Ability_List[] = { ABIL_FLY, "Fly", 3, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_STOP_FLYING, "Stop Flying", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_JUMP, "Jump Attack", 0, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_HELLFIRE, "Hellfire", 0, 150, 200, 0, 0, ABFLAG_NONE}, { ABIL_DELAYED_FIREBALL, "Release Delayed Fireball", @@ -248,6 +251,7 @@ static const ability_def Ability_List[] = { ABIL_EVOKE_TURN_INVISIBLE, "Evoke Invisibility", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TURN_VISIBLE, "Turn Visible", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_EVOKE_JUMP, "Evoke Jump Attack", 2, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_EVOKE_FLIGHT, "Evoke Flight", 1, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_FOG, "Evoke Fog", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TELEPORT_CONTROL, "Evoke Teleport Control", 4, 0, 200, 0, 0, ABFLAG_NONE}, @@ -921,7 +925,6 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - case ABIL_BREATHE_FROST: case ABIL_BREATHE_POISON: case ABIL_SPIT_ACID: @@ -941,7 +944,12 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - + // copied from spit poison + case ABIL_JUMP: + failure = ((you.species == SP_FELID) ? 20 : 40) + - 10 * player_mutation_level(MUT_JUMP) + - you.experience_level; + break; case ABIL_FLY: failure = 45 - (3 * you.experience_level); break; @@ -995,7 +1003,9 @@ talent get_talent(ability_type ability, bool check_confused) case ABIL_EVOKE_BLINK: failure = 40 - you.skill(SK_EVOCATIONS, 2); break; - + case ABIL_EVOKE_JUMP: + failure = 30 - you.skill(SK_EVOCATIONS, 2); + break; case ABIL_EVOKE_BERSERK: case ABIL_EVOKE_FOG: case ABIL_EVOKE_TELEPORT_CONTROL: @@ -1240,6 +1250,10 @@ void no_ability_msg() if (you.flight_mode()) mpr("You're already flying!"); } + else if (you.species == SP_FELID && you.airborne()) + { + mpr("You can't jump while flying."); + } else if (silenced(you.pos()) && you.religion != GOD_NO_GOD) { // At the very least the player has "Renounce Religion", but @@ -1340,6 +1354,7 @@ static bool _check_ability_possible(const ability_def& abil, bool hungerCheck = true, bool quiet = false) { + if (silenced(you.pos()) && you.religion != GOD_NEMELEX_XOBEH) { talent tal = get_talent(abil.ability, false); @@ -1573,6 +1588,13 @@ bool activate_talent(const talent& tal) return false; } + if ((tal.which == ABIL_EVOKE_JUMP || tal.which == ABIL_JUMP) + && !you.can_jump()) + { + crawl_state.zero_turns_taken(); + return false; + } + if ((tal.which == ABIL_EVOKE_FLIGHT || tal.which == ABIL_TRAN_BAT) && you.liquefied_ground()) { @@ -1883,7 +1905,15 @@ static bool _do_ability(const ability_def& abil) break; } - + case ABIL_JUMP: + { + int jump_level = player_jump_level(false); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.experience_level)); + break; + } case ABIL_RECHARGING: if (recharge_wand() <= 0) return false; // fail message is already given @@ -2138,7 +2168,15 @@ static bool _do_ability(const ability_def& abil) else fly_player(you.skill(SK_EVOCATIONS, 2) + 30); break; - + case ABIL_EVOKE_JUMP: + { + int jump_level = player_jump_level(true); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.skill(SK_EVOCATIONS, 1))); + break; + } case ABIL_EVOKE_FOG: // cloak of the Thief mpr("With a swish of your cloak, you release a cloud of fog."); big_cloud(random_smoke_type(), &you, you.pos(), 50, 8 + random2(8)); @@ -3080,7 +3118,11 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) if (you.species == SP_DEEP_DWARF) _add_talent(talents, ABIL_RECHARGING, check_confused); - + if (player_mutation_level(MUT_JUMP) + && !you.evokable_jump() && !you.airborne()) + { + _add_talent(talents, ABIL_JUMP, check_confused); + } // Spit Poison. Nontransformed nagas can upgrade to Breathe Poison. // Transformed nagas, or non-nagas, can only get Spit Poison. if (you.species == SP_NAGA @@ -3092,8 +3134,7 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } else if (player_mutation_level(MUT_SPIT_POISON) || you.species == SP_NAGA) - { - _add_talent(talents, ABIL_SPIT_POISON, check_confused); + { _add_talent(talents, ABIL_SPIT_POISON, check_confused); } if (player_genus(GENPC_DRACONIAN)) @@ -3229,6 +3270,9 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } } + if (you.evokable_jump() && !you.airborne()) + _add_talent(talents, ABIL_EVOKE_JUMP, check_confused); + if (you.wearing(EQ_RINGS, RING_TELEPORTATION) && !crawl_state.game_is_sprint()) { @@ -3497,3 +3541,46 @@ int scaling_cost::cost(int max) const { return (value < 0) ? (-value) : ((value * max + 500) / 1000); } + + +bool _jump_player(int jump_range) +{ + coord_def landing; + direction_chooser_args args; + targetter_jump tgt(&you, jump_range); + dist jdirect; + + args.restricts = DIR_JUMP; + args.mode = TARG_HOSTILE; + args.just_looking = false; + args.needs_path = true; + args.may_target_monster = true; + args.may_target_self = false; + args.target_prefix = NULL; + args.top_prompt = "Aiming: <white>Jump Attack</white>"; + args.behaviour = NULL; + args.cancel_at_self = true; + args.hitfunc = &tgt; + args.range = jump_range; + direction(jdirect, args); + if (!jdirect.isValid) + { + // Check for user cancel. + canned_msg(MSG_OK); + return false; + } + + if (!check_moveto(jdirect.target, "jump")) + return false; + // Our jump path was blocked by an invisible monster, so waste a turn. + if (tgt.jump_is_blocked) + { + mpr("You rebound off of something unseen!"); + } + else if (!fight_jump(&you, actor_at(jdirect.target), + tgt.landing_site)) + { + return false; + } + return true; +} diff --git a/crawl-ref/source/actor.cc b/crawl-ref/source/actor.cc index a612e2a..ec82979 100644 --- a/crawl-ref/source/actor.cc +++ b/crawl-ref/source/actor.cc @@ -386,6 +386,16 @@ int actor::evokable_flight(bool calc_unid) const + scan_artefacts(ARTP_FLY, calc_unid); } +// Return an int so we know whether an item is the sole source. +int actor::evokable_jump(bool calc_unid) const +{ + if (suppressed() + || (is_player() && player_jump_level(true) <= player_jump_level(false))) + return 0; + return wearing_ego(EQ_ALL_ARMOUR, SPARM_JUMPING, calc_unid) + + scan_artefacts(ARTP_JUMP, calc_unid); +} + int actor::spirit_shield(bool calc_unid, bool items) const { int ss = 0; diff --git a/crawl-ref/source/actor.h b/crawl-ref/source/actor.h index 59b0973..3bd977b 100644 --- a/crawl-ref/source/actor.h +++ b/crawl-ref/source/actor.h @@ -171,6 +171,7 @@ public: virtual bool can_see_invisible() const = 0; virtual bool invisible() const = 0; virtual bool nightvision() const = 0; + virtual bool can_jump() const = 0; // Would looker be able to see the actor when in LOS? virtual bool visible_to(const actor *looker) const = 0; @@ -316,6 +317,7 @@ public: // Return an int so we know whether an item is the sole source. virtual int evokable_flight(bool calc_unid = true) const; + virtual int evokable_jump(bool calc_unid = true) const; virtual int spirit_shield(bool calc_unid = true, bool items = true) const; virtual flight_type flight_mode() const = 0; diff --git a/crawl-ref/source/dat/descript/ability.txt b/crawl-ref/source/dat/descript/ability.txt index bfa125d..75d1484 100644 --- a/crawl-ref/source/dat/descript/ability.txt +++ b/crawl-ref/source/dat/descript/ability.txt @@ -74,7 +74,17 @@ Start flying. During flight you can safely cross water and similar obstacles. Be warned, though, that flight may time out at inopportune moments and cause you to fall to your death. %%%% -Hellfire +Jump Attack + +Jump attack at limited distance, covering ground with great speed. You may +target an enemy to perform a jumping attack with increased damage and landing in +some position next to your target. You cannot jump over flying enemies nor +giant enemies not submerged in deep water or lava. Flying, standing or swimming +in liquids, or the use of stasis will also prevent you from using this ability. +Felids have an innate ability to jump attack that increases in range at +experience levels 6 and 12. + +%%%% Hellfire Blast your enemies with hellfire. %%%% @@ -123,6 +133,11 @@ Evoke Flight <Fly> %%%% +Evoke Jump Attack + +<Jump Attack> +%%%% + Stop Flying Stop flying. diff --git a/crawl-ref/source/dat/descript/backgrounds.txt b/crawl-ref/source/dat/descript/backgrounds.txt index 446e640..cc1f17a 100644 --- a/crawl-ref/source/dat/descript/backgrounds.txt +++ b/crawl-ref/source/dat/descript/backgrounds.txt @@ -43,6 +43,11 @@ Death Knight Death Knights are melee fighters who command the undead in the name of Yredelemnul the Dark. %%%% +Dragoon + +Dragoons harness the power of magical armour that allows them to +launch a devestating jumping attack on multiple foes. +%%%% Earth Elementalist Earth Elementalists know the Sandblast spell, and carry stones to increase the diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index 01dfa25..5b2db64 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -224,6 +224,7 @@ static vector<string> _randart_propnames(const item_def& item, { "+Inv", ARTP_INVISIBLE, 2 }, { "+Fly", ARTP_FLY, 2 }, { "+Fog", ARTP_FOG, 2 }, + { "+Jump", ARTP_JUMP, 2 }, // Resists, also really important { "rElec", ARTP_ELECTRICITY, 2 }, @@ -418,6 +419,7 @@ static string _randart_descrip(const item_def &item) { ARTP_EYESIGHT, "It enhances your eyesight.", false}, { ARTP_INVISIBLE, "It lets you turn invisible.", false}, { ARTP_FLY, "It lets you fly.", false}, + { ARTP_JUMP, "It lets perform a jumping attack.", false}, { ARTP_BLINK, "It lets you blink.", false}, { ARTP_BERSERK, "It lets you go berserk.", false}, { ARTP_NOISES, "It makes noises.", false}, @@ -1324,6 +1326,10 @@ static string _describe_armour(const item_def &item, bool verbose) description += "It can be activated to allow its wearer to " "fly indefinitely."; break; + case SPARM_JUMPING: + description += "It can be activated to allow its wearer to " + "perform a jumping attack."; + break; case SPARM_MAGIC_RESISTANCE: description += "It increases its wearer's resistance " "to enchantments."; diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index 4e2713b..dc7c5f3 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -109,6 +109,8 @@ static bool _find_feature(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); static bool _find_fprop_unoccupied(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc); #ifndef USE_TILE_LOCAL static bool _find_mlist(const coord_def& where, int mode, bool need_path, @@ -175,8 +177,8 @@ static void _wizard_make_friendly(monster* m) #endif dist::dist() - : isValid(false), isTarget(false), isEndpoint(false), - isCancel(true), choseRay(false), target(), delta(), ray() + : isValid(false), isTarget(false), isEndpoint(false), isCancel(true), + choseRay(false), target(), delta(), ray() { } @@ -379,6 +381,9 @@ void direction_chooser::print_key_hints() const case DIR_DIR: case DIR_TARGET_OBJECT: break; + case DIR_JUMP: + prompt += hint_string; + break; } } @@ -513,6 +518,7 @@ direction_chooser::direction_chooser(dist& moves_, show_items_once = false; target_unshifted = Options.target_unshifted_dirs; + } class view_desc_proc @@ -1013,89 +1019,107 @@ static void _update_mlist(bool enable) } #endif -// Find a good square to start targetting from. -coord_def direction_chooser::find_default_target() const +// Try to find an enemy monster to target +bool direction_chooser::find_default_monster_target(coord_def& result) const { - coord_def result = you.pos(); bool success = false; - - if (restricts == DIR_TARGET_OBJECT) + int search_range = range; + bool (*find_targ)(const coord_def&, int, bool, int, targetter*); + + // First try to pick our previous target. + const monster* mons_target = get_current_target(); + if (mons_target != NULL + && (mode != TARG_EVOLVABLE_PLANTS + && mons_attitude(mons_target) == ATT_HOSTILE + || mode == TARG_ENEMY && !mons_target->friendly() + || mode == TARG_EVOLVABLE_PLANTS + && mons_is_evolvable(mons_target) + || mode == TARG_HOSTILE_UNDEAD && !mons_target->friendly() + && mons_target->holiness() == MH_UNDEAD + || mode == TARG_INJURED_FRIEND + && (mons_target->friendly() && mons_get_damage_level(mons_target) > MDAM_OKAY + || (!mons_target->wont_attack() + && !mons_target->neutral() + && is_pacifiable(mons_target) >= 0))) + && in_range(mons_target->pos())) { - // Try to find an object. - success = _find_square_wrapper(result, 1, _find_object, - needs_path, TARG_ANY, range, hitfunc, - true, LS_FLIPVH); + success = true; + result = mons_target->pos(); } - else if (mode == TARG_ENEMY || mode == TARG_HOSTILE - || mode == TARG_HOSTILE_SUBMERGED - || mode == TARG_EVOLVABLE_PLANTS - || mode == TARG_HOSTILE_UNDEAD - || mode == TARG_INJURED_FRIEND) + if (!success) { - // Try to find an enemy monster. - - // First try to pick our previous target. - const monster* mon_target = get_current_target(); - if (mon_target != NULL - && (mode != TARG_EVOLVABLE_PLANTS - && mons_attitude(mon_target) == ATT_HOSTILE - || mode == TARG_ENEMY && !mon_target->friendly() - || mode == TARG_EVOLVABLE_PLANTS - && mons_is_evolvable(mon_target) - || mode == TARG_HOSTILE_UNDEAD && !mon_target->friendly() - && mon_target->holiness() == MH_UNDEAD - || mode == TARG_INJURED_FRIEND - && (mon_target->friendly() && mons_get_damage_level(mon_target) > MDAM_OKAY - || (!mon_target->wont_attack() - && !mon_target->neutral() - && is_pacifiable(mon_target) >= 0))) - && in_range(mon_target->pos())) + // Have to increase search_range by one so monsters out of range but + // with landing sites in-range are found. + if (restricts == DIR_JUMP) { - result = mon_target->pos(); - success = true; + find_targ = _find_jump_attack_mons; + search_range += 1; } else { - // The previous target is no good. Try to find one from scratch. - success = _find_square_wrapper(result, 1, _find_monster, + find_targ = _find_monster; + } + // The previous target is no good. Try to find one from scratch. + success = _find_square_wrapper(result, 1, find_targ, needs_path, mode, + search_range, hitfunc, true); + + // We might be able to hit monsters in LOS that are outside of + // normal range, but inside explosion/cloud range + if (!success && hitfunc && restricts != DIR_JUMP + && (you.current_vision > range || hitfunc->can_affect_walls())) + { + success = _find_square_wrapper(result, 1, _find_monster_expl, needs_path, mode, range, hitfunc, true); + } - // We might be able to hit monsters in LOS that are outside of - // normal range, but inside explosion/cloud range - if (!success - && hitfunc && hitfunc->can_affect_outside_range() - && (you.current_vision > range || hitfunc->can_affect_walls())) - { - success = _find_square_wrapper(result, 1, _find_monster_expl, - needs_path, mode, range, hitfunc, - true); - } - // If we couldn't, maybe it was because of line-of-fire issues. - // Check if that's happening, and inform the user (because it's - // pretty confusing.) - if (!success - && needs_path - && _find_square_wrapper(result, 1, _find_monster, - false, mode, range, hitfunc, true)) + // If we couldn't, maybe it was because of line-of-fire issues. + // Check if that's happening, and inform the user (because it's + // pretty confusing.) + if (!success && needs_path + && _find_square_wrapper(result, 1, find_targ, false, mode, + search_range, hitfunc, true)) + { + // Special colouring in tutorial or hints mode. + const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; + mpr("All monsters which could be auto-targeted are covered by " + "a wall or statue which interrupts your line of fire, even " + "though it doesn't interrupt your line of sight.", + need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); + + if (need_hint) { - // Special colouring in tutorial or hints mode. - const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; - mpr("All monsters which could be auto-targeted are covered by " - "a wall or statue which interrupts your line of fire, even " - "though it doesn't interrupt your line of sight.", - need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); - - if (need_hint) - { mpr("To return to the main mode, press <w>Escape</w>.", MSGCH_TUTORIAL); Hints.hints_events[HINT_TARGET_NO_FOE] = false; - } } } } + return success; +} + +// Find a good square to start targetting from. +void direction_chooser::set_default_target() +{ + coord_def result = you.pos(); + bool success = false; + + if (restricts == DIR_TARGET_OBJECT) + { + // Try to find an object. + success = _find_square_wrapper(result, 1, _find_object, + needs_path, TARG_ANY, range, hitfunc, + true, LS_FLIPVH); + } + else if (mode == TARG_ENEMY || mode == TARG_HOSTILE + || mode == TARG_HOSTILE_SUBMERGED + || mode == TARG_EVOLVABLE_PLANTS + || mode == TARG_HOSTILE_UNDEAD + || mode == TARG_INJURED_FRIEND) + { + success = find_default_monster_target(result); + } // Evolution can also auto-target mold squares (but shouldn't if // there are any monsters to evolve), so try _find_square_wrapper // again @@ -1108,8 +1132,7 @@ coord_def direction_chooser::find_default_target() const if (!success) result = you.pos(); - - return result; + set_target(result); } const coord_def& direction_chooser::target() const @@ -1119,6 +1142,20 @@ const coord_def& direction_chooser::target() const void direction_chooser::set_target(const coord_def& new_target) { + coord_def jump_pos; + set<coord_def>::const_iterator site; + monster *mons; + + if (restricts == DIR_JUMP) + { + mons = monster_at(new_target); + if (((mons && you.can_see(mons)) + || env.map_knowledge(new_target).invisible_monster()) + && hitfunc->has_additional_sites(new_target, true)) + valid_jump = true; + else + valid_jump = false; + } moves.target = new_target; } @@ -1161,11 +1198,22 @@ void direction_chooser::draw_beam_if_needed() #ifndef USE_TILE_LOCAL int bcol = BLACK; if (aff < 0) + { bcol = DARKGREY; + } else if (aff < AFF_YES) + { bcol = (*ri == target()) ? RED : MAGENTA; - else + } + else if (aff == AFF_YES) + { bcol = (*ri == target()) ? LIGHTRED : LIGHTMAGENTA; + } + // Jump attack landing sites + else + { + bcol = (*ri == target()) ? LIGHTGREEN : GREEN; + } _draw_ray_glyph(*ri, bcol, '*', bcol | COLFLAG_REVERSE); #endif } @@ -1283,15 +1331,23 @@ void direction_chooser::update_previous_target() const bool direction_chooser::select(bool allow_out_of_range, bool endpoint) { + const monster* mons = monster_at(target()); + + if (restricts == DIR_JUMP && !valid_jump) + { + if (mons && you.can_see(mons)) + mpr("The monster cannot be jump-attacked there."); + else + mpr("There is no monster to jump-attack there!"); + return false; + } if (!allow_out_of_range && !in_range(target())) { mpr(hitfunc? hitfunc->why_not : "That is beyond the maximum range.", MSGCH_EXAMINE_FILTER); return false; } - - const monster* m = monster_at(target()); - moves.isEndpoint = endpoint || (m && _mon_exposed(m)); + moves.isEndpoint = endpoint || (mons && _mon_exposed(mons)); moves.isValid = true; moves.isTarget = true; update_previous_target(); @@ -1627,9 +1683,12 @@ void direction_chooser::handle_movement_key(command_type key_command, const coord_def& delta = Compass[compass_idx]; const bool unshifted = (shift_direction(key_command) != key_command); if (unshifted) + { set_target(target() + delta); - else + } else + { *loop_done = select_compass_direction(delta); + } } } @@ -1983,7 +2042,7 @@ bool direction_chooser::do_main_loop() } // Redraw whatever is necessary. - if (old_target != target()) + if (restricts == DIR_JUMP || old_target != target()) { have_beam = show_beam && find_ray(you.pos(), target(), beam, opc_solid_see, BDS_DEFAULT); @@ -2035,8 +2094,11 @@ bool direction_chooser::choose_direction() // init moves.delta.reset(); - // Find a default target. - set_target(Options.default_target ? find_default_target() : you.pos()); + // Set a default target. + if (Options.default_target) + set_default_target(); + else + set_target(you.pos()); objfind_pos = monsfind_pos = target(); // If requested, show the beam on startup. @@ -2410,14 +2472,14 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, if ((mode == TARG_FRIEND || mode == TARG_ANY) && where == you.pos()) return true; - // Don't target out of range. - if (!_is_target_in_range(where, range, hitfunc)) + // Don't target out of range + if (hitfunc && !_is_target_in_range(where, range, hitfunc)) return false; const monster* mon = monster_at(where); // No monster or outside LOS. - if (mon == NULL || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) + if (!mon || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) return false; // Monster in LOS but only via glass walls, so no direct path. @@ -2433,9 +2495,39 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, return _want_target_monster(mon, mode); } +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc) +{ +#ifdef CLUA_BINDINGS + { + coord_def dp = grid2player(where); + // We could pass more info here. + maybe_bool x = clua.callmbooleanfn("ch_target_jump", "dd", + dp.x, dp.y); + if (x != MB_MAYBE) + return tobool(x); + } +#endif + + // Need a monster to attack; this checks that the monster is a valid target. + if (!_find_monster(where, mode, need_path, range, hitfunc)) + return false; + // Can't jump on yourself + if (where == you.pos()) + return false; + + return hitfunc->has_additional_sites(where, false); +} + + + + static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc) { + const monster* mons; + coord_def jump_pos; + ASSERT(hitfunc); #ifdef CLUA_BINDINGS @@ -2466,16 +2558,17 @@ static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, return false; if (hitfunc->set_aim(where)) + { for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri) { - if (hitfunc->is_affected(*ri) >= AFF_YES) + mons = monster_at(*ri); + if (mons && hitfunc->is_affected(*ri) == AFF_YES) { - const monster* mon = monster_at(*ri); - if (mon && _mons_is_valid_target(mon, mode, range)) - return _want_target_monster(mon, mode); + if (_mons_is_valid_target(mons, mode, range)) + return _want_target_monster(mons, mode); } } - + } return false; } diff --git a/crawl-ref/source/directn.h b/crawl-ref/source/directn.h index e5e5a0c..412fa27 100644 --- a/crawl-ref/source/directn.h +++ b/crawl-ref/source/directn.h @@ -66,6 +66,7 @@ public: bool isEndpoint; // Does the player want the attack to stop at target? bool isCancel; // user cancelled (usually <ESC> key) bool choseRay; // user wants a specific beam + coord_def target; // target x,y or logical extension of beam to map edge coord_def delta; // delta x and y if direction - always -1,0,1 ray_def ray; // ray chosen if necessary @@ -116,7 +117,7 @@ private: bool do_main_loop(); // Return the location where targetting should start. - coord_def find_default_target() const; + void set_default_target(); void handle_mlist_cycle_command(command_type key_command); void handle_wizard_command(command_type key_command, bool* loop_done); @@ -162,6 +163,7 @@ private: actor* targeted_actor() const; monster* targeted_monster() const; + bool find_default_monster_target(coord_def& result) const; // Functions which print things to the user. // Each one is commented with a sample output. @@ -247,6 +249,8 @@ private: bool show_beam; // Does the user want the beam displayed? bool have_beam; // Is the currently stored beam valid? coord_def objfind_pos, monsfind_pos; // Cycling memory + bool valid_jump; // If jumping, do we currently have a monster + // target with a valid landing position? // What we need to redraw. bool need_beam_redraw; @@ -256,7 +260,6 @@ private: bool show_items_once; // Should we show items this time? bool target_unshifted; // Do unshifted direction keys fire? - // Default behaviour, saved across instances. static targetting_behaviour stock_behaviour; diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 9b3079f..710607f 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -54,7 +54,7 @@ enum ability_type ABIL_BREATHE_MEPHITIC, ABIL_SPIT_ACID, ABIL_BLINK, - + ABIL_JUMP, // Others ABIL_DELAYED_FIREBALL, ABIL_END_TRANSFORMATION, @@ -82,6 +82,7 @@ enum ability_type ABIL_EVOKE_BLINK, ABIL_EVOKE_TURN_INVISIBLE, ABIL_EVOKE_TURN_VISIBLE, + ABIL_EVOKE_JUMP, ABIL_EVOKE_FLIGHT, #if TAG_MAJOR_VERSION == 34 ABIL_EVOKE_STOP_LEVITATING, @@ -1875,6 +1876,7 @@ enum job_type JOB_DEATH_KNIGHT, JOB_ABYSSAL_KNIGHT, JOB_JESTER, + JOB_DRAGOON, NUM_JOBS, // always after the last job JOB_UNKNOWN = 100, @@ -2793,6 +2795,7 @@ enum mutation_type MUT_HEAT_RESISTANCE, MUT_HERBIVOROUS, MUT_HURL_HELLFIRE, + MUT_JUMP, MUT_FAST, MUT_FAST_METABOLISM, MUT_FLEXIBLE_WEAK, @@ -2997,6 +3000,7 @@ enum artefact_prop_type #if TAG_MAJOR_VERSION != 34 ARTP_FOG, #endif + ARTP_JUMP, ARTP_BLINK, ARTP_BERSERK, ARTP_NOISES, @@ -3511,6 +3515,7 @@ enum targetting_type DIR_TARGET, // smite targetting DIR_DIR, // needs a clear line to target DIR_TARGET_OBJECT, // targets items + DIR_JUMP, // a jump target }; enum torment_source_type @@ -3901,6 +3906,10 @@ enum tile_flags ENUM_INT64 //// General + // Should go up with RAY/RAY_OOR, but they need to be exclusive for those + // flags and there's no room. + TILE_FLAG_LANDING = 0x20000000000ULL, + // Mask for the tile index itself. TILE_FLAG_MASK = 0x0000FFFFULL, }; diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index b2a94b7..74ba75f 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -183,6 +183,45 @@ bool fight_melee(actor *attacker, actor *defender, bool *did_hit, bool simu) return true; } +/* Handles jump attack between player and defender. If defender is + * null, we are only doing secondary attacks to additional_targets + */ +bool fight_jump(actor *attacker, actor *defender, coord_def landing_pos, + bool *did_hit) +{ + coord_def current_pos = attacker->pos(); + + ASSERT(!crawl_state.game_is_arena()); + // Can't damage orbs this way. + if (mons_is_projectile(defender->type) && !you.confused()) + { + you.turn_is_over = false; + return false; + } + melee_attack first_attk(attacker, defender, -1, -1, false, true); + + // Check if the player is fighting with something unsuitable, + // or someone unsuitable. + if (you.can_see(defender) + && !wielded_weapon_check(first_attk.weapon)) + { + you.turn_is_over = false; + return false; + } + + move_player_to_grid(landing_pos, false, true); + // Attack was cancelled, so reset position... + if (!first_attk.attack() && first_attk.cancel_attack) + { + you.turn_is_over = false; + move_player_to_grid(current_pos, false, true); + return false; + } + if (did_hit) + *did_hit = first_attk.did_hit; + return true; +} + unchivalric_attack_type is_unchivalric_attack(const actor *attacker, const actor *defender) { @@ -394,7 +433,9 @@ bool wielded_weapon_check(item_def *weapon, bool no_message) return true; } -static bool _cleave_dont_harm(const actor* attacker, const actor* defender) +// Used by cleave and jump attack to determine if multi-hit targets will be +// attacked. +bool dont_harm(const actor* attacker, const actor* defender) { return (mons_aligned(attacker, defender) || attacker == &you && defender->wont_attack() @@ -420,7 +461,7 @@ void get_cleave_targets(const actor* attacker, const coord_def& def, int dir, break; actor * target = actor_at(atk + atk_vector); - if (target && !_cleave_dont_harm(attacker, target)) + if (target && !dont_harm(attacker, target)) targets.push_back(target); } } @@ -446,7 +487,7 @@ void attack_cleave_targets(actor* attacker, list<actor*> &targets, { actor* def = targets.front(); if (attacker->alive() && def && def->alive() - && !_cleave_dont_harm(attacker, def)) + && !dont_harm(attacker, def)) { melee_attack attck(attacker, def, attack_number, ++effective_attack_number, true); diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h index 6c026e9..d789463 100644 --- a/crawl-ref/source/fight.h +++ b/crawl-ref/source/fight.h @@ -29,6 +29,9 @@ enum unchivalric_attack_type bool fight_melee(actor *attacker, actor *defender, bool *did_hit = NULL, bool simu = false); +bool fight_jump(actor *attacker, actor *defender, coord_def landing_pos, + bool *did_hit = NULL); + int resist_adjust_damage(actor *defender, beam_type flavour, int res, int rawdamage, bool ranged = false); @@ -47,5 +50,5 @@ void get_all_cleave_targets(const actor* attacker, const coord_def& def, void attack_cleave_targets(actor* attacker, list<actor*> &targets, int attack_number = 0, int effective_attack_number = 0); - +bool dont_harm(const actor* attacker, const actor* defender); #endif diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc index 19bb80e..489b9f6 100644 --- a/crawl-ref/source/itemname.cc +++ b/crawl-ref/source/itemname.cc @@ -517,6 +517,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return "intelligence"; case SPARM_PONDEROUSNESS: return "ponderousness"; case SPARM_FLYING: return "flying"; + case SPARM_JUMPING: return "jumping"; case SPARM_MAGIC_RESISTANCE: return "magic resistance"; case SPARM_PROTECTION: return "protection"; case SPARM_STEALTH: return "stealth"; @@ -546,6 +547,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return " {Int+3}"; case SPARM_PONDEROUSNESS: return " {ponderous}"; case SPARM_FLYING: return " {Fly}"; + case SPARM_JUMPING: return " {Jump}"; case SPARM_MAGIC_RESISTANCE: return " {MR+}"; case SPARM_PROTECTION: return " {AC+3}"; case SPARM_STEALTH: return " {Stlth+}"; diff --git a/crawl-ref/source/itemprop-enum.h b/crawl-ref/source/itemprop-enum.h index 6a9289c..2a46943 100644 --- a/crawl-ref/source/itemprop-enum.h +++ b/crawl-ref/source/itemprop-enum.h @@ -179,7 +179,6 @@ enum jewellery_type RING_FIRE, RING_ICE, RING_TELEPORT_CONTROL, - NUM_RINGS, // keep as last ring; should not overlap // with amulets! // RINGS after num_rings are for unique types for artefacts @@ -345,6 +344,7 @@ enum special_armour_type SPARM_INTELLIGENCE, SPARM_PONDEROUSNESS, SPARM_FLYING, + SPARM_JUMPING, SPARM_MAGIC_RESISTANCE, SPARM_PROTECTION, SPARM_STEALTH, diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc index ab007ef..02e7d2e 100644 --- a/crawl-ref/source/itemprop.cc +++ b/crawl-ref/source/itemprop.cc @@ -2573,7 +2573,8 @@ bool gives_ability(const item_def &item) return false; const special_armour_type ego = get_armour_ego_type(item); - if (ego == SPARM_DARKNESS || ego == SPARM_FLYING) + if (ego == SPARM_DARKNESS || ego == SPARM_FLYING + || ego == SPARM_JUMPING) return true; break; } diff --git a/crawl-ref/source/jobs.cc b/crawl-ref/source/jobs.cc index 564f396..d9cd6ce 100644 --- a/crawl-ref/source/jobs.cc +++ b/crawl-ref/source/jobs.cc @@ -15,7 +15,7 @@ static const char * Job_Abbrev_List[ NUM_JOBS ] = "St", #endif "Mo", "Wr", "Wn", "Ar", "AM", - "DK", "AK", "Jr" }; + "DK", "AK", "Jr", "Dn" }; static const char * Job_Name_List[ NUM_JOBS ] = { "Fighter", "Wizard", "Priest", @@ -29,7 +29,7 @@ static const char * Job_Name_List[ NUM_JOBS ] = "Stalker", #endif "Monk", "Warper", "Wanderer", "Artificer", "Arcane Marksman", - "Death Knight", "Abyssal Knight", "Jester" }; + "Death Knight", "Abyssal Knight", "Jester", "Dragoon" }; const char *get_job_abbrev(int which_job) { diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc index 7342073..d5f440f 100644 --- a/crawl-ref/source/main.cc +++ b/crawl-ref/source/main.cc @@ -3153,7 +3153,6 @@ static void _player_reacts() } _regenerate_hp_and_mp(capped_time); - recharge_rods(you.time_taken, false); // Reveal adjacent mimics. diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index 128a5de..b54409e 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -2293,6 +2293,7 @@ bool is_armour_brand_ok(int type, int brand, bool strict) // deliberate fall-through case SPARM_RUNNING: case SPARM_STEALTH: + case SPARM_JUMPING: return (slot == EQ_BOOTS); case SPARM_ARCHMAGI: diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index 9cb253f..808a6e3 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -4553,6 +4553,7 @@ static int _str_to_ego(item_spec &spec, string ego_str) "intelligence", "ponderousness", "flying", + "jumping", "magic_resistance", "protection", "stealth", diff --git a/crawl-ref/source/melee_attack.cc b/crawl-ref/source/melee_attack.cc index 542d9df..41c7171 100644 --- a/crawl-ref/source/melee_attack.cc +++ b/crawl-ref/source/melee_attack.cc @@ -106,7 +106,7 @@ static bool _form_uses_xl() */ melee_attack::melee_attack(actor *attk, actor *defn, int attack_num, int effective_attack_num, - bool is_cleaving) + bool is_cleaving, bool is_jump_attack) : // Call attack's constructor ::attack(attk, defn), @@ -114,7 +114,8 @@ melee_attack::melee_attack(actor *attk, actor *defn, effective_attack_number(effective_attack_num), skip_chaos_message(false), special_damage_flavour(BEAM_NONE), stab_attempt(false), stab_bonus(0), cleaving(is_cleaving), - miscast_level(-1), miscast_type(SPTYP_NONE), miscast_target(NULL), + jumping_attack(is_jump_attack), miscast_level(-1), miscast_type(SPTYP_NONE), + miscast_target(NULL), simu(false) { attack_occurred = false; @@ -124,8 +125,8 @@ melee_attack::melee_attack(actor *attk, actor *defn, if (_form_uses_xl()) wpn_skill = SK_FIGHTING; // for stabbing, mostly to_hit = calc_to_hit(); - can_cleave = wpn_skill == SK_AXES && attacker != defender - && !attacker->confused(); + can_cleave = !jumping_attack && wpn_skill == SK_AXES && attacker != defender + && !attacker->confused(); attacker_armour_tohit_penalty = div_rand_round(attacker->armour_tohit_penalty(true, 20), 20); @@ -828,9 +829,10 @@ bool melee_attack::handle_phase_aux() if (attacker->is_player()) { // returns whether an aux attack successfully took place + // additional attacks from cleave don't get aux if (!defender->as_monster()->friendly() && adjacent(defender->pos(), attacker->pos()) - && !cleaving) // additional attacks from cleave don't get aux + && !cleaving) { player_aux_unarmed(); } @@ -1802,7 +1804,8 @@ int melee_attack::player_apply_final_multipliers(int damage) //cleave damage modifier if (cleaving) damage = cleave_damage_mod(damage); - + else if (jumping_attack) + damage = jump_damage_mod(damage); // not additive, statues are supposed to be bad with tiny toothpicks but // deal crushing blows with big weapons if (you.form == TRAN_STATUE) @@ -4047,6 +4050,9 @@ void melee_attack::player_stab_check() break; } + if (stab_bonus && jumping_attack) + stab_bonus = min(6, stab_bonus + 2); + // See if we need to roll against dexterity / stabbing. if (stab_attempt && roll_needed) { @@ -5333,6 +5339,12 @@ int melee_attack::cleave_damage_mod(int dam) return div_rand_round(dam * 3, 4); } +// jump attack modifier: 120% of base damage +int melee_attack::jump_damage_mod(int dam) +{ + return div_rand_round(dam * 6, 5); +} + void melee_attack::chaos_affect_actor(actor *victim) { melee_attack attk(victim, victim); diff --git a/crawl-ref/source/melee_attack.h b/crawl-ref/source/melee_attack.h index bd44533..a21e99f 100644 --- a/crawl-ref/source/melee_attack.h +++ b/crawl-ref/source/melee_attack.h @@ -45,6 +45,7 @@ public: bool can_cleave; list<actor*> cleave_targets; bool cleaving; // additional attack from cleaving + bool jumping_attack; // Miscast to cause after special damage is done. If miscast_level == 0 // the miscast is discarded if special_damage_message isn't empty. @@ -57,7 +58,7 @@ public: public: melee_attack(actor *attacker, actor *defender, int attack_num = -1, int effective_attack_num = -1, - bool is_cleaving = false); + bool is_cleaving = false, bool is_jump_attack = false); // Applies attack damage and other effects. bool attack(); @@ -106,6 +107,8 @@ private: /* Axe cleaving */ void cleave_setup(); int cleave_damage_mod(int dam); + int jump_damage_mod(int dam); + int jump_additional_damage_mod(int dam); /* Mutation Effects */ void do_spines(); diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc index ca223ed..2500f12 100644 --- a/crawl-ref/source/monster.cc +++ b/crawl-ref/source/monster.cc @@ -4775,6 +4775,24 @@ bool monster::can_go_berserk() const return true; } +bool monster::can_jump() const +{ + if (mons_intel(this) == I_PLANT) + return false; + + if (swimming() || airborne()) + return false; + + if (paralysed() || petrified() || petrifying() || asleep()) + return false; + + if (has_ench(ENCH_FATIGUE)) + return false; + + + return true; +} + bool monster::berserk() const { return (has_ench(ENCH_BERSERK) || has_ench(ENCH_INSANE)); diff --git a/crawl-ref/source/monster.h b/crawl-ref/source/monster.h index c2a4ba2..1dc6483 100644 --- a/crawl-ref/source/monster.h +++ b/crawl-ref/source/monster.h @@ -299,6 +299,7 @@ public: void attacking(actor *other); bool can_go_berserk() const; + bool can_jump() const; void go_berserk(bool intentional, bool potion = false); void go_frenzy(); bool berserk() const; diff --git a/crawl-ref/source/mutation-data.h b/crawl-ref/source/mutation-data.h index 622936c..5ddd1c8 100644 --- a/crawl-ref/source/mutation-data.h +++ b/crawl-ref/source/mutation-data.h @@ -414,6 +414,24 @@ "breathe flames" }, +{ MUT_JUMP, 4, 3, false, false, false, + "jump", + + {"You can jump attack at a short distance.", + "You can jump attack at a medium distance.", + "You can jump attack at a long distance."}, + + {"You feel sure on your feet.", + "You feel sure on your feet.", + "You feel sure on your feet."}, + + {"You feel less sure on your feet.", + "You feel less sure on your feet.", + "You feel less sure on your feet."}, + + "jump" +}, + { MUT_BLINK, 3, 3, false, false, false, "blink", diff --git a/crawl-ref/source/mutation.cc b/crawl-ref/source/mutation.cc index c77b937..bf99837 100644 --- a/crawl-ref/source/mutation.cc +++ b/crawl-ref/source/mutation.cc @@ -1656,7 +1656,6 @@ bool mutate(mutation_type which_mutation, const string &reason, bool failMsg, case MUT_HIGH_MAGIC: calc_mp(); break; - case MUT_PASSIVE_MAPPING: add_daction(DACT_REAUTOMAP); break; diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc index daefd44..415744a 100644 --- a/crawl-ref/source/newgame.cc +++ b/crawl-ref/source/newgame.cc @@ -1010,7 +1010,7 @@ static void _construct_backgrounds_menu(const newgame_def* ng, "Warrior", coord_def(0, 0), 15, {JOB_FIGHTER, JOB_GLADIATOR, JOB_MONK, JOB_HUNTER, JOB_ASSASSIN, - JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} + JOB_DRAGOON, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} }, { "Adventurer", @@ -1758,6 +1758,7 @@ static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice, case JOB_WARPER: case JOB_HUNTER: case JOB_ARCANE_MARKSMAN: + case JOB_DRAGOON: break; default: return true; diff --git a/crawl-ref/source/ng-restr.cc b/crawl-ref/source/ng-restr.cc index 930a60c..dd24161 100644 --- a/crawl-ref/source/ng-restr.cc +++ b/crawl-ref/source/ng-restr.cc @@ -553,7 +553,21 @@ char_choice_restriction job_allowed(species_type speci, job_type job) default: return CC_UNRESTRICTED; } - + case JOB_DRAGOON: + switch (speci) + { + case SP_CENTAUR: + case SP_DJINNI: + case SP_OCTOPODE: + case SP_OGRE: + case SP_NAGA: + case SP_SPRIGGAN: + case SP_TENGU: + case SP_TROLL: + return CC_RESTRICTED; + default: + return CC_UNRESTRICTED; + } case JOB_WANDERER: return CC_RESTRICTED; diff --git a/crawl-ref/source/ng-setup.cc b/crawl-ref/source/ng-setup.cc index ba978e4..de1bd73 100644 --- a/crawl-ref/source/ng-setup.cc +++ b/crawl-ref/source/ng-setup.cc @@ -162,6 +162,7 @@ static void _jobs_stat_init(job_type which_job) case JOB_FIGHTER: s = 8; i = 0; d = 4; hp = 15; mp = 0; break; case JOB_BERSERKER: s = 9; i = -1; d = 4; hp = 15; mp = 0; break; case JOB_GLADIATOR: s = 7; i = 0; d = 5; hp = 14; mp = 0; break; + case JOB_DRAGOON: s = 6; i = 0; d = 6; hp = 13; mp = 2; break; case JOB_SKALD: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; case JOB_CHAOS_KNIGHT: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; @@ -343,6 +344,7 @@ void give_basic_mutations(species_type speci) you.mutation[MUT_FAST] = 1; you.mutation[MUT_CARNIVOROUS] = 3; you.mutation[MUT_SLOW_METABOLISM] = 1; + you.mutation[MUT_JUMP] = 1; break; case SP_OCTOPODE: you.mutation[MUT_CAMOUFLAGE] = 1; @@ -566,6 +568,28 @@ static void _give_items_skills(const newgame_def& ng) you.skills[SK_DODGING] = 3; weap_skill = 3; break; + case JOB_DRAGOON: + // Equipment. + newgame_make_item(0, EQ_WEAPON, OBJ_WEAPONS, WPN_SHORT_SWORD); + _update_weapon(ng); + newgame_make_item(1, EQ_BODY_ARMOUR, OBJ_ARMOUR, ARM_LEATHER_ARMOUR, + ARM_ANIMAL_SKIN); + newgame_make_item(2, EQ_BOOTS, OBJ_ARMOUR, ARM_BOOTS); + + you.skills[SK_FIGHTING] = 2; + if (you_can_wear(EQ_BOOTS)) + { + you.inv[2].special = SPARM_JUMPING; + you.skills[SK_EVOCATIONS] = 2; + } + else + { + newgame_make_item(2, EQ_HELMET, OBJ_ARMOUR, ARM_HELMET, ARM_CAP); + you.skills[SK_STEALTH] = 2; + } + you.skills[SK_DODGING] = 3; + weap_skill = 3; + break; case JOB_MONK: you.equip[EQ_WEAPON] = -1; // Monks fight unarmed. diff --git a/crawl-ref/source/player-act.cc b/crawl-ref/source/player-act.cc index d5ea690..dced00d 100644 --- a/crawl-ref/source/player-act.cc +++ b/crawl-ref/source/player-act.cc @@ -159,7 +159,7 @@ bool player::is_habitable_feat(dungeon_feature_type actual_grid) const if (airborne() || species == SP_DJINNI) return true; - if (actual_grid == DNGN_LAVA + if (actual_grid == DNGN_LAVA && species != SP_LAVA_ORC || actual_grid == DNGN_DEEP_WATER && !can_swim()) { return false; @@ -720,6 +720,68 @@ bool player::can_go_berserk(bool intentional, bool potion, bool quiet) const return true; } +bool player::can_jump(bool quiet) const +{ + if (duration[DUR_EXHAUSTED]) + { + if (!quiet) + mpr("You're too exhausted to jump."); + // or else they won't notice -- no message here + return false; + } + + // Stasis, but only for identified amulets; unided amulets will + // trigger when the player attempts to activate jump, + // auto-iding at that point, but also killing the jump and + // wasting a turn. + if (you.stasis(false)) + { + if (!quiet) + { + const item_def *amulet = you.slot_item(EQ_AMULET, false); + mprf("You cannot jump with %s on.", + amulet? amulet->name(DESC_YOUR).c_str() : "your amulet"); + } + return false; + } + if (you.airborne()) + { + if (!quiet) + mpr("You can't jump while in the air."); + return false; + } + if (you.in_water()) + { + if (!quiet) + mpr("You can't jump while in water."); + return false; + } + if (feat_is_lava(grd(you.pos()))) + { + if (!quiet) + mpr("You can't jump while standing in lava."); + return false; + } + if (you.liquefied_ground()) + { + if (!quiet) + mpr("You can't jump while stuck in this mess."); + return false; + } + if (you.is_constricted()) + { + if (!quiet) + mpr("You can't jump while being constricted."); + return false; + } + + return true; +} + +bool player::can_jump() const +{ + return can_jump(false); +} bool player::berserk() const { return (duration[DUR_BERSERK]); diff --git a/crawl-ref/source/player-equip.cc b/crawl-ref/source/player-equip.cc index 7749b7a..ce99ca5 100644 --- a/crawl-ref/source/player-equip.cc +++ b/crawl-ref/source/player-equip.cc @@ -315,6 +315,17 @@ static void _equip_artefact_effect(item_def &item, bool *show_msgs, bool unmeld) mpr("You feel a build-up of mutagenic energy."); artefact_wpn_learn_prop(item, ARTP_MUTAGENIC); } + if (unknown_proprt(ARTP_JUMP)) + { + if (msg) + { + if (player_jump_level(true) > player_jump_level(false)) + mpr("You feel more sure on your feet."); + else + mpr("Your feet feel light for a moment."); + } + artefact_wpn_learn_prop(item, ARTP_JUMP); + } if (!unmeld && !item.cursed() && proprt[ARTP_CURSED] > 0 && one_chance_in(proprt[ARTP_CURSED])) @@ -929,6 +940,11 @@ static void _equip_armour_effect(item_def& arm, bool unmeld) mpr("You feel rather light."); break; + case SPARM_JUMPING: + if (you.evokable_jump()) + mpr("You feel more sure on your feet."); + break; + case SPARM_MAGIC_RESISTANCE: mpr("You feel resistant to hostile enchantments."); break; diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index c9cae77..be5f1fe 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -127,7 +127,8 @@ static void _moveto_maybe_repel_stairs() } } -static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) +static bool _check_moveto_cloud(const coord_def& p, const string &move_verb, + bool interactive = true) { const int cloud = env.cgrid(p); if (cloud != EMPTY_CLOUD && !you.confused()) @@ -151,22 +152,29 @@ static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) return true; } - string prompt = make_stringf("Really %s into that cloud of %s?", - move_verb.c_str(), - cloud_name_at_index(cloud).c_str()); - learned_something_new(HINT_CLOUD_WARNING); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { + string prompt = make_stringf("Really %s into that cloud of %s?", + move_verb.c_str(), + cloud_name_at_index(cloud).c_str()); + learned_something_new(HINT_CLOUD_WARNING); + + if (!yesno(prompt.c_str(), false, 'n')) + { canned_msg(MSG_OK); return false; + } + } else + { + return false; } } } return true; } -static bool _check_moveto_trap(const coord_def& p, const string &move_verb) + static bool _check_moveto_trap(const coord_def& p, const string &move_verb, + bool interactive = true) { // If there's no trap, let's go. trap_def* trap = find_trap(p); @@ -175,28 +183,40 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) if (trap->type == TRAP_ZOT && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Do you really want to %s into the Zot trap", - move_verb.c_str()); - - if (!yes_or_no("%s", prompt.c_str())) + if (interactive) + { + string prompt = make_stringf("Do you really want to %s into the Zot trap", + move_verb.c_str()); + if (!yes_or_no("%s", prompt.c_str())) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } else if (!trap->is_safe() && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Really %s %s that %s?", - move_verb.c_str(), - (trap->type == TRAP_ALARM || trap->type == TRAP_PLATE) ? "onto" - : "into", - feature_description_at(p, false, DESC_BASENAME, false).c_str()); - - if (!yesno(prompt.c_str(), true, 'n')) + if (interactive) + { + string prompt = make_stringf( + "Really %s %s that %s?", + move_verb.c_str(), + (trap->type == TRAP_ALARM + || trap->type == TRAP_PLATE) ? "onto" + : "into", + feature_description_at(p, false, + DESC_BASENAME, + false).c_str()); + if (!yesno(prompt.c_str(), true, 'n')) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } @@ -204,7 +224,7 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) } static bool _check_moveto_dangerous(const coord_def& p, const string& msg, - bool cling = true) + bool cling = true, bool interactive = true) { if (you.can_swim() && feat_is_water(env.grid(p)) || you.airborne() || cling && you.can_cling_to(p) @@ -213,28 +233,30 @@ static bool _check_moveto_dangerous(const coord_def& p, const string& msg, return true; } - if (msg != "") - mpr(msg.c_str()); - else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) - mpr("You cannot swim in your current form."); - else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) - && is_feat_dangerous(env.grid(p))) + if (interactive) { - mpr("You cannot enter lava in your current form."); + if (msg != "") + mpr(msg.c_str()); + else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) + mpr("You cannot swim in your current form."); + else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) + && is_feat_dangerous(env.grid(p))) + { + mpr("You cannot enter lava in your current form."); + } + else + canned_msg(MSG_UNTHINKING_ACT); } - else - canned_msg(MSG_UNTHINKING_ACT); - return false; } static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive = true) { if (you.is_wall_clinging() && (move_verb == "blink" || move_verb == "passwall")) { - return _check_moveto_dangerous(p, msg, false); + return _check_moveto_dangerous(p, msg, false, interactive); } if (!need_expiration_warning() && need_expiration_warning(p) @@ -243,60 +265,69 @@ static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, if (!_check_moveto_dangerous(p, msg)) return false; - string prompt; + if (interactive) + { + string prompt; - if (msg != "") - prompt = msg + " "; + if (msg != "") + prompt = msg + " "; - prompt += "Are you sure you want to " + move_verb; + prompt += "Are you sure you want to " + move_verb; - if (you.ground_level()) - prompt += " into "; - else - prompt += " over "; + if (you.ground_level()) + prompt += " into "; + else + prompt += " over "; - prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; + prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; - prompt += need_expiration_warning(DUR_FLIGHT, p) - ? " while you are losing your buoyancy?" - : " while your transformation is expiring?"; + prompt += need_expiration_warning(DUR_FLIGHT, p) + ? " while you are losing your buoyancy?" + : " while your transformation is expiring?"; - if (!yesno(prompt.c_str(), false, 'n')) - { - canned_msg(MSG_OK); - return false; + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } - return _check_moveto_dangerous(p, msg); + return _check_moveto_dangerous(p, msg, true, interactive); } -static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb) +static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb, + bool interactive = true) { + string prompt; + if (is_excluded(p) && !is_stair_exclusion(p) && !is_excluded(you.pos()) && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf("Really %s into a travel-excluded area?", - move_verb.c_str()); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { - canned_msg(MSG_OK); - return false; + prompt = make_stringf("Really %s into a travel-excluded area?", + move_verb.c_str()); + + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } return true; } bool check_moveto(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive) { - return (_check_moveto_terrain(p, move_verb, msg) - && _check_moveto_cloud(p, move_verb) - && _check_moveto_trap(p, move_verb) - && _check_moveto_exclusion(p, move_verb)); + return (_check_moveto_terrain(p, move_verb, msg, interactive) + && _check_moveto_cloud(p, move_verb, interactive) + && _check_moveto_trap(p, move_verb, interactive) + && _check_moveto_exclusion(p, move_verb, interactive)); } static void _splash() @@ -530,6 +561,22 @@ bool player_genus(genus_type which_genus, species_type species) return (species_genus(species) == which_genus); } +int player_jump_level(bool is_evoke) +{ + int level = 0; + + if (is_evoke) + { + level = 3 + (you.skill(SK_EVOCATIONS, 1) >= 5) + + (you.skill(SK_EVOCATIONS, 1) >= 10); + } + else + { + level = 2 + player_mutation_level(MUT_JUMP); + } + return(level); +} + // If transform is true, compare with current transformation instead // of (or in addition to) underlying species. // (See mon-data.h for species/genus use.) @@ -3595,8 +3642,10 @@ void level_change(int source, const char* aux, bool skip_attribute_increase) } if (you.experience_level == 6 || you.experience_level == 12) + { perma_mutate(MUT_SHAGGY_FUR, 1, "growing up"); - + perma_mutate(MUT_JUMP, 1, "growing up"); + } _felid_extra_life(); break; @@ -4673,6 +4722,7 @@ bool enough_mp(int minimum, bool suppress_msg, bool include_items) return true; } + bool enough_zp(int minimum, bool suppress_msg) { ASSERT(!crawl_state.game_is_arena()); @@ -4834,6 +4884,7 @@ void set_mp(int new_amount) you.redraw_magic_points = true; } + // If trans is true, being berserk and/or transformed is taken into account // here. Else, the base hp is calculated. If rotted is true, calculate the // real max hp you'd have if the rotting was cured. diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h index dddf445..a62d7f9 100644 --- a/crawl-ref/source/player.h +++ b/crawl-ref/source/player.h @@ -562,6 +562,8 @@ public: bool can_go_berserk() const; bool can_go_berserk(bool intentional, bool potion = false, bool quiet = false) const; + bool can_jump() const; + bool can_jump(bool quiet) const; void go_berserk(bool intentional, bool potion = false); bool berserk() const; bool has_lifeforce() const; @@ -791,7 +793,7 @@ void moveto_location_effects(dungeon_feature_type old_feat, const coord_def& old_pos=coord_def()); bool check_moveto(const coord_def& p, const string &move_verb = "step", - const string &msg = ""); + const string &msg = "", bool interactive = true); void move_player_to_grid(const coord_def& p, bool stepped, bool allow_shift); bool is_map_persistent(void); @@ -814,6 +816,7 @@ bool is_effectively_light_armour(const item_def *item); bool player_effectively_in_light_armour(); bool player_under_penance(void); +int player_jump_level(bool is_evoke); int burden_change(void); diff --git a/crawl-ref/source/rltiles/dc-abilities.txt b/crawl-ref/source/rltiles/dc-abilities.txt index fcbf3cc..1557f28 100644 --- a/crawl-ref/source/rltiles/dc-abilities.txt +++ b/crawl-ref/source/rltiles/dc-abilities.txt @@ -18,9 +18,11 @@ evoke_fog ABILITY_EVOKE_FOG evoke_invisibility_end ABILITY_EVOKE_INVISIBILITY_END evoke_invisibility ABILITY_EVOKE_INVISIBILITY flight ABILITY_EVOKE_FLIGHT +jump ABILITY_EVOKE_JUMP evoke_teleport ABILITY_EVOKE_TELEPORT flight_end ABILITY_FLIGHT_END flight ABILITY_FLIGHT +jump ABILITY_JUMP hellfire ABILITY_HELLFIRE mummy_restoration ABILITY_MUMMY_RESTORATION recharge ABILITY_RECHARGE diff --git a/crawl-ref/source/rltiles/dc-feat.txt b/crawl-ref/source/rltiles/dc-feat.txt index d8ee583..3a9f38a 100644 --- a/crawl-ref/source/rltiles/dc-feat.txt +++ b/crawl-ref/source/rltiles/dc-feat.txt @@ -721,6 +721,7 @@ effect/disjunct0 DISJUNCT effect/disjunct1 effect/disjunct2 effect/disjunct3 +landing LANDING effect/heataura0 HEAT_AURA effect/heataura1 effect/heataura2 diff --git a/crawl-ref/source/rltiles/dc-item.txt b/crawl-ref/source/rltiles/dc-item.txt index 1180806..e9b48d5 100644 --- a/crawl-ref/source/rltiles/dc-item.txt +++ b/crawl-ref/source/rltiles/dc-item.txt @@ -606,6 +606,7 @@ i-dexterity BRAND_ARM_DEXTERITY i-intelligence BRAND_ARM_INTELLIGENCE i-ponderous BRAND_ARM_PONDEROUSNESS i-flying BRAND_ARM_FLYING +i-jumping BRAND_ARM_JUMPING i-magic-res BRAND_ARM_MAGIC_RESISTANCE i-protection BRAND_ARM_PROTECTION %rim 0 diff --git a/crawl-ref/source/rltiles/gui/abilities/jump.png b/crawl-ref/source/rltiles/gui/abilities/jump.png new file mode 100644 index 0000000000000000000000000000000000000000..bb80d7233abf4dd4c155c8f598f9a6dfbd06c963 GIT binary patch literal 957 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VB8tt6XFWwQVZ18)v>a&BAaGv zYU=0b2NE&p;^pNP6ciK^5>j+IrlFyssi|pXWMpGwV{dQoarBP2w|7ubP;hW?czAeJ zR8(ACTtY%ZQc_Y%N=kZqdRA6ec6N4tetvOraaC1Sb#--3O%2ex`uh5YhK8o5rk0kL z*4Eaxwzl^6_U`WP-rnB6zP|qc{s|K%Oqw)l%9JTnr%s(QW5&FB^XAW=zhJ?FB}<kp zUAlDn^5v^mty;5Y&DynV*R5N(apT6#n>TOSvSsVmt=qP3+rEAKjvYI8?%cU+*RFm0 z_U+%l|G<F*2M-=Rbm-9G!-tO?Idb&q(PPJs9Y22j#EBCpPo6w=>eT7er_Y=@bN1}n z^XJcBxNzaprAwDDU%q<v>a}avu3x`?>(;H?w{PFMbLZ~eyZ7$hyMO=wg9i^DKYsk= z$&;r~pFVr`?D_NOFJ8QO_3G8@*RS8adGr4L`;Q+#e){z3>({T}zkmPv^XH#GfBydc z`|sbs|Ns9_`8hEd7_f>ZL4Lsuk~&7_>Gcg$X3Si;dH;!1r_Wxwe)Imb_n$uf`1Kp4 ze##S0b6|{5@^o<wskpUvVtDW&2NAdMK#^+Z>YZUBZ}x`2*}Hhcf+nE^U5&^iT@gy( z{v?UtV@j;}c*gGS&$Rv4<rnueZrsC{^dn04$On^&bEiq(eci-)*t0N1-b%r)Ny*DG zcSTCvpLO~ZI9s?h9_;uh^fy(I(d$Bxnas4i^Z6%aa&FusBrs>%=Xk-CXoYpBK9oKE zy!&2jP1Vvf><lj#2}w_n={woH{Hryu(=55?3Gvz<aSoMp?WX4zUi=)|Q?$L~chUwv z6=Q|ehdS@KewXi>ut0Tc%Nf@*lj1%maI%!y6$T0y&i|~u>O(_X(&xHkS&v-qoDu$> zs@b(*LF%8I&*uuczM840+U3Gotsgi2ddvB~n~b897C*W6_*+6w^7U1sMRsx89M`gY z3>WirYiyd-Jom#Z@jQd45&S{x@5;PlYzRFxKWXz^W8=4%7zE}8y!evFe!7t7a7lZu zb<a0H>CSdj*<8!DSMq@-&zLlYm;1??UFY8#PcfWu+o|%of%KJQGwWVG6_P5=EOl<* l$9qLXtLsY7CYdk&e;BiK3wkGX7}<d`iKnZd%Q~loCIG90%-a9} literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png b/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png new file mode 100644 index 0000000000000000000000000000000000000000..006c2a1b1ae0395eef6e8266ee3398fd6e7203b5 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^S0D`piY~`Ij^1(5J%0<x zVJr#q3ubV5b|VeQarSg^43W5;oFH-M0Hc?2T!Yb#?GBPf`3rkq-4}4}dH>x}WaIj1 z_P`_i>g}g|{QFznpxM;KgeB{ocnPy0&kT`l4rYemO~SlSy5p=sHhH@GxvX<aXaWEM C-Y|Or literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/misc/landing.png b/crawl-ref/source/rltiles/misc/landing.png new file mode 100644 index 0000000000000000000000000000000000000000..17d5001279d4e1bcfcb74b824f12a9be9f7265a9 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJd7dtgArYK!L!!A3DDVW|-1z-% zYdOPquBi_$l=46KPr1;QD-=AlKRV{_5mB|G^enCg%q0zc7aAYLelo5<-aVna<4Irs zg;%kg_AqL`66L$}tcyYQN3@4r<D_I4^Lst)g#z)PCo@+yeB@%uHr)PYCJV=khDe6c zhOOHynOIjTGhJVNaPzM9TlecSG|m%9+W+L4;FaF^x0*mJ7_A)G%TzoiFFxqs`3mS# N22WQ%mvv4FO#uI5RRRD2 literal 0 HcmV?d00001 diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc index 92c604a..cc22a8b 100644 --- a/crawl-ref/source/shopping.cc +++ b/crawl-ref/source/shopping.cc @@ -1140,6 +1140,9 @@ int artefact_value(const item_def &item) if (prop[ ARTP_BERSERK ]) ret += 5; + if (prop[ ARTP_JUMP ]) + ret += 5; + if (prop[ ARTP_INVISIBLE ]) ret += 20; @@ -1704,6 +1707,7 @@ unsigned int item_value(item_def item, bool ident) case SPARM_SEE_INVISIBLE: case SPARM_INTELLIGENCE: case SPARM_FLYING: + case SPARM_JUMPING: case SPARM_PRESERVATION: case SPARM_STEALTH: case SPARM_STRENGTH: diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc index c9f555b..0d770d9 100644 --- a/crawl-ref/source/target.cc +++ b/crawl-ref/source/target.cc @@ -11,6 +11,7 @@ #include "itemprop.h" #include "libutil.h" #include "losglobal.h" +#include "player.h" #include "spl-damage.h" #include "terrain.h" @@ -52,6 +53,11 @@ bool targetter::anyone_there(coord_def loc) return actor_at(loc); } +bool targetter::has_additional_sites(coord_def loc, bool allow_harmful) +{ + return false; +} + targetter_beam::targetter_beam(const actor *act, int range, zap_type zap, int pow, int min_ex_rad, int max_ex_rad) : min_expl_rad(min_ex_rad), @@ -875,3 +881,230 @@ aff_type targetter_spray::is_affected(coord_def loc) return affected; } + +targetter_jump::targetter_jump(const actor* act, int range) +{ + ASSERT(act); + agent = act; + origin = act->pos(); + range2 = dist_range(range); + jump_is_blocked = false; +} + +bool targetter_jump::valid_aim(coord_def a) +{ + actor *act; + bool is_valid = true; + coord_def c, jump_pos; + ray_def ray; + + act = actor_at(a); + if ((origin - a).abs() > range2) + { + is_valid = notify_fail("Out of range."); + } + // If there's an actor we can see, check that we have at least one valid + // landing site for a jump attack based on what the agent can see. + else if (act && agent->can_see(act)) + { + if(!has_additional_sites(a, true)) + { + if (no_landing_reason == BLOCKED_FLYING) + is_valid = notify_fail("A flying creature is in the way."); + else if (no_landing_reason == BLOCKED_GIANT) + is_valid = notify_fail("A giant creature is in the way."); + else if (no_landing_reason == BLOCKED_MOVE) + is_valid = notify_fail("There is no safe place to move near the" + " monster."); + else if (no_landing_reason == BLOCKED_PATH) + is_valid = notify_fail("A dungeon feature is in the way."); + } + return is_valid; + } + else if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS)) + { + if (agent->see_cell(a)) + is_valid = notify_fail("There's something in the way."); + else + is_valid = notify_fail("You cannot see that place."); + } + else if (feat_is_solid(grd(a))) + { + is_valid = notify_fail("There's something in the way."); + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + is_valid = notify_fail("A dungeon feature is in the way"); + return is_valid; + } + return is_valid; +} + +bool targetter_jump::valid_landing(coord_def a, bool check_invis) +{ + actor *act; + ray_def ray; + + if (grd(a) == DNGN_OPEN_SEA || grd(a) == DNGN_LAVA_SEA + || !agent->is_habitable(a) || (origin - a).abs() > range2 - 1) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + if (agent->is_player()) + { + monster* beholder = you.get_beholder(a); + if (beholder) + { + return false; + blocked_landing_reason = BLOCKED_MOVE; + } + + monster* fearmonger = you.get_fearmonger(a); + if (fearmonger) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + blocked_landing_reason = BLOCKED_PATH; + return false; + } + // Check if a landing site is invalid due to a visible monster obstructing + // the path. + ray.advance(); + while(map_bounds(ray.pos())) + { + act = actor_at(ray.pos()); + if (ray.pos() == a) + { + if (act && (!check_invis || agent->can_see(act))) + { + blocked_landing_reason = BLOCKED_OCCUPIED; + return false; + } + break; + } + const dungeon_feature_type grid = grd(ray.pos()); + if (act && (!check_invis || agent->can_see(act))) + { + + // Can't jump over airborn enemies nor gient enemies not in deep + // water or lava. + if (act->airborne()) + { + blocked_landing_reason = BLOCKED_FLYING; + return false; + } + else if(act->body_size() == SIZE_GIANT + && grid != DNGN_DEEP_WATER && grid != DNGN_LAVA) + { + blocked_landing_reason = BLOCKED_GIANT; + return false; + } + } + ray.advance(); + } + return true; +} + +aff_type targetter_jump::is_affected(coord_def loc) +{ + aff_type aff = AFF_NO; + + if (loc == aim) + aff = AFF_YES; + else if (additional_sites.count(loc)) + aff = AFF_LANDING; + return aff; +} + +// Handle setting the aim for jump, which is the landing position of the jump. +// If something unsee either occupies the aim positiion or blocks the jump path, +// indicate that with jump_is_blocked. +bool targetter_jump::set_aim(coord_def a) +{ + actor *act; + ray_def ray; + set<coord_def>::const_iterator site; + + if (a == origin) + return false; + if (!targetter::set_aim(a)) + return false; + + jump_is_blocked = false; + act = actor_at(aim); + // Can't set aim if we can't see anything at the location. + if (!env.map_knowledge(aim).invisible_monster() + && (!act || !agent->can_see(act))) + return false; + // Find our set of landing sites, choose one at random, and see if it's + // actually blocked. + set_additional_sites(aim); + if (additional_sites.size()) + { + int site_ind = random2(additional_sites.size()); + for (site = additional_sites.begin(); site_ind > 0; site++) + site_ind--; + landing_site = *site; + if (!valid_landing(landing_site, false)) + jump_is_blocked = true; + return true; + } + return false; +} + +void targetter_jump::set_additional_sites(coord_def a) +{ + get_additional_sites(a); + additional_sites = temp_sites; +} + +void targetter_jump::get_additional_sites(coord_def a) +{ + bool agent_adjacent = a.distance_from(agent->pos()) == 1; + temp_sites.clear(); + + no_landing_reason = BLOCKED_NONE; + for (adjacent_iterator ai(a, false); ai; ++ai) + { + // See if site is valid, record a putative reason for why no sites were + // found. A flying or giant monster blocking the landing site gets + // priority as an reason, since it's very jump-specific. + if (!agent_adjacent || agent->pos().distance_from(*ai) > 1) + { + if (valid_landing(*ai)) + { + temp_sites.insert(*ai); + no_landing_reason = BLOCKED_NONE; + } + else if (no_landing_reason != BLOCKED_FLYING + && no_landing_reason != BLOCKED_GIANT) + { + no_landing_reason = blocked_landing_reason; + } + } + } +} + +// See if we can find at least one valid landing position for the +// given monster. Possibly we also care to exclude sites harmful to +// the player. +bool targetter_jump::has_additional_sites(coord_def a, bool allow_harmful) +{ + set<coord_def>::const_iterator site; + get_additional_sites(a); + // Find a valid jump_attack position in the adjacent squares, chosing the + // valid position closest to the player. + for (site = temp_sites.begin(); + site != temp_sites.end(); site++) + { + if (allow_harmful || (agent->is_player() + && check_moveto(*site, "jump", "", false))) + return true; + } + return false; +} diff --git a/crawl-ref/source/target.h b/crawl-ref/source/target.h index 0ec6882..d2aa800 100644 --- a/crawl-ref/source/target.h +++ b/crawl-ref/source/target.h @@ -12,6 +12,7 @@ enum aff_type // sign and non-zeroness matters AFF_YES, // intended/likely to affect // If you want to extend this to pass the probability somehow, feel free to, // just keep AFF_YES the minimal "bright" value. + AFF_LANDING, // Valid jump attack landing site }; class targetter @@ -30,6 +31,7 @@ public: virtual bool can_affect_walls(); virtual aff_type is_affected(coord_def loc) = 0; + virtual bool has_additional_sites(coord_def a, bool allow_harmful); protected: bool anyone_there(coord_def loc); }; @@ -188,4 +190,39 @@ private: int range2; }; +enum jump_block_reason +{ + BLOCKED_NONE, + BLOCKED_OCCUPIED, + BLOCKED_FLYING, + BLOCKED_GIANT, + BLOCKED_MOVE, + BLOCKED_PATH, +}; + +class targetter_jump : public targetter +{ +public: + targetter_jump(const actor* act, int range); + + bool valid_aim(coord_def a); + bool set_aim(coord_def a); + bool jump_is_blocked; + aff_type is_affected(coord_def loc); + bool has_additional_sites(coord_def a, bool allow_harmful); + set<coord_def> additional_sites; + coord_def landing_site; +private: + void set_additional_sites(coord_def a); + void get_additional_sites(coord_def a); + bool valid_landing(coord_def a, bool check_invis = true); + jump_block_reason no_landing_reason; + jump_block_reason blocked_landing_reason; + set<coord_def> temp_sites; + int _range; + int range2; +}; + + + #endif diff --git a/crawl-ref/source/tiledgnbuf.cc b/crawl-ref/source/tiledgnbuf.cc index cf4483f..de57a9e 100644 --- a/crawl-ref/source/tiledgnbuf.cc +++ b/crawl-ref/source/tiledgnbuf.cc @@ -288,6 +288,8 @@ void DungeonCellBuffer::pack_background(int x, int y, const packed_cell &cell) m_buf_feat.add(TILE_RAY, x, y); else if (bg & TILE_FLAG_RAY_OOR) m_buf_feat.add(TILE_RAY_OUT_OF_RANGE, x, y); + else if (bg & TILE_FLAG_LANDING) + m_buf_feat.add(TILE_LANDING, x, y); } } diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index 2f9b867..764d543 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -5149,6 +5149,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_SPIT_ACID; case ABIL_BLINK: return TILEG_ABILITY_BLINK; + case ABIL_JUMP: + return TILEG_ABILITY_JUMP; // Others case ABIL_DELAYED_FIREBALL: @@ -5192,6 +5194,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_EVOKE_INVISIBILITY_END; case ABIL_EVOKE_FLIGHT: return TILEG_ABILITY_EVOKE_FLIGHT; + case ABIL_EVOKE_JUMP: + return TILEG_ABILITY_EVOKE_JUMP; case ABIL_EVOKE_FOG: return TILEG_ABILITY_EVOKE_FOG; diff --git a/crawl-ref/source/tileview.cc b/crawl-ref/source/tileview.cc index 5718aef..9c898e1 100644 --- a/crawl-ref/source/tileview.cc +++ b/crawl-ref/source/tileview.cc @@ -966,10 +966,16 @@ void tile_place_ray(const coord_def &gc, aff_type in_range) void tile_draw_rays(bool reset_count) { + tileidx_t flag; + for (unsigned int i = 0; i < num_tile_rays; i++) { - tileidx_t flag = tile_ray_vec[i].in_range > AFF_MAYBE ? TILE_FLAG_RAY - : TILE_FLAG_RAY_OOR; + if (tile_ray_vec[i].in_range < AFF_YES) + flag = TILE_FLAG_RAY_OOR; + else if (tile_ray_vec[i].in_range == AFF_YES) + flag = TILE_FLAG_RAY; + else if (tile_ray_vec[i].in_range == AFF_LANDING) + flag = TILE_FLAG_LANDING; env.tile_bg(tile_ray_vec[i].ep) |= flag; } diff --git a/crawl-ref/source/webserver/game_data/static/cell_renderer.js b/crawl-ref/source/webserver/game_data/static/cell_renderer.js index 73df042..c3a2b97 100644 --- a/crawl-ref/source/webserver/game_data/static/cell_renderer.js +++ b/crawl-ref/source/webserver/game_data/static/cell_renderer.js @@ -662,6 +662,9 @@ function ($, view_data, main, tileinfo_player, icons, dngn, enums, map_knowledge this.draw_dngn(dngn.RAY, x, y); else if (bg.RAY_OOR) this.draw_dngn(dngn.RAY_OUT_OF_RANGE, x, y); + else if (bg.LANDING) + this.draw_dngn(dngn.LANDING, x, y); + } }, diff --git a/crawl-ref/source/webserver/game_data/static/enums.js b/crawl-ref/source/webserver/game_data/static/enums.js index 18ec2d9..cde3a92 100644 --- a/crawl-ref/source/webserver/game_data/static/enums.js +++ b/crawl-ref/source/webserver/game_data/static/enums.js @@ -239,7 +239,7 @@ define(function () { bg_flags.flags.ELDRITCH_NE = [0, 0x04]; bg_flags.flags.ELDRITCH_SE = [0, 0x08]; bg_flags.flags.ELDRITCH_SW = [0, 0x10]; - + bg_flags.flags.LANDING = [0, 0x200]; bg_flags.mask = 0x0000FFFF; exports.prepare_fg_flags = function (tileidx) diff --git a/crawl-ref/source/wiz-item.cc b/crawl-ref/source/wiz-item.cc index ffef8ee..085d915 100644 --- a/crawl-ref/source/wiz-item.cc +++ b/crawl-ref/source/wiz-item.cc @@ -262,6 +262,7 @@ static const char* _prop_name[] = { "SInv", "Inv", "Fly", + "Jump", "Blnk", "Bers", "Nois", @@ -305,6 +306,7 @@ static int8_t _prop_type[] = { ARTP_VAL_BOOL, //EYESIGHT ARTP_VAL_BOOL, //INVISIBLE ARTP_VAL_BOOL, //FLIGHT + ARTP_VAL_BOOL, //JUMPING ARTP_VAL_BOOL, //BLINK ARTP_VAL_BOOL, //BERSERK ARTP_VAL_POS, //NOISES @@ -1166,6 +1168,7 @@ static void _debug_acquirement_stats(FILE *ostat) "intelligence", "ponderous", "flight", + "jumping", "magic reistance", "protection", "stealth", diff --git a/crawl-ref/source/wiz-you.cc b/crawl-ref/source/wiz-you.cc index 096893e..03b26f4 100644 --- a/crawl-ref/source/wiz-you.cc +++ b/crawl-ref/source/wiz-you.cc @@ -284,6 +284,7 @@ void wizard_heal(bool super_heal) you.duration[DUR_CONF] = 0; you.duration[DUR_MISLED] = 0; you.duration[DUR_POISONING] = 0; + you.duration[DUR_EXHAUSTED] = 0; set_hp(you.hp_max); set_mp(you.max_magic_points); set_hunger(10999, true); -- 1.7.4.4 ![]() From 50f3e707057dac4b8ee79fccde7eb167bf9170b9 Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Thu, 13 Jun 2013 21:30:10 -0500 Subject: [PATCH] Fix player warnings for jump attack --- crawl-ref/source/abl-show.cc | 91 ++++++- crawl-ref/source/actor.cc | 10 + crawl-ref/source/actor.h | 2 + crawl-ref/source/dat/descript/ability.txt | 22 ++- crawl-ref/source/dat/descript/backgrounds.txt | 5 + crawl-ref/source/describe.cc | 6 + crawl-ref/source/directn.cc | 261 +++++++++++++------- crawl-ref/source/directn.h | 7 +- crawl-ref/source/enum.h | 11 +- crawl-ref/source/fight.cc | 90 +++++++- crawl-ref/source/fight.h | 6 +- crawl-ref/source/itemname.cc | 2 + crawl-ref/source/itemprop-enum.h | 2 +- crawl-ref/source/itemprop.cc | 3 +- crawl-ref/source/jobs.cc | 4 +- crawl-ref/source/main.cc | 1 - crawl-ref/source/makeitem.cc | 1 + crawl-ref/source/mapdef.cc | 1 + crawl-ref/source/melee_attack.cc | 84 +++++-- crawl-ref/source/melee_attack.h | 9 +- crawl-ref/source/misc.cc | 34 ++- crawl-ref/source/misc.h | 8 +- crawl-ref/source/monster.cc | 18 ++ crawl-ref/source/monster.h | 1 + crawl-ref/source/mutation-data.h | 18 ++ crawl-ref/source/mutation.cc | 1 - crawl-ref/source/newgame.cc | 3 +- crawl-ref/source/ng-restr.cc | 16 ++- crawl-ref/source/ng-setup.cc | 24 ++ crawl-ref/source/player-act.cc | 44 ++++- crawl-ref/source/player-equip.cc | 16 ++ crawl-ref/source/player.cc | 183 +++++++++----- crawl-ref/source/player.h | 5 +- crawl-ref/source/rltiles/dc-abilities.txt | 2 + crawl-ref/source/rltiles/dc-feat.txt | 1 + crawl-ref/source/rltiles/dc-item.txt | 1 + crawl-ref/source/rltiles/gui/abilities/jump.png | Bin 0 -> 957 bytes .../rltiles/item/armour/brands/i-jumping.png | Bin 0 -> 161 bytes crawl-ref/source/rltiles/misc/landing.png | Bin 0 -> 215 bytes crawl-ref/source/shopping.cc | 4 + crawl-ref/source/target.cc | 233 +++++++++++++++++ crawl-ref/source/target.h | 37 +++ crawl-ref/source/tiledgnbuf.cc | 2 + crawl-ref/source/tilepick.cc | 4 + crawl-ref/source/tileview.cc | 10 +- .../webserver/game_data/static/cell_renderer.js | 3 + .../source/webserver/game_data/static/enums.js | 2 +- crawl-ref/source/wiz-item.cc | 3 + crawl-ref/source/wiz-you.cc | 1 + 49 files changed, 1075 insertions(+), 217 deletions(-) create mode 100644 crawl-ref/source/rltiles/gui/abilities/jump.png create mode 100644 crawl-ref/source/rltiles/item/armour/brands/i-jumping.png create mode 100644 crawl-ref/source/rltiles/misc/landing.png diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc index 62cee08..b23cdf1 100644 --- a/crawl-ref/source/abl-show.cc +++ b/crawl-ref/source/abl-show.cc @@ -40,6 +40,7 @@ #include "evoke.h" #include "macro.h" #include "maps.h" +#include "melee_attack.h" #include "message.h" #include "menu.h" #include "misc.h" @@ -109,6 +110,7 @@ static void _pay_ability_costs(const ability_def& abil, int zpcost); static int _scale_piety_cost(ability_type abil, int original_cost); static string _zd_mons_description_for_ability(const ability_def &abil); static monster_type _monster_for_ability(const ability_def& abil); +static bool _jump_player(int jump_range); /** * This all needs to be split into data/util/show files @@ -223,6 +225,7 @@ static const ability_def Ability_List[] = { ABIL_FLY, "Fly", 3, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_STOP_FLYING, "Stop Flying", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_JUMP, "Jump Attack", 0, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_HELLFIRE, "Hellfire", 0, 150, 200, 0, 0, ABFLAG_NONE}, { ABIL_DELAYED_FIREBALL, "Release Delayed Fireball", @@ -248,6 +251,7 @@ static const ability_def Ability_List[] = { ABIL_EVOKE_TURN_INVISIBLE, "Evoke Invisibility", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TURN_VISIBLE, "Turn Visible", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_EVOKE_JUMP, "Evoke Jump Attack", 2, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_EVOKE_FLIGHT, "Evoke Flight", 1, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_FOG, "Evoke Fog", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TELEPORT_CONTROL, "Evoke Teleport Control", 4, 0, 200, 0, 0, ABFLAG_NONE}, @@ -921,7 +925,6 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - case ABIL_BREATHE_FROST: case ABIL_BREATHE_POISON: case ABIL_SPIT_ACID: @@ -941,7 +944,12 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - + // copied from spit poison + case ABIL_JUMP: + failure = ((you.species == SP_FELID) ? 20 : 40) + - 10 * player_mutation_level(MUT_JUMP) + - you.experience_level; + break; case ABIL_FLY: failure = 45 - (3 * you.experience_level); break; @@ -995,7 +1003,9 @@ talent get_talent(ability_type ability, bool check_confused) case ABIL_EVOKE_BLINK: failure = 40 - you.skill(SK_EVOCATIONS, 2); break; - + case ABIL_EVOKE_JUMP: + failure = 30 - you.skill(SK_EVOCATIONS, 2); + break; case ABIL_EVOKE_BERSERK: case ABIL_EVOKE_FOG: case ABIL_EVOKE_TELEPORT_CONTROL: @@ -1340,6 +1350,7 @@ static bool _check_ability_possible(const ability_def& abil, bool hungerCheck = true, bool quiet = false) { + if (silenced(you.pos()) && you.religion != GOD_NEMELEX_XOBEH) { talent tal = get_talent(abil.ability, false); @@ -1573,6 +1584,13 @@ bool activate_talent(const talent& tal) return false; } + if ((tal.which == ABIL_EVOKE_JUMP || tal.which == ABIL_JUMP) + && !you.can_jump()) + { + crawl_state.zero_turns_taken(); + return false; + } + if ((tal.which == ABIL_EVOKE_FLIGHT || tal.which == ABIL_TRAN_BAT) && you.liquefied_ground()) { @@ -1883,7 +1901,15 @@ static bool _do_ability(const ability_def& abil) break; } - + case ABIL_JUMP: + { + int jump_level = player_jump_level(false); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.experience_level)); + break; + } case ABIL_RECHARGING: if (recharge_wand() <= 0) return false; // fail message is already given @@ -2138,7 +2164,15 @@ static bool _do_ability(const ability_def& abil) else fly_player(you.skill(SK_EVOCATIONS, 2) + 30); break; - + case ABIL_EVOKE_JUMP: + { + int jump_level = player_jump_level(true); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.skill(SK_EVOCATIONS, 1))); + break; + } case ABIL_EVOKE_FOG: // cloak of the Thief mpr("With a swish of your cloak, you release a cloud of fog."); big_cloud(random_smoke_type(), &you, you.pos(), 50, 8 + random2(8)); @@ -3080,7 +3114,11 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) if (you.species == SP_DEEP_DWARF) _add_talent(talents, ABIL_RECHARGING, check_confused); - + if (player_mutation_level(MUT_JUMP) + && !you.evokable_jump()) + { + _add_talent(talents, ABIL_JUMP, check_confused); + } // Spit Poison. Nontransformed nagas can upgrade to Breathe Poison. // Transformed nagas, or non-nagas, can only get Spit Poison. if (you.species == SP_NAGA @@ -3092,8 +3130,7 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } else if (player_mutation_level(MUT_SPIT_POISON) || you.species == SP_NAGA) - { - _add_talent(talents, ABIL_SPIT_POISON, check_confused); + { _add_talent(talents, ABIL_SPIT_POISON, check_confused); } if (player_genus(GENPC_DRACONIAN)) @@ -3229,6 +3266,9 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } } + if (you.evokable_jump()) + _add_talent(talents, ABIL_EVOKE_JUMP, check_confused); + if (you.wearing(EQ_RINGS, RING_TELEPORTATION) && !crawl_state.game_is_sprint()) { @@ -3497,3 +3537,38 @@ int scaling_cost::cost(int max) const { return (value < 0) ? (-value) : ((value * max + 500) / 1000); } + + +bool _jump_player(int jump_range) +{ + coord_def landing; + direction_chooser_args args; + targetter_jump tgt(&you, jump_range); + dist jdirect; + + args.restricts = DIR_JUMP; + args.mode = TARG_HOSTILE; + args.just_looking = false; + args.needs_path = true; + args.may_target_monster = true; + args.may_target_self = false; + args.target_prefix = NULL; + args.top_prompt = "Aiming: <white>Jump Attack</white>"; + args.behaviour = NULL; + args.cancel_at_self = true; + args.hitfunc = &tgt; + args.range = jump_range; + direction(jdirect, args); + if (!jdirect.isValid) + { + // Check for user cancel. + canned_msg(MSG_OK); + return false; + } + + if (!check_moveto(jdirect.target, "jump") + || !fight_jump(&you, actor_at(jdirect.target), tgt.jump_is_blocked, + tgt.landing_site, tgt.additional_sites)) + return false; + return true; +} diff --git a/crawl-ref/source/actor.cc b/crawl-ref/source/actor.cc index a612e2a..ec82979 100644 --- a/crawl-ref/source/actor.cc +++ b/crawl-ref/source/actor.cc @@ -386,6 +386,16 @@ int actor::evokable_flight(bool calc_unid) const + scan_artefacts(ARTP_FLY, calc_unid); } +// Return an int so we know whether an item is the sole source. +int actor::evokable_jump(bool calc_unid) const +{ + if (suppressed() + || (is_player() && player_jump_level(true) <= player_jump_level(false))) + return 0; + return wearing_ego(EQ_ALL_ARMOUR, SPARM_JUMPING, calc_unid) + + scan_artefacts(ARTP_JUMP, calc_unid); +} + int actor::spirit_shield(bool calc_unid, bool items) const { int ss = 0; diff --git a/crawl-ref/source/actor.h b/crawl-ref/source/actor.h index 59b0973..3bd977b 100644 --- a/crawl-ref/source/actor.h +++ b/crawl-ref/source/actor.h @@ -171,6 +171,7 @@ public: virtual bool can_see_invisible() const = 0; virtual bool invisible() const = 0; virtual bool nightvision() const = 0; + virtual bool can_jump() const = 0; // Would looker be able to see the actor when in LOS? virtual bool visible_to(const actor *looker) const = 0; @@ -316,6 +317,7 @@ public: // Return an int so we know whether an item is the sole source. virtual int evokable_flight(bool calc_unid = true) const; + virtual int evokable_jump(bool calc_unid = true) const; virtual int spirit_shield(bool calc_unid = true, bool items = true) const; virtual flight_type flight_mode() const = 0; diff --git a/crawl-ref/source/dat/descript/ability.txt b/crawl-ref/source/dat/descript/ability.txt index bfa125d..a8db947 100644 --- a/crawl-ref/source/dat/descript/ability.txt +++ b/crawl-ref/source/dat/descript/ability.txt @@ -74,7 +74,17 @@ Start flying. During flight you can safely cross water and similar obstacles. Be warned, though, that flight may time out at inopportune moments and cause you to fall to your death. %%%% -Hellfire +Jump Attack + +Jump attack at limited distance, covering ground with great speed. You may +target an enemy to perform a jumping attack with increased damage and landing in +some position next to your target. You cannot jump over flying enemies nor +giant enemies not submerged in deep water or lava. Standing or swimming in +water, lava, or liquified ground will also prevent you from using this ability. +Felids have an innate ability to jump attack that increases in range at +experience levels 6 and 12. + +%%%% Hellfire Blast your enemies with hellfire. %%%% @@ -123,6 +133,16 @@ Evoke Flight <Fly> %%%% +Evoke Jump Attack + +Jump attack at limited distance, covering ground with great speed. You may +target an enemy to perform a jumping attack with increased damage and landing in +some position next to your target. You cannot jump over flying enemies nor +giant enemies not submerged in deep water or lava. Standing or swimming in +water, lava, or liquified ground will also prevent you from using this ability. +At evocations skill levels 5 and 10, the range of your jump attack increases. +%%%% + Stop Flying Stop flying. diff --git a/crawl-ref/source/dat/descript/backgrounds.txt b/crawl-ref/source/dat/descript/backgrounds.txt index 446e640..cc1f17a 100644 --- a/crawl-ref/source/dat/descript/backgrounds.txt +++ b/crawl-ref/source/dat/descript/backgrounds.txt @@ -43,6 +43,11 @@ Death Knight Death Knights are melee fighters who command the undead in the name of Yredelemnul the Dark. %%%% +Dragoon + +Dragoons harness the power of magical armour that allows them to +launch a devestating jumping attack on multiple foes. +%%%% Earth Elementalist Earth Elementalists know the Sandblast spell, and carry stones to increase the diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index 01dfa25..5b2db64 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -224,6 +224,7 @@ static vector<string> _randart_propnames(const item_def& item, { "+Inv", ARTP_INVISIBLE, 2 }, { "+Fly", ARTP_FLY, 2 }, { "+Fog", ARTP_FOG, 2 }, + { "+Jump", ARTP_JUMP, 2 }, // Resists, also really important { "rElec", ARTP_ELECTRICITY, 2 }, @@ -418,6 +419,7 @@ static string _randart_descrip(const item_def &item) { ARTP_EYESIGHT, "It enhances your eyesight.", false}, { ARTP_INVISIBLE, "It lets you turn invisible.", false}, { ARTP_FLY, "It lets you fly.", false}, + { ARTP_JUMP, "It lets perform a jumping attack.", false}, { ARTP_BLINK, "It lets you blink.", false}, { ARTP_BERSERK, "It lets you go berserk.", false}, { ARTP_NOISES, "It makes noises.", false}, @@ -1324,6 +1326,10 @@ static string _describe_armour(const item_def &item, bool verbose) description += "It can be activated to allow its wearer to " "fly indefinitely."; break; + case SPARM_JUMPING: + description += "It can be activated to allow its wearer to " + "perform a jumping attack."; + break; case SPARM_MAGIC_RESISTANCE: description += "It increases its wearer's resistance " "to enchantments."; diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index 4e2713b..dc7c5f3 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -109,6 +109,8 @@ static bool _find_feature(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); static bool _find_fprop_unoccupied(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc); #ifndef USE_TILE_LOCAL static bool _find_mlist(const coord_def& where, int mode, bool need_path, @@ -175,8 +177,8 @@ static void _wizard_make_friendly(monster* m) #endif dist::dist() - : isValid(false), isTarget(false), isEndpoint(false), - isCancel(true), choseRay(false), target(), delta(), ray() + : isValid(false), isTarget(false), isEndpoint(false), isCancel(true), + choseRay(false), target(), delta(), ray() { } @@ -379,6 +381,9 @@ void direction_chooser::print_key_hints() const case DIR_DIR: case DIR_TARGET_OBJECT: break; + case DIR_JUMP: + prompt += hint_string; + break; } } @@ -513,6 +518,7 @@ direction_chooser::direction_chooser(dist& moves_, show_items_once = false; target_unshifted = Options.target_unshifted_dirs; + } class view_desc_proc @@ -1013,89 +1019,107 @@ static void _update_mlist(bool enable) } #endif -// Find a good square to start targetting from. -coord_def direction_chooser::find_default_target() const +// Try to find an enemy monster to target +bool direction_chooser::find_default_monster_target(coord_def& result) const { - coord_def result = you.pos(); bool success = false; - - if (restricts == DIR_TARGET_OBJECT) + int search_range = range; + bool (*find_targ)(const coord_def&, int, bool, int, targetter*); + + // First try to pick our previous target. + const monster* mons_target = get_current_target(); + if (mons_target != NULL + && (mode != TARG_EVOLVABLE_PLANTS + && mons_attitude(mons_target) == ATT_HOSTILE + || mode == TARG_ENEMY && !mons_target->friendly() + || mode == TARG_EVOLVABLE_PLANTS + && mons_is_evolvable(mons_target) + || mode == TARG_HOSTILE_UNDEAD && !mons_target->friendly() + && mons_target->holiness() == MH_UNDEAD + || mode == TARG_INJURED_FRIEND + && (mons_target->friendly() && mons_get_damage_level(mons_target) > MDAM_OKAY + || (!mons_target->wont_attack() + && !mons_target->neutral() + && is_pacifiable(mons_target) >= 0))) + && in_range(mons_target->pos())) { - // Try to find an object. - success = _find_square_wrapper(result, 1, _find_object, - needs_path, TARG_ANY, range, hitfunc, - true, LS_FLIPVH); + success = true; + result = mons_target->pos(); } - else if (mode == TARG_ENEMY || mode == TARG_HOSTILE - || mode == TARG_HOSTILE_SUBMERGED - || mode == TARG_EVOLVABLE_PLANTS - || mode == TARG_HOSTILE_UNDEAD - || mode == TARG_INJURED_FRIEND) + if (!success) { - // Try to find an enemy monster. - - // First try to pick our previous target. - const monster* mon_target = get_current_target(); - if (mon_target != NULL - && (mode != TARG_EVOLVABLE_PLANTS - && mons_attitude(mon_target) == ATT_HOSTILE - || mode == TARG_ENEMY && !mon_target->friendly() - || mode == TARG_EVOLVABLE_PLANTS - && mons_is_evolvable(mon_target) - || mode == TARG_HOSTILE_UNDEAD && !mon_target->friendly() - && mon_target->holiness() == MH_UNDEAD - || mode == TARG_INJURED_FRIEND - && (mon_target->friendly() && mons_get_damage_level(mon_target) > MDAM_OKAY - || (!mon_target->wont_attack() - && !mon_target->neutral() - && is_pacifiable(mon_target) >= 0))) - && in_range(mon_target->pos())) + // Have to increase search_range by one so monsters out of range but + // with landing sites in-range are found. + if (restricts == DIR_JUMP) { - result = mon_target->pos(); - success = true; + find_targ = _find_jump_attack_mons; + search_range += 1; } else { - // The previous target is no good. Try to find one from scratch. - success = _find_square_wrapper(result, 1, _find_monster, + find_targ = _find_monster; + } + // The previous target is no good. Try to find one from scratch. + success = _find_square_wrapper(result, 1, find_targ, needs_path, mode, + search_range, hitfunc, true); + + // We might be able to hit monsters in LOS that are outside of + // normal range, but inside explosion/cloud range + if (!success && hitfunc && restricts != DIR_JUMP + && (you.current_vision > range || hitfunc->can_affect_walls())) + { + success = _find_square_wrapper(result, 1, _find_monster_expl, needs_path, mode, range, hitfunc, true); + } - // We might be able to hit monsters in LOS that are outside of - // normal range, but inside explosion/cloud range - if (!success - && hitfunc && hitfunc->can_affect_outside_range() - && (you.current_vision > range || hitfunc->can_affect_walls())) - { - success = _find_square_wrapper(result, 1, _find_monster_expl, - needs_path, mode, range, hitfunc, - true); - } - // If we couldn't, maybe it was because of line-of-fire issues. - // Check if that's happening, and inform the user (because it's - // pretty confusing.) - if (!success - && needs_path - && _find_square_wrapper(result, 1, _find_monster, - false, mode, range, hitfunc, true)) + // If we couldn't, maybe it was because of line-of-fire issues. + // Check if that's happening, and inform the user (because it's + // pretty confusing.) + if (!success && needs_path + && _find_square_wrapper(result, 1, find_targ, false, mode, + search_range, hitfunc, true)) + { + // Special colouring in tutorial or hints mode. + const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; + mpr("All monsters which could be auto-targeted are covered by " + "a wall or statue which interrupts your line of fire, even " + "though it doesn't interrupt your line of sight.", + need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); + + if (need_hint) { - // Special colouring in tutorial or hints mode. - const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; - mpr("All monsters which could be auto-targeted are covered by " - "a wall or statue which interrupts your line of fire, even " - "though it doesn't interrupt your line of sight.", - need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); - - if (need_hint) - { mpr("To return to the main mode, press <w>Escape</w>.", MSGCH_TUTORIAL); Hints.hints_events[HINT_TARGET_NO_FOE] = false; - } } } } + return success; +} + +// Find a good square to start targetting from. +void direction_chooser::set_default_target() +{ + coord_def result = you.pos(); + bool success = false; + + if (restricts == DIR_TARGET_OBJECT) + { + // Try to find an object. + success = _find_square_wrapper(result, 1, _find_object, + needs_path, TARG_ANY, range, hitfunc, + true, LS_FLIPVH); + } + else if (mode == TARG_ENEMY || mode == TARG_HOSTILE + || mode == TARG_HOSTILE_SUBMERGED + || mode == TARG_EVOLVABLE_PLANTS + || mode == TARG_HOSTILE_UNDEAD + || mode == TARG_INJURED_FRIEND) + { + success = find_default_monster_target(result); + } // Evolution can also auto-target mold squares (but shouldn't if // there are any monsters to evolve), so try _find_square_wrapper // again @@ -1108,8 +1132,7 @@ coord_def direction_chooser::find_default_target() const if (!success) result = you.pos(); - - return result; + set_target(result); } const coord_def& direction_chooser::target() const @@ -1119,6 +1142,20 @@ const coord_def& direction_chooser::target() const void direction_chooser::set_target(const coord_def& new_target) { + coord_def jump_pos; + set<coord_def>::const_iterator site; + monster *mons; + + if (restricts == DIR_JUMP) + { + mons = monster_at(new_target); + if (((mons && you.can_see(mons)) + || env.map_knowledge(new_target).invisible_monster()) + && hitfunc->has_additional_sites(new_target, true)) + valid_jump = true; + else + valid_jump = false; + } moves.target = new_target; } @@ -1161,11 +1198,22 @@ void direction_chooser::draw_beam_if_needed() #ifndef USE_TILE_LOCAL int bcol = BLACK; if (aff < 0) + { bcol = DARKGREY; + } else if (aff < AFF_YES) + { bcol = (*ri == target()) ? RED : MAGENTA; - else + } + else if (aff == AFF_YES) + { bcol = (*ri == target()) ? LIGHTRED : LIGHTMAGENTA; + } + // Jump attack landing sites + else + { + bcol = (*ri == target()) ? LIGHTGREEN : GREEN; + } _draw_ray_glyph(*ri, bcol, '*', bcol | COLFLAG_REVERSE); #endif } @@ -1283,15 +1331,23 @@ void direction_chooser::update_previous_target() const bool direction_chooser::select(bool allow_out_of_range, bool endpoint) { + const monster* mons = monster_at(target()); + + if (restricts == DIR_JUMP && !valid_jump) + { + if (mons && you.can_see(mons)) + mpr("The monster cannot be jump-attacked there."); + else + mpr("There is no monster to jump-attack there!"); + return false; + } if (!allow_out_of_range && !in_range(target())) { mpr(hitfunc? hitfunc->why_not : "That is beyond the maximum range.", MSGCH_EXAMINE_FILTER); return false; } - - const monster* m = monster_at(target()); - moves.isEndpoint = endpoint || (m && _mon_exposed(m)); + moves.isEndpoint = endpoint || (mons && _mon_exposed(mons)); moves.isValid = true; moves.isTarget = true; update_previous_target(); @@ -1627,9 +1683,12 @@ void direction_chooser::handle_movement_key(command_type key_command, const coord_def& delta = Compass[compass_idx]; const bool unshifted = (shift_direction(key_command) != key_command); if (unshifted) + { set_target(target() + delta); - else + } else + { *loop_done = select_compass_direction(delta); + } } } @@ -1983,7 +2042,7 @@ bool direction_chooser::do_main_loop() } // Redraw whatever is necessary. - if (old_target != target()) + if (restricts == DIR_JUMP || old_target != target()) { have_beam = show_beam && find_ray(you.pos(), target(), beam, opc_solid_see, BDS_DEFAULT); @@ -2035,8 +2094,11 @@ bool direction_chooser::choose_direction() // init moves.delta.reset(); - // Find a default target. - set_target(Options.default_target ? find_default_target() : you.pos()); + // Set a default target. + if (Options.default_target) + set_default_target(); + else + set_target(you.pos()); objfind_pos = monsfind_pos = target(); // If requested, show the beam on startup. @@ -2410,14 +2472,14 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, if ((mode == TARG_FRIEND || mode == TARG_ANY) && where == you.pos()) return true; - // Don't target out of range. - if (!_is_target_in_range(where, range, hitfunc)) + // Don't target out of range + if (hitfunc && !_is_target_in_range(where, range, hitfunc)) return false; const monster* mon = monster_at(where); // No monster or outside LOS. - if (mon == NULL || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) + if (!mon || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) return false; // Monster in LOS but only via glass walls, so no direct path. @@ -2433,9 +2495,39 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, return _want_target_monster(mon, mode); } +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc) +{ +#ifdef CLUA_BINDINGS + { + coord_def dp = grid2player(where); + // We could pass more info here. + maybe_bool x = clua.callmbooleanfn("ch_target_jump", "dd", + dp.x, dp.y); + if (x != MB_MAYBE) + return tobool(x); + } +#endif + + // Need a monster to attack; this checks that the monster is a valid target. + if (!_find_monster(where, mode, need_path, range, hitfunc)) + return false; + // Can't jump on yourself + if (where == you.pos()) + return false; + + return hitfunc->has_additional_sites(where, false); +} + + + + static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc) { + const monster* mons; + coord_def jump_pos; + ASSERT(hitfunc); #ifdef CLUA_BINDINGS @@ -2466,16 +2558,17 @@ static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, return false; if (hitfunc->set_aim(where)) + { for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri) { - if (hitfunc->is_affected(*ri) >= AFF_YES) + mons = monster_at(*ri); + if (mons && hitfunc->is_affected(*ri) == AFF_YES) { - const monster* mon = monster_at(*ri); - if (mon && _mons_is_valid_target(mon, mode, range)) - return _want_target_monster(mon, mode); + if (_mons_is_valid_target(mons, mode, range)) + return _want_target_monster(mons, mode); } } - + } return false; } diff --git a/crawl-ref/source/directn.h b/crawl-ref/source/directn.h index e5e5a0c..412fa27 100644 --- a/crawl-ref/source/directn.h +++ b/crawl-ref/source/directn.h @@ -66,6 +66,7 @@ public: bool isEndpoint; // Does the player want the attack to stop at target? bool isCancel; // user cancelled (usually <ESC> key) bool choseRay; // user wants a specific beam + coord_def target; // target x,y or logical extension of beam to map edge coord_def delta; // delta x and y if direction - always -1,0,1 ray_def ray; // ray chosen if necessary @@ -116,7 +117,7 @@ private: bool do_main_loop(); // Return the location where targetting should start. - coord_def find_default_target() const; + void set_default_target(); void handle_mlist_cycle_command(command_type key_command); void handle_wizard_command(command_type key_command, bool* loop_done); @@ -162,6 +163,7 @@ private: actor* targeted_actor() const; monster* targeted_monster() const; + bool find_default_monster_target(coord_def& result) const; // Functions which print things to the user. // Each one is commented with a sample output. @@ -247,6 +249,8 @@ private: bool show_beam; // Does the user want the beam displayed? bool have_beam; // Is the currently stored beam valid? coord_def objfind_pos, monsfind_pos; // Cycling memory + bool valid_jump; // If jumping, do we currently have a monster + // target with a valid landing position? // What we need to redraw. bool need_beam_redraw; @@ -256,7 +260,6 @@ private: bool show_items_once; // Should we show items this time? bool target_unshifted; // Do unshifted direction keys fire? - // Default behaviour, saved across instances. static targetting_behaviour stock_behaviour; diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 9b3079f..710607f 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -54,7 +54,7 @@ enum ability_type ABIL_BREATHE_MEPHITIC, ABIL_SPIT_ACID, ABIL_BLINK, - + ABIL_JUMP, // Others ABIL_DELAYED_FIREBALL, ABIL_END_TRANSFORMATION, @@ -82,6 +82,7 @@ enum ability_type ABIL_EVOKE_BLINK, ABIL_EVOKE_TURN_INVISIBLE, ABIL_EVOKE_TURN_VISIBLE, + ABIL_EVOKE_JUMP, ABIL_EVOKE_FLIGHT, #if TAG_MAJOR_VERSION == 34 ABIL_EVOKE_STOP_LEVITATING, @@ -1875,6 +1876,7 @@ enum job_type JOB_DEATH_KNIGHT, JOB_ABYSSAL_KNIGHT, JOB_JESTER, + JOB_DRAGOON, NUM_JOBS, // always after the last job JOB_UNKNOWN = 100, @@ -2793,6 +2795,7 @@ enum mutation_type MUT_HEAT_RESISTANCE, MUT_HERBIVOROUS, MUT_HURL_HELLFIRE, + MUT_JUMP, MUT_FAST, MUT_FAST_METABOLISM, MUT_FLEXIBLE_WEAK, @@ -2997,6 +3000,7 @@ enum artefact_prop_type #if TAG_MAJOR_VERSION != 34 ARTP_FOG, #endif + ARTP_JUMP, ARTP_BLINK, ARTP_BERSERK, ARTP_NOISES, @@ -3511,6 +3515,7 @@ enum targetting_type DIR_TARGET, // smite targetting DIR_DIR, // needs a clear line to target DIR_TARGET_OBJECT, // targets items + DIR_JUMP, // a jump target }; enum torment_source_type @@ -3901,6 +3906,10 @@ enum tile_flags ENUM_INT64 //// General + // Should go up with RAY/RAY_OOR, but they need to be exclusive for those + // flags and there's no room. + TILE_FLAG_LANDING = 0x20000000000ULL, + // Mask for the tile index itself. TILE_FLAG_MASK = 0x0000FFFFULL, }; diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index b2a94b7..ba3049d 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -21,6 +21,7 @@ #include "itemprop.h" #include "melee_attack.h" #include "mgen_data.h" +#include "misc.h" #include "mon-behv.h" #include "mon-cast.h" #include "mon-place.h" @@ -183,6 +184,86 @@ bool fight_melee(actor *attacker, actor *defender, bool *did_hit, bool simu) return true; } +// Handles jump attack between attacker and defender. +bool fight_jump(actor *attacker, actor *defender, bool jump_blocked, + coord_def landing_pos, set<coord_def> landing_sites, + bool *did_hit) +{ + bool sanctuary_warning = false; + set<coord_def>::const_iterator site; + + ASSERT(!crawl_state.game_is_arena()); + + // Can't damage orbs this way. + if (mons_is_projectile(defender->type) && !you.confused()) + { + you.turn_is_over = false; + return false; + } + melee_attack first_attk(attacker, defender, -1, -1, false, true, + jump_blocked, landing_pos); + + // Check if the player is fighting with something unsuitable, + // or someone unsuitable. + if (you.can_see(defender) + && !wielded_weapon_check(first_attk.weapon)) + { + you.turn_is_over = false; + return false; + } + // Do player warnings for electrocution and sanctuary based on possible + // landing sites. + if (attacker->is_player() && defender->is_monster() + && attacker->damage_brand(-1) == SPWPN_ELECTROCUTION) + { + for (site = landing_sites.begin(); site != landing_sites.end(); site++) + { + bool ground_level = !you.airborne() && !you.can_cling_to(*site) + && you.species != SP_DJINNI; + if (!you.received_weapon_warning + && adjacent(*site, defender->pos()) + && (feat_is_water(grd(defender->pos())) && defender->ground_level()) + && (feat_is_water(grd(*site)) && ground_level) + && !attacker->res_elec()) + + { + string prompt = "Really attack with "; + if (attacker->weapon(-1)) + prompt += attacker->weapon(-1)->name(DESC_YOUR); + else + prompt += "your electric unarmed attack"; + prompt += " when you might land in water? "; + if (yesno(prompt.c_str(), true, 'n')) + { + you.received_weapon_warning = true; + } + else + { + canned_msg(MSG_OK); + you.turn_is_over = false; + return false; + } + } + else if (!sanctuary_warning) + { + if (stop_attack_prompt(defender->as_monster(), false, + *site, false, nullptr, true, *site)) + return false; + else + sanctuary_warning = true; + } + } + } + if (!first_attk.attack() && first_attk.cancel_attack) + { + you.turn_is_over = false; + return false; + } + if (did_hit) + *did_hit = first_attk.did_hit; + return true; +} + unchivalric_attack_type is_unchivalric_attack(const actor *attacker, const actor *defender) { @@ -394,7 +475,9 @@ bool wielded_weapon_check(item_def *weapon, bool no_message) return true; } -static bool _cleave_dont_harm(const actor* attacker, const actor* defender) +// Used by cleave and jump attack to determine if multi-hit targets will be +// attacked. +bool dont_harm(const actor* attacker, const actor* defender) { return (mons_aligned(attacker, defender) || attacker == &you && defender->wont_attack() @@ -420,7 +503,7 @@ void get_cleave_targets(const actor* attacker, const coord_def& def, int dir, break; actor * target = actor_at(atk + atk_vector); - if (target && !_cleave_dont_harm(attacker, target)) + if (target && !dont_harm(attacker, target)) targets.push_back(target); } } @@ -446,7 +529,7 @@ void attack_cleave_targets(actor* attacker, list<actor*> &targets, { actor* def = targets.front(); if (attacker->alive() && def && def->alive() - && !_cleave_dont_harm(attacker, def)) + && !dont_harm(attacker, def)) { melee_attack attck(attacker, def, attack_number, ++effective_attack_number, true); @@ -455,3 +538,4 @@ void attack_cleave_targets(actor* attacker, list<actor*> &targets, targets.pop_front(); } } + diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h index 6c026e9..9b95efb 100644 --- a/crawl-ref/source/fight.h +++ b/crawl-ref/source/fight.h @@ -29,6 +29,10 @@ enum unchivalric_attack_type bool fight_melee(actor *attacker, actor *defender, bool *did_hit = NULL, bool simu = false); +bool fight_jump(actor *attacker, actor *defender, bool jump_blocked, + coord_def landing_pos, set<coord_def> landing_sites, + bool *did_hit = NULL); + int resist_adjust_damage(actor *defender, beam_type flavour, int res, int rawdamage, bool ranged = false); @@ -47,5 +51,5 @@ void get_all_cleave_targets(const actor* attacker, const coord_def& def, void attack_cleave_targets(actor* attacker, list<actor*> &targets, int attack_number = 0, int effective_attack_number = 0); - +bool dont_harm(const actor* attacker, const actor* defender); #endif diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc index 19bb80e..489b9f6 100644 --- a/crawl-ref/source/itemname.cc +++ b/crawl-ref/source/itemname.cc @@ -517,6 +517,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return "intelligence"; case SPARM_PONDEROUSNESS: return "ponderousness"; case SPARM_FLYING: return "flying"; + case SPARM_JUMPING: return "jumping"; case SPARM_MAGIC_RESISTANCE: return "magic resistance"; case SPARM_PROTECTION: return "protection"; case SPARM_STEALTH: return "stealth"; @@ -546,6 +547,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return " {Int+3}"; case SPARM_PONDEROUSNESS: return " {ponderous}"; case SPARM_FLYING: return " {Fly}"; + case SPARM_JUMPING: return " {Jump}"; case SPARM_MAGIC_RESISTANCE: return " {MR+}"; case SPARM_PROTECTION: return " {AC+3}"; case SPARM_STEALTH: return " {Stlth+}"; diff --git a/crawl-ref/source/itemprop-enum.h b/crawl-ref/source/itemprop-enum.h index 6a9289c..2a46943 100644 --- a/crawl-ref/source/itemprop-enum.h +++ b/crawl-ref/source/itemprop-enum.h @@ -179,7 +179,6 @@ enum jewellery_type RING_FIRE, RING_ICE, RING_TELEPORT_CONTROL, - NUM_RINGS, // keep as last ring; should not overlap // with amulets! // RINGS after num_rings are for unique types for artefacts @@ -345,6 +344,7 @@ enum special_armour_type SPARM_INTELLIGENCE, SPARM_PONDEROUSNESS, SPARM_FLYING, + SPARM_JUMPING, SPARM_MAGIC_RESISTANCE, SPARM_PROTECTION, SPARM_STEALTH, diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc index ab007ef..02e7d2e 100644 --- a/crawl-ref/source/itemprop.cc +++ b/crawl-ref/source/itemprop.cc @@ -2573,7 +2573,8 @@ bool gives_ability(const item_def &item) return false; const special_armour_type ego = get_armour_ego_type(item); - if (ego == SPARM_DARKNESS || ego == SPARM_FLYING) + if (ego == SPARM_DARKNESS || ego == SPARM_FLYING + || ego == SPARM_JUMPING) return true; break; } diff --git a/crawl-ref/source/jobs.cc b/crawl-ref/source/jobs.cc index 73bd2b3..fa7e2ad 100644 --- a/crawl-ref/source/jobs.cc +++ b/crawl-ref/source/jobs.cc @@ -15,7 +15,7 @@ static const char * Job_Abbrev_List[ NUM_JOBS ] = "St", #endif "Mo", "Wr", "Wn", "Ar", "AM", - "DK", "AK", "Jr" }; + "DK", "AK", "Jr", "Dn" }; static const char * Job_Name_List[ NUM_JOBS ] = { "Fighter", "Wizard", "Priest", @@ -29,7 +29,7 @@ static const char * Job_Name_List[ NUM_JOBS ] = "Stalker", #endif "Monk", "Warper", "Wanderer", "Artificer", "Arcane Marksman", - "Death Knight", "Abyssal Knight", "Jester" }; + "Death Knight", "Abyssal Knight", "Jester", "Dragoon" }; const char *get_job_abbrev(int which_job) { diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc index 7342073..d5f440f 100644 --- a/crawl-ref/source/main.cc +++ b/crawl-ref/source/main.cc @@ -3153,7 +3153,6 @@ static void _player_reacts() } _regenerate_hp_and_mp(capped_time); - recharge_rods(you.time_taken, false); // Reveal adjacent mimics. diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index 128a5de..b54409e 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -2293,6 +2293,7 @@ bool is_armour_brand_ok(int type, int brand, bool strict) // deliberate fall-through case SPARM_RUNNING: case SPARM_STEALTH: + case SPARM_JUMPING: return (slot == EQ_BOOTS); case SPARM_ARCHMAGI: diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index 9cb253f..808a6e3 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -4553,6 +4553,7 @@ static int _str_to_ego(item_spec &spec, string ego_str) "intelligence", "ponderousness", "flying", + "jumping", "magic_resistance", "protection", "stealth", diff --git a/crawl-ref/source/melee_attack.cc b/crawl-ref/source/melee_attack.cc index 542d9df..5ffbf72 100644 --- a/crawl-ref/source/melee_attack.cc +++ b/crawl-ref/source/melee_attack.cc @@ -106,7 +106,8 @@ static bool _form_uses_xl() */ melee_attack::melee_attack(actor *attk, actor *defn, int attack_num, int effective_attack_num, - bool is_cleaving) + bool is_cleaving, bool is_jump_attack, + bool is_jump_blocked, coord_def attack_pos) : // Call attack's constructor ::attack(attk, defn), @@ -114,6 +115,7 @@ melee_attack::melee_attack(actor *attk, actor *defn, effective_attack_number(effective_attack_num), skip_chaos_message(false), special_damage_flavour(BEAM_NONE), stab_attempt(false), stab_bonus(0), cleaving(is_cleaving), + jumping_attack(is_jump_attack), jump_blocked(is_jump_blocked), miscast_level(-1), miscast_type(SPTYP_NONE), miscast_target(NULL), simu(false) { @@ -124,9 +126,14 @@ melee_attack::melee_attack(actor *attk, actor *defn, if (_form_uses_xl()) wpn_skill = SK_FIGHTING; // for stabbing, mostly to_hit = calc_to_hit(); - can_cleave = wpn_skill == SK_AXES && attacker != defender - && !attacker->confused(); + can_cleave = !jumping_attack && wpn_skill == SK_AXES && attacker != defender + && !attacker->confused(); + if (jumping_attack) + attack_position = attack_pos; + else + attack_position = attacker->pos(); + attacker_armour_tohit_penalty = div_rand_round(attacker->armour_tohit_penalty(true, 20), 20); attacker_shield_tohit_penalty = @@ -174,7 +181,7 @@ melee_attack::melee_attack(actor *attk, actor *defn, } attacker_visible = attacker->observable(); - attacker_invisible = (!attacker_visible && you.see_cell(attacker->pos())); + attacker_invisible = (!attacker_visible && you.see_cell(attack_position)); defender_visible = defender && defender->observable(); defender_invisible = (!defender_visible && defender && you.see_cell(defender->pos())); @@ -202,7 +209,7 @@ bool melee_attack::can_reach() bool melee_attack::handle_phase_attempted() { // Skip invalid and dummy attacks. - if ((!adjacent(attacker->pos(), defender->pos()) && !can_reach()) + if ((!adjacent(attack_position, defender->pos()) && !jumping_attack && !can_reach()) || attk_type == AT_SHOOT || attk_type == AT_CONSTRICT && !attacker->can_constrict(defender)) { @@ -218,9 +225,10 @@ bool melee_attack::handle_phase_attempted() || (weapon && is_unrandom_artefact(*weapon) && weapon->special == UNRAND_DEVASTATOR)) { - if (damage_brand == SPWPN_ELECTROCUTION - && adjacent(attacker->pos(), defender->pos()) - && _conduction_affected(attacker->pos()) + // This check is handled in fight_jump() for jump attacks + if (!jumping_attack && damage_brand == SPWPN_ELECTROCUTION + && adjacent(attack_position, defender->pos()) + && _conduction_affected(attack_position) && !attacker->res_elec() && !you.received_weapon_warning) { @@ -269,8 +277,10 @@ bool melee_attack::handle_phase_attempted() return false; } } - else if (stop_attack_prompt(defender->as_monster(), false, - attacker->pos())) + // Jump attack did this check in jump_fight() + else if (!jumping_attack + && stop_attack_prompt(defender->as_monster(), false, + attack_position)) { cancel_attack = true; return false; @@ -290,9 +300,17 @@ bool melee_attack::handle_phase_attempted() return false; } } - // Set delay now that we know the attack won't be cancelled. + + if (attacker->is_player()) { + if (jump_blocked) + { + mpr("You rebound off of something unseen!"); + return false; + } + move_player_to_grid(attack_position, false, true); + // Set delay now that we know the attack won't be cancelled. you.time_taken = calc_attack_delay(); if (weapon) { @@ -407,7 +425,7 @@ bool melee_attack::handle_phase_attempted() if (attk_flavour == AF_SHADOWSTAB && defender && !defender->can_see(attacker)) { - if (you.see_cell(attacker->pos())) + if (you.see_cell(attack_position)) mprf("%s strikes at %s from the darkness!", attacker->name(DESC_THE, true).c_str(), defender->name(DESC_THE).c_str()); @@ -469,7 +487,7 @@ bool melee_attack::handle_phase_dodged() } } - if (attacker != defender && adjacent(defender->pos(), attacker->pos())) + if (attacker != defender && adjacent(defender->pos(), attack_position)) { if (attacker->alive() && (defender->is_player() ? @@ -828,9 +846,10 @@ bool melee_attack::handle_phase_aux() if (attacker->is_player()) { // returns whether an aux attack successfully took place + // additional attacks from cleave don't get aux if (!defender->as_monster()->friendly() - && adjacent(defender->pos(), attacker->pos()) - && !cleaving) // additional attacks from cleave don't get aux + && adjacent(defender->pos(), attack_position) + && !cleaving) { player_aux_unarmed(); } @@ -876,6 +895,7 @@ bool melee_attack::handle_phase_end() */ bool melee_attack::attack() { + if (!cleaving && !handle_phase_attempted()) return false; @@ -937,7 +957,7 @@ bool melee_attack::attack() handle_phase_blocked(); else { - if (attacker != defender && adjacent(defender->pos(), attacker->pos())) + if (attacker != defender && adjacent(defender->pos(), attack_position)) { // Check for defender Spines do_spines(); @@ -973,7 +993,7 @@ bool melee_attack::attack() // Remove sanctuary if - through some attack - it was violated. if (env.sanctuary_time > 0 && attack_occurred && !cancel_attack && attacker != defender && !attacker->confused() - && (is_sanctuary(attacker->pos()) || is_sanctuary(defender->pos())) + && (is_sanctuary(attack_position) || is_sanctuary(defender->pos())) && (attacker->is_player() || attacker->as_monster()->friendly())) { remove_sanctuary(true); @@ -1802,7 +1822,8 @@ int melee_attack::player_apply_final_multipliers(int damage) //cleave damage modifier if (cleaving) damage = cleave_damage_mod(damage); - + else if (jumping_attack) + damage = jump_damage_mod(damage); // not additive, statues are supposed to be bad with tiny toothpicks but // deal crushing blows with big weapons if (you.form == TRAN_STATUE) @@ -2777,11 +2798,11 @@ void melee_attack::chaos_affects_attacker() coord_def dest(-1, -1); // Prefer to send it under the defender. - if (defender->alive() && defender->pos() != attacker->pos()) + if (defender->alive() && defender->pos() != attack_position) dest = defender->pos(); // Move stairs out from under the attacker. - if (one_chance_in(100) && move_stairs(attacker->pos(), dest)) + if (one_chance_in(100) && move_stairs(attack_position, dest)) { #ifdef NOTE_DEBUG_CHAOS_EFFECTS take_note(Note(NOTE_MESSAGE, 0, 0, @@ -2793,7 +2814,7 @@ void melee_attack::chaos_affects_attacker() // Dump attacker or items under attacker to another level. if (is_valid_shaft_level() && (attacker->will_trigger_shaft() - || igrd(attacker->pos()) != NON_ITEM) + || igrd(attack_position) != NON_ITEM) && one_chance_in(1000)) { (void) attacker->do_shaft(); @@ -2808,7 +2829,7 @@ void melee_attack::chaos_affects_attacker() if (weapon && one_chance_in(1000)) { mprf("Smoke pours forth from %s!", wep_name(DESC_YOUR).c_str()); - big_cloud(random_smoke_type(), &you, attacker->pos(), 20, + big_cloud(random_smoke_type(), &you, attack_position, 20, 4 + random2(8)); #ifdef NOTE_DEBUG_CHAOS_EFFECTS take_note(Note(NOTE_MESSAGE, 0, 0, @@ -2818,7 +2839,7 @@ void melee_attack::chaos_affects_attacker() } // Make a loud noise. - if (weapon && player_can_hear(attacker->pos()) + if (weapon && player_can_hear(attack_position) && one_chance_in(200)) { string msg = ""; @@ -2843,7 +2864,7 @@ void melee_attack::chaos_affects_attacker() if (!msg.empty()) { mpr(msg.c_str(), MSGCH_SOUND); - noisy(15, attacker->pos(), attacker->mindex()); + noisy(15, attack_position, attacker->mindex()); #ifdef NOTE_DEBUG_CHAOS_EFFECTS take_note(Note(NOTE_MESSAGE, 0, 0, "CHAOS affects attacker: noise"), true); @@ -4047,6 +4068,9 @@ void melee_attack::player_stab_check() break; } + if (stab_bonus && jumping_attack) + stab_bonus = min(6, stab_bonus + 2); + // See if we need to roll against dexterity / stabbing. if (stab_attempt && roll_needed) { @@ -4301,7 +4325,7 @@ string melee_attack::mons_attack_desc() return ""; string ret; - int dist = (attacker->pos() - defender->pos()).abs(); + int dist = (attack_position - defender->pos()).abs(); if (dist > 2) { ASSERT(can_reach()); @@ -5255,7 +5279,7 @@ bool melee_attack::do_knockback(bool trample) break; coord_def old_pos = defender->pos(); - coord_def new_pos = defender->pos() + defender->pos() - attacker->pos(); + coord_def new_pos = defender->pos() + defender->pos() - attack_position; // need a valid tile if (grd(new_pos) < DNGN_SHALLOW_WATER @@ -5333,6 +5357,12 @@ int melee_attack::cleave_damage_mod(int dam) return div_rand_round(dam * 3, 4); } +// jump attack modifier: 120% of base damage +int melee_attack::jump_damage_mod(int dam) +{ + return div_rand_round(dam * 6, 5); +} + void melee_attack::chaos_affect_actor(actor *victim) { melee_attack attk(victim, victim); @@ -5764,7 +5794,7 @@ bool melee_attack::_player_vampire_draws_blood(const monster* mon, const int dam ASSERT(you.species == SP_VAMPIRE); if (!_vamp_wants_blood_from_monster(mon) || - (!adjacent(defender->pos(), attacker->pos()) && needs_bite_msg)) + (!adjacent(defender->pos(), attack_position) && needs_bite_msg)) { return false; } diff --git a/crawl-ref/source/melee_attack.h b/crawl-ref/source/melee_attack.h index bd44533..ddd3005 100644 --- a/crawl-ref/source/melee_attack.h +++ b/crawl-ref/source/melee_attack.h @@ -45,6 +45,9 @@ public: bool can_cleave; list<actor*> cleave_targets; bool cleaving; // additional attack from cleaving + bool jumping_attack; + bool jump_blocked; + coord_def attack_position; // Miscast to cause after special damage is done. If miscast_level == 0 // the miscast is discarded if special_damage_message isn't empty. @@ -57,7 +60,9 @@ public: public: melee_attack(actor *attacker, actor *defender, int attack_num = -1, int effective_attack_num = -1, - bool is_cleaving = false); + bool is_cleaving = false, bool is_jump_attack = false, + bool is_jump_blocked = false, + coord_def attack_pos = coord_def(0, 0)); // Applies attack damage and other effects. bool attack(); @@ -106,6 +111,8 @@ private: /* Axe cleaving */ void cleave_setup(); int cleave_damage_mod(int dam); + int jump_damage_mod(int dam); + int jump_additional_damage_mod(int dam); /* Mutation Effects */ void do_spines(); diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc index dc3354f..635c68c 100644 --- a/crawl-ref/source/misc.cc +++ b/crawl-ref/source/misc.cc @@ -2253,18 +2253,34 @@ int speed_to_duration(int speed) return div_rand_round(100, speed); } -bool bad_attack(const monster *mon, string& adj, string& suffix) +bool bad_attack(const monster *mon, string& adj, string& suffix, + bool jump_check_landing, coord_def attack_pos) { ASSERT(!crawl_state.game_is_arena()); + bool jump_landing_warning = false; + coord_def attack_position; + if (!you.can_see(mon)) return false; + if (!jump_check_landing) + attack_pos = you.pos(); + adj.clear(); suffix.clear(); - if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos())) - suffix = ", despite your sanctuary"; - + if (is_sanctuary(attack_pos) || is_sanctuary(mon->pos())) + { + if (jump_check_landing) + { + suffix = ", when you might land in your sanctuary"; + jump_landing_warning = true; + } + else + { + suffix = ", despite your sanctuary"; + } + } if (mon->friendly()) { if (you.religion == GOD_OKAWARU) @@ -2290,13 +2306,15 @@ bool bad_attack(const monster *mon, string& adj, string& suffix) { return true; } - - return !adj.empty() || !suffix.empty(); + + return (jump_check_landing && jump_landing_warning) + || (!adj.empty() || !suffix.empty()); } bool stop_attack_prompt(const monster* mon, bool beam_attack, coord_def beam_target, bool autohit_first, - bool *prompted) + bool *prompted, bool jump_check_landing, + coord_def attack_pos) { if (prompted) *prompted = false; @@ -2308,7 +2326,7 @@ bool stop_attack_prompt(const monster* mon, bool beam_attack, return false; string adj, suffix; - if (!bad_attack(mon, adj, suffix)) + if (!bad_attack(mon, adj, suffix, jump_check_landing, attack_pos)) return false; // Listed in the form: "your rat", "Blork the orc". diff --git a/crawl-ref/source/misc.h b/crawl-ref/source/misc.h index 383f616..c079bdc 100644 --- a/crawl-ref/source/misc.h +++ b/crawl-ref/source/misc.h @@ -113,10 +113,14 @@ bool scramble(void); bool interrupt_cmd_repeat(activity_interrupt_type ai, const activity_interrupt_data &at); -bool bad_attack(const monster *mon, string& adj, string& suffix); +bool bad_attack(const monster *mon, string& adj, string& suffix, + bool jump_check_landing = false, + coord_def attack_pos = coord_def(0, 0)); bool stop_attack_prompt(const monster* mon, bool beam_attack, coord_def beam_target, bool autohit_first = false, - bool *prompted = nullptr); + bool *prompted = nullptr, + bool jump_check_landing = false, + coord_def attack_pos = coord_def(0, 0)); bool stop_attack_prompt(targetter &hitfunc, string verb, bool (*affects)(const actor *victim) = 0); diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc index ca223ed..712e7cb 100644 --- a/crawl-ref/source/monster.cc +++ b/crawl-ref/source/monster.cc @@ -4775,6 +4775,24 @@ bool monster::can_go_berserk() const return true; } +bool monster::can_jump() const +{ + if (mons_intel(this) == I_PLANT) + return false; + + if (swimming()) + return false; + + if (paralysed() || petrified() || petrifying() || asleep()) + return false; + + if (has_ench(ENCH_FATIGUE)) + return false; + + + return true; +} + bool monster::berserk() const { return (has_ench(ENCH_BERSERK) || has_ench(ENCH_INSANE)); diff --git a/crawl-ref/source/monster.h b/crawl-ref/source/monster.h index c2a4ba2..1dc6483 100644 --- a/crawl-ref/source/monster.h +++ b/crawl-ref/source/monster.h @@ -299,6 +299,7 @@ public: void attacking(actor *other); bool can_go_berserk() const; + bool can_jump() const; void go_berserk(bool intentional, bool potion = false); void go_frenzy(); bool berserk() const; diff --git a/crawl-ref/source/mutation-data.h b/crawl-ref/source/mutation-data.h index 622936c..5ddd1c8 100644 --- a/crawl-ref/source/mutation-data.h +++ b/crawl-ref/source/mutation-data.h @@ -414,6 +414,24 @@ "breathe flames" }, +{ MUT_JUMP, 4, 3, false, false, false, + "jump", + + {"You can jump attack at a short distance.", + "You can jump attack at a medium distance.", + "You can jump attack at a long distance."}, + + {"You feel sure on your feet.", + "You feel sure on your feet.", + "You feel sure on your feet."}, + + {"You feel less sure on your feet.", + "You feel less sure on your feet.", + "You feel less sure on your feet."}, + + "jump" +}, + { MUT_BLINK, 3, 3, false, false, false, "blink", diff --git a/crawl-ref/source/mutation.cc b/crawl-ref/source/mutation.cc index c77b937..bf99837 100644 --- a/crawl-ref/source/mutation.cc +++ b/crawl-ref/source/mutation.cc @@ -1656,7 +1656,6 @@ bool mutate(mutation_type which_mutation, const string &reason, bool failMsg, case MUT_HIGH_MAGIC: calc_mp(); break; - case MUT_PASSIVE_MAPPING: add_daction(DACT_REAUTOMAP); break; diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc index 6cd668d..2ec3c63 100644 --- a/crawl-ref/source/newgame.cc +++ b/crawl-ref/source/newgame.cc @@ -982,7 +982,7 @@ static void _construct_backgrounds_menu(const newgame_def* ng, "Warrior", coord_def(0, 0), 15, {JOB_FIGHTER, JOB_GLADIATOR, JOB_MONK, JOB_HUNTER, JOB_ASSASSIN, - JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} + JOB_DRAGOON, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} }, { "Adventurer", @@ -1730,6 +1730,7 @@ static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice, case JOB_WARPER: case JOB_HUNTER: case JOB_ARCANE_MARKSMAN: + case JOB_DRAGOON: break; default: return true; diff --git a/crawl-ref/source/ng-restr.cc b/crawl-ref/source/ng-restr.cc index 8271a8e..ec28fd2 100644 --- a/crawl-ref/source/ng-restr.cc +++ b/crawl-ref/source/ng-restr.cc @@ -553,7 +553,21 @@ char_choice_restriction job_allowed(species_type speci, job_type job) default: return CC_UNRESTRICTED; } - + case JOB_DRAGOON: + switch (speci) + { + case SP_CENTAUR: + case SP_DJINNI: + case SP_OCTOPODE: + case SP_OGRE: + case SP_NAGA: + case SP_SPRIGGAN: + case SP_TENGU: + case SP_TROLL: + return CC_RESTRICTED; + default: + return CC_UNRESTRICTED; + } case JOB_WANDERER: return CC_RESTRICTED; diff --git a/crawl-ref/source/ng-setup.cc b/crawl-ref/source/ng-setup.cc index ba978e4..de1bd73 100644 --- a/crawl-ref/source/ng-setup.cc +++ b/crawl-ref/source/ng-setup.cc @@ -162,6 +162,7 @@ static void _jobs_stat_init(job_type which_job) case JOB_FIGHTER: s = 8; i = 0; d = 4; hp = 15; mp = 0; break; case JOB_BERSERKER: s = 9; i = -1; d = 4; hp = 15; mp = 0; break; case JOB_GLADIATOR: s = 7; i = 0; d = 5; hp = 14; mp = 0; break; + case JOB_DRAGOON: s = 6; i = 0; d = 6; hp = 13; mp = 2; break; case JOB_SKALD: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; case JOB_CHAOS_KNIGHT: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; @@ -343,6 +344,7 @@ void give_basic_mutations(species_type speci) you.mutation[MUT_FAST] = 1; you.mutation[MUT_CARNIVOROUS] = 3; you.mutation[MUT_SLOW_METABOLISM] = 1; + you.mutation[MUT_JUMP] = 1; break; case SP_OCTOPODE: you.mutation[MUT_CAMOUFLAGE] = 1; @@ -566,6 +568,28 @@ static void _give_items_skills(const newgame_def& ng) you.skills[SK_DODGING] = 3; weap_skill = 3; break; + case JOB_DRAGOON: + // Equipment. + newgame_make_item(0, EQ_WEAPON, OBJ_WEAPONS, WPN_SHORT_SWORD); + _update_weapon(ng); + newgame_make_item(1, EQ_BODY_ARMOUR, OBJ_ARMOUR, ARM_LEATHER_ARMOUR, + ARM_ANIMAL_SKIN); + newgame_make_item(2, EQ_BOOTS, OBJ_ARMOUR, ARM_BOOTS); + + you.skills[SK_FIGHTING] = 2; + if (you_can_wear(EQ_BOOTS)) + { + you.inv[2].special = SPARM_JUMPING; + you.skills[SK_EVOCATIONS] = 2; + } + else + { + newgame_make_item(2, EQ_HELMET, OBJ_ARMOUR, ARM_HELMET, ARM_CAP); + you.skills[SK_STEALTH] = 2; + } + you.skills[SK_DODGING] = 3; + weap_skill = 3; + break; case JOB_MONK: you.equip[EQ_WEAPON] = -1; // Monks fight unarmed. diff --git a/crawl-ref/source/player-act.cc b/crawl-ref/source/player-act.cc index d5ea690..9982c96 100644 --- a/crawl-ref/source/player-act.cc +++ b/crawl-ref/source/player-act.cc @@ -159,7 +159,7 @@ bool player::is_habitable_feat(dungeon_feature_type actual_grid) const if (airborne() || species == SP_DJINNI) return true; - if (actual_grid == DNGN_LAVA + if (actual_grid == DNGN_LAVA && species != SP_LAVA_ORC || actual_grid == DNGN_DEEP_WATER && !can_swim()) { return false; @@ -720,6 +720,48 @@ bool player::can_go_berserk(bool intentional, bool potion, bool quiet) const return true; } +bool player::can_jump(bool quiet) const +{ + if (duration[DUR_EXHAUSTED]) + { + if (!quiet) + mpr("You're too exhausted to jump."); + // or else they won't notice -- no message here + return false; + } + + if (you.in_water()) + { + if (!quiet) + mpr("You can't jump while in water."); + return false; + } + if (feat_is_lava(grd(you.pos()))) + { + if (!quiet) + mpr("You can't jump while standing in lava."); + return false; + } + if (you.liquefied_ground()) + { + if (!quiet) + mpr("You can't jump while stuck in this mess."); + return false; + } + if (you.is_constricted()) + { + if (!quiet) + mpr("You can't jump while being constricted."); + return false; + } + + return true; +} + +bool player::can_jump() const +{ + return can_jump(false); +} bool player::berserk() const { return (duration[DUR_BERSERK]); diff --git a/crawl-ref/source/player-equip.cc b/crawl-ref/source/player-equip.cc index 7749b7a..ce99ca5 100644 --- a/crawl-ref/source/player-equip.cc +++ b/crawl-ref/source/player-equip.cc @@ -315,6 +315,17 @@ static void _equip_artefact_effect(item_def &item, bool *show_msgs, bool unmeld) mpr("You feel a build-up of mutagenic energy."); artefact_wpn_learn_prop(item, ARTP_MUTAGENIC); } + if (unknown_proprt(ARTP_JUMP)) + { + if (msg) + { + if (player_jump_level(true) > player_jump_level(false)) + mpr("You feel more sure on your feet."); + else + mpr("Your feet feel light for a moment."); + } + artefact_wpn_learn_prop(item, ARTP_JUMP); + } if (!unmeld && !item.cursed() && proprt[ARTP_CURSED] > 0 && one_chance_in(proprt[ARTP_CURSED])) @@ -929,6 +940,11 @@ static void _equip_armour_effect(item_def& arm, bool unmeld) mpr("You feel rather light."); break; + case SPARM_JUMPING: + if (you.evokable_jump()) + mpr("You feel more sure on your feet."); + break; + case SPARM_MAGIC_RESISTANCE: mpr("You feel resistant to hostile enchantments."); break; diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index 592193d..dbc53bd 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -127,7 +127,8 @@ static void _moveto_maybe_repel_stairs() } } -static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) +static bool _check_moveto_cloud(const coord_def& p, const string &move_verb, + bool interactive = true) { const int cloud = env.cgrid(p); if (cloud != EMPTY_CLOUD && !you.confused()) @@ -151,22 +152,29 @@ static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) return true; } - string prompt = make_stringf("Really %s into that cloud of %s?", - move_verb.c_str(), - cloud_name_at_index(cloud).c_str()); - learned_something_new(HINT_CLOUD_WARNING); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { + string prompt = make_stringf("Really %s into that cloud of %s?", + move_verb.c_str(), + cloud_name_at_index(cloud).c_str()); + learned_something_new(HINT_CLOUD_WARNING); + + if (!yesno(prompt.c_str(), false, 'n')) + { canned_msg(MSG_OK); return false; + } + } else + { + return false; } } } return true; } -static bool _check_moveto_trap(const coord_def& p, const string &move_verb) + static bool _check_moveto_trap(const coord_def& p, const string &move_verb, + bool interactive = true) { // If there's no trap, let's go. trap_def* trap = find_trap(p); @@ -175,28 +183,40 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) if (trap->type == TRAP_ZOT && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Do you really want to %s into the Zot trap", - move_verb.c_str()); - - if (!yes_or_no("%s", prompt.c_str())) + if (interactive) + { + string prompt = make_stringf("Do you really want to %s into the Zot trap", + move_verb.c_str()); + if (!yes_or_no("%s", prompt.c_str())) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } else if (!trap->is_safe() && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Really %s %s that %s?", - move_verb.c_str(), - (trap->type == TRAP_ALARM || trap->type == TRAP_PLATE) ? "onto" - : "into", - feature_description_at(p, false, DESC_BASENAME, false).c_str()); - - if (!yesno(prompt.c_str(), true, 'n')) + if (interactive) + { + string prompt = make_stringf( + "Really %s %s that %s?", + move_verb.c_str(), + (trap->type == TRAP_ALARM + || trap->type == TRAP_PLATE) ? "onto" + : "into", + feature_description_at(p, false, + DESC_BASENAME, + false).c_str()); + if (!yesno(prompt.c_str(), true, 'n')) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } @@ -204,7 +224,7 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) } static bool _check_moveto_dangerous(const coord_def& p, const string& msg, - bool cling = true) + bool cling = true, bool interactive = true) { if (you.can_swim() && feat_is_water(env.grid(p)) || you.airborne() || cling && you.can_cling_to(p) @@ -213,28 +233,30 @@ static bool _check_moveto_dangerous(const coord_def& p, const string& msg, return true; } - if (msg != "") - mpr(msg.c_str()); - else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) - mpr("You cannot swim in your current form."); - else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) - && is_feat_dangerous(env.grid(p))) + if (interactive) { - mpr("You cannot enter lava in your current form."); + if (msg != "") + mpr(msg.c_str()); + else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) + mpr("You cannot swim in your current form."); + else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) + && is_feat_dangerous(env.grid(p))) + { + mpr("You cannot enter lava in your current form."); + } + else + canned_msg(MSG_UNTHINKING_ACT); } - else - canned_msg(MSG_UNTHINKING_ACT); - return false; } static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive = true) { if (you.is_wall_clinging() && (move_verb == "blink" || move_verb == "passwall")) { - return _check_moveto_dangerous(p, msg, false); + return _check_moveto_dangerous(p, msg, false, interactive); } if (!need_expiration_warning() && need_expiration_warning(p) @@ -243,60 +265,69 @@ static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, if (!_check_moveto_dangerous(p, msg)) return false; - string prompt; + if (interactive) + { + string prompt; - if (msg != "") - prompt = msg + " "; + if (msg != "") + prompt = msg + " "; - prompt += "Are you sure you want to " + move_verb; + prompt += "Are you sure you want to " + move_verb; - if (you.ground_level()) - prompt += " into "; - else - prompt += " over "; + if (you.ground_level()) + prompt += " into "; + else + prompt += " over "; - prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; + prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; - prompt += need_expiration_warning(DUR_FLIGHT, p) - ? " while you are losing your buoyancy?" - : " while your transformation is expiring?"; + prompt += need_expiration_warning(DUR_FLIGHT, p) + ? " while you are losing your buoyancy?" + : " while your transformation is expiring?"; - if (!yesno(prompt.c_str(), false, 'n')) - { - canned_msg(MSG_OK); - return false; + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } - return _check_moveto_dangerous(p, msg); + return _check_moveto_dangerous(p, msg, true, interactive); } -static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb) +static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb, + bool interactive = true) { + string prompt; + if (is_excluded(p) && !is_stair_exclusion(p) && !is_excluded(you.pos()) && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf("Really %s into a travel-excluded area?", - move_verb.c_str()); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { - canned_msg(MSG_OK); - return false; + prompt = make_stringf("Really %s into a travel-excluded area?", + move_verb.c_str()); + + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } return true; } bool check_moveto(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive) { - return (_check_moveto_terrain(p, move_verb, msg) - && _check_moveto_cloud(p, move_verb) - && _check_moveto_trap(p, move_verb) - && _check_moveto_exclusion(p, move_verb)); + return (_check_moveto_terrain(p, move_verb, msg, interactive) + && _check_moveto_cloud(p, move_verb, interactive) + && _check_moveto_trap(p, move_verb, interactive) + && _check_moveto_exclusion(p, move_verb, interactive)); } static void _splash() @@ -530,6 +561,22 @@ bool player_genus(genus_type which_genus, species_type species) return (species_genus(species) == which_genus); } +int player_jump_level(bool is_evoke) +{ + int level = 0; + + if (is_evoke) + { + level = 3 + (you.skill(SK_EVOCATIONS, 1) >= 5) + + (you.skill(SK_EVOCATIONS, 1) >= 10); + } + else + { + level = 2 + player_mutation_level(MUT_JUMP); + } + return(level); +} + // If transform is true, compare with current transformation instead // of (or in addition to) underlying species. // (See mon-data.h for species/genus use.) @@ -3595,8 +3642,10 @@ void level_change(int source, const char* aux, bool skip_attribute_increase) } if (you.experience_level == 6 || you.experience_level == 12) + { perma_mutate(MUT_SHAGGY_FUR, 1, "growing up"); - + perma_mutate(MUT_JUMP, 1, "growing up"); + } _felid_extra_life(); break; @@ -4682,6 +4731,7 @@ bool enough_mp(int minimum, bool suppress_msg, bool include_items) return true; } + bool enough_zp(int minimum, bool suppress_msg) { ASSERT(!crawl_state.game_is_arena()); @@ -4843,6 +4893,7 @@ void set_mp(int new_amount) you.redraw_magic_points = true; } + // If trans is true, being berserk and/or transformed is taken into account // here. Else, the base hp is calculated. If rotted is true, calculate the // real max hp you'd have if the rotting was cured. diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h index dddf445..a62d7f9 100644 --- a/crawl-ref/source/player.h +++ b/crawl-ref/source/player.h @@ -562,6 +562,8 @@ public: bool can_go_berserk() const; bool can_go_berserk(bool intentional, bool potion = false, bool quiet = false) const; + bool can_jump() const; + bool can_jump(bool quiet) const; void go_berserk(bool intentional, bool potion = false); bool berserk() const; bool has_lifeforce() const; @@ -791,7 +793,7 @@ void moveto_location_effects(dungeon_feature_type old_feat, const coord_def& old_pos=coord_def()); bool check_moveto(const coord_def& p, const string &move_verb = "step", - const string &msg = ""); + const string &msg = "", bool interactive = true); void move_player_to_grid(const coord_def& p, bool stepped, bool allow_shift); bool is_map_persistent(void); @@ -814,6 +816,7 @@ bool is_effectively_light_armour(const item_def *item); bool player_effectively_in_light_armour(); bool player_under_penance(void); +int player_jump_level(bool is_evoke); int burden_change(void); diff --git a/crawl-ref/source/rltiles/dc-abilities.txt b/crawl-ref/source/rltiles/dc-abilities.txt index fcbf3cc..1557f28 100644 --- a/crawl-ref/source/rltiles/dc-abilities.txt +++ b/crawl-ref/source/rltiles/dc-abilities.txt @@ -18,9 +18,11 @@ evoke_fog ABILITY_EVOKE_FOG evoke_invisibility_end ABILITY_EVOKE_INVISIBILITY_END evoke_invisibility ABILITY_EVOKE_INVISIBILITY flight ABILITY_EVOKE_FLIGHT +jump ABILITY_EVOKE_JUMP evoke_teleport ABILITY_EVOKE_TELEPORT flight_end ABILITY_FLIGHT_END flight ABILITY_FLIGHT +jump ABILITY_JUMP hellfire ABILITY_HELLFIRE mummy_restoration ABILITY_MUMMY_RESTORATION recharge ABILITY_RECHARGE diff --git a/crawl-ref/source/rltiles/dc-feat.txt b/crawl-ref/source/rltiles/dc-feat.txt index d8ee583..3a9f38a 100644 --- a/crawl-ref/source/rltiles/dc-feat.txt +++ b/crawl-ref/source/rltiles/dc-feat.txt @@ -721,6 +721,7 @@ effect/disjunct0 DISJUNCT effect/disjunct1 effect/disjunct2 effect/disjunct3 +landing LANDING effect/heataura0 HEAT_AURA effect/heataura1 effect/heataura2 diff --git a/crawl-ref/source/rltiles/dc-item.txt b/crawl-ref/source/rltiles/dc-item.txt index 1180806..e9b48d5 100644 --- a/crawl-ref/source/rltiles/dc-item.txt +++ b/crawl-ref/source/rltiles/dc-item.txt @@ -606,6 +606,7 @@ i-dexterity BRAND_ARM_DEXTERITY i-intelligence BRAND_ARM_INTELLIGENCE i-ponderous BRAND_ARM_PONDEROUSNESS i-flying BRAND_ARM_FLYING +i-jumping BRAND_ARM_JUMPING i-magic-res BRAND_ARM_MAGIC_RESISTANCE i-protection BRAND_ARM_PROTECTION %rim 0 diff --git a/crawl-ref/source/rltiles/gui/abilities/jump.png b/crawl-ref/source/rltiles/gui/abilities/jump.png new file mode 100644 index 0000000000000000000000000000000000000000..bb80d7233abf4dd4c155c8f598f9a6dfbd06c963 GIT binary patch literal 957 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VB8tt6XFWwQVZ18)v>a&BAaGv zYU=0b2NE&p;^pNP6ciK^5>j+IrlFyssi|pXWMpGwV{dQoarBP2w|7ubP;hW?czAeJ zR8(ACTtY%ZQc_Y%N=kZqdRA6ec6N4tetvOraaC1Sb#--3O%2ex`uh5YhK8o5rk0kL z*4Eaxwzl^6_U`WP-rnB6zP|qc{s|K%Oqw)l%9JTnr%s(QW5&FB^XAW=zhJ?FB}<kp zUAlDn^5v^mty;5Y&DynV*R5N(apT6#n>TOSvSsVmt=qP3+rEAKjvYI8?%cU+*RFm0 z_U+%l|G<F*2M-=Rbm-9G!-tO?Idb&q(PPJs9Y22j#EBCpPo6w=>eT7er_Y=@bN1}n z^XJcBxNzaprAwDDU%q<v>a}avu3x`?>(;H?w{PFMbLZ~eyZ7$hyMO=wg9i^DKYsk= z$&;r~pFVr`?D_NOFJ8QO_3G8@*RS8adGr4L`;Q+#e){z3>({T}zkmPv^XH#GfBydc z`|sbs|Ns9_`8hEd7_f>ZL4Lsuk~&7_>Gcg$X3Si;dH;!1r_Wxwe)Imb_n$uf`1Kp4 ze##S0b6|{5@^o<wskpUvVtDW&2NAdMK#^+Z>YZUBZ}x`2*}Hhcf+nE^U5&^iT@gy( z{v?UtV@j;}c*gGS&$Rv4<rnueZrsC{^dn04$On^&bEiq(eci-)*t0N1-b%r)Ny*DG zcSTCvpLO~ZI9s?h9_;uh^fy(I(d$Bxnas4i^Z6%aa&FusBrs>%=Xk-CXoYpBK9oKE zy!&2jP1Vvf><lj#2}w_n={woH{Hryu(=55?3Gvz<aSoMp?WX4zUi=)|Q?$L~chUwv z6=Q|ehdS@KewXi>ut0Tc%Nf@*lj1%maI%!y6$T0y&i|~u>O(_X(&xHkS&v-qoDu$> zs@b(*LF%8I&*uuczM840+U3Gotsgi2ddvB~n~b897C*W6_*+6w^7U1sMRsx89M`gY z3>WirYiyd-Jom#Z@jQd45&S{x@5;PlYzRFxKWXz^W8=4%7zE}8y!evFe!7t7a7lZu zb<a0H>CSdj*<8!DSMq@-&zLlYm;1??UFY8#PcfWu+o|%of%KJQGwWVG6_P5=EOl<* l$9qLXtLsY7CYdk&e;BiK3wkGX7}<d`iKnZd%Q~loCIG90%-a9} literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png b/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png new file mode 100644 index 0000000000000000000000000000000000000000..006c2a1b1ae0395eef6e8266ee3398fd6e7203b5 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^S0D`piY~`Ij^1(5J%0<x zVJr#q3ubV5b|VeQarSg^43W5;oFH-M0Hc?2T!Yb#?GBPf`3rkq-4}4}dH>x}WaIj1 z_P`_i>g}g|{QFznpxM;KgeB{ocnPy0&kT`l4rYemO~SlSy5p=sHhH@GxvX<aXaWEM C-Y|Or literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/misc/landing.png b/crawl-ref/source/rltiles/misc/landing.png new file mode 100644 index 0000000000000000000000000000000000000000..17d5001279d4e1bcfcb74b824f12a9be9f7265a9 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJd7dtgArYK!L!!A3DDVW|-1z-% zYdOPquBi_$l=46KPr1;QD-=AlKRV{_5mB|G^enCg%q0zc7aAYLelo5<-aVna<4Irs zg;%kg_AqL`66L$}tcyYQN3@4r<D_I4^Lst)g#z)PCo@+yeB@%uHr)PYCJV=khDe6c zhOOHynOIjTGhJVNaPzM9TlecSG|m%9+W+L4;FaF^x0*mJ7_A)G%TzoiFFxqs`3mS# N22WQ%mvv4FO#uI5RRRD2 literal 0 HcmV?d00001 diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc index 92c604a..cc22a8b 100644 --- a/crawl-ref/source/shopping.cc +++ b/crawl-ref/source/shopping.cc @@ -1140,6 +1140,9 @@ int artefact_value(const item_def &item) if (prop[ ARTP_BERSERK ]) ret += 5; + if (prop[ ARTP_JUMP ]) + ret += 5; + if (prop[ ARTP_INVISIBLE ]) ret += 20; @@ -1704,6 +1707,7 @@ unsigned int item_value(item_def item, bool ident) case SPARM_SEE_INVISIBLE: case SPARM_INTELLIGENCE: case SPARM_FLYING: + case SPARM_JUMPING: case SPARM_PRESERVATION: case SPARM_STEALTH: case SPARM_STRENGTH: diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc index c9f555b..0d770d9 100644 --- a/crawl-ref/source/target.cc +++ b/crawl-ref/source/target.cc @@ -11,6 +11,7 @@ #include "itemprop.h" #include "libutil.h" #include "losglobal.h" +#include "player.h" #include "spl-damage.h" #include "terrain.h" @@ -52,6 +53,11 @@ bool targetter::anyone_there(coord_def loc) return actor_at(loc); } +bool targetter::has_additional_sites(coord_def loc, bool allow_harmful) +{ + return false; +} + targetter_beam::targetter_beam(const actor *act, int range, zap_type zap, int pow, int min_ex_rad, int max_ex_rad) : min_expl_rad(min_ex_rad), @@ -875,3 +881,230 @@ aff_type targetter_spray::is_affected(coord_def loc) return affected; } + +targetter_jump::targetter_jump(const actor* act, int range) +{ + ASSERT(act); + agent = act; + origin = act->pos(); + range2 = dist_range(range); + jump_is_blocked = false; +} + +bool targetter_jump::valid_aim(coord_def a) +{ + actor *act; + bool is_valid = true; + coord_def c, jump_pos; + ray_def ray; + + act = actor_at(a); + if ((origin - a).abs() > range2) + { + is_valid = notify_fail("Out of range."); + } + // If there's an actor we can see, check that we have at least one valid + // landing site for a jump attack based on what the agent can see. + else if (act && agent->can_see(act)) + { + if(!has_additional_sites(a, true)) + { + if (no_landing_reason == BLOCKED_FLYING) + is_valid = notify_fail("A flying creature is in the way."); + else if (no_landing_reason == BLOCKED_GIANT) + is_valid = notify_fail("A giant creature is in the way."); + else if (no_landing_reason == BLOCKED_MOVE) + is_valid = notify_fail("There is no safe place to move near the" + " monster."); + else if (no_landing_reason == BLOCKED_PATH) + is_valid = notify_fail("A dungeon feature is in the way."); + } + return is_valid; + } + else if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS)) + { + if (agent->see_cell(a)) + is_valid = notify_fail("There's something in the way."); + else + is_valid = notify_fail("You cannot see that place."); + } + else if (feat_is_solid(grd(a))) + { + is_valid = notify_fail("There's something in the way."); + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + is_valid = notify_fail("A dungeon feature is in the way"); + return is_valid; + } + return is_valid; +} + +bool targetter_jump::valid_landing(coord_def a, bool check_invis) +{ + actor *act; + ray_def ray; + + if (grd(a) == DNGN_OPEN_SEA || grd(a) == DNGN_LAVA_SEA + || !agent->is_habitable(a) || (origin - a).abs() > range2 - 1) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + if (agent->is_player()) + { + monster* beholder = you.get_beholder(a); + if (beholder) + { + return false; + blocked_landing_reason = BLOCKED_MOVE; + } + + monster* fearmonger = you.get_fearmonger(a); + if (fearmonger) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + blocked_landing_reason = BLOCKED_PATH; + return false; + } + // Check if a landing site is invalid due to a visible monster obstructing + // the path. + ray.advance(); + while(map_bounds(ray.pos())) + { + act = actor_at(ray.pos()); + if (ray.pos() == a) + { + if (act && (!check_invis || agent->can_see(act))) + { + blocked_landing_reason = BLOCKED_OCCUPIED; + return false; + } + break; + } + const dungeon_feature_type grid = grd(ray.pos()); + if (act && (!check_invis || agent->can_see(act))) + { + + // Can't jump over airborn enemies nor gient enemies not in deep + // water or lava. + if (act->airborne()) + { + blocked_landing_reason = BLOCKED_FLYING; + return false; + } + else if(act->body_size() == SIZE_GIANT + && grid != DNGN_DEEP_WATER && grid != DNGN_LAVA) + { + blocked_landing_reason = BLOCKED_GIANT; + return false; + } + } + ray.advance(); + } + return true; +} + +aff_type targetter_jump::is_affected(coord_def loc) +{ + aff_type aff = AFF_NO; + + if (loc == aim) + aff = AFF_YES; + else if (additional_sites.count(loc)) + aff = AFF_LANDING; + return aff; +} + +// Handle setting the aim for jump, which is the landing position of the jump. +// If something unsee either occupies the aim positiion or blocks the jump path, +// indicate that with jump_is_blocked. +bool targetter_jump::set_aim(coord_def a) +{ + actor *act; + ray_def ray; + set<coord_def>::const_iterator site; + + if (a == origin) + return false; + if (!targetter::set_aim(a)) + return false; + + jump_is_blocked = false; + act = actor_at(aim); + // Can't set aim if we can't see anything at the location. + if (!env.map_knowledge(aim).invisible_monster() + && (!act || !agent->can_see(act))) + return false; + // Find our set of landing sites, choose one at random, and see if it's + // actually blocked. + set_additional_sites(aim); + if (additional_sites.size()) + { + int site_ind = random2(additional_sites.size()); + for (site = additional_sites.begin(); site_ind > 0; site++) + site_ind--; + landing_site = *site; + if (!valid_landing(landing_site, false)) + jump_is_blocked = true; + return true; + } + return false; +} + +void targetter_jump::set_additional_sites(coord_def a) +{ + get_additional_sites(a); + additional_sites = temp_sites; +} + +void targetter_jump::get_additional_sites(coord_def a) +{ + bool agent_adjacent = a.distance_from(agent->pos()) == 1; + temp_sites.clear(); + + no_landing_reason = BLOCKED_NONE; + for (adjacent_iterator ai(a, false); ai; ++ai) + { + // See if site is valid, record a putative reason for why no sites were + // found. A flying or giant monster blocking the landing site gets + // priority as an reason, since it's very jump-specific. + if (!agent_adjacent || agent->pos().distance_from(*ai) > 1) + { + if (valid_landing(*ai)) + { + temp_sites.insert(*ai); + no_landing_reason = BLOCKED_NONE; + } + else if (no_landing_reason != BLOCKED_FLYING + && no_landing_reason != BLOCKED_GIANT) + { + no_landing_reason = blocked_landing_reason; + } + } + } +} + +// See if we can find at least one valid landing position for the +// given monster. Possibly we also care to exclude sites harmful to +// the player. +bool targetter_jump::has_additional_sites(coord_def a, bool allow_harmful) +{ + set<coord_def>::const_iterator site; + get_additional_sites(a); + // Find a valid jump_attack position in the adjacent squares, chosing the + // valid position closest to the player. + for (site = temp_sites.begin(); + site != temp_sites.end(); site++) + { + if (allow_harmful || (agent->is_player() + && check_moveto(*site, "jump", "", false))) + return true; + } + return false; +} diff --git a/crawl-ref/source/target.h b/crawl-ref/source/target.h index 0ec6882..d2aa800 100644 --- a/crawl-ref/source/target.h +++ b/crawl-ref/source/target.h @@ -12,6 +12,7 @@ enum aff_type // sign and non-zeroness matters AFF_YES, // intended/likely to affect // If you want to extend this to pass the probability somehow, feel free to, // just keep AFF_YES the minimal "bright" value. + AFF_LANDING, // Valid jump attack landing site }; class targetter @@ -30,6 +31,7 @@ public: virtual bool can_affect_walls(); virtual aff_type is_affected(coord_def loc) = 0; + virtual bool has_additional_sites(coord_def a, bool allow_harmful); protected: bool anyone_there(coord_def loc); }; @@ -188,4 +190,39 @@ private: int range2; }; +enum jump_block_reason +{ + BLOCKED_NONE, + BLOCKED_OCCUPIED, + BLOCKED_FLYING, + BLOCKED_GIANT, + BLOCKED_MOVE, + BLOCKED_PATH, +}; + +class targetter_jump : public targetter +{ +public: + targetter_jump(const actor* act, int range); + + bool valid_aim(coord_def a); + bool set_aim(coord_def a); + bool jump_is_blocked; + aff_type is_affected(coord_def loc); + bool has_additional_sites(coord_def a, bool allow_harmful); + set<coord_def> additional_sites; + coord_def landing_site; +private: + void set_additional_sites(coord_def a); + void get_additional_sites(coord_def a); + bool valid_landing(coord_def a, bool check_invis = true); + jump_block_reason no_landing_reason; + jump_block_reason blocked_landing_reason; + set<coord_def> temp_sites; + int _range; + int range2; +}; + + + #endif diff --git a/crawl-ref/source/tiledgnbuf.cc b/crawl-ref/source/tiledgnbuf.cc index cf4483f..de57a9e 100644 --- a/crawl-ref/source/tiledgnbuf.cc +++ b/crawl-ref/source/tiledgnbuf.cc @@ -288,6 +288,8 @@ void DungeonCellBuffer::pack_background(int x, int y, const packed_cell &cell) m_buf_feat.add(TILE_RAY, x, y); else if (bg & TILE_FLAG_RAY_OOR) m_buf_feat.add(TILE_RAY_OUT_OF_RANGE, x, y); + else if (bg & TILE_FLAG_LANDING) + m_buf_feat.add(TILE_LANDING, x, y); } } diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index 2f9b867..764d543 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -5149,6 +5149,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_SPIT_ACID; case ABIL_BLINK: return TILEG_ABILITY_BLINK; + case ABIL_JUMP: + return TILEG_ABILITY_JUMP; // Others case ABIL_DELAYED_FIREBALL: @@ -5192,6 +5194,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_EVOKE_INVISIBILITY_END; case ABIL_EVOKE_FLIGHT: return TILEG_ABILITY_EVOKE_FLIGHT; + case ABIL_EVOKE_JUMP: + return TILEG_ABILITY_EVOKE_JUMP; case ABIL_EVOKE_FOG: return TILEG_ABILITY_EVOKE_FOG; diff --git a/crawl-ref/source/tileview.cc b/crawl-ref/source/tileview.cc index 5718aef..9c898e1 100644 --- a/crawl-ref/source/tileview.cc +++ b/crawl-ref/source/tileview.cc @@ -966,10 +966,16 @@ void tile_place_ray(const coord_def &gc, aff_type in_range) void tile_draw_rays(bool reset_count) { + tileidx_t flag; + for (unsigned int i = 0; i < num_tile_rays; i++) { - tileidx_t flag = tile_ray_vec[i].in_range > AFF_MAYBE ? TILE_FLAG_RAY - : TILE_FLAG_RAY_OOR; + if (tile_ray_vec[i].in_range < AFF_YES) + flag = TILE_FLAG_RAY_OOR; + else if (tile_ray_vec[i].in_range == AFF_YES) + flag = TILE_FLAG_RAY; + else if (tile_ray_vec[i].in_range == AFF_LANDING) + flag = TILE_FLAG_LANDING; env.tile_bg(tile_ray_vec[i].ep) |= flag; } diff --git a/crawl-ref/source/webserver/game_data/static/cell_renderer.js b/crawl-ref/source/webserver/game_data/static/cell_renderer.js index 73df042..c3a2b97 100644 --- a/crawl-ref/source/webserver/game_data/static/cell_renderer.js +++ b/crawl-ref/source/webserver/game_data/static/cell_renderer.js @@ -662,6 +662,9 @@ function ($, view_data, main, tileinfo_player, icons, dngn, enums, map_knowledge this.draw_dngn(dngn.RAY, x, y); else if (bg.RAY_OOR) this.draw_dngn(dngn.RAY_OUT_OF_RANGE, x, y); + else if (bg.LANDING) + this.draw_dngn(dngn.LANDING, x, y); + } }, diff --git a/crawl-ref/source/webserver/game_data/static/enums.js b/crawl-ref/source/webserver/game_data/static/enums.js index 18ec2d9..cde3a92 100644 --- a/crawl-ref/source/webserver/game_data/static/enums.js +++ b/crawl-ref/source/webserver/game_data/static/enums.js @@ -239,7 +239,7 @@ define(function () { bg_flags.flags.ELDRITCH_NE = [0, 0x04]; bg_flags.flags.ELDRITCH_SE = [0, 0x08]; bg_flags.flags.ELDRITCH_SW = [0, 0x10]; - + bg_flags.flags.LANDING = [0, 0x200]; bg_flags.mask = 0x0000FFFF; exports.prepare_fg_flags = function (tileidx) diff --git a/crawl-ref/source/wiz-item.cc b/crawl-ref/source/wiz-item.cc index ffef8ee..085d915 100644 --- a/crawl-ref/source/wiz-item.cc +++ b/crawl-ref/source/wiz-item.cc @@ -262,6 +262,7 @@ static const char* _prop_name[] = { "SInv", "Inv", "Fly", + "Jump", "Blnk", "Bers", "Nois", @@ -305,6 +306,7 @@ static int8_t _prop_type[] = { ARTP_VAL_BOOL, //EYESIGHT ARTP_VAL_BOOL, //INVISIBLE ARTP_VAL_BOOL, //FLIGHT + ARTP_VAL_BOOL, //JUMPING ARTP_VAL_BOOL, //BLINK ARTP_VAL_BOOL, //BERSERK ARTP_VAL_POS, //NOISES @@ -1166,6 +1168,7 @@ static void _debug_acquirement_stats(FILE *ostat) "intelligence", "ponderous", "flight", + "jumping", "magic reistance", "protection", "stealth", diff --git a/crawl-ref/source/wiz-you.cc b/crawl-ref/source/wiz-you.cc index 096893e..03b26f4 100644 --- a/crawl-ref/source/wiz-you.cc +++ b/crawl-ref/source/wiz-you.cc @@ -284,6 +284,7 @@ void wizard_heal(bool super_heal) you.duration[DUR_CONF] = 0; you.duration[DUR_MISLED] = 0; you.duration[DUR_POISONING] = 0; + you.duration[DUR_EXHAUSTED] = 0; set_hp(you.hp_max); set_mp(you.max_magic_points); set_hunger(10999, true); -- 1.7.4.4 ![]() From b167c5554a49ae480267d1fbee874234d4156fa2 Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Fri, 14 Jun 2013 11:58:51 -0500 Subject: [PATCH] Make +Jump get added to randartes (same frequency as +Fly/Blink/Rage), arte property description fix, whitespace cleanups --- crawl-ref/source/abl-show.cc | 91 ++++++- crawl-ref/source/actor.cc | 10 + crawl-ref/source/actor.h | 2 + crawl-ref/source/artefact.cc | 9 + crawl-ref/source/dat/descript/ability.txt | 22 ++- crawl-ref/source/dat/descript/backgrounds.txt | 5 + crawl-ref/source/describe.cc | 6 + crawl-ref/source/directn.cc | 261 +++++++++++++------- crawl-ref/source/directn.h | 7 +- crawl-ref/source/enum.h | 11 +- crawl-ref/source/fight.cc | 89 +++++++- crawl-ref/source/fight.h | 6 +- crawl-ref/source/itemname.cc | 2 + crawl-ref/source/itemprop-enum.h | 2 +- crawl-ref/source/itemprop.cc | 3 +- crawl-ref/source/jobs.cc | 4 +- crawl-ref/source/main.cc | 1 - crawl-ref/source/makeitem.cc | 1 + crawl-ref/source/mapdef.cc | 1 + crawl-ref/source/melee_attack.cc | 84 +++++-- crawl-ref/source/melee_attack.h | 9 +- crawl-ref/source/misc.cc | 32 ++- crawl-ref/source/misc.h | 8 +- crawl-ref/source/monster.cc | 18 ++ crawl-ref/source/monster.h | 1 + crawl-ref/source/mutation-data.h | 18 ++ crawl-ref/source/mutation.cc | 1 - crawl-ref/source/newgame.cc | 3 +- crawl-ref/source/ng-restr.cc | 16 ++- crawl-ref/source/ng-setup.cc | 24 ++ crawl-ref/source/player-act.cc | 44 ++++- crawl-ref/source/player-equip.cc | 16 ++ crawl-ref/source/player.cc | 183 +++++++++----- crawl-ref/source/player.h | 5 +- crawl-ref/source/rltiles/dc-abilities.txt | 2 + crawl-ref/source/rltiles/dc-feat.txt | 1 + crawl-ref/source/rltiles/dc-item.txt | 1 + crawl-ref/source/rltiles/gui/abilities/jump.png | Bin 0 -> 957 bytes .../rltiles/item/armour/brands/i-jumping.png | Bin 0 -> 161 bytes crawl-ref/source/rltiles/misc/landing.png | Bin 0 -> 215 bytes crawl-ref/source/shopping.cc | 4 + crawl-ref/source/target.cc | 233 +++++++++++++++++ crawl-ref/source/target.h | 37 +++ crawl-ref/source/tiledgnbuf.cc | 2 + crawl-ref/source/tilepick.cc | 4 + crawl-ref/source/tileview.cc | 10 +- .../webserver/game_data/static/cell_renderer.js | 3 + .../source/webserver/game_data/static/enums.js | 2 +- crawl-ref/source/wiz-item.cc | 3 + crawl-ref/source/wiz-you.cc | 1 + 50 files changed, 1082 insertions(+), 216 deletions(-) create mode 100644 crawl-ref/source/rltiles/gui/abilities/jump.png create mode 100644 crawl-ref/source/rltiles/item/armour/brands/i-jumping.png create mode 100644 crawl-ref/source/rltiles/misc/landing.png diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc index 62cee08..b23cdf1 100644 --- a/crawl-ref/source/abl-show.cc +++ b/crawl-ref/source/abl-show.cc @@ -40,6 +40,7 @@ #include "evoke.h" #include "macro.h" #include "maps.h" +#include "melee_attack.h" #include "message.h" #include "menu.h" #include "misc.h" @@ -109,6 +110,7 @@ static void _pay_ability_costs(const ability_def& abil, int zpcost); static int _scale_piety_cost(ability_type abil, int original_cost); static string _zd_mons_description_for_ability(const ability_def &abil); static monster_type _monster_for_ability(const ability_def& abil); +static bool _jump_player(int jump_range); /** * This all needs to be split into data/util/show files @@ -223,6 +225,7 @@ static const ability_def Ability_List[] = { ABIL_FLY, "Fly", 3, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_STOP_FLYING, "Stop Flying", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_JUMP, "Jump Attack", 0, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_HELLFIRE, "Hellfire", 0, 150, 200, 0, 0, ABFLAG_NONE}, { ABIL_DELAYED_FIREBALL, "Release Delayed Fireball", @@ -248,6 +251,7 @@ static const ability_def Ability_List[] = { ABIL_EVOKE_TURN_INVISIBLE, "Evoke Invisibility", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TURN_VISIBLE, "Turn Visible", 0, 0, 0, 0, 0, ABFLAG_NONE}, + { ABIL_EVOKE_JUMP, "Evoke Jump Attack", 2, 0, 125, 0, 0, ABFLAG_EXHAUSTION}, { ABIL_EVOKE_FLIGHT, "Evoke Flight", 1, 0, 100, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_FOG, "Evoke Fog", 2, 0, 250, 0, 0, ABFLAG_NONE}, { ABIL_EVOKE_TELEPORT_CONTROL, "Evoke Teleport Control", 4, 0, 200, 0, 0, ABFLAG_NONE}, @@ -921,7 +925,6 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - case ABIL_BREATHE_FROST: case ABIL_BREATHE_POISON: case ABIL_SPIT_ACID: @@ -941,7 +944,12 @@ talent get_talent(ability_type ability, bool check_confused) if (you.form == TRAN_DRAGON) failure -= 20; break; - + // copied from spit poison + case ABIL_JUMP: + failure = ((you.species == SP_FELID) ? 20 : 40) + - 10 * player_mutation_level(MUT_JUMP) + - you.experience_level; + break; case ABIL_FLY: failure = 45 - (3 * you.experience_level); break; @@ -995,7 +1003,9 @@ talent get_talent(ability_type ability, bool check_confused) case ABIL_EVOKE_BLINK: failure = 40 - you.skill(SK_EVOCATIONS, 2); break; - + case ABIL_EVOKE_JUMP: + failure = 30 - you.skill(SK_EVOCATIONS, 2); + break; case ABIL_EVOKE_BERSERK: case ABIL_EVOKE_FOG: case ABIL_EVOKE_TELEPORT_CONTROL: @@ -1340,6 +1350,7 @@ static bool _check_ability_possible(const ability_def& abil, bool hungerCheck = true, bool quiet = false) { + if (silenced(you.pos()) && you.religion != GOD_NEMELEX_XOBEH) { talent tal = get_talent(abil.ability, false); @@ -1573,6 +1584,13 @@ bool activate_talent(const talent& tal) return false; } + if ((tal.which == ABIL_EVOKE_JUMP || tal.which == ABIL_JUMP) + && !you.can_jump()) + { + crawl_state.zero_turns_taken(); + return false; + } + if ((tal.which == ABIL_EVOKE_FLIGHT || tal.which == ABIL_TRAN_BAT) && you.liquefied_ground()) { @@ -1883,7 +1901,15 @@ static bool _do_ability(const ability_def& abil) break; } - + case ABIL_JUMP: + { + int jump_level = player_jump_level(false); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.experience_level)); + break; + } case ABIL_RECHARGING: if (recharge_wand() <= 0) return false; // fail message is already given @@ -2138,7 +2164,15 @@ static bool _do_ability(const ability_def& abil) else fly_player(you.skill(SK_EVOCATIONS, 2) + 30); break; - + case ABIL_EVOKE_JUMP: + { + int jump_level = player_jump_level(true); + if(!_jump_player(jump_level)) + return false; + you.increase_duration(DUR_EXHAUSTED, 3 + random2(10) + + random2(30 - you.skill(SK_EVOCATIONS, 1))); + break; + } case ABIL_EVOKE_FOG: // cloak of the Thief mpr("With a swish of your cloak, you release a cloud of fog."); big_cloud(random_smoke_type(), &you, you.pos(), 50, 8 + random2(8)); @@ -3080,7 +3114,11 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) if (you.species == SP_DEEP_DWARF) _add_talent(talents, ABIL_RECHARGING, check_confused); - + if (player_mutation_level(MUT_JUMP) + && !you.evokable_jump()) + { + _add_talent(talents, ABIL_JUMP, check_confused); + } // Spit Poison. Nontransformed nagas can upgrade to Breathe Poison. // Transformed nagas, or non-nagas, can only get Spit Poison. if (you.species == SP_NAGA @@ -3092,8 +3130,7 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } else if (player_mutation_level(MUT_SPIT_POISON) || you.species == SP_NAGA) - { - _add_talent(talents, ABIL_SPIT_POISON, check_confused); + { _add_talent(talents, ABIL_SPIT_POISON, check_confused); } if (player_genus(GENPC_DRACONIAN)) @@ -3229,6 +3266,9 @@ vector<talent> your_talents(bool check_confused, bool include_unusable) } } + if (you.evokable_jump()) + _add_talent(talents, ABIL_EVOKE_JUMP, check_confused); + if (you.wearing(EQ_RINGS, RING_TELEPORTATION) && !crawl_state.game_is_sprint()) { @@ -3497,3 +3537,38 @@ int scaling_cost::cost(int max) const { return (value < 0) ? (-value) : ((value * max + 500) / 1000); } + + +bool _jump_player(int jump_range) +{ + coord_def landing; + direction_chooser_args args; + targetter_jump tgt(&you, jump_range); + dist jdirect; + + args.restricts = DIR_JUMP; + args.mode = TARG_HOSTILE; + args.just_looking = false; + args.needs_path = true; + args.may_target_monster = true; + args.may_target_self = false; + args.target_prefix = NULL; + args.top_prompt = "Aiming: <white>Jump Attack</white>"; + args.behaviour = NULL; + args.cancel_at_self = true; + args.hitfunc = &tgt; + args.range = jump_range; + direction(jdirect, args); + if (!jdirect.isValid) + { + // Check for user cancel. + canned_msg(MSG_OK); + return false; + } + + if (!check_moveto(jdirect.target, "jump") + || !fight_jump(&you, actor_at(jdirect.target), tgt.jump_is_blocked, + tgt.landing_site, tgt.additional_sites)) + return false; + return true; +} diff --git a/crawl-ref/source/actor.cc b/crawl-ref/source/actor.cc index a612e2a..ec82979 100644 --- a/crawl-ref/source/actor.cc +++ b/crawl-ref/source/actor.cc @@ -386,6 +386,16 @@ int actor::evokable_flight(bool calc_unid) const + scan_artefacts(ARTP_FLY, calc_unid); } +// Return an int so we know whether an item is the sole source. +int actor::evokable_jump(bool calc_unid) const +{ + if (suppressed() + || (is_player() && player_jump_level(true) <= player_jump_level(false))) + return 0; + return wearing_ego(EQ_ALL_ARMOUR, SPARM_JUMPING, calc_unid) + + scan_artefacts(ARTP_JUMP, calc_unid); +} + int actor::spirit_shield(bool calc_unid, bool items) const { int ss = 0; diff --git a/crawl-ref/source/actor.h b/crawl-ref/source/actor.h index 59b0973..3bd977b 100644 --- a/crawl-ref/source/actor.h +++ b/crawl-ref/source/actor.h @@ -171,6 +171,7 @@ public: virtual bool can_see_invisible() const = 0; virtual bool invisible() const = 0; virtual bool nightvision() const = 0; + virtual bool can_jump() const = 0; // Would looker be able to see the actor when in LOS? virtual bool visible_to(const actor *looker) const = 0; @@ -316,6 +317,7 @@ public: // Return an int so we know whether an item is the sole source. virtual int evokable_flight(bool calc_unid = true) const; + virtual int evokable_jump(bool calc_unid = true) const; virtual int spirit_shield(bool calc_unid = true, bool items = true) const; virtual flight_type flight_mode() const = 0; diff --git a/crawl-ref/source/artefact.cc b/crawl-ref/source/artefact.cc index 321e70c..5fcee08 100644 --- a/crawl-ref/source/artefact.cc +++ b/crawl-ref/source/artefact.cc @@ -1002,6 +1002,15 @@ static void _get_randart_properties(const item_def &item, power_level++; } + // jump attack + if (!done_powers + && one_chance_in(10) + && (aclass != OBJ_WEAPONS || !is_range_weapon(item))) + { + proprt[ARTP_JUMP] = 1; + power_level++; + } + if (!done_powers && one_chance_in(10) && aclass == OBJ_ARMOUR && (atype == ARM_CAP || atype == ARM_SHIELD)) { diff --git a/crawl-ref/source/dat/descript/ability.txt b/crawl-ref/source/dat/descript/ability.txt index bfa125d..2fca172 100644 --- a/crawl-ref/source/dat/descript/ability.txt +++ b/crawl-ref/source/dat/descript/ability.txt @@ -74,7 +74,17 @@ Start flying. During flight you can safely cross water and similar obstacles. Be warned, though, that flight may time out at inopportune moments and cause you to fall to your death. %%%% -Hellfire +Jump Attack + +Jump attack at limited distance, covering ground with great speed. You may +target an enemy to perform a jumping attack with increased damage and landing in +some position next to your target. You cannot jump over flying enemies nor +giant enemies not submerged in deep water or lava. Standing or swimming in +water, lava, or liquified ground will also prevent you from using this ability. +Felids have an innate ability to jump attack that increases in range at +experience levels 6 and 12. + +%%%% Hellfire Blast your enemies with hellfire. %%%% @@ -123,6 +133,16 @@ Evoke Flight <Fly> %%%% +Evoke Jump Attack + +Jump attack at limited distance, covering ground with great speed. You may +target an enemy to perform a jumping attack with increased damage and landing in +some position next to your target. You cannot jump over flying enemies nor +giant enemies not submerged in deep water or lava. Standing or swimming in +water, lava, or liquified ground will also prevent you from using this ability. +At evocations skill levels 5 and 10, the range of your jump attack increases. +%%%% + Stop Flying Stop flying. diff --git a/crawl-ref/source/dat/descript/backgrounds.txt b/crawl-ref/source/dat/descript/backgrounds.txt index 446e640..cc1f17a 100644 --- a/crawl-ref/source/dat/descript/backgrounds.txt +++ b/crawl-ref/source/dat/descript/backgrounds.txt @@ -43,6 +43,11 @@ Death Knight Death Knights are melee fighters who command the undead in the name of Yredelemnul the Dark. %%%% +Dragoon + +Dragoons harness the power of magical armour that allows them to +launch a devestating jumping attack on multiple foes. +%%%% Earth Elementalist Earth Elementalists know the Sandblast spell, and carry stones to increase the diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc index 01dfa25..34f8e66 100644 --- a/crawl-ref/source/describe.cc +++ b/crawl-ref/source/describe.cc @@ -224,6 +224,7 @@ static vector<string> _randart_propnames(const item_def& item, { "+Inv", ARTP_INVISIBLE, 2 }, { "+Fly", ARTP_FLY, 2 }, { "+Fog", ARTP_FOG, 2 }, + { "+Jump", ARTP_JUMP, 2 }, // Resists, also really important { "rElec", ARTP_ELECTRICITY, 2 }, @@ -418,6 +419,7 @@ static string _randart_descrip(const item_def &item) { ARTP_EYESIGHT, "It enhances your eyesight.", false}, { ARTP_INVISIBLE, "It lets you turn invisible.", false}, { ARTP_FLY, "It lets you fly.", false}, + { ARTP_JUMP, "It lets you perform a jump-attack.", false}, { ARTP_BLINK, "It lets you blink.", false}, { ARTP_BERSERK, "It lets you go berserk.", false}, { ARTP_NOISES, "It makes noises.", false}, @@ -1324,6 +1326,10 @@ static string _describe_armour(const item_def &item, bool verbose) description += "It can be activated to allow its wearer to " "fly indefinitely."; break; + case SPARM_JUMPING: + description += "It can be activated to allow its wearer to " + "perform a jumping attack."; + break; case SPARM_MAGIC_RESISTANCE: description += "It increases its wearer's resistance " "to enchantments."; diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index 4e2713b..dc7c5f3 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -109,6 +109,8 @@ static bool _find_feature(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); static bool _find_fprop_unoccupied(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc); +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc); #ifndef USE_TILE_LOCAL static bool _find_mlist(const coord_def& where, int mode, bool need_path, @@ -175,8 +177,8 @@ static void _wizard_make_friendly(monster* m) #endif dist::dist() - : isValid(false), isTarget(false), isEndpoint(false), - isCancel(true), choseRay(false), target(), delta(), ray() + : isValid(false), isTarget(false), isEndpoint(false), isCancel(true), + choseRay(false), target(), delta(), ray() { } @@ -379,6 +381,9 @@ void direction_chooser::print_key_hints() const case DIR_DIR: case DIR_TARGET_OBJECT: break; + case DIR_JUMP: + prompt += hint_string; + break; } } @@ -513,6 +518,7 @@ direction_chooser::direction_chooser(dist& moves_, show_items_once = false; target_unshifted = Options.target_unshifted_dirs; + } class view_desc_proc @@ -1013,89 +1019,107 @@ static void _update_mlist(bool enable) } #endif -// Find a good square to start targetting from. -coord_def direction_chooser::find_default_target() const +// Try to find an enemy monster to target +bool direction_chooser::find_default_monster_target(coord_def& result) const { - coord_def result = you.pos(); bool success = false; - - if (restricts == DIR_TARGET_OBJECT) + int search_range = range; + bool (*find_targ)(const coord_def&, int, bool, int, targetter*); + + // First try to pick our previous target. + const monster* mons_target = get_current_target(); + if (mons_target != NULL + && (mode != TARG_EVOLVABLE_PLANTS + && mons_attitude(mons_target) == ATT_HOSTILE + || mode == TARG_ENEMY && !mons_target->friendly() + || mode == TARG_EVOLVABLE_PLANTS + && mons_is_evolvable(mons_target) + || mode == TARG_HOSTILE_UNDEAD && !mons_target->friendly() + && mons_target->holiness() == MH_UNDEAD + || mode == TARG_INJURED_FRIEND + && (mons_target->friendly() && mons_get_damage_level(mons_target) > MDAM_OKAY + || (!mons_target->wont_attack() + && !mons_target->neutral() + && is_pacifiable(mons_target) >= 0))) + && in_range(mons_target->pos())) { - // Try to find an object. - success = _find_square_wrapper(result, 1, _find_object, - needs_path, TARG_ANY, range, hitfunc, - true, LS_FLIPVH); + success = true; + result = mons_target->pos(); } - else if (mode == TARG_ENEMY || mode == TARG_HOSTILE - || mode == TARG_HOSTILE_SUBMERGED - || mode == TARG_EVOLVABLE_PLANTS - || mode == TARG_HOSTILE_UNDEAD - || mode == TARG_INJURED_FRIEND) + if (!success) { - // Try to find an enemy monster. - - // First try to pick our previous target. - const monster* mon_target = get_current_target(); - if (mon_target != NULL - && (mode != TARG_EVOLVABLE_PLANTS - && mons_attitude(mon_target) == ATT_HOSTILE - || mode == TARG_ENEMY && !mon_target->friendly() - || mode == TARG_EVOLVABLE_PLANTS - && mons_is_evolvable(mon_target) - || mode == TARG_HOSTILE_UNDEAD && !mon_target->friendly() - && mon_target->holiness() == MH_UNDEAD - || mode == TARG_INJURED_FRIEND - && (mon_target->friendly() && mons_get_damage_level(mon_target) > MDAM_OKAY - || (!mon_target->wont_attack() - && !mon_target->neutral() - && is_pacifiable(mon_target) >= 0))) - && in_range(mon_target->pos())) + // Have to increase search_range by one so monsters out of range but + // with landing sites in-range are found. + if (restricts == DIR_JUMP) { - result = mon_target->pos(); - success = true; + find_targ = _find_jump_attack_mons; + search_range += 1; } else { - // The previous target is no good. Try to find one from scratch. - success = _find_square_wrapper(result, 1, _find_monster, + find_targ = _find_monster; + } + // The previous target is no good. Try to find one from scratch. + success = _find_square_wrapper(result, 1, find_targ, needs_path, mode, + search_range, hitfunc, true); + + // We might be able to hit monsters in LOS that are outside of + // normal range, but inside explosion/cloud range + if (!success && hitfunc && restricts != DIR_JUMP + && (you.current_vision > range || hitfunc->can_affect_walls())) + { + success = _find_square_wrapper(result, 1, _find_monster_expl, needs_path, mode, range, hitfunc, true); + } - // We might be able to hit monsters in LOS that are outside of - // normal range, but inside explosion/cloud range - if (!success - && hitfunc && hitfunc->can_affect_outside_range() - && (you.current_vision > range || hitfunc->can_affect_walls())) - { - success = _find_square_wrapper(result, 1, _find_monster_expl, - needs_path, mode, range, hitfunc, - true); - } - // If we couldn't, maybe it was because of line-of-fire issues. - // Check if that's happening, and inform the user (because it's - // pretty confusing.) - if (!success - && needs_path - && _find_square_wrapper(result, 1, _find_monster, - false, mode, range, hitfunc, true)) + // If we couldn't, maybe it was because of line-of-fire issues. + // Check if that's happening, and inform the user (because it's + // pretty confusing.) + if (!success && needs_path + && _find_square_wrapper(result, 1, find_targ, false, mode, + search_range, hitfunc, true)) + { + // Special colouring in tutorial or hints mode. + const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; + mpr("All monsters which could be auto-targeted are covered by " + "a wall or statue which interrupts your line of fire, even " + "though it doesn't interrupt your line of sight.", + need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); + + if (need_hint) { - // Special colouring in tutorial or hints mode. - const bool need_hint = Hints.hints_events[HINT_TARGET_NO_FOE]; - mpr("All monsters which could be auto-targeted are covered by " - "a wall or statue which interrupts your line of fire, even " - "though it doesn't interrupt your line of sight.", - need_hint ? MSGCH_TUTORIAL : MSGCH_PROMPT); - - if (need_hint) - { mpr("To return to the main mode, press <w>Escape</w>.", MSGCH_TUTORIAL); Hints.hints_events[HINT_TARGET_NO_FOE] = false; - } } } } + return success; +} + +// Find a good square to start targetting from. +void direction_chooser::set_default_target() +{ + coord_def result = you.pos(); + bool success = false; + + if (restricts == DIR_TARGET_OBJECT) + { + // Try to find an object. + success = _find_square_wrapper(result, 1, _find_object, + needs_path, TARG_ANY, range, hitfunc, + true, LS_FLIPVH); + } + else if (mode == TARG_ENEMY || mode == TARG_HOSTILE + || mode == TARG_HOSTILE_SUBMERGED + || mode == TARG_EVOLVABLE_PLANTS + || mode == TARG_HOSTILE_UNDEAD + || mode == TARG_INJURED_FRIEND) + { + success = find_default_monster_target(result); + } // Evolution can also auto-target mold squares (but shouldn't if // there are any monsters to evolve), so try _find_square_wrapper // again @@ -1108,8 +1132,7 @@ coord_def direction_chooser::find_default_target() const if (!success) result = you.pos(); - - return result; + set_target(result); } const coord_def& direction_chooser::target() const @@ -1119,6 +1142,20 @@ const coord_def& direction_chooser::target() const void direction_chooser::set_target(const coord_def& new_target) { + coord_def jump_pos; + set<coord_def>::const_iterator site; + monster *mons; + + if (restricts == DIR_JUMP) + { + mons = monster_at(new_target); + if (((mons && you.can_see(mons)) + || env.map_knowledge(new_target).invisible_monster()) + && hitfunc->has_additional_sites(new_target, true)) + valid_jump = true; + else + valid_jump = false; + } moves.target = new_target; } @@ -1161,11 +1198,22 @@ void direction_chooser::draw_beam_if_needed() #ifndef USE_TILE_LOCAL int bcol = BLACK; if (aff < 0) + { bcol = DARKGREY; + } else if (aff < AFF_YES) + { bcol = (*ri == target()) ? RED : MAGENTA; - else + } + else if (aff == AFF_YES) + { bcol = (*ri == target()) ? LIGHTRED : LIGHTMAGENTA; + } + // Jump attack landing sites + else + { + bcol = (*ri == target()) ? LIGHTGREEN : GREEN; + } _draw_ray_glyph(*ri, bcol, '*', bcol | COLFLAG_REVERSE); #endif } @@ -1283,15 +1331,23 @@ void direction_chooser::update_previous_target() const bool direction_chooser::select(bool allow_out_of_range, bool endpoint) { + const monster* mons = monster_at(target()); + + if (restricts == DIR_JUMP && !valid_jump) + { + if (mons && you.can_see(mons)) + mpr("The monster cannot be jump-attacked there."); + else + mpr("There is no monster to jump-attack there!"); + return false; + } if (!allow_out_of_range && !in_range(target())) { mpr(hitfunc? hitfunc->why_not : "That is beyond the maximum range.", MSGCH_EXAMINE_FILTER); return false; } - - const monster* m = monster_at(target()); - moves.isEndpoint = endpoint || (m && _mon_exposed(m)); + moves.isEndpoint = endpoint || (mons && _mon_exposed(mons)); moves.isValid = true; moves.isTarget = true; update_previous_target(); @@ -1627,9 +1683,12 @@ void direction_chooser::handle_movement_key(command_type key_command, const coord_def& delta = Compass[compass_idx]; const bool unshifted = (shift_direction(key_command) != key_command); if (unshifted) + { set_target(target() + delta); - else + } else + { *loop_done = select_compass_direction(delta); + } } } @@ -1983,7 +2042,7 @@ bool direction_chooser::do_main_loop() } // Redraw whatever is necessary. - if (old_target != target()) + if (restricts == DIR_JUMP || old_target != target()) { have_beam = show_beam && find_ray(you.pos(), target(), beam, opc_solid_see, BDS_DEFAULT); @@ -2035,8 +2094,11 @@ bool direction_chooser::choose_direction() // init moves.delta.reset(); - // Find a default target. - set_target(Options.default_target ? find_default_target() : you.pos()); + // Set a default target. + if (Options.default_target) + set_default_target(); + else + set_target(you.pos()); objfind_pos = monsfind_pos = target(); // If requested, show the beam on startup. @@ -2410,14 +2472,14 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, if ((mode == TARG_FRIEND || mode == TARG_ANY) && where == you.pos()) return true; - // Don't target out of range. - if (!_is_target_in_range(where, range, hitfunc)) + // Don't target out of range + if (hitfunc && !_is_target_in_range(where, range, hitfunc)) return false; const monster* mon = monster_at(where); // No monster or outside LOS. - if (mon == NULL || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) + if (!mon || !cell_see_cell(you.pos(), where, LOS_DEFAULT)) return false; // Monster in LOS but only via glass walls, so no direct path. @@ -2433,9 +2495,39 @@ static bool _find_monster(const coord_def& where, int mode, bool need_path, return _want_target_monster(mon, mode); } +static bool _find_jump_attack_mons(const coord_def& where, int mode, bool need_path, + int range, targetter *hitfunc) +{ +#ifdef CLUA_BINDINGS + { + coord_def dp = grid2player(where); + // We could pass more info here. + maybe_bool x = clua.callmbooleanfn("ch_target_jump", "dd", + dp.x, dp.y); + if (x != MB_MAYBE) + return tobool(x); + } +#endif + + // Need a monster to attack; this checks that the monster is a valid target. + if (!_find_monster(where, mode, need_path, range, hitfunc)) + return false; + // Can't jump on yourself + if (where == you.pos()) + return false; + + return hitfunc->has_additional_sites(where, false); +} + + + + static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, int range, targetter *hitfunc) { + const monster* mons; + coord_def jump_pos; + ASSERT(hitfunc); #ifdef CLUA_BINDINGS @@ -2466,16 +2558,17 @@ static bool _find_monster_expl(const coord_def& where, int mode, bool need_path, return false; if (hitfunc->set_aim(where)) + { for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri) { - if (hitfunc->is_affected(*ri) >= AFF_YES) + mons = monster_at(*ri); + if (mons && hitfunc->is_affected(*ri) == AFF_YES) { - const monster* mon = monster_at(*ri); - if (mon && _mons_is_valid_target(mon, mode, range)) - return _want_target_monster(mon, mode); + if (_mons_is_valid_target(mons, mode, range)) + return _want_target_monster(mons, mode); } } - + } return false; } diff --git a/crawl-ref/source/directn.h b/crawl-ref/source/directn.h index e5e5a0c..412fa27 100644 --- a/crawl-ref/source/directn.h +++ b/crawl-ref/source/directn.h @@ -66,6 +66,7 @@ public: bool isEndpoint; // Does the player want the attack to stop at target? bool isCancel; // user cancelled (usually <ESC> key) bool choseRay; // user wants a specific beam + coord_def target; // target x,y or logical extension of beam to map edge coord_def delta; // delta x and y if direction - always -1,0,1 ray_def ray; // ray chosen if necessary @@ -116,7 +117,7 @@ private: bool do_main_loop(); // Return the location where targetting should start. - coord_def find_default_target() const; + void set_default_target(); void handle_mlist_cycle_command(command_type key_command); void handle_wizard_command(command_type key_command, bool* loop_done); @@ -162,6 +163,7 @@ private: actor* targeted_actor() const; monster* targeted_monster() const; + bool find_default_monster_target(coord_def& result) const; // Functions which print things to the user. // Each one is commented with a sample output. @@ -247,6 +249,8 @@ private: bool show_beam; // Does the user want the beam displayed? bool have_beam; // Is the currently stored beam valid? coord_def objfind_pos, monsfind_pos; // Cycling memory + bool valid_jump; // If jumping, do we currently have a monster + // target with a valid landing position? // What we need to redraw. bool need_beam_redraw; @@ -256,7 +260,6 @@ private: bool show_items_once; // Should we show items this time? bool target_unshifted; // Do unshifted direction keys fire? - // Default behaviour, saved across instances. static targetting_behaviour stock_behaviour; diff --git a/crawl-ref/source/enum.h b/crawl-ref/source/enum.h index 9b3079f..710607f 100644 --- a/crawl-ref/source/enum.h +++ b/crawl-ref/source/enum.h @@ -54,7 +54,7 @@ enum ability_type ABIL_BREATHE_MEPHITIC, ABIL_SPIT_ACID, ABIL_BLINK, - + ABIL_JUMP, // Others ABIL_DELAYED_FIREBALL, ABIL_END_TRANSFORMATION, @@ -82,6 +82,7 @@ enum ability_type ABIL_EVOKE_BLINK, ABIL_EVOKE_TURN_INVISIBLE, ABIL_EVOKE_TURN_VISIBLE, + ABIL_EVOKE_JUMP, ABIL_EVOKE_FLIGHT, #if TAG_MAJOR_VERSION == 34 ABIL_EVOKE_STOP_LEVITATING, @@ -1875,6 +1876,7 @@ enum job_type JOB_DEATH_KNIGHT, JOB_ABYSSAL_KNIGHT, JOB_JESTER, + JOB_DRAGOON, NUM_JOBS, // always after the last job JOB_UNKNOWN = 100, @@ -2793,6 +2795,7 @@ enum mutation_type MUT_HEAT_RESISTANCE, MUT_HERBIVOROUS, MUT_HURL_HELLFIRE, + MUT_JUMP, MUT_FAST, MUT_FAST_METABOLISM, MUT_FLEXIBLE_WEAK, @@ -2997,6 +3000,7 @@ enum artefact_prop_type #if TAG_MAJOR_VERSION != 34 ARTP_FOG, #endif + ARTP_JUMP, ARTP_BLINK, ARTP_BERSERK, ARTP_NOISES, @@ -3511,6 +3515,7 @@ enum targetting_type DIR_TARGET, // smite targetting DIR_DIR, // needs a clear line to target DIR_TARGET_OBJECT, // targets items + DIR_JUMP, // a jump target }; enum torment_source_type @@ -3901,6 +3906,10 @@ enum tile_flags ENUM_INT64 //// General + // Should go up with RAY/RAY_OOR, but they need to be exclusive for those + // flags and there's no room. + TILE_FLAG_LANDING = 0x20000000000ULL, + // Mask for the tile index itself. TILE_FLAG_MASK = 0x0000FFFFULL, }; diff --git a/crawl-ref/source/fight.cc b/crawl-ref/source/fight.cc index b2a94b7..bf7b755 100644 --- a/crawl-ref/source/fight.cc +++ b/crawl-ref/source/fight.cc @@ -21,6 +21,7 @@ #include "itemprop.h" #include "melee_attack.h" #include "mgen_data.h" +#include "misc.h" #include "mon-behv.h" #include "mon-cast.h" #include "mon-place.h" @@ -183,6 +184,86 @@ bool fight_melee(actor *attacker, actor *defender, bool *did_hit, bool simu) return true; } +// Handles jump attack between attacker and defender. +bool fight_jump(actor *attacker, actor *defender, bool jump_blocked, + coord_def landing_pos, set<coord_def> landing_sites, + bool *did_hit) +{ + bool sanctuary_warning = false; + set<coord_def>::const_iterator site; + + ASSERT(!crawl_state.game_is_arena()); + + // Can't damage orbs this way. + if (mons_is_projectile(defender->type) && !you.confused()) + { + you.turn_is_over = false; + return false; + } + melee_attack first_attk(attacker, defender, -1, -1, false, true, + jump_blocked, landing_pos); + + // Check if the player is fighting with something unsuitable, + // or someone unsuitable. + if (you.can_see(defender) + && !wielded_weapon_check(first_attk.weapon)) + { + you.turn_is_over = false; + return false; + } + // Do player warnings for electrocution and sanctuary based on possible + // landing sites. + if (attacker->is_player() && defender->is_monster() + && attacker->damage_brand(-1) == SPWPN_ELECTROCUTION) + { + for (site = landing_sites.begin(); site != landing_sites.end(); site++) + { + bool ground_level = !you.airborne() && !you.can_cling_to(*site) + && you.species != SP_DJINNI; + if (!you.received_weapon_warning + && adjacent(*site, defender->pos()) + && (feat_is_water(grd(defender->pos())) && defender->ground_level()) + && (feat_is_water(grd(*site)) && ground_level) + && !attacker->res_elec()) + + { + string prompt = "Really attack with "; + if (attacker->weapon(-1)) + prompt += attacker->weapon(-1)->name(DESC_YOUR); + else + prompt += "your electric unarmed attack"; + prompt += " when you might land in water? "; + if (yesno(prompt.c_str(), true, 'n')) + { + you.received_weapon_warning = true; + } + else + { + canned_msg(MSG_OK); + you.turn_is_over = false; + return false; + } + } + else if (!sanctuary_warning) + { + if (stop_attack_prompt(defender->as_monster(), false, + *site, false, nullptr, true, *site)) + return false; + else + sanctuary_warning = true; + } + } + } + if (!first_attk.attack() && first_attk.cancel_attack) + { + you.turn_is_over = false; + return false; + } + if (did_hit) + *did_hit = first_attk.did_hit; + return true; +} + unchivalric_attack_type is_unchivalric_attack(const actor *attacker, const actor *defender) { @@ -394,7 +475,9 @@ bool wielded_weapon_check(item_def *weapon, bool no_message) return true; } -static bool _cleave_dont_harm(const actor* attacker, const actor* defender) +// Used by cleave and jump attack to determine if multi-hit targets will be +// attacked. +bool dont_harm(const actor* attacker, const actor* defender) { return (mons_aligned(attacker, defender) || attacker == &you && defender->wont_attack() @@ -420,7 +503,7 @@ void get_cleave_targets(const actor* attacker, const coord_def& def, int dir, break; actor * target = actor_at(atk + atk_vector); - if (target && !_cleave_dont_harm(attacker, target)) + if (target && !dont_harm(attacker, target)) targets.push_back(target); } } @@ -446,7 +529,7 @@ void attack_cleave_targets(actor* attacker, list<actor*> &targets, { actor* def = targets.front(); if (attacker->alive() && def && def->alive() - && !_cleave_dont_harm(attacker, def)) + && !dont_harm(attacker, def)) { melee_attack attck(attacker, def, attack_number, ++effective_attack_number, true); diff --git a/crawl-ref/source/fight.h b/crawl-ref/source/fight.h index 6c026e9..2bd655d 100644 --- a/crawl-ref/source/fight.h +++ b/crawl-ref/source/fight.h @@ -29,6 +29,10 @@ enum unchivalric_attack_type bool fight_melee(actor *attacker, actor *defender, bool *did_hit = NULL, bool simu = false); +bool fight_jump(actor *attacker, actor *defender, bool jump_blocked, + coord_def landing_pos, set<coord_def> landing_sites, + bool *did_hit = NULL); + int resist_adjust_damage(actor *defender, beam_type flavour, int res, int rawdamage, bool ranged = false); @@ -47,5 +51,5 @@ void get_all_cleave_targets(const actor* attacker, const coord_def& def, void attack_cleave_targets(actor* attacker, list<actor*> &targets, int attack_number = 0, int effective_attack_number = 0); - +bool dont_harm(const actor* attacker, const actor* defender); #endif diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc index 19bb80e..489b9f6 100644 --- a/crawl-ref/source/itemname.cc +++ b/crawl-ref/source/itemname.cc @@ -517,6 +517,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return "intelligence"; case SPARM_PONDEROUSNESS: return "ponderousness"; case SPARM_FLYING: return "flying"; + case SPARM_JUMPING: return "jumping"; case SPARM_MAGIC_RESISTANCE: return "magic resistance"; case SPARM_PROTECTION: return "protection"; case SPARM_STEALTH: return "stealth"; @@ -546,6 +547,7 @@ const char* armour_ego_name(const item_def& item, bool terse) case SPARM_INTELLIGENCE: return " {Int+3}"; case SPARM_PONDEROUSNESS: return " {ponderous}"; case SPARM_FLYING: return " {Fly}"; + case SPARM_JUMPING: return " {Jump}"; case SPARM_MAGIC_RESISTANCE: return " {MR+}"; case SPARM_PROTECTION: return " {AC+3}"; case SPARM_STEALTH: return " {Stlth+}"; diff --git a/crawl-ref/source/itemprop-enum.h b/crawl-ref/source/itemprop-enum.h index 6a9289c..2a46943 100644 --- a/crawl-ref/source/itemprop-enum.h +++ b/crawl-ref/source/itemprop-enum.h @@ -179,7 +179,6 @@ enum jewellery_type RING_FIRE, RING_ICE, RING_TELEPORT_CONTROL, - NUM_RINGS, // keep as last ring; should not overlap // with amulets! // RINGS after num_rings are for unique types for artefacts @@ -345,6 +344,7 @@ enum special_armour_type SPARM_INTELLIGENCE, SPARM_PONDEROUSNESS, SPARM_FLYING, + SPARM_JUMPING, SPARM_MAGIC_RESISTANCE, SPARM_PROTECTION, SPARM_STEALTH, diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc index ab007ef..02e7d2e 100644 --- a/crawl-ref/source/itemprop.cc +++ b/crawl-ref/source/itemprop.cc @@ -2573,7 +2573,8 @@ bool gives_ability(const item_def &item) return false; const special_armour_type ego = get_armour_ego_type(item); - if (ego == SPARM_DARKNESS || ego == SPARM_FLYING) + if (ego == SPARM_DARKNESS || ego == SPARM_FLYING + || ego == SPARM_JUMPING) return true; break; } diff --git a/crawl-ref/source/jobs.cc b/crawl-ref/source/jobs.cc index ba87a7f..64c0099 100644 --- a/crawl-ref/source/jobs.cc +++ b/crawl-ref/source/jobs.cc @@ -15,7 +15,7 @@ static const char * Job_Abbrev_List[ NUM_JOBS ] = "St", #endif "Mo", "Wr", "Wn", "Ar", "AM", - "DK", "AK", "Jr" }; + "DK", "AK", "Jr", "Dn" }; static const char * Job_Name_List[ NUM_JOBS ] = { "Fighter", "Wizard", "Priest", @@ -29,7 +29,7 @@ static const char * Job_Name_List[ NUM_JOBS ] = "Stalker", #endif "Monk", "Warper", "Wanderer", "Artificer", "Arcane Marksman", - "Death Knight", "Abyssal Knight", "Jester" }; + "Death Knight", "Abyssal Knight", "Jester", "Dragoon" }; const char *get_job_abbrev(int which_job) { diff --git a/crawl-ref/source/main.cc b/crawl-ref/source/main.cc index 7342073..d5f440f 100644 --- a/crawl-ref/source/main.cc +++ b/crawl-ref/source/main.cc @@ -3153,7 +3153,6 @@ static void _player_reacts() } _regenerate_hp_and_mp(capped_time); - recharge_rods(you.time_taken, false); // Reveal adjacent mimics. diff --git a/crawl-ref/source/makeitem.cc b/crawl-ref/source/makeitem.cc index 128a5de..b54409e 100644 --- a/crawl-ref/source/makeitem.cc +++ b/crawl-ref/source/makeitem.cc @@ -2293,6 +2293,7 @@ bool is_armour_brand_ok(int type, int brand, bool strict) // deliberate fall-through case SPARM_RUNNING: case SPARM_STEALTH: + case SPARM_JUMPING: return (slot == EQ_BOOTS); case SPARM_ARCHMAGI: diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index f7e0eb3..033f4a4 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -4561,6 +4561,7 @@ static int _str_to_ego(item_spec &spec, string ego_str) "intelligence", "ponderousness", "flying", + "jumping", "magic_resistance", "protection", "stealth", diff --git a/crawl-ref/source/melee_attack.cc b/crawl-ref/source/melee_attack.cc index 542d9df..b3e43c4 100644 --- a/crawl-ref/source/melee_attack.cc +++ b/crawl-ref/source/melee_attack.cc @@ -106,7 +106,8 @@ static bool _form_uses_xl() */ melee_attack::melee_attack(actor *attk, actor *defn, int attack_num, int effective_attack_num, - bool is_cleaving) + bool is_cleaving, bool is_jump_attack, + bool is_jump_blocked, coord_def attack_pos) : // Call attack's constructor ::attack(attk, defn), @@ -114,6 +115,7 @@ melee_attack::melee_attack(actor *attk, actor *defn, effective_attack_number(effective_attack_num), skip_chaos_message(false), special_damage_flavour(BEAM_NONE), stab_attempt(false), stab_bonus(0), cleaving(is_cleaving), + jumping_attack(is_jump_attack), jump_blocked(is_jump_blocked), miscast_level(-1), miscast_type(SPTYP_NONE), miscast_target(NULL), simu(false) { @@ -124,8 +126,13 @@ melee_attack::melee_attack(actor *attk, actor *defn, if (_form_uses_xl()) wpn_skill = SK_FIGHTING; // for stabbing, mostly to_hit = calc_to_hit(); - can_cleave = wpn_skill == SK_AXES && attacker != defender - && !attacker->confused(); + can_cleave = !jumping_attack && wpn_skill == SK_AXES && attacker != defender + && !attacker->confused(); + + if (jumping_attack) + attack_position = attack_pos; + else + attack_position = attacker->pos(); attacker_armour_tohit_penalty = div_rand_round(attacker->armour_tohit_penalty(true, 20), 20); @@ -174,7 +181,7 @@ melee_attack::melee_attack(actor *attk, actor *defn, } attacker_visible = attacker->observable(); - attacker_invisible = (!attacker_visible && you.see_cell(attacker->pos())); + attacker_invisible = (!attacker_visible && you.see_cell(attack_position)); defender_visible = defender && defender->observable(); defender_invisible = (!defender_visible && defender && you.see_cell(defender->pos())); @@ -202,7 +209,7 @@ bool melee_attack::can_reach() bool melee_attack::handle_phase_attempted() { // Skip invalid and dummy attacks. - if ((!adjacent(attacker->pos(), defender->pos()) && !can_reach()) + if ((!adjacent(attack_position, defender->pos()) && !jumping_attack && !can_reach()) || attk_type == AT_SHOOT || attk_type == AT_CONSTRICT && !attacker->can_constrict(defender)) { @@ -218,9 +225,10 @@ bool melee_attack::handle_phase_attempted() || (weapon && is_unrandom_artefact(*weapon) && weapon->special == UNRAND_DEVASTATOR)) { - if (damage_brand == SPWPN_ELECTROCUTION - && adjacent(attacker->pos(), defender->pos()) - && _conduction_affected(attacker->pos()) + // This check is handled in fight_jump() for jump attacks + if (!jumping_attack && damage_brand == SPWPN_ELECTROCUTION + && adjacent(attack_position, defender->pos()) + && _conduction_affected(attack_position) && !attacker->res_elec() && !you.received_weapon_warning) { @@ -269,8 +277,10 @@ bool melee_attack::handle_phase_attempted() return false; } } - else if (stop_attack_prompt(defender->as_monster(), false, - attacker->pos())) + // Jump attack did this check in jump_fight() + else if (!jumping_attack + && stop_attack_prompt(defender->as_monster(), false, + attack_position)) { cancel_attack = true; return false; @@ -290,9 +300,17 @@ bool melee_attack::handle_phase_attempted() return false; } } - // Set delay now that we know the attack won't be cancelled. + + if (attacker->is_player()) { + if (jump_blocked) + { + mpr("You rebound off of something unseen!"); + return false; + } + move_player_to_grid(attack_position, false, true); + // Set delay now that we know the attack won't be cancelled. you.time_taken = calc_attack_delay(); if (weapon) { @@ -407,7 +425,7 @@ bool melee_attack::handle_phase_attempted() if (attk_flavour == AF_SHADOWSTAB && defender && !defender->can_see(attacker)) { - if (you.see_cell(attacker->pos())) + if (you.see_cell(attack_position)) mprf("%s strikes at %s from the darkness!", attacker->name(DESC_THE, true).c_str(), defender->name(DESC_THE).c_str()); @@ -469,7 +487,7 @@ bool melee_attack::handle_phase_dodged() } } - if (attacker != defender && adjacent(defender->pos(), attacker->pos())) + if (attacker != defender && adjacent(defender->pos(), attack_position)) { if (attacker->alive() && (defender->is_player() ? @@ -828,9 +846,10 @@ bool melee_attack::handle_phase_aux() if (attacker->is_player()) { // returns whether an aux attack successfully took place + // additional attacks from cleave don't get aux if (!defender->as_monster()->friendly() - && adjacent(defender->pos(), attacker->pos()) - && !cleaving) // additional attacks from cleave don't get aux + && adjacent(defender->pos(), attack_position) + && !cleaving) { player_aux_unarmed(); } @@ -876,6 +895,7 @@ bool melee_attack::handle_phase_end() */ bool melee_attack::attack() { + if (!cleaving && !handle_phase_attempted()) return false; @@ -937,7 +957,7 @@ bool melee_attack::attack() handle_phase_blocked(); else { - if (attacker != defender && adjacent(defender->pos(), attacker->pos())) + if (attacker != defender && adjacent(defender->pos(), attack_position)) { // Check for defender Spines do_spines(); @@ -973,7 +993,7 @@ bool melee_attack::attack() // Remove sanctuary if - through some attack - it was violated. if (env.sanctuary_time > 0 && attack_occurred && !cancel_attack && attacker != defender && !attacker->confused() - && (is_sanctuary(attacker->pos()) || is_sanctuary(defender->pos())) + && (is_sanctuary(attack_position) || is_sanctuary(defender->pos())) && (attacker->is_player() || attacker->as_monster()->friendly())) { remove_sanctuary(true); @@ -1802,7 +1822,8 @@ int melee_attack::player_apply_final_multipliers(int damage) //cleave damage modifier if (cleaving) damage = cleave_damage_mod(damage); - + else if (jumping_attack) + damage = jump_damage_mod(damage); // not additive, statues are supposed to be bad with tiny toothpicks but // deal crushing blows with big weapons if (you.form == TRAN_STATUE) @@ -2777,11 +2798,11 @@ void melee_attack::chaos_affects_attacker() coord_def dest(-1, -1); // Prefer to send it under the defender. - if (defender->alive() && defender->pos() != attacker->pos()) + if (defender->alive() && defender->pos() != attack_position) dest = defender->pos(); // Move stairs out from under the attacker. - if (one_chance_in(100) && move_stairs(attacker->pos(), dest)) + if (one_chance_in(100) && move_stairs(attack_position, dest)) { #ifdef NOTE_DEBUG_CHAOS_EFFECTS take_note(Note(NOTE_MESSAGE, 0, 0, @@ -2793,7 +2814,7 @@ void melee_attack::chaos_affects_attacker() // Dump attacker or items under attacker to another level. if (is_valid_shaft_level() && (attacker->will_trigger_shaft() - || igrd(attacker->pos()) != NON_ITEM) + || igrd(attack_position) != NON_ITEM) && one_chance_in(1000)) { (void) attacker->do_shaft(); @@ -2808,7 +2829,7 @@ void melee_attack::chaos_affects_attacker() if (weapon && one_chance_in(1000)) { mprf("Smoke pours forth from %s!", wep_name(DESC_YOUR).c_str()); - big_cloud(random_smoke_type(), &you, attacker->pos(), 20, + big_cloud(random_smoke_type(), &you, attack_position, 20, 4 + random2(8)); #ifdef NOTE_DEBUG_CHAOS_EFFECTS take_note(Note(NOTE_MESSAGE, 0, 0, @@ -2818,7 +2839,7 @@ void melee_attack::chaos_affects_attacker() } // Make a loud noise. - if (weapon && player_can_hear(attacker->pos()) + if (weapon && player_can_hear(attack_position) && one_chance_in(200)) { string msg = ""; @@ -2843,7 +2864,7 @@ void melee_attack::chaos_affects_attacker() if (!msg.empty()) { mpr(msg.c_str(), MSGCH_SOUND); - noisy(15, attacker->pos(), attacker->mindex()); + noisy(15, attack_position, attacker->mindex()); #ifdef NOTE_DEBUG_CHAOS_EFFECTS take_note(Note(NOTE_MESSAGE, 0, 0, "CHAOS affects attacker: noise"), true); @@ -4047,6 +4068,9 @@ void melee_attack::player_stab_check() break; } + if (stab_bonus && jumping_attack) + stab_bonus = min(6, stab_bonus + 2); + // See if we need to roll against dexterity / stabbing. if (stab_attempt && roll_needed) { @@ -4301,7 +4325,7 @@ string melee_attack::mons_attack_desc() return ""; string ret; - int dist = (attacker->pos() - defender->pos()).abs(); + int dist = (attack_position - defender->pos()).abs(); if (dist > 2) { ASSERT(can_reach()); @@ -5255,7 +5279,7 @@ bool melee_attack::do_knockback(bool trample) break; coord_def old_pos = defender->pos(); - coord_def new_pos = defender->pos() + defender->pos() - attacker->pos(); + coord_def new_pos = defender->pos() + defender->pos() - attack_position; // need a valid tile if (grd(new_pos) < DNGN_SHALLOW_WATER @@ -5333,6 +5357,12 @@ int melee_attack::cleave_damage_mod(int dam) return div_rand_round(dam * 3, 4); } +// jump attack modifier: 120% of base damage +int melee_attack::jump_damage_mod(int dam) +{ + return div_rand_round(dam * 6, 5); +} + void melee_attack::chaos_affect_actor(actor *victim) { melee_attack attk(victim, victim); @@ -5764,7 +5794,7 @@ bool melee_attack::_player_vampire_draws_blood(const monster* mon, const int dam ASSERT(you.species == SP_VAMPIRE); if (!_vamp_wants_blood_from_monster(mon) || - (!adjacent(defender->pos(), attacker->pos()) && needs_bite_msg)) + (!adjacent(defender->pos(), attack_position) && needs_bite_msg)) { return false; } diff --git a/crawl-ref/source/melee_attack.h b/crawl-ref/source/melee_attack.h index bd44533..ddd3005 100644 --- a/crawl-ref/source/melee_attack.h +++ b/crawl-ref/source/melee_attack.h @@ -45,6 +45,9 @@ public: bool can_cleave; list<actor*> cleave_targets; bool cleaving; // additional attack from cleaving + bool jumping_attack; + bool jump_blocked; + coord_def attack_position; // Miscast to cause after special damage is done. If miscast_level == 0 // the miscast is discarded if special_damage_message isn't empty. @@ -57,7 +60,9 @@ public: public: melee_attack(actor *attacker, actor *defender, int attack_num = -1, int effective_attack_num = -1, - bool is_cleaving = false); + bool is_cleaving = false, bool is_jump_attack = false, + bool is_jump_blocked = false, + coord_def attack_pos = coord_def(0, 0)); // Applies attack damage and other effects. bool attack(); @@ -106,6 +111,8 @@ private: /* Axe cleaving */ void cleave_setup(); int cleave_damage_mod(int dam); + int jump_damage_mod(int dam); + int jump_additional_damage_mod(int dam); /* Mutation Effects */ void do_spines(); diff --git a/crawl-ref/source/misc.cc b/crawl-ref/source/misc.cc index dc3354f..03580e8 100644 --- a/crawl-ref/source/misc.cc +++ b/crawl-ref/source/misc.cc @@ -2253,18 +2253,34 @@ int speed_to_duration(int speed) return div_rand_round(100, speed); } -bool bad_attack(const monster *mon, string& adj, string& suffix) +bool bad_attack(const monster *mon, string& adj, string& suffix, + bool jump_check_landing, coord_def attack_pos) { ASSERT(!crawl_state.game_is_arena()); + bool jump_landing_warning = false; + coord_def attack_position; + if (!you.can_see(mon)) return false; + if (!jump_check_landing) + attack_pos = you.pos(); + adj.clear(); suffix.clear(); - if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos())) - suffix = ", despite your sanctuary"; - + if (is_sanctuary(attack_pos) || is_sanctuary(mon->pos())) + { + if (jump_check_landing) + { + suffix = ", when you might land in your sanctuary"; + jump_landing_warning = true; + } + else + { + suffix = ", despite your sanctuary"; + } + } if (mon->friendly()) { if (you.religion == GOD_OKAWARU) @@ -2291,12 +2307,14 @@ bool bad_attack(const monster *mon, string& adj, string& suffix) return true; } - return !adj.empty() || !suffix.empty(); + return (jump_check_landing && jump_landing_warning) + || (!adj.empty() || !suffix.empty()); } bool stop_attack_prompt(const monster* mon, bool beam_attack, coord_def beam_target, bool autohit_first, - bool *prompted) + bool *prompted, bool jump_check_landing, + coord_def attack_pos) { if (prompted) *prompted = false; @@ -2308,7 +2326,7 @@ bool stop_attack_prompt(const monster* mon, bool beam_attack, return false; string adj, suffix; - if (!bad_attack(mon, adj, suffix)) + if (!bad_attack(mon, adj, suffix, jump_check_landing, attack_pos)) return false; // Listed in the form: "your rat", "Blork the orc". diff --git a/crawl-ref/source/misc.h b/crawl-ref/source/misc.h index 383f616..c079bdc 100644 --- a/crawl-ref/source/misc.h +++ b/crawl-ref/source/misc.h @@ -113,10 +113,14 @@ bool scramble(void); bool interrupt_cmd_repeat(activity_interrupt_type ai, const activity_interrupt_data &at); -bool bad_attack(const monster *mon, string& adj, string& suffix); +bool bad_attack(const monster *mon, string& adj, string& suffix, + bool jump_check_landing = false, + coord_def attack_pos = coord_def(0, 0)); bool stop_attack_prompt(const monster* mon, bool beam_attack, coord_def beam_target, bool autohit_first = false, - bool *prompted = nullptr); + bool *prompted = nullptr, + bool jump_check_landing = false, + coord_def attack_pos = coord_def(0, 0)); bool stop_attack_prompt(targetter &hitfunc, string verb, bool (*affects)(const actor *victim) = 0); diff --git a/crawl-ref/source/monster.cc b/crawl-ref/source/monster.cc index ca223ed..712e7cb 100644 --- a/crawl-ref/source/monster.cc +++ b/crawl-ref/source/monster.cc @@ -4775,6 +4775,24 @@ bool monster::can_go_berserk() const return true; } +bool monster::can_jump() const +{ + if (mons_intel(this) == I_PLANT) + return false; + + if (swimming()) + return false; + + if (paralysed() || petrified() || petrifying() || asleep()) + return false; + + if (has_ench(ENCH_FATIGUE)) + return false; + + + return true; +} + bool monster::berserk() const { return (has_ench(ENCH_BERSERK) || has_ench(ENCH_INSANE)); diff --git a/crawl-ref/source/monster.h b/crawl-ref/source/monster.h index c2a4ba2..1dc6483 100644 --- a/crawl-ref/source/monster.h +++ b/crawl-ref/source/monster.h @@ -299,6 +299,7 @@ public: void attacking(actor *other); bool can_go_berserk() const; + bool can_jump() const; void go_berserk(bool intentional, bool potion = false); void go_frenzy(); bool berserk() const; diff --git a/crawl-ref/source/mutation-data.h b/crawl-ref/source/mutation-data.h index e34b436..7053e27 100644 --- a/crawl-ref/source/mutation-data.h +++ b/crawl-ref/source/mutation-data.h @@ -411,6 +411,24 @@ "breathe flames" }, +{ MUT_JUMP, 4, 3, false, false, false, + "jump", + + {"You can jump attack at a short distance.", + "You can jump attack at a medium distance.", + "You can jump attack at a long distance."}, + + {"You feel sure on your feet.", + "You feel sure on your feet.", + "You feel sure on your feet."}, + + {"You feel less sure on your feet.", + "You feel less sure on your feet.", + "You feel less sure on your feet."}, + + "jump" +}, + { MUT_BLINK, 3, 3, false, false, false, "blink", diff --git a/crawl-ref/source/mutation.cc b/crawl-ref/source/mutation.cc index c77b937..bf99837 100644 --- a/crawl-ref/source/mutation.cc +++ b/crawl-ref/source/mutation.cc @@ -1656,7 +1656,6 @@ bool mutate(mutation_type which_mutation, const string &reason, bool failMsg, case MUT_HIGH_MAGIC: calc_mp(); break; - case MUT_PASSIVE_MAPPING: add_daction(DACT_REAUTOMAP); break; diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc index 6cd668d..2ec3c63 100644 --- a/crawl-ref/source/newgame.cc +++ b/crawl-ref/source/newgame.cc @@ -982,7 +982,7 @@ static void _construct_backgrounds_menu(const newgame_def* ng, "Warrior", coord_def(0, 0), 15, {JOB_FIGHTER, JOB_GLADIATOR, JOB_MONK, JOB_HUNTER, JOB_ASSASSIN, - JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} + JOB_DRAGOON, JOB_UNKNOWN, JOB_UNKNOWN, JOB_UNKNOWN} }, { "Adventurer", @@ -1730,6 +1730,7 @@ static bool _choose_weapon(newgame_def* ng, newgame_def* ng_choice, case JOB_WARPER: case JOB_HUNTER: case JOB_ARCANE_MARKSMAN: + case JOB_DRAGOON: break; default: return true; diff --git a/crawl-ref/source/ng-restr.cc b/crawl-ref/source/ng-restr.cc index 0a666d0..7ff0728 100644 --- a/crawl-ref/source/ng-restr.cc +++ b/crawl-ref/source/ng-restr.cc @@ -553,7 +553,21 @@ char_choice_restriction job_allowed(species_type speci, job_type job) default: return CC_UNRESTRICTED; } - + case JOB_DRAGOON: + switch (speci) + { + case SP_CENTAUR: + case SP_DJINNI: + case SP_OCTOPODE: + case SP_OGRE: + case SP_NAGA: + case SP_SPRIGGAN: + case SP_TENGU: + case SP_TROLL: + return CC_RESTRICTED; + default: + return CC_UNRESTRICTED; + } case JOB_WANDERER: return CC_RESTRICTED; diff --git a/crawl-ref/source/ng-setup.cc b/crawl-ref/source/ng-setup.cc index ba978e4..de1bd73 100644 --- a/crawl-ref/source/ng-setup.cc +++ b/crawl-ref/source/ng-setup.cc @@ -162,6 +162,7 @@ static void _jobs_stat_init(job_type which_job) case JOB_FIGHTER: s = 8; i = 0; d = 4; hp = 15; mp = 0; break; case JOB_BERSERKER: s = 9; i = -1; d = 4; hp = 15; mp = 0; break; case JOB_GLADIATOR: s = 7; i = 0; d = 5; hp = 14; mp = 0; break; + case JOB_DRAGOON: s = 6; i = 0; d = 6; hp = 13; mp = 2; break; case JOB_SKALD: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; case JOB_CHAOS_KNIGHT: s = 4; i = 4; d = 4; hp = 13; mp = 1; break; @@ -343,6 +344,7 @@ void give_basic_mutations(species_type speci) you.mutation[MUT_FAST] = 1; you.mutation[MUT_CARNIVOROUS] = 3; you.mutation[MUT_SLOW_METABOLISM] = 1; + you.mutation[MUT_JUMP] = 1; break; case SP_OCTOPODE: you.mutation[MUT_CAMOUFLAGE] = 1; @@ -566,6 +568,28 @@ static void _give_items_skills(const newgame_def& ng) you.skills[SK_DODGING] = 3; weap_skill = 3; break; + case JOB_DRAGOON: + // Equipment. + newgame_make_item(0, EQ_WEAPON, OBJ_WEAPONS, WPN_SHORT_SWORD); + _update_weapon(ng); + newgame_make_item(1, EQ_BODY_ARMOUR, OBJ_ARMOUR, ARM_LEATHER_ARMOUR, + ARM_ANIMAL_SKIN); + newgame_make_item(2, EQ_BOOTS, OBJ_ARMOUR, ARM_BOOTS); + + you.skills[SK_FIGHTING] = 2; + if (you_can_wear(EQ_BOOTS)) + { + you.inv[2].special = SPARM_JUMPING; + you.skills[SK_EVOCATIONS] = 2; + } + else + { + newgame_make_item(2, EQ_HELMET, OBJ_ARMOUR, ARM_HELMET, ARM_CAP); + you.skills[SK_STEALTH] = 2; + } + you.skills[SK_DODGING] = 3; + weap_skill = 3; + break; case JOB_MONK: you.equip[EQ_WEAPON] = -1; // Monks fight unarmed. diff --git a/crawl-ref/source/player-act.cc b/crawl-ref/source/player-act.cc index d5ea690..9982c96 100644 --- a/crawl-ref/source/player-act.cc +++ b/crawl-ref/source/player-act.cc @@ -159,7 +159,7 @@ bool player::is_habitable_feat(dungeon_feature_type actual_grid) const if (airborne() || species == SP_DJINNI) return true; - if (actual_grid == DNGN_LAVA + if (actual_grid == DNGN_LAVA && species != SP_LAVA_ORC || actual_grid == DNGN_DEEP_WATER && !can_swim()) { return false; @@ -720,6 +720,48 @@ bool player::can_go_berserk(bool intentional, bool potion, bool quiet) const return true; } +bool player::can_jump(bool quiet) const +{ + if (duration[DUR_EXHAUSTED]) + { + if (!quiet) + mpr("You're too exhausted to jump."); + // or else they won't notice -- no message here + return false; + } + + if (you.in_water()) + { + if (!quiet) + mpr("You can't jump while in water."); + return false; + } + if (feat_is_lava(grd(you.pos()))) + { + if (!quiet) + mpr("You can't jump while standing in lava."); + return false; + } + if (you.liquefied_ground()) + { + if (!quiet) + mpr("You can't jump while stuck in this mess."); + return false; + } + if (you.is_constricted()) + { + if (!quiet) + mpr("You can't jump while being constricted."); + return false; + } + + return true; +} + +bool player::can_jump() const +{ + return can_jump(false); +} bool player::berserk() const { return (duration[DUR_BERSERK]); diff --git a/crawl-ref/source/player-equip.cc b/crawl-ref/source/player-equip.cc index 7749b7a..ce99ca5 100644 --- a/crawl-ref/source/player-equip.cc +++ b/crawl-ref/source/player-equip.cc @@ -315,6 +315,17 @@ static void _equip_artefact_effect(item_def &item, bool *show_msgs, bool unmeld) mpr("You feel a build-up of mutagenic energy."); artefact_wpn_learn_prop(item, ARTP_MUTAGENIC); } + if (unknown_proprt(ARTP_JUMP)) + { + if (msg) + { + if (player_jump_level(true) > player_jump_level(false)) + mpr("You feel more sure on your feet."); + else + mpr("Your feet feel light for a moment."); + } + artefact_wpn_learn_prop(item, ARTP_JUMP); + } if (!unmeld && !item.cursed() && proprt[ARTP_CURSED] > 0 && one_chance_in(proprt[ARTP_CURSED])) @@ -929,6 +940,11 @@ static void _equip_armour_effect(item_def& arm, bool unmeld) mpr("You feel rather light."); break; + case SPARM_JUMPING: + if (you.evokable_jump()) + mpr("You feel more sure on your feet."); + break; + case SPARM_MAGIC_RESISTANCE: mpr("You feel resistant to hostile enchantments."); break; diff --git a/crawl-ref/source/player.cc b/crawl-ref/source/player.cc index 592193d..dbc53bd 100644 --- a/crawl-ref/source/player.cc +++ b/crawl-ref/source/player.cc @@ -127,7 +127,8 @@ static void _moveto_maybe_repel_stairs() } } -static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) +static bool _check_moveto_cloud(const coord_def& p, const string &move_verb, + bool interactive = true) { const int cloud = env.cgrid(p); if (cloud != EMPTY_CLOUD && !you.confused()) @@ -151,22 +152,29 @@ static bool _check_moveto_cloud(const coord_def& p, const string &move_verb) return true; } - string prompt = make_stringf("Really %s into that cloud of %s?", - move_verb.c_str(), - cloud_name_at_index(cloud).c_str()); - learned_something_new(HINT_CLOUD_WARNING); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { + string prompt = make_stringf("Really %s into that cloud of %s?", + move_verb.c_str(), + cloud_name_at_index(cloud).c_str()); + learned_something_new(HINT_CLOUD_WARNING); + + if (!yesno(prompt.c_str(), false, 'n')) + { canned_msg(MSG_OK); return false; + } + } else + { + return false; } } } return true; } -static bool _check_moveto_trap(const coord_def& p, const string &move_verb) + static bool _check_moveto_trap(const coord_def& p, const string &move_verb, + bool interactive = true) { // If there's no trap, let's go. trap_def* trap = find_trap(p); @@ -175,28 +183,40 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) if (trap->type == TRAP_ZOT && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Do you really want to %s into the Zot trap", - move_verb.c_str()); - - if (!yes_or_no("%s", prompt.c_str())) + if (interactive) + { + string prompt = make_stringf("Do you really want to %s into the Zot trap", + move_verb.c_str()); + if (!yes_or_no("%s", prompt.c_str())) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } else if (!trap->is_safe() && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf( - "Really %s %s that %s?", - move_verb.c_str(), - (trap->type == TRAP_ALARM || trap->type == TRAP_PLATE) ? "onto" - : "into", - feature_description_at(p, false, DESC_BASENAME, false).c_str()); - - if (!yesno(prompt.c_str(), true, 'n')) + if (interactive) + { + string prompt = make_stringf( + "Really %s %s that %s?", + move_verb.c_str(), + (trap->type == TRAP_ALARM + || trap->type == TRAP_PLATE) ? "onto" + : "into", + feature_description_at(p, false, + DESC_BASENAME, + false).c_str()); + if (!yesno(prompt.c_str(), true, 'n')) + { + canned_msg(MSG_OK); + return false; + } + } else { - canned_msg(MSG_OK); return false; } } @@ -204,7 +224,7 @@ static bool _check_moveto_trap(const coord_def& p, const string &move_verb) } static bool _check_moveto_dangerous(const coord_def& p, const string& msg, - bool cling = true) + bool cling = true, bool interactive = true) { if (you.can_swim() && feat_is_water(env.grid(p)) || you.airborne() || cling && you.can_cling_to(p) @@ -213,28 +233,30 @@ static bool _check_moveto_dangerous(const coord_def& p, const string& msg, return true; } - if (msg != "") - mpr(msg.c_str()); - else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) - mpr("You cannot swim in your current form."); - else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) - && is_feat_dangerous(env.grid(p))) + if (interactive) { - mpr("You cannot enter lava in your current form."); + if (msg != "") + mpr(msg.c_str()); + else if (you.species == SP_MERFOLK && feat_is_water(env.grid(p))) + mpr("You cannot swim in your current form."); + else if (you.species == SP_LAVA_ORC && feat_is_lava(env.grid(p)) + && is_feat_dangerous(env.grid(p))) + { + mpr("You cannot enter lava in your current form."); + } + else + canned_msg(MSG_UNTHINKING_ACT); } - else - canned_msg(MSG_UNTHINKING_ACT); - return false; } static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive = true) { if (you.is_wall_clinging() && (move_verb == "blink" || move_verb == "passwall")) { - return _check_moveto_dangerous(p, msg, false); + return _check_moveto_dangerous(p, msg, false, interactive); } if (!need_expiration_warning() && need_expiration_warning(p) @@ -243,60 +265,69 @@ static bool _check_moveto_terrain(const coord_def& p, const string &move_verb, if (!_check_moveto_dangerous(p, msg)) return false; - string prompt; + if (interactive) + { + string prompt; - if (msg != "") - prompt = msg + " "; + if (msg != "") + prompt = msg + " "; - prompt += "Are you sure you want to " + move_verb; + prompt += "Are you sure you want to " + move_verb; - if (you.ground_level()) - prompt += " into "; - else - prompt += " over "; + if (you.ground_level()) + prompt += " into "; + else + prompt += " over "; - prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; + prompt += env.grid(p) == DNGN_DEEP_WATER ? "deep water" : "lava"; - prompt += need_expiration_warning(DUR_FLIGHT, p) - ? " while you are losing your buoyancy?" - : " while your transformation is expiring?"; + prompt += need_expiration_warning(DUR_FLIGHT, p) + ? " while you are losing your buoyancy?" + : " while your transformation is expiring?"; - if (!yesno(prompt.c_str(), false, 'n')) - { - canned_msg(MSG_OK); - return false; + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } - return _check_moveto_dangerous(p, msg); + return _check_moveto_dangerous(p, msg, true, interactive); } -static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb) +static bool _check_moveto_exclusion(const coord_def& p, const string &move_verb, + bool interactive = true) { + string prompt; + if (is_excluded(p) && !is_stair_exclusion(p) && !is_excluded(you.pos()) && !crawl_state.disables[DIS_CONFIRMATIONS]) { - string prompt = make_stringf("Really %s into a travel-excluded area?", - move_verb.c_str()); - - if (!yesno(prompt.c_str(), false, 'n')) + if (interactive) { - canned_msg(MSG_OK); - return false; + prompt = make_stringf("Really %s into a travel-excluded area?", + move_verb.c_str()); + + if (!yesno(prompt.c_str(), false, 'n')) + { + canned_msg(MSG_OK); + return false; + } } } return true; } bool check_moveto(const coord_def& p, const string &move_verb, - const string &msg) + const string &msg, bool interactive) { - return (_check_moveto_terrain(p, move_verb, msg) - && _check_moveto_cloud(p, move_verb) - && _check_moveto_trap(p, move_verb) - && _check_moveto_exclusion(p, move_verb)); + return (_check_moveto_terrain(p, move_verb, msg, interactive) + && _check_moveto_cloud(p, move_verb, interactive) + && _check_moveto_trap(p, move_verb, interactive) + && _check_moveto_exclusion(p, move_verb, interactive)); } static void _splash() @@ -530,6 +561,22 @@ bool player_genus(genus_type which_genus, species_type species) return (species_genus(species) == which_genus); } +int player_jump_level(bool is_evoke) +{ + int level = 0; + + if (is_evoke) + { + level = 3 + (you.skill(SK_EVOCATIONS, 1) >= 5) + + (you.skill(SK_EVOCATIONS, 1) >= 10); + } + else + { + level = 2 + player_mutation_level(MUT_JUMP); + } + return(level); +} + // If transform is true, compare with current transformation instead // of (or in addition to) underlying species. // (See mon-data.h for species/genus use.) @@ -3595,8 +3642,10 @@ void level_change(int source, const char* aux, bool skip_attribute_increase) } if (you.experience_level == 6 || you.experience_level == 12) + { perma_mutate(MUT_SHAGGY_FUR, 1, "growing up"); - + perma_mutate(MUT_JUMP, 1, "growing up"); + } _felid_extra_life(); break; @@ -4682,6 +4731,7 @@ bool enough_mp(int minimum, bool suppress_msg, bool include_items) return true; } + bool enough_zp(int minimum, bool suppress_msg) { ASSERT(!crawl_state.game_is_arena()); @@ -4843,6 +4893,7 @@ void set_mp(int new_amount) you.redraw_magic_points = true; } + // If trans is true, being berserk and/or transformed is taken into account // here. Else, the base hp is calculated. If rotted is true, calculate the // real max hp you'd have if the rotting was cured. diff --git a/crawl-ref/source/player.h b/crawl-ref/source/player.h index dddf445..a62d7f9 100644 --- a/crawl-ref/source/player.h +++ b/crawl-ref/source/player.h @@ -562,6 +562,8 @@ public: bool can_go_berserk() const; bool can_go_berserk(bool intentional, bool potion = false, bool quiet = false) const; + bool can_jump() const; + bool can_jump(bool quiet) const; void go_berserk(bool intentional, bool potion = false); bool berserk() const; bool has_lifeforce() const; @@ -791,7 +793,7 @@ void moveto_location_effects(dungeon_feature_type old_feat, const coord_def& old_pos=coord_def()); bool check_moveto(const coord_def& p, const string &move_verb = "step", - const string &msg = ""); + const string &msg = "", bool interactive = true); void move_player_to_grid(const coord_def& p, bool stepped, bool allow_shift); bool is_map_persistent(void); @@ -814,6 +816,7 @@ bool is_effectively_light_armour(const item_def *item); bool player_effectively_in_light_armour(); bool player_under_penance(void); +int player_jump_level(bool is_evoke); int burden_change(void); diff --git a/crawl-ref/source/rltiles/dc-abilities.txt b/crawl-ref/source/rltiles/dc-abilities.txt index fcbf3cc..1557f28 100644 --- a/crawl-ref/source/rltiles/dc-abilities.txt +++ b/crawl-ref/source/rltiles/dc-abilities.txt @@ -18,9 +18,11 @@ evoke_fog ABILITY_EVOKE_FOG evoke_invisibility_end ABILITY_EVOKE_INVISIBILITY_END evoke_invisibility ABILITY_EVOKE_INVISIBILITY flight ABILITY_EVOKE_FLIGHT +jump ABILITY_EVOKE_JUMP evoke_teleport ABILITY_EVOKE_TELEPORT flight_end ABILITY_FLIGHT_END flight ABILITY_FLIGHT +jump ABILITY_JUMP hellfire ABILITY_HELLFIRE mummy_restoration ABILITY_MUMMY_RESTORATION recharge ABILITY_RECHARGE diff --git a/crawl-ref/source/rltiles/dc-feat.txt b/crawl-ref/source/rltiles/dc-feat.txt index d8ee583..3a9f38a 100644 --- a/crawl-ref/source/rltiles/dc-feat.txt +++ b/crawl-ref/source/rltiles/dc-feat.txt @@ -721,6 +721,7 @@ effect/disjunct0 DISJUNCT effect/disjunct1 effect/disjunct2 effect/disjunct3 +landing LANDING effect/heataura0 HEAT_AURA effect/heataura1 effect/heataura2 diff --git a/crawl-ref/source/rltiles/dc-item.txt b/crawl-ref/source/rltiles/dc-item.txt index 1180806..e9b48d5 100644 --- a/crawl-ref/source/rltiles/dc-item.txt +++ b/crawl-ref/source/rltiles/dc-item.txt @@ -606,6 +606,7 @@ i-dexterity BRAND_ARM_DEXTERITY i-intelligence BRAND_ARM_INTELLIGENCE i-ponderous BRAND_ARM_PONDEROUSNESS i-flying BRAND_ARM_FLYING +i-jumping BRAND_ARM_JUMPING i-magic-res BRAND_ARM_MAGIC_RESISTANCE i-protection BRAND_ARM_PROTECTION %rim 0 diff --git a/crawl-ref/source/rltiles/gui/abilities/jump.png b/crawl-ref/source/rltiles/gui/abilities/jump.png new file mode 100644 index 0000000000000000000000000000000000000000..bb80d7233abf4dd4c155c8f598f9a6dfbd06c963 GIT binary patch literal 957 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VB8tt6XFWwQVZ18)v>a&BAaGv zYU=0b2NE&p;^pNP6ciK^5>j+IrlFyssi|pXWMpGwV{dQoarBP2w|7ubP;hW?czAeJ zR8(ACTtY%ZQc_Y%N=kZqdRA6ec6N4tetvOraaC1Sb#--3O%2ex`uh5YhK8o5rk0kL z*4Eaxwzl^6_U`WP-rnB6zP|qc{s|K%Oqw)l%9JTnr%s(QW5&FB^XAW=zhJ?FB}<kp zUAlDn^5v^mty;5Y&DynV*R5N(apT6#n>TOSvSsVmt=qP3+rEAKjvYI8?%cU+*RFm0 z_U+%l|G<F*2M-=Rbm-9G!-tO?Idb&q(PPJs9Y22j#EBCpPo6w=>eT7er_Y=@bN1}n z^XJcBxNzaprAwDDU%q<v>a}avu3x`?>(;H?w{PFMbLZ~eyZ7$hyMO=wg9i^DKYsk= z$&;r~pFVr`?D_NOFJ8QO_3G8@*RS8adGr4L`;Q+#e){z3>({T}zkmPv^XH#GfBydc z`|sbs|Ns9_`8hEd7_f>ZL4Lsuk~&7_>Gcg$X3Si;dH;!1r_Wxwe)Imb_n$uf`1Kp4 ze##S0b6|{5@^o<wskpUvVtDW&2NAdMK#^+Z>YZUBZ}x`2*}Hhcf+nE^U5&^iT@gy( z{v?UtV@j;}c*gGS&$Rv4<rnueZrsC{^dn04$On^&bEiq(eci-)*t0N1-b%r)Ny*DG zcSTCvpLO~ZI9s?h9_;uh^fy(I(d$Bxnas4i^Z6%aa&FusBrs>%=Xk-CXoYpBK9oKE zy!&2jP1Vvf><lj#2}w_n={woH{Hryu(=55?3Gvz<aSoMp?WX4zUi=)|Q?$L~chUwv z6=Q|ehdS@KewXi>ut0Tc%Nf@*lj1%maI%!y6$T0y&i|~u>O(_X(&xHkS&v-qoDu$> zs@b(*LF%8I&*uuczM840+U3Gotsgi2ddvB~n~b897C*W6_*+6w^7U1sMRsx89M`gY z3>WirYiyd-Jom#Z@jQd45&S{x@5;PlYzRFxKWXz^W8=4%7zE}8y!evFe!7t7a7lZu zb<a0H>CSdj*<8!DSMq@-&zLlYm;1??UFY8#PcfWu+o|%of%KJQGwWVG6_P5=EOl<* l$9qLXtLsY7CYdk&e;BiK3wkGX7}<d`iKnZd%Q~loCIG90%-a9} literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png b/crawl-ref/source/rltiles/item/armour/brands/i-jumping.png new file mode 100644 index 0000000000000000000000000000000000000000..006c2a1b1ae0395eef6e8266ee3398fd6e7203b5 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^S0D`piY~`Ij^1(5J%0<x zVJr#q3ubV5b|VeQarSg^43W5;oFH-M0Hc?2T!Yb#?GBPf`3rkq-4}4}dH>x}WaIj1 z_P`_i>g}g|{QFznpxM;KgeB{ocnPy0&kT`l4rYemO~SlSy5p=sHhH@GxvX<aXaWEM C-Y|Or literal 0 HcmV?d00001 diff --git a/crawl-ref/source/rltiles/misc/landing.png b/crawl-ref/source/rltiles/misc/landing.png new file mode 100644 index 0000000000000000000000000000000000000000..17d5001279d4e1bcfcb74b824f12a9be9f7265a9 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJd7dtgArYK!L!!A3DDVW|-1z-% zYdOPquBi_$l=46KPr1;QD-=AlKRV{_5mB|G^enCg%q0zc7aAYLelo5<-aVna<4Irs zg;%kg_AqL`66L$}tcyYQN3@4r<D_I4^Lst)g#z)PCo@+yeB@%uHr)PYCJV=khDe6c zhOOHynOIjTGhJVNaPzM9TlecSG|m%9+W+L4;FaF^x0*mJ7_A)G%TzoiFFxqs`3mS# N22WQ%mvv4FO#uI5RRRD2 literal 0 HcmV?d00001 diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc index 92c604a..cc22a8b 100644 --- a/crawl-ref/source/shopping.cc +++ b/crawl-ref/source/shopping.cc @@ -1140,6 +1140,9 @@ int artefact_value(const item_def &item) if (prop[ ARTP_BERSERK ]) ret += 5; + if (prop[ ARTP_JUMP ]) + ret += 5; + if (prop[ ARTP_INVISIBLE ]) ret += 20; @@ -1704,6 +1707,7 @@ unsigned int item_value(item_def item, bool ident) case SPARM_SEE_INVISIBLE: case SPARM_INTELLIGENCE: case SPARM_FLYING: + case SPARM_JUMPING: case SPARM_PRESERVATION: case SPARM_STEALTH: case SPARM_STRENGTH: diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc index c9f555b..0d770d9 100644 --- a/crawl-ref/source/target.cc +++ b/crawl-ref/source/target.cc @@ -11,6 +11,7 @@ #include "itemprop.h" #include "libutil.h" #include "losglobal.h" +#include "player.h" #include "spl-damage.h" #include "terrain.h" @@ -52,6 +53,11 @@ bool targetter::anyone_there(coord_def loc) return actor_at(loc); } +bool targetter::has_additional_sites(coord_def loc, bool allow_harmful) +{ + return false; +} + targetter_beam::targetter_beam(const actor *act, int range, zap_type zap, int pow, int min_ex_rad, int max_ex_rad) : min_expl_rad(min_ex_rad), @@ -875,3 +881,230 @@ aff_type targetter_spray::is_affected(coord_def loc) return affected; } + +targetter_jump::targetter_jump(const actor* act, int range) +{ + ASSERT(act); + agent = act; + origin = act->pos(); + range2 = dist_range(range); + jump_is_blocked = false; +} + +bool targetter_jump::valid_aim(coord_def a) +{ + actor *act; + bool is_valid = true; + coord_def c, jump_pos; + ray_def ray; + + act = actor_at(a); + if ((origin - a).abs() > range2) + { + is_valid = notify_fail("Out of range."); + } + // If there's an actor we can see, check that we have at least one valid + // landing site for a jump attack based on what the agent can see. + else if (act && agent->can_see(act)) + { + if(!has_additional_sites(a, true)) + { + if (no_landing_reason == BLOCKED_FLYING) + is_valid = notify_fail("A flying creature is in the way."); + else if (no_landing_reason == BLOCKED_GIANT) + is_valid = notify_fail("A giant creature is in the way."); + else if (no_landing_reason == BLOCKED_MOVE) + is_valid = notify_fail("There is no safe place to move near the" + " monster."); + else if (no_landing_reason == BLOCKED_PATH) + is_valid = notify_fail("A dungeon feature is in the way."); + } + return is_valid; + } + else if (a != origin && !cell_see_cell(origin, a, LOS_NO_TRANS)) + { + if (agent->see_cell(a)) + is_valid = notify_fail("There's something in the way."); + else + is_valid = notify_fail("You cannot see that place."); + } + else if (feat_is_solid(grd(a))) + { + is_valid = notify_fail("There's something in the way."); + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + is_valid = notify_fail("A dungeon feature is in the way"); + return is_valid; + } + return is_valid; +} + +bool targetter_jump::valid_landing(coord_def a, bool check_invis) +{ + actor *act; + ray_def ray; + + if (grd(a) == DNGN_OPEN_SEA || grd(a) == DNGN_LAVA_SEA + || !agent->is_habitable(a) || (origin - a).abs() > range2 - 1) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + if (agent->is_player()) + { + monster* beholder = you.get_beholder(a); + if (beholder) + { + return false; + blocked_landing_reason = BLOCKED_MOVE; + } + + monster* fearmonger = you.get_fearmonger(a); + if (fearmonger) + { + blocked_landing_reason = BLOCKED_MOVE; + return false; + } + } + if (!find_ray(agent->pos(), a, ray, opc_no_trans)) + { + blocked_landing_reason = BLOCKED_PATH; + return false; + } + // Check if a landing site is invalid due to a visible monster obstructing + // the path. + ray.advance(); + while(map_bounds(ray.pos())) + { + act = actor_at(ray.pos()); + if (ray.pos() == a) + { + if (act && (!check_invis || agent->can_see(act))) + { + blocked_landing_reason = BLOCKED_OCCUPIED; + return false; + } + break; + } + const dungeon_feature_type grid = grd(ray.pos()); + if (act && (!check_invis || agent->can_see(act))) + { + + // Can't jump over airborn enemies nor gient enemies not in deep + // water or lava. + if (act->airborne()) + { + blocked_landing_reason = BLOCKED_FLYING; + return false; + } + else if(act->body_size() == SIZE_GIANT + && grid != DNGN_DEEP_WATER && grid != DNGN_LAVA) + { + blocked_landing_reason = BLOCKED_GIANT; + return false; + } + } + ray.advance(); + } + return true; +} + +aff_type targetter_jump::is_affected(coord_def loc) +{ + aff_type aff = AFF_NO; + + if (loc == aim) + aff = AFF_YES; + else if (additional_sites.count(loc)) + aff = AFF_LANDING; + return aff; +} + +// Handle setting the aim for jump, which is the landing position of the jump. +// If something unsee either occupies the aim positiion or blocks the jump path, +// indicate that with jump_is_blocked. +bool targetter_jump::set_aim(coord_def a) +{ + actor *act; + ray_def ray; + set<coord_def>::const_iterator site; + + if (a == origin) + return false; + if (!targetter::set_aim(a)) + return false; + + jump_is_blocked = false; + act = actor_at(aim); + // Can't set aim if we can't see anything at the location. + if (!env.map_knowledge(aim).invisible_monster() + && (!act || !agent->can_see(act))) + return false; + // Find our set of landing sites, choose one at random, and see if it's + // actually blocked. + set_additional_sites(aim); + if (additional_sites.size()) + { + int site_ind = random2(additional_sites.size()); + for (site = additional_sites.begin(); site_ind > 0; site++) + site_ind--; + landing_site = *site; + if (!valid_landing(landing_site, false)) + jump_is_blocked = true; + return true; + } + return false; +} + +void targetter_jump::set_additional_sites(coord_def a) +{ + get_additional_sites(a); + additional_sites = temp_sites; +} + +void targetter_jump::get_additional_sites(coord_def a) +{ + bool agent_adjacent = a.distance_from(agent->pos()) == 1; + temp_sites.clear(); + + no_landing_reason = BLOCKED_NONE; + for (adjacent_iterator ai(a, false); ai; ++ai) + { + // See if site is valid, record a putative reason for why no sites were + // found. A flying or giant monster blocking the landing site gets + // priority as an reason, since it's very jump-specific. + if (!agent_adjacent || agent->pos().distance_from(*ai) > 1) + { + if (valid_landing(*ai)) + { + temp_sites.insert(*ai); + no_landing_reason = BLOCKED_NONE; + } + else if (no_landing_reason != BLOCKED_FLYING + && no_landing_reason != BLOCKED_GIANT) + { + no_landing_reason = blocked_landing_reason; + } + } + } +} + +// See if we can find at least one valid landing position for the +// given monster. Possibly we also care to exclude sites harmful to +// the player. +bool targetter_jump::has_additional_sites(coord_def a, bool allow_harmful) +{ + set<coord_def>::const_iterator site; + get_additional_sites(a); + // Find a valid jump_attack position in the adjacent squares, chosing the + // valid position closest to the player. + for (site = temp_sites.begin(); + site != temp_sites.end(); site++) + { + if (allow_harmful || (agent->is_player() + && check_moveto(*site, "jump", "", false))) + return true; + } + return false; +} diff --git a/crawl-ref/source/target.h b/crawl-ref/source/target.h index 0ec6882..d2aa800 100644 --- a/crawl-ref/source/target.h +++ b/crawl-ref/source/target.h @@ -12,6 +12,7 @@ enum aff_type // sign and non-zeroness matters AFF_YES, // intended/likely to affect // If you want to extend this to pass the probability somehow, feel free to, // just keep AFF_YES the minimal "bright" value. + AFF_LANDING, // Valid jump attack landing site }; class targetter @@ -30,6 +31,7 @@ public: virtual bool can_affect_walls(); virtual aff_type is_affected(coord_def loc) = 0; + virtual bool has_additional_sites(coord_def a, bool allow_harmful); protected: bool anyone_there(coord_def loc); }; @@ -188,4 +190,39 @@ private: int range2; }; +enum jump_block_reason +{ + BLOCKED_NONE, + BLOCKED_OCCUPIED, + BLOCKED_FLYING, + BLOCKED_GIANT, + BLOCKED_MOVE, + BLOCKED_PATH, +}; + +class targetter_jump : public targetter +{ +public: + targetter_jump(const actor* act, int range); + + bool valid_aim(coord_def a); + bool set_aim(coord_def a); + bool jump_is_blocked; + aff_type is_affected(coord_def loc); + bool has_additional_sites(coord_def a, bool allow_harmful); + set<coord_def> additional_sites; + coord_def landing_site; +private: + void set_additional_sites(coord_def a); + void get_additional_sites(coord_def a); + bool valid_landing(coord_def a, bool check_invis = true); + jump_block_reason no_landing_reason; + jump_block_reason blocked_landing_reason; + set<coord_def> temp_sites; + int _range; + int range2; +}; + + + #endif diff --git a/crawl-ref/source/tiledgnbuf.cc b/crawl-ref/source/tiledgnbuf.cc index cf4483f..de57a9e 100644 --- a/crawl-ref/source/tiledgnbuf.cc +++ b/crawl-ref/source/tiledgnbuf.cc @@ -288,6 +288,8 @@ void DungeonCellBuffer::pack_background(int x, int y, const packed_cell &cell) m_buf_feat.add(TILE_RAY, x, y); else if (bg & TILE_FLAG_RAY_OOR) m_buf_feat.add(TILE_RAY_OUT_OF_RANGE, x, y); + else if (bg & TILE_FLAG_LANDING) + m_buf_feat.add(TILE_LANDING, x, y); } } diff --git a/crawl-ref/source/tilepick.cc b/crawl-ref/source/tilepick.cc index 2f9b867..764d543 100644 --- a/crawl-ref/source/tilepick.cc +++ b/crawl-ref/source/tilepick.cc @@ -5149,6 +5149,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_SPIT_ACID; case ABIL_BLINK: return TILEG_ABILITY_BLINK; + case ABIL_JUMP: + return TILEG_ABILITY_JUMP; // Others case ABIL_DELAYED_FIREBALL: @@ -5192,6 +5194,8 @@ tileidx_t tileidx_ability(const ability_type ability) return TILEG_ABILITY_EVOKE_INVISIBILITY_END; case ABIL_EVOKE_FLIGHT: return TILEG_ABILITY_EVOKE_FLIGHT; + case ABIL_EVOKE_JUMP: + return TILEG_ABILITY_EVOKE_JUMP; case ABIL_EVOKE_FOG: return TILEG_ABILITY_EVOKE_FOG; diff --git a/crawl-ref/source/tileview.cc b/crawl-ref/source/tileview.cc index 5718aef..9c898e1 100644 --- a/crawl-ref/source/tileview.cc +++ b/crawl-ref/source/tileview.cc @@ -966,10 +966,16 @@ void tile_place_ray(const coord_def &gc, aff_type in_range) void tile_draw_rays(bool reset_count) { + tileidx_t flag; + for (unsigned int i = 0; i < num_tile_rays; i++) { - tileidx_t flag = tile_ray_vec[i].in_range > AFF_MAYBE ? TILE_FLAG_RAY - : TILE_FLAG_RAY_OOR; + if (tile_ray_vec[i].in_range < AFF_YES) + flag = TILE_FLAG_RAY_OOR; + else if (tile_ray_vec[i].in_range == AFF_YES) + flag = TILE_FLAG_RAY; + else if (tile_ray_vec[i].in_range == AFF_LANDING) + flag = TILE_FLAG_LANDING; env.tile_bg(tile_ray_vec[i].ep) |= flag; } diff --git a/crawl-ref/source/webserver/game_data/static/cell_renderer.js b/crawl-ref/source/webserver/game_data/static/cell_renderer.js index 73df042..c3a2b97 100644 --- a/crawl-ref/source/webserver/game_data/static/cell_renderer.js +++ b/crawl-ref/source/webserver/game_data/static/cell_renderer.js @@ -662,6 +662,9 @@ function ($, view_data, main, tileinfo_player, icons, dngn, enums, map_knowledge this.draw_dngn(dngn.RAY, x, y); else if (bg.RAY_OOR) this.draw_dngn(dngn.RAY_OUT_OF_RANGE, x, y); + else if (bg.LANDING) + this.draw_dngn(dngn.LANDING, x, y); + } }, diff --git a/crawl-ref/source/webserver/game_data/static/enums.js b/crawl-ref/source/webserver/game_data/static/enums.js index 18ec2d9..cde3a92 100644 --- a/crawl-ref/source/webserver/game_data/static/enums.js +++ b/crawl-ref/source/webserver/game_data/static/enums.js @@ -239,7 +239,7 @@ define(function () { bg_flags.flags.ELDRITCH_NE = [0, 0x04]; bg_flags.flags.ELDRITCH_SE = [0, 0x08]; bg_flags.flags.ELDRITCH_SW = [0, 0x10]; - + bg_flags.flags.LANDING = [0, 0x200]; bg_flags.mask = 0x0000FFFF; exports.prepare_fg_flags = function (tileidx) diff --git a/crawl-ref/source/wiz-item.cc b/crawl-ref/source/wiz-item.cc index ffef8ee..085d915 100644 --- a/crawl-ref/source/wiz-item.cc +++ b/crawl-ref/source/wiz-item.cc @@ -262,6 +262,7 @@ static const char* _prop_name[] = { "SInv", "Inv", "Fly", + "Jump", "Blnk", "Bers", "Nois", @@ -305,6 +306,7 @@ static int8_t _prop_type[] = { ARTP_VAL_BOOL, //EYESIGHT ARTP_VAL_BOOL, //INVISIBLE ARTP_VAL_BOOL, //FLIGHT + ARTP_VAL_BOOL, //JUMPING ARTP_VAL_BOOL, //BLINK ARTP_VAL_BOOL, //BERSERK ARTP_VAL_POS, //NOISES @@ -1166,6 +1168,7 @@ static void _debug_acquirement_stats(FILE *ostat) "intelligence", "ponderous", "flight", + "jumping", "magic reistance", "protection", "stealth", diff --git a/crawl-ref/source/wiz-you.cc b/crawl-ref/source/wiz-you.cc index 096893e..03b26f4 100644 --- a/crawl-ref/source/wiz-you.cc +++ b/crawl-ref/source/wiz-you.cc @@ -284,6 +284,7 @@ void wizard_heal(bool super_heal) you.duration[DUR_CONF] = 0; you.duration[DUR_MISLED] = 0; you.duration[DUR_POISONING] = 0; + you.duration[DUR_EXHAUSTED] = 0; set_hp(you.hp_max); set_mp(you.max_magic_points); set_hunger(10999, true); -- 1.7.4.4 ![]() ![]() ![]() ![]() From 0c5968ef492b14106e06f1a7abf9f06f38a6046f Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Thu, 3 Oct 2013 19:14:14 -0500 Subject: [PATCH 1/4] Remove an unecessary range modification when finding jump attack targets. --- crawl-ref/source/directn.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc index 551fbe6..4e10d97 100644 --- a/crawl-ref/source/directn.cc +++ b/crawl-ref/source/directn.cc @@ -1053,13 +1053,8 @@ bool direction_chooser::find_default_monster_target(coord_def& result) const } if (!success) { - // Have to increase search_range by one so monsters out of range but - // with landing sites in-range are found. if (restricts == DIR_JUMP) - { find_targ = _find_jump_attack_mons; - search_range += 1; - } else find_targ = _find_monster; -- 1.7.11.5 From 05ffd673686bb4d623a4f6e0340e1dd6b0e97c76 Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Thu, 3 Oct 2013 19:24:45 -0500 Subject: [PATCH 2/4] Make the jump landing sites not have a range restriction. Previously the landing sites had a movement range of one less than the attack range, which mean fewer possible landing sites at further attack distances. This meant it was less favourable to jump attack closer enemies in the sense that there were more possible landing sites, and the furthest diagonal attacks were most favourable since they had one landing site. Now the number of landing sites isn't a function of attack distance, so closer jumps aren't penalized. --- crawl-ref/source/target.cc | 5 +---- crawl-ref/source/target.h | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc index d4bc3ce..a5e788b 100644 --- a/crawl-ref/source/target.cc +++ b/crawl-ref/source/target.cc @@ -888,7 +888,6 @@ targetter_jump::targetter_jump(const actor* act, int range) agent = act; origin = act->pos(); range2 = dist_range(range); - landing_range2 = dist_range(range - 1); jump_is_blocked = false; } @@ -938,10 +937,8 @@ bool targetter_jump::valid_landing(coord_def a, bool check_invis) actor *act; ray_def ray; - if ((origin - a).abs() > landing_range2) - return false; if (grd(a) == DNGN_OPEN_SEA || grd(a) == DNGN_LAVA_SEA - || !agent->is_habitable(a) || (origin - a).abs() > range2 - 1) + || !agent->is_habitable(a)) { blocked_landing_reason = BLOCKED_MOVE; return false; diff --git a/crawl-ref/source/target.h b/crawl-ref/source/target.h index 4ccee5b..17b8acc 100644 --- a/crawl-ref/source/target.h +++ b/crawl-ref/source/target.h @@ -220,7 +220,6 @@ private: jump_block_reason blocked_landing_reason; set<coord_def> temp_sites; int range2; - int landing_range2; }; -- 1.7.11.5 From 26e00c6a1616f5be7dffa2fc45a1e0e0a74f1572 Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Thu, 3 Oct 2013 19:39:58 -0500 Subject: [PATCH 3/4] Give a proper message when you attempt to target yourself with jump-attack. --- crawl-ref/source/target.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crawl-ref/source/target.cc b/crawl-ref/source/target.cc index a5e788b..db01f48 100644 --- a/crawl-ref/source/target.cc +++ b/crawl-ref/source/target.cc @@ -897,7 +897,11 @@ bool targetter_jump::valid_aim(coord_def a) coord_def c, jump_pos; ray_def ray; - if ((origin - a).abs() > range2) + if (origin == a) + { + is_valid = notify_fail("You cannot jump-attack yourself."); + } + else if ((origin - a).abs() > range2) { is_valid = notify_fail("Out of range."); } -- 1.7.11.5 From 8477c13cdf099140b381a03475dfdcca1195750e Mon Sep 17 00:00:00 2001 From: gammafunk <gammafunk@gmail.com> Date: Thu, 3 Oct 2013 20:02:48 -0500 Subject: [PATCH 4/4] Make the jump-attack stab nerf constant across categories. Instead of +2 nerf to stab_bonus for all except the worst category, which made the last two categories the same under jump attack, just give +1 unconditionally. The player has to be closer to jump-stab now that the attack range has been reduced by two. --- crawl-ref/source/melee_attack.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crawl-ref/source/melee_attack.cc b/crawl-ref/source/melee_attack.cc index 7ec8ca0..01ce070 100644 --- a/crawl-ref/source/melee_attack.cc +++ b/crawl-ref/source/melee_attack.cc @@ -4145,7 +4145,7 @@ void melee_attack::player_stab_check() } if (stab_bonus && jumping_attack) - stab_bonus = min(6, stab_bonus + 2); + stab_bonus += 1; // See if we need to roll against dexterity / stabbing. if (stab_attempt && roll_needed) -- 1.7.11.5 |
||||||
|
![]() |
|
(0023135) Lightli (reporter) 2013-06-10 02:47 |
On paper this already looks to be more balanced than stuff that actually made it into crawl. Like djinn. Will post again on whether or not it's balanced and, more importantly, if it's fun. |
(0023137) qoala (reporter) 2013-06-10 02:49 |
I uploaded a modification of this patch (generated by applying the patch, resolving conflicts and generating a new patch from the results) which should apply cleanly to current trunk. |
(0023140) Reaver (developer) 2013-06-10 08:06 edited on: 2013-06-10 08:22 |
I'm added a patch which switches to and HP cost for jumping, as discussed in the thread. It also deletes some weird cases where you couldn't jump. EDIT: It appears my patch isn't working. I compiled it, but I was so caught up in the fun of jumping I forgot to check if it caused hp to drop, which it doesn't. Looking closely, I think I only modified the part which displays what the cost is, since I assumed that it also changed the actual costs. |
(0023170) gammafunk (administrator) 2013-06-13 04:36 |
This is jump patch version 2, reworked based on discussions with the dev. team. The second version of the v2 patch (-corrected) has unneeded changes removed and whitespace fixes from checkwhite, so please use that version. Summary of changes: 1. You can only use the ability to jump attack an enemy or invisible monster marker; no more standalone 'cblink' jump. 2. The landing site is randomly chosen from the valid sites near the monster rather than being chosen by the player; no more double-targetting. If something invisible turns out to be blocking the randomly chosen landing site chosen, the jump fails with an appropriate message and the turn is wasted. If the player jump-attacks an invisible monster marker and no monster turns out to be at the chosen landing site, the attack won't happen, but the player is still moved. 3. Jump attack sets exhaustion after on jump, so no more double-jumping. The timeout used is the same as the breath timeout for breath attack, which is reduced by either evocations or XL, depending on whether the jump is from the ego or the mutation. 4. The damage increase is greatly reduced, from 200% to 20%. I kept in a damage increase despite some resistance to this in discussion. Since we set exhaustion and because the damage increase is so minor, any player who took the turns to 'kite' a slower enemy by jump attacking, running away to wait out exhaustion, and then jump attacking again would get far better use out of those turns by simply doing ordinary melee after the first jump. I think the damage increase is a nice incentive to use the ability at the beginning of a significant fight, but of course I'm open to tweaking or removing this. 5. Starting attack range increased from 2 to 3, up to a max of 5 instead of 4. The movement range is still one less than the attack range, so the player can move nore more than 4 spaces away at the highest jump ability level (which is 3). Jump attack at range 2 was pretty bad at start since it gave very little in the way of any positional advantage. The max movement of 4 may be two high; we could remove the third jump level or tweak it in other ways. 6. I've removed the MP cose for the player mutation and kept the 2mp cost for the evocation ability. An HP cost has been suggested, but even a 2HP cost is almost 30% of a felid's starting HP. 7. Species that can't wear boots are restricted from Dragoon, get an unbranded helmet/cap if they still choose the class. 8. No more landing cursor or alternate landing site tiles, since we don't need them. |
(0023177) gammafunk (administrator) 2013-06-13 23:29 |
Some problems that I need to fix in the current patch: 1. Attacking friendly and certain other warnings happen after the player has been moved to landing site, allowing the player to see the chosen landing site while the warning prompt is active. Will make the warnings occur before the player is moved to the chosen landing site 2. Attacking with elec should give a warning whenever a possible landing site puts the the player in water and there's susceptibility to self-electrocution. |
(0023181) gammafunk (administrator) 2013-06-14 04:57 edited on: 2013-06-14 09:07 |
Updated patch to latest version: jump_attack_v3-g8b0bc899 Player movement warnings should work correctly now. This should be a complete, and version of the more balanced jump attack suggested by discussion with the dev. team, and further feedback/testing is welcome. * Removed jump restrictions for player flight and stasis. Chei still allows jump, since there's not a consensus on this. * The bug where the player was moved to the chosen landing site before attack warnings occurred has been fixed. The warning messages work as expected, and I've tested electrocution, attacking friendlies, and sanctuary. * If any possible landing site would cause player electrocution or violate a sanctuary, the player is prompted correctly. There's code from melee_attack::handle_phase_attempted() that's somewhat duplicated in fight_jump() to handle this, but it's not too many lines |
(0023188) gammafunk (administrator) 2013-06-14 19:05 |
Updated patch to jump_attack_v4-g3e32d0a6.patch * Make +Jump get added to randartes with same frequency as +Fly/Blink/Rage * Whitespace and description cleanups |
(0023189) gammafunk (administrator) 2013-06-14 19:42 |
Updated patch to jump_attack_v5-g3e32d0a6.patch * Make boots of jumping get generated amongst random items |
(0023191) gammafunk (administrator) 2013-06-14 21:15 |
Updated patch to jump_attack_v6-g0407d63d9.patch * Appropriate jump-attack message in event log |
(0023204) KiloByte (manager) 2013-06-16 00:34 |
I don't think I like hats of jumping (eh...), or the Dragoon class. You don't have a class that starts with boots of running, discs of storms, etc. |
(0023205) gammafunk (administrator) 2013-06-16 01:02 |
Hats of jumping are already gone; you have to use the boots or get the property on an artefact in the current version. Dragoon class is certainly next on the chopping block. There's some talk about maybe giving Ar boots of jumping, since the jump ability is a fairly mild one, but mumra is still working on Ar. I think I'll just remove Dragoon for now and use wizmode to test. To help with merges and future work, I'm going to make a clone of trunk on gitorious with a jump_attack branch and push future changes there. That branch will a commit for the base ability, followed by commits for probably each of: the evocations abilility, the artefact property, the player mutation, and giving the mutation to felids. Then it'll be easier for the dev. team to pick the functional parts they want as well as any individual commits I make in the future. |
(0023207) KiloByte (manager) 2013-06-16 01:17 |
That would be great, having a large feature come as a stack of separate commits rather than a monolitic patch makes them far easier to review, both now and in the future. |
(0023210) gammafunk (administrator) 2013-06-16 09:53 |
Ok, I've made my own clone repo with a jump_attack branch: gitorious.org:~gammafunk/crawl/crawl-gammafunk.git The jump_attack branch has 7 commits, based from g8987dc3f3. 1. jump attack ability; this is by far the largest 2. Add jump armor ego 3. Add boots of jumping 4. Add jump artefact property 5. Add the player mutation 6. Give felids the jump mutation at start (increasing with level-up) 7. Fix a bug where the movement range wasn't properly checked Dragoons are gone from this branch; you have to find the boots, get it on an artefact, or play as a felid to get the ability. I've also uploaded the 7 commits as git patch file jump_attack_v7-g8987dc3f3.patch. From now on I'll just make commits to my repos. |
(0023287) gammafunk (administrator) 2013-06-24 21:45 |
I've updated the jump_attack branch on my repo, and wanted to point out one problem with the current version. If any landing site is dangerous (as determined by check_moveto()), yet habitable, we don't properly warn the player. In the current version, a monster won't be chosen as a default target if they have only dangerous landing sites, but there's nothing done if only some sites are dangerous. We have proper warnings for self-electrocution, attacking friendlies, and conduct violations. There are probably some double-warnings that need fixing if there are violations due to both target and landing sites, but the player is warned at least. I should probably be using/adapting the promping mechanics of check_moveto(), ideally in direction_chooser so that a cancel returns the player directly to targetting. The problem is that one could have different types of danger in one set of landing sites for a target. For instance if the player had temporary flight that's expiring, and some sites were in a freezing cloud, some in deep water. There can even be a mix of different types of clouds over the set of landing sites. I'm not sure if we'd want to try making a generic message or try making multiple prompts. Repo (branch jump_attack): git://gitorious.org/~gammafunk/crawl/crawl-gammafunk.git [^] Changes: * Merged recent master branch * Don't choose a monster as default target if its landing sites are all dangerous, yet habitable; The player can still target this monster manually. * Fix a bug where the default_place variable in direction_chooser was being ignored. * Cleanups and source documentation in targetter_jump |
(0023868) neil (administrator) 2013-08-25 07:09 |
This is now an experimental branch on CSZO: select (T)runk -> (X)perimental -> (J)ump attack. |
(0024112) neil (administrator) 2013-09-29 18:13 |
Zermako got a crash when jump attacking; it looks like there were no monsters in sight? http://dobrazupa.org/morgue/Zermako/crash-Zermako-20130929-160940.txt [^] |
(0024118) gammafunk (administrator) 2013-09-30 05:47 |
I've updated the jump_attack branch to fix the last remaining issues with jump, for the current design. I'm not sure if the crash is addressed by any of these fixes. I can spend some time later trying to track it down on the old version. If there are no major objections to the jump design, it might be best to test this in trunk rather than in a dedicated branch. We'd want to remove the 'give boots of jumping to all players that can wear them' commit. Also the icons are stolen from the flight ego/ability. I can remove these if it's better to have missing tiles. Changes: * You can jump-attack an empty space, but if it turns out there's no monster present, the jump fails, wasting the turn. This allows the player to try to jump-attack invis enemies. It's certainly possible to keep the restriction to visible enemies, if that's deemed better * Proper warnings are generated if your jump attack might land you in dangerous clouds, traps, above dangerous terrain with expiring flight/transformation, or in an exclusion. * There should be no double-prompting for conduct warnings. All checks such as these occur in fight_jump() instead of the melee_attack class so as to avoid double-prompting. It would be nice to move the logic fully into melee_attack, but it would require passing more junk to that class, which is used an awful lot. |
(0024148) gammafunk (administrator) 2013-10-04 03:23 |
I've uploaded jump_attack_tweaks_33dbfe7a3.patch with 4 commits, two of which are cleanups, and two which tweak the jump-attack behavior in light of the jump-attack range reduction to 3. |
(0024149) neil (administrator) 2013-10-04 04:14 |
All of this is trunk as of 0.14-a0-147-gbdb8dd0, with some fixes up to and including 0.14-a0-151-gd5000a5. Thanks! |
Mantis 1.1.8[^] Copyright © 2000 - 2009 Mantis Group |