unit.hpp

Go to the documentation of this file.
00001 /* $Id: unit.hpp 54122 2012-05-08 00:05:33Z fendrin $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License as published by
00008    the Free Software Foundation; either version 2 of the License, or
00009    (at your option) any later version.
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY.
00012 
00013    See the COPYING file for more details.
00014 */
00015 
00016 /** @file */
00017 
00018 #ifndef UNIT_H_INCLUDED
00019 #define UNIT_H_INCLUDED
00020 
00021 #include "boost/tuple/tuple.hpp"
00022 #include "formula_callable.hpp"
00023 #include "resources.hpp"
00024 #include "unit_types.hpp"
00025 #include "unit_map.hpp"
00026 
00027 class display;
00028 class game_state;
00029 class vconfig;
00030 class team;
00031 
00032 class unit_ability_list
00033 {
00034 public:
00035     unit_ability_list() :
00036         cfgs()
00037     {
00038     }
00039 
00040     bool empty() const;
00041 
00042     std::pair<int,map_location> highest(const std::string& key, int def=0) const;
00043     std::pair<int,map_location> lowest(const std::string& key, int def=0) const;
00044 
00045     std::vector<std::pair<const config *, map_location> > cfgs;
00046 };
00047 
00048 
00049 class unit
00050 {
00051 public:
00052     /**
00053      * Clear the unit status cache for all units. Currently only the hidden
00054      * status of units is cached this way.
00055      */
00056     static void clear_status_caches();
00057 
00058     friend struct unit_movement_resetter;
00059     // Copy constructor
00060     unit(const unit& u);
00061     /** Initializes a unit from a config */
00062     unit(const config& cfg, bool use_traits = false, game_state *state = NULL, const vconfig* vcfg = NULL);
00063     /**
00064       * Initializes a unit from a unit type
00065       * only real_unit may have random traits, name and gender
00066       * (to prevent OOS caused by RNG calls)
00067       */
00068     unit(const unit_type* t, int side, bool real_unit,
00069         unit_race::GENDER gender = unit_race::NUM_GENDERS);
00070     virtual ~unit();
00071     unit& operator=(const unit&);
00072 
00073 
00074     /** Advances this unit to another type */
00075     void advance_to(const unit_type *t, bool use_traits = false,
00076         game_state *state = 0)
00077     {
00078         advance_to(cfg_, t, use_traits, state);
00079     }
00080     const std::vector<std::string>& advances_to() const { return advances_to_; }
00081     const std::vector<std::string> advances_to_translated() const;
00082     void set_advances_to(const std::vector<std::string>& advances_to);
00083 
00084     /** The type id of the unit */
00085     const std::string& type_id() const { return type_; }
00086     const unit_type* type() const;
00087 
00088     /** id assigned by wml */
00089     const std::string& id() const { if (id_.empty()) return type_name(); else return id_; }
00090     /** The unique internal ID of the unit */
00091     size_t underlying_id() const { return underlying_id_; }
00092 
00093     /** The unit type name */
00094     const t_string& type_name() const {return type_name_;}
00095     const std::string& undead_variation() const {return undead_variation_;}
00096 
00097     /** The unit name for display */
00098     const t_string &name() const { return name_; }
00099     void set_name(const t_string &name) { name_ = name; }
00100     void rename(const std::string& name) {if (!unrenamable_) name_= name;}
00101 
00102     /** The unit's profile */
00103     std::string small_profile() const;
00104     std::string big_profile() const;
00105     /** Information about the unit -- a detailed description of it */
00106     t_string unit_description() const { return cfg_["description"]; }
00107 
00108     int hitpoints() const { return hit_points_; }
00109     int max_hitpoints() const { return max_hit_points_; }
00110     void set_hitpoints(int hp) { hit_points_ = hp; }
00111     int experience() const { return experience_; }
00112     int max_experience() const { return max_experience_; }
00113     void set_experience(int xp) { experience_ = xp; }
00114     int level() const { return level_; }
00115     void remove_movement_ai();
00116     void remove_attacks_ai();
00117 
00118     /** Colors for the unit's *current* hitpoints.
00119      * @returns a color between green and red representing
00120      * how wounded the unit is.
00121      * The maximum_hitpoints are considered as base.
00122      */
00123     SDL_Color hp_color() const;
00124     /** Colors for the unit's hitpoints.
00125      * @param hitpoints the amount of hitpoints the color represents.
00126      * @returns the color considering the current hitpoints as base.
00127      */
00128     SDL_Color hp_color(int hitpoints) const;
00129     /** Colors for the unit's XP. */
00130     SDL_Color xp_color() const;
00131     /** Set to true for some scenario-specific units which should not be renamed */
00132     bool unrenamable() const { return unrenamable_; }
00133     int side() const { return side_; }
00134     std::string side_id() const;
00135     const std::string& team_color() const { return flag_rgb_; }
00136     unit_race::GENDER gender() const { return gender_; }
00137     void set_side(unsigned int new_side) { side_ = new_side; }
00138     fixed_t alpha() const { return alpha_; }
00139 
00140     bool can_recruit() const { return canrecruit_; }
00141     const std::vector<std::string>& recruits() const
00142         { return recruit_list_; }
00143     void set_recruits(const std::vector<std::string>& recruits);
00144     const config& recall_filter() const { return filter_recall_; }
00145 
00146     bool incapacitated() const { return get_state(STATE_PETRIFIED); }
00147     int total_movement() const { return max_movement_; }
00148     int movement_left() const { return (movement_ == 0 || incapacitated()) ? 0 : movement_; }
00149     int vision() const { return vision_ < 0 ? max_movement_ : vision_; }
00150     int jamming() const { return jamming_; }
00151     void set_hold_position(bool value) { hold_position_ = value; }
00152     bool hold_position() const { return hold_position_; }
00153     void set_user_end_turn(bool value=true) { end_turn_ = value; }
00154     bool user_end_turn() const { return end_turn_; }
00155     int attacks_left() const { return (attacks_left_ == 0 || incapacitated()) ? 0 : attacks_left_; }
00156     int max_attacks() const { return max_attacks_; }
00157     void set_movement(int moves);
00158     void set_attacks(int left) { attacks_left_ = std::max<int>(0, left); }
00159     void unit_hold_position() { hold_position_ = end_turn_ = true; }
00160     void new_turn();
00161     void end_turn();
00162     void new_scenario();
00163     /** Called on every draw */
00164     void refresh();
00165 
00166     bool take_hit(int damage) { hit_points_ -= damage; return hit_points_ <= 0; }
00167     void heal(int amount);
00168     void heal_all() { hit_points_ = max_hitpoints(); }
00169     bool resting() const { return resting_; }
00170     void set_resting(bool rest) { resting_ = rest; }
00171 
00172     const std::map<std::string,std::string> get_states() const;
00173     bool get_state(const std::string& state) const;
00174     void set_state(const std::string &state, bool value);
00175     enum state_t { STATE_SLOWED = 0, STATE_POISONED, STATE_PETRIFIED,
00176         STATE_UNCOVERED, STATE_NOT_MOVED, STATE_UNHEALABLE, STATE_GUARDIAN, STATE_UNKNOWN = -1 };
00177     void set_state(state_t state, bool value);
00178     bool get_state(state_t state) const;
00179     static state_t get_known_boolean_state_id(const std::string &state);
00180     static std::map<std::string, state_t> get_known_boolean_state_names();
00181 
00182     bool has_moved() const { return movement_left() != total_movement(); }
00183     bool has_goto() const { return get_goto().valid(); }
00184     bool emits_zoc() const { return emit_zoc_ && !incapacitated();}
00185     bool matches_id(const std::string& unit_id) const;
00186     /* cfg: standard unit filter */
00187     bool matches_filter(const vconfig& cfg,const map_location& loc,bool use_flat_tod=false) const;
00188     const std::vector<std::string>& overlays() const { return overlays_; }
00189 
00190     void write(config& cfg) const;
00191 
00192     void set_role(const std::string& role) { role_ = role; }
00193     const std::string &get_role() const { return role_; }
00194 
00195     const std::vector<attack_type>& attacks() const { return attacks_; }
00196     std::vector<attack_type>& attacks() { return attacks_; }
00197 
00198     int damage_from(const attack_type& attack,bool attacker,const map_location& loc) const { return resistance_against(attack,attacker,loc); }
00199 
00200     /** A SDL surface, ready for display for place where we need a still-image of the unit. */
00201     const surface still_image(bool scaled = false) const;
00202 
00203     /** draw a unit.  */
00204     void redraw_unit();
00205     /** Clear unit_halo_  */
00206     void clear_haloes();
00207 
00208     void set_standing(bool with_bars = true);
00209 
00210     void set_ghosted(bool with_bars = true);
00211     void set_disabled_ghosted(bool with_bars = true);
00212 
00213     void set_idling();
00214     void set_selecting();
00215     unit_animation* get_animation() {  return anim_;};
00216     const unit_animation* get_animation() const {  return anim_;};
00217     void set_facing(map_location::DIRECTION dir);
00218     map_location::DIRECTION facing() const { return facing_; }
00219 
00220     bool invalidate(const map_location &loc);
00221     const std::vector<t_string>& trait_names() const { return trait_names_; }
00222     const std::vector<t_string>& trait_descriptions() const { return trait_descriptions_; }
00223     std::vector<std::string> get_traits_list() const;
00224 
00225     int cost () const { return unit_value_; }
00226 
00227     const map_location &get_location() const { return loc_; }
00228     /** To be called by unit_map or for temporary units only. */
00229     void set_location(const map_location &loc) { loc_ = loc; }
00230 
00231     const map_location& get_goto() const { return goto_; }
00232     void set_goto(const map_location& new_goto) { goto_ = new_goto; }
00233 
00234     int upkeep() const;
00235     bool loyal() const;
00236 
00237     void set_hidden(bool state);
00238     bool get_hidden() const { return hidden_; }
00239     bool is_flying() const { return flying_; }
00240     bool is_fearless() const { return is_fearless_; }
00241     bool is_healthy() const { return is_healthy_; }
00242     int movement_cost(const t_translation::t_terrain terrain) const;
00243     int vision_cost(const t_translation::t_terrain terrain) const;
00244     int jamming_cost(const t_translation::t_terrain terrain) const;
00245     int defense_modifier(t_translation::t_terrain terrain) const;
00246     int resistance_against(const std::string& damage_name,bool attacker,const map_location& loc) const;
00247     int resistance_against(const attack_type& damage_type,bool attacker,const map_location& loc) const
00248         {return resistance_against(damage_type.type(), attacker, loc);};
00249 
00250     //return resistances without any abilities applied
00251     utils::string_map get_base_resistances() const;
00252 //      std::map<terrain_type::TERRAIN,int> movement_type() const;
00253 
00254     bool can_advance() const { return advances_to_.empty()==false || get_modification_advances().empty() == false; }
00255     bool advances() const { return experience_ >= max_experience() && can_advance(); }
00256 
00257     std::map<std::string,std::string> advancement_icons() const;
00258     std::vector<std::pair<std::string,std::string> > amla_icons() const;
00259 
00260     std::vector<config> get_modification_advances() const;
00261     config::const_child_itors modification_advancements() const
00262     { return cfg_.child_range("advancement"); }
00263 
00264     size_t modification_count(const std::string& type, const std::string& id) const;
00265 
00266     void add_modification(const std::string& type, const config& modification,
00267                       bool no_add=false);
00268 
00269     bool move_interrupted() const { return movement_left() > 0 && interrupted_move_.x >= 0 && interrupted_move_.y >= 0; }
00270     const map_location& get_interrupted_move() const { return interrupted_move_; }
00271     void set_interrupted_move(const map_location& interrupted_move) { interrupted_move_ = interrupted_move; }
00272 
00273     /** States for animation. */
00274     enum STATE {
00275         STATE_STANDING,   /** anim must fit in a hex */
00276         STATE_FORGET,     /** animation will be automatically replaced by a standing anim when finished */
00277         STATE_ANIM};      /** normal anims */
00278     void start_animation(int start_time, const unit_animation *animation,
00279         bool with_bars,  const std::string &text = "",
00280         Uint32 text_color = 0, STATE state = STATE_ANIM);
00281 
00282     /** The name of the file to game_display (used in menus). */
00283     std::string absolute_image() const;
00284     std::string image_halo() const { return cfg_["halo"]; }
00285 
00286     std::string image_ellipse() const { return cfg_["ellipse"]; }
00287 
00288     config &variables() { return variables_; }
00289     const config &variables() const { return variables_; }
00290 
00291     std::string usage() const { return cfg_["usage"]; }
00292     unit_type::ALIGNMENT alignment() const { return alignment_; }
00293     const unit_race* race() const { return race_; }
00294 
00295     const unit_animation* choose_animation(const display& disp,
00296                 const map_location& loc, const std::string& event,
00297                 const map_location& second_loc = map_location::null_location,
00298             const int damage=0,
00299             const unit_animation::hit_type hit_type = unit_animation::INVALID,
00300             const attack_type* attack=NULL,const attack_type* second_attack = NULL,
00301             int swing_num =0) const;
00302 
00303     /**
00304      * Returns true if the unit is currently under effect by an ability with this given TAG NAME.
00305      * This means that the ability could be owned by the unit itself, or by an adjacent unit.
00306      */
00307     bool get_ability_bool(const std::string& tag_name, const map_location& loc) const;
00308     /**
00309      * Returns true if the unit is currently under effect by an ability with this given TAG NAME.
00310      * This means that the ability could be owned by the unit itself, or by an adjacent unit.
00311      */
00312     bool get_ability_bool(const std::string &tag_name) const
00313     { return get_ability_bool(tag_name, loc_); }
00314     unit_ability_list get_abilities(const std::string &tag_name, const map_location& loc) const;
00315     unit_ability_list get_abilities(const std::string &tag_name) const
00316     { return get_abilities(tag_name, loc_); }
00317     /** Tuple of: neutral ability name, gendered ability name, description */
00318     std::vector<boost::tuple<t_string,t_string,t_string> > ability_tooltips(bool force_active = false) const;
00319     std::vector<std::string> get_ability_list() const;
00320     bool has_ability_type(const std::string& ability) const;
00321 
00322     const game_logic::map_formula_callable_ptr& formula_vars() const { return formula_vars_; }
00323     void add_formula_var(std::string str, variant var);
00324     bool has_formula() const { return !unit_formula_.empty(); }
00325     bool has_loop_formula() const { return !unit_loop_formula_.empty(); }
00326     bool has_priority_formula() const { return !unit_priority_formula_.empty(); }
00327     const std::string& get_formula() const { return unit_formula_; }
00328     const std::string& get_loop_formula() const { return unit_loop_formula_; }
00329     const std::string& get_priority_formula() const { return unit_priority_formula_; }
00330 
00331     void backup_state();
00332     void apply_modifications();
00333     void generate_traits(bool musthaveonly=false, game_state* state = 0);
00334     void generate_name(rand_rng::simple_rng *rng = 0);
00335 
00336     // Only see_all=true use caching
00337     bool invisible(const map_location& loc, bool see_all=true) const;
00338 
00339     bool is_visible_to_team(team const& team, bool const see_all = true, gamemap const& map = *resources::game_map) const;
00340 
00341     /** Mark this unit as clone so it can be inserted to unit_map
00342      * @returns                   self (for convenience)
00343      **/
00344     unit& clone(bool is_temporary=true);
00345 
00346     std::string TC_image_mods() const;
00347     const std::string& effect_image_mods() const;
00348     std::string image_mods() const;
00349 
00350     /**
00351      * Gets the portrait for a unit.
00352      *
00353      * @param size                The size of the portrait.
00354      * @param side                The side the portrait is shown on.
00355      *
00356      * @returns                   The portrait with the wanted size.
00357      * @retval NULL               The wanted portrait doesn't exist.
00358      */
00359     const tportrait* portrait(
00360         const unsigned size, const tportrait::tside side) const;
00361 
00362 private:
00363     void advance_to(const config &old_cfg, const unit_type *t,
00364         bool use_traits, game_state *state);
00365 
00366     bool internal_matches_filter(const vconfig& cfg,const map_location& loc,
00367         bool use_flat_tod) const;
00368     /*
00369      * cfg: an ability WML structure
00370      */
00371     bool ability_active(const std::string& ability,const config& cfg,const map_location& loc) const;
00372     bool ability_affects_adjacent(const std::string& ability,const config& cfg,int dir,const map_location& loc) const;
00373     bool ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const;
00374     bool resistance_filter_matches(const config& cfg,bool attacker,const std::string& damage_name, int res) const;
00375 
00376     bool has_ability_by_id(const std::string& ability) const;
00377     void remove_ability_by_id(const std::string& ability);
00378 
00379     /** register a trait's name and its description for UI's use*/
00380     void add_trait_description(const config& trait, const t_string& description);
00381 
00382     void set_underlying_id();
00383 
00384     config cfg_;
00385     map_location loc_;
00386 
00387     std::vector<std::string> advances_to_;
00388     std::string type_;
00389     const unit_race* race_;
00390     std::string id_;
00391     t_string name_;
00392     size_t underlying_id_;
00393     t_string type_name_;
00394     std::string undead_variation_;
00395     std::string variation_;
00396 
00397     int hit_points_;
00398     int max_hit_points_;
00399     int experience_;
00400     int max_experience_;
00401     int level_;
00402     bool canrecruit_;
00403     std::vector<std::string> recruit_list_;
00404     unit_type::ALIGNMENT alignment_;
00405     std::string flag_rgb_;
00406     std::string image_mods_;
00407 
00408     bool unrenamable_;
00409     int side_;
00410     const unit_race::GENDER gender_;
00411 
00412     fixed_t alpha_;
00413 
00414     std::string unit_formula_;
00415     std::string unit_loop_formula_;
00416     std::string unit_priority_formula_;
00417     game_logic::map_formula_callable_ptr formula_vars_;
00418 
00419     int movement_;
00420     int max_movement_;
00421     mutable std::map<t_translation::t_terrain, int> movement_costs_; // movement cost cache
00422     int vision_;
00423     mutable std::map<t_translation::t_terrain, int> vision_costs_;   // view cost cache
00424     int jamming_;
00425     mutable std::map<t_translation::t_terrain, int> jamming_costs_;  // jamming cost cache
00426     mutable defense_cache defense_mods_; // defense modifiers cache
00427     bool hold_position_;
00428     bool end_turn_;
00429     bool resting_;
00430     int attacks_left_;
00431     int max_attacks_;
00432 
00433     std::set<std::string> states_;
00434     std::vector<bool> known_boolean_states_;
00435     static std::map<std::string, state_t> known_boolean_state_names_;
00436     config variables_;
00437     config events_;
00438     config filter_recall_;
00439     bool emit_zoc_;
00440     STATE state_;
00441 
00442     std::vector<std::string> overlays_;
00443 
00444     std::string role_;
00445     std::vector<attack_type> attacks_;
00446     map_location::DIRECTION facing_;
00447 
00448     std::vector<t_string> trait_names_;
00449     std::vector<t_string> trait_descriptions_;
00450 
00451     int unit_value_;
00452     map_location goto_, interrupted_move_;
00453 
00454     bool flying_, is_fearless_, is_healthy_;
00455 
00456     utils::string_map modification_descriptions_;
00457     // Animations:
00458     std::vector<unit_animation> animations_;
00459 
00460     unit_animation *anim_;
00461     int next_idling_;
00462     int frame_begin_time_;
00463 
00464 
00465     int unit_halo_;
00466     bool getsHit_;
00467     bool refreshing_; // avoid infinite recursion
00468     bool hidden_;
00469     bool draw_bars_;
00470 
00471     config modifications_;
00472 
00473     friend void attack_type::set_specials_context(const map_location& loc, const map_location&, const unit& un, bool) const;
00474 
00475     /** Hold the visibility status cache for a unit, mutable since it's a cache. */
00476     mutable std::map<map_location, bool> invisibility_cache_;
00477 
00478     /**
00479      * Clears the cache.
00480      *
00481      * Since we don't change the state of the object we're marked const (also
00482      * required since the objects in the cache need to be marked const).
00483      */
00484     void clear_visibility_cache() const { invisibility_cache_.clear(); }
00485 };
00486 
00487 /**
00488  * Object which temporarily resets a unit's movement.
00489  *
00490  * @warning
00491  * The unit whose movement is reset may not be deleted while a
00492  * @ref unit_movement_resetter object 'holds'. So best use it only in a small
00493  * scope.
00494  */
00495 struct unit_movement_resetter
00496     : private boost::noncopyable
00497 {
00498     unit_movement_resetter(unit& u, bool operate=true);
00499     ~unit_movement_resetter();
00500 
00501 private:
00502     unit& u_;
00503     int moves_;
00504 };
00505 
00506 /// Used to find units in vectors by their ID. (Convenience wrapper)
00507 std::vector<unit>::iterator find_if_matches_id(
00508         std::vector<unit> &unit_list,
00509         const std::string &unit_id);
00510 /// Used to find units in vectors by their ID. (Convenience wrapper)
00511 std::vector<unit>::const_iterator find_if_matches_id(
00512         const std::vector<unit> &unit_list,
00513         const std::string &unit_id);
00514 /// Used to erase units from vectors by their ID. (Convenience wrapper)
00515 std::vector<unit>::iterator erase_if_matches_id(
00516         std::vector<unit> &unit_list,
00517         const std::string &unit_id);
00518 
00519 /** Returns the number of units of the side @a side_num. */
00520 int side_units(int side_num);
00521 
00522 /** Returns the total cost of units of side @a side_num. */
00523 int side_units_cost(int side_num);
00524 
00525 int side_upkeep(int side_num);
00526 
00527 unit_map::iterator find_visible_unit(const map_location &loc,
00528     const team &current_team, bool see_all = false);
00529 
00530 unit *get_visible_unit(const map_location &loc,
00531     const team &current_team, bool see_all = false);
00532 
00533 struct team_data
00534 {
00535     team_data() :
00536         units(0),
00537         upkeep(0),
00538         villages(0),
00539         expenses(0),
00540         net_income(0),
00541         gold(0),
00542         teamname()
00543     {
00544     }
00545 
00546     int units, upkeep, villages, expenses, net_income, gold;
00547     std::string teamname;
00548 };
00549 
00550 team_data calculate_team_data(const class team& tm, int side);
00551 
00552 /**
00553  * This object is used to temporary place a unit in the unit map, swapping out
00554  * any unit that is already there.  On destruction, it restores the unit map to
00555  * its original.
00556  */
00557 struct temporary_unit_placer
00558 {
00559     temporary_unit_placer(unit_map& m, const map_location& loc, unit& u);
00560     virtual  ~temporary_unit_placer();
00561 
00562 private:
00563     unit_map& m_;
00564     const map_location loc_;
00565     unit *temp_;
00566 };
00567 
00568 /**
00569  * This object is used to temporary remove a unit from the unit map.
00570  * On destruction, it restores the unit map to its original.
00571  * unit_map iterators to this unit must not be accessed while the unit is temporarily
00572  * removed, otherwise a collision will happen when trying to reinsert the unit.
00573  */
00574 struct temporary_unit_remover
00575 {
00576     temporary_unit_remover(unit_map& m, const map_location& loc);
00577     virtual  ~temporary_unit_remover();
00578 
00579 private:
00580     unit_map& m_;
00581     const map_location loc_;
00582     unit *temp_;
00583 };
00584 
00585 
00586 /**
00587  * This object is used to temporary move a unit in the unit map, swapping out
00588  * any unit that is already there.  On destruction, it restores the unit map to
00589  * its original.
00590  */
00591 struct temporary_unit_mover
00592 {
00593     temporary_unit_mover(unit_map& m, const map_location& src,  const map_location& dst);
00594     virtual  ~temporary_unit_mover();
00595 
00596 private:
00597     unit_map& m_;
00598     const map_location src_;
00599     const map_location dst_;
00600     unit *temp_;
00601 };
00602 
00603 /**
00604  * Gets a checksum for a unit.
00605  *
00606  * In MP games the descriptions are locally generated and might differ, so it
00607  * should be possible to discard them.  Not sure whether replays suffer the
00608  * same problem.
00609  *
00610  *  @param u                    the unit
00611  *
00612  *  @returns                    the checksum for a unit
00613  */
00614 std::string get_checksum(const unit& u);
00615 
00616 #endif
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:13 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs