From 3ca8f49337fff75c7dcdf12ac7954a877f7607dc Mon Sep 17 00:00:00 2001 From: OzoneH3 Date: Sat, 16 Nov 2019 15:06:24 +0000 Subject: [PATCH] Create template from already existing character (#35467) * initial progress * Create template from character. * tidy up code --- src/avatar.cpp | 95 +++++++++++++++++++++++++++++++ src/avatar.h | 27 +++++++++ src/character.cpp | 6 +- src/character.h | 3 +- src/main_menu.cpp | 94 +++++++++++++++++++++++-------- src/main_menu.h | 3 +- src/newcharacter.cpp | 129 ++++++++++--------------------------------- 7 files changed, 229 insertions(+), 128 deletions(-) diff --git a/src/avatar.cpp b/src/avatar.cpp index 390b50bab3a9c..ed702634113e8 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -193,6 +193,14 @@ mission *avatar::get_active_mission() const return active_mission; } +void avatar::reset_all_misions() +{ + active_mission = nullptr; + active_missions.clear(); + completed_missions.clear(); + failed_missions.clear(); +} + tripoint avatar::get_active_mission_target() const { if( active_mission == nullptr ) { @@ -1583,3 +1591,90 @@ bool avatar::invoke_item( item *used, const std::string &method ) { return Character::invoke_item( used, method ); } + +points_left::points_left() +{ + limit = MULTI_POOL; + init_from_options(); +} + +void points_left::init_from_options() +{ + stat_points = get_option( "INITIAL_STAT_POINTS" ); + trait_points = get_option( "INITIAL_TRAIT_POINTS" ); + skill_points = get_option( "INITIAL_SKILL_POINTS" ); +} + +// Highest amount of points to spend on stats without points going invalid +int points_left::stat_points_left() const +{ + switch( limit ) { + case FREEFORM: + case ONE_POOL: + return stat_points + trait_points + skill_points; + case MULTI_POOL: + return std::min( trait_points_left(), + stat_points + std::min( 0, trait_points + skill_points ) ); + case TRANSFER: + return 0; + } + + return 0; +} + +int points_left::trait_points_left() const +{ + switch( limit ) { + case FREEFORM: + case ONE_POOL: + return stat_points + trait_points + skill_points; + case MULTI_POOL: + return stat_points + trait_points + std::min( 0, skill_points ); + case TRANSFER: + return 0; + } + + return 0; +} + +int points_left::skill_points_left() const +{ + return stat_points + trait_points + skill_points; +} + +bool points_left::is_freeform() +{ + return limit == FREEFORM; +} + +bool points_left::is_valid() +{ + return is_freeform() || + ( stat_points_left() >= 0 && trait_points_left() >= 0 && + skill_points_left() >= 0 ); +} + +bool points_left::has_spare() +{ + return !is_freeform() && is_valid() && skill_points_left() > 0; +} + +std::string points_left::to_string() +{ + if( limit == MULTI_POOL ) { + return string_format( + _( "Points left: %d%c%d%c%d=%d" ), + stat_points_left() >= 0 ? "light_gray" : "red", stat_points, + trait_points >= 0 ? '+' : '-', + trait_points_left() >= 0 ? "light_gray" : "red", abs( trait_points ), + skill_points >= 0 ? '+' : '-', + skill_points_left() >= 0 ? "light_gray" : "red", abs( skill_points ), + is_valid() ? "light_gray" : "red", stat_points + trait_points + skill_points ); + } else if( limit == ONE_POOL ) { + return string_format( _( "Points left: %4d" ), skill_points_left() ); + } else if( limit == TRANSFER ) { + return _( "Character Transfer: No changes can be made." ); + } else { + return _( "Freeform" ); + } +} diff --git a/src/avatar.h b/src/avatar.h index 3661f2780e7da..d67abc1ca8571 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -44,6 +44,7 @@ class avatar : public player bool create( character_type type, const std::string &tempname = "" ); void randomize( bool random_scenario, points_left &points, bool play_now = false ); bool load_template( const std::string &template_name, points_left &points ); + void save_template( const std::string &name, const points_left &points ); bool is_avatar() const override { return true; @@ -80,6 +81,8 @@ class avatar : public player void update_mental_focus(); /** Resets stats, and applies effects in an idempotent manner */ void reset_stats() override; + /** Resets all missions before saving character to template */ + void reset_all_misions(); std::vector get_active_missions() const; std::vector get_completed_missions() const; @@ -219,4 +222,28 @@ class avatar : public player int per_upgrade = 0; }; +struct points_left { + int stat_points; + int trait_points; + int skill_points; + + enum point_limit : int { + FREEFORM = 0, + ONE_POOL, + MULTI_POOL, + TRANSFER, + } limit; + + points_left(); + void init_from_options(); + // Highest amount of points to spend on stats without points going invalid + int stat_points_left() const; + int trait_points_left() const; + int skill_points_left() const; + bool is_freeform(); + bool is_valid(); + bool has_spare(); + std::string to_string(); +}; + #endif diff --git a/src/character.cpp b/src/character.cpp index 1e1ab8bc4623e..ab6a41fd2d63e 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -280,11 +280,11 @@ Character::~Character() = default; Character::Character( Character && ) = default; Character &Character::operator=( Character && ) = default; -void Character::setID( character_id i ) +void Character::setID( character_id i, bool force ) { - if( id.is_valid() ) { + if( id.is_valid() && !force ) { debugmsg( "tried to set id of a npc/player, but has already a id: %d", id.get_value() ); - } else if( !i.is_valid() ) { + } else if( !i.is_valid() && !force ) { debugmsg( "tried to set invalid id of a npc/player: %d", i.get_value() ); } else { id = i; diff --git a/src/character.h b/src/character.h index ff6d811470f24..495c5f1b24f76 100644 --- a/src/character.h +++ b/src/character.h @@ -199,7 +199,8 @@ class Character : public Creature, public visitable character_id getID() const; // sets the ID, will *only* succeed when the current id is not valid - void setID( character_id i ); + // allows forcing a -1 id which is required for templates to not throw errors + void setID( character_id i, bool force = false ); field_type_id bloodType() const override; field_type_id gibType() const override; diff --git a/src/main_menu.cpp b/src/main_menu.cpp index 760100334079d..985d9020198fa 100644 --- a/src/main_menu.cpp +++ b/src/main_menu.cpp @@ -316,6 +316,7 @@ void main_menu::init_strings() vWorldSubItems.push_back( pgettext( "Main Menu|World", "eset World" ) ); vWorldSubItems.push_back( pgettext( "Main Menu|World", "how World Mods" ) ); vWorldSubItems.push_back( pgettext( "Main Menu|World", "opy World Settings" ) ); + vWorldSubItems.push_back( pgettext( "Main Menu|World", "Character to emplate" ) ); vWorldHotkeys.clear(); for( const std::string &item : vWorldSubItems ) { @@ -877,32 +878,43 @@ bool main_menu::new_character_tab() return start; } -bool main_menu::load_character_tab() +bool main_menu::load_character_tab( bool transfer ) { bool start = false; const auto all_worldnames = world_generator->all_worldnames(); - const size_t last_world_pos = std::find( all_worldnames.begin(), all_worldnames.end(), - world_generator->last_world_name ) - all_worldnames.begin(); - if( last_world_pos < all_worldnames.size() ) { - sel2 = last_world_pos; + if( transfer ) { + layer = 3; + sel1 = 2; + sel2 -= 1; + sel3 = 0; savegames = world_generator->get_world( all_worldnames[sel2] )->world_saves; - } - const size_t last_character_pos = std::find_if( savegames.begin(), savegames.end(), - []( const save_t &it ) { - return it.player_name() == world_generator->last_character_name; - } ) - savegames.begin(); - if( last_character_pos < savegames.size() ) { - sel3 = last_character_pos; } else { - sel3 = 0; + const size_t last_world_pos = std::find( all_worldnames.begin(), all_worldnames.end(), + world_generator->last_world_name ) - all_worldnames.begin(); + if( last_world_pos < all_worldnames.size() ) { + sel2 = last_world_pos; + savegames = world_generator->get_world( all_worldnames[sel2] )->world_saves; + } + + const size_t last_character_pos = std::find_if( savegames.begin(), savegames.end(), + []( const save_t &it ) { + return it.player_name() == world_generator->last_character_name; + } ) - savegames.begin(); + if( last_character_pos < savegames.size() ) { + sel3 = last_character_pos; + } else { + sel3 = 0; + } } + const int offset_x = transfer ? 25 : 15; + const int offset_y = transfer ? -1 : 0; while( !start && sel1 == 2 && ( layer == 2 || layer == 3 ) ) { - print_menu( w_open, 2, menu_offset ); + print_menu( w_open, transfer ? 3 : 2, menu_offset ); if( layer == 2 && sel1 == 2 ) { if( all_worldnames.empty() ) { - mvwprintz( w_open, menu_offset + point( 15 + extra_w / 2, -2 ), + mvwprintz( w_open, menu_offset + point( offset_x + extra_w / 2, -2 ), c_red, "%s", _( "No Worlds found!" ) ); on_error(); } else { @@ -918,7 +930,7 @@ bool main_menu::load_character_tab() color1 = c_white; color2 = h_white; } - mvwprintz( w_open, point( 15 + menu_offset.x + extra_w / 2, line ), + mvwprintz( w_open, point( offset_x + menu_offset.x + extra_w / 2, line + offset_y ), ( sel2 == i ? color2 : color1 ), "%s (%d)", world_name, savegames_count ); } @@ -960,11 +972,11 @@ bool main_menu::load_character_tab() savegames.erase( new_end, savegames.end() ); } - mvwprintz( w_open, menu_offset + point( 15 + extra_w / 2, -2 - sel2 ), h_white, + mvwprintz( w_open, menu_offset + point( offset_x + extra_w / 2, -2 - sel2 + offset_y ), h_white, "%s", wn ); if( savegames.empty() ) { - mvwprintz( w_open, menu_offset + point( 40 + extra_w / 2, -2 - sel2 ), + mvwprintz( w_open, menu_offset + point( 40 + extra_w / 2, -2 - sel2 + offset_y ), c_red, "%s", _( "No save games found!" ) ); on_error(); } else { @@ -972,7 +984,7 @@ bool main_menu::load_character_tab() for( const auto &savename : savegames ) { const bool selected = sel3 + line == menu_offset.y - 2; - mvwprintz( w_open, point( 40 + menu_offset.x + extra_w / 2, line-- ), + mvwprintz( w_open, point( 40 + menu_offset.x + extra_w / 2, line-- + offset_y ), selected ? h_white : c_white, "%s", savename.player_name() ); } @@ -982,7 +994,7 @@ bool main_menu::load_character_tab() std::string action = handle_input_timeout( ctxt ); if( errflag && action != "TIMEOUT" ) { clear_error(); - layer = 2; + layer = transfer ? 1 : 2; } else if( action == "DOWN" ) { if( sel3 > 0 ) { sel3--; @@ -996,7 +1008,7 @@ bool main_menu::load_character_tab() sel3 = 0; } } else if( action == "LEFT" || action == "QUIT" ) { - layer = 2; + layer = transfer ? 1 : 2; sel3 = 0; print_menu( w_open, sel1, menu_offset ); } @@ -1004,11 +1016,13 @@ bool main_menu::load_character_tab() if( sel3 >= 0 && sel3 < static_cast( savegames.size() ) ) { werase( w_background ); wrefresh( w_background ); + WORLDPTR world = world_generator->get_world( all_worldnames[sel2] ); world_generator->last_world_name = world->world_name; world_generator->last_character_name = savegames[sel3].player_name(); world_generator->save_last_world_info(); world_generator->set_active_world( world ); + try { g->setup(); } catch( const std::exception &err ) { @@ -1023,14 +1037,45 @@ bool main_menu::load_character_tab() } } } // end while + + if( transfer ) { + layer = 3; + sel1 = 3; + sel2++; + sel3 = vWorldSubItems.size() - 1; + } + return start; } void main_menu::world_tab() { - while( sel1 == 3 && ( layer == 2 || layer == 3 ) ) { + while( sel1 == 3 && ( layer == 2 || layer == 3 || layer == 4 ) ) { print_menu( w_open, 3, menu_offset ); - if( layer == 3 ) { // World Menu + if( layer == 4 ) { //Character to Template + if( load_character_tab( true ) ) { + points_left points; + points.stat_points = 0; + points.trait_points = 0; + points.skill_points = 0; + points.limit = points_left::TRANSFER; + + g->u.setID( character_id(), true ); + g->u.reset_all_misions(); + g->u.save_template( g->u.name, points ); + + g->u = avatar(); + MAPBUFFER.reset(); + overmap_buffer.clear(); + + load_char_templates(); + + werase( w_background ); + wrefresh( w_background ); + + layer = 3; + } + } else if( layer == 3 ) { // World Menu // Show options for Destroy, Reset worlds. // Reset and Destroy ask for world to modify. // Reset empties world of everything but options, then makes new world within it. @@ -1109,6 +1154,9 @@ void main_menu::world_tab() } else if( sel3 == 3 ) { // Copy World settings layer = 2; world_generator->make_new_world( true, all_worldnames[sel2 - 1] ); + } else if( sel3 == 4 ) { // Character to Template + layer = 4; + sel4 = 0; } if( query_yes ) { diff --git a/src/main_menu.h b/src/main_menu.h index f114b27a9db05..7b63c44b78294 100644 --- a/src/main_menu.h +++ b/src/main_menu.h @@ -53,7 +53,7 @@ class main_menu // Tab functions. They return whether a game was started or not. The ones that can never // start a game have a void return type. bool new_character_tab(); - bool load_character_tab(); + bool load_character_tab( bool transfer = false ); void world_tab(); /* @@ -67,6 +67,7 @@ class main_menu int sel1 = 1; int sel2 = 1; int sel3 = 1; + int sel4 = 1; int layer = 1; point LAST_TERM; catacurses::window w_open; diff --git a/src/newcharacter.cpp b/src/newcharacter.cpp index b69aed3322bb2..4bd2167633845 100644 --- a/src/newcharacter.cpp +++ b/src/newcharacter.cpp @@ -53,8 +53,6 @@ #include "pimpl.h" #include "type_id.h" -struct points_left; - // Colors used in this file: (Most else defaults to c_light_gray) #define COL_STAT_ACT c_white // Selected stat #define COL_STAT_BONUS c_light_green // Bonus @@ -87,90 +85,6 @@ struct points_left; static int skill_increment_cost( const Character &u, const skill_id &skill ); -struct points_left { - int stat_points; - int trait_points; - int skill_points; - - enum point_limit : int { - FREEFORM = 0, - ONE_POOL, - MULTI_POOL - } limit; - - points_left() { - limit = MULTI_POOL; - init_from_options(); - } - - void init_from_options() { - stat_points = get_option( "INITIAL_STAT_POINTS" ); - trait_points = get_option( "INITIAL_TRAIT_POINTS" ); - skill_points = get_option( "INITIAL_SKILL_POINTS" ); - } - - // Highest amount of points to spend on stats without points going invalid - int stat_points_left() const { - switch( limit ) { - case FREEFORM: - case ONE_POOL: - return stat_points + trait_points + skill_points; - case MULTI_POOL: - return std::min( trait_points_left(), - stat_points + std::min( 0, trait_points + skill_points ) ); - } - - return 0; - } - - int trait_points_left() const { - switch( limit ) { - case FREEFORM: - case ONE_POOL: - return stat_points + trait_points + skill_points; - case MULTI_POOL: - return stat_points + trait_points + std::min( 0, skill_points ); - } - - return 0; - } - - int skill_points_left() const { - return stat_points + trait_points + skill_points; - } - - bool is_freeform() { - return limit == FREEFORM; - } - - bool is_valid() { - return is_freeform() || - ( stat_points_left() >= 0 && trait_points_left() >= 0 && - skill_points_left() >= 0 ); - } - - bool has_spare() { - return !is_freeform() && is_valid() && skill_points_left() > 0; - } - - std::string to_string() { - if( limit == MULTI_POOL ) { - return string_format( - _( "Points left: %d%c%d%c%d=%d" ), - stat_points_left() >= 0 ? "light_gray" : "red", stat_points, - trait_points >= 0 ? '+' : '-', - trait_points_left() >= 0 ? "light_gray" : "red", abs( trait_points ), - skill_points >= 0 ? '+' : '-', - skill_points_left() >= 0 ? "light_gray" : "red", abs( skill_points ), - is_valid() ? "light_gray" : "red", stat_points + trait_points + skill_points ); - } else if( limit == ONE_POOL ) { - return string_format( _( "Points left: %4d" ), skill_points_left() ); - } else { - return _( "Freeform" ); - } - } -}; - enum struct tab_direction { NONE, FORWARD, @@ -190,7 +104,6 @@ tab_direction set_description( const catacurses::window &w, avatar &you, bool al points_left &points ); static cata::optional query_for_template_name(); -static void save_template( const avatar &u, const std::string &name, const points_left &points ); void reset_scenario( avatar &u, const scenario *scen ); void Character::pick_name( bool bUseDefault ) @@ -465,7 +378,10 @@ bool avatar::create( character_type type, const std::string &tempname ) } // We want to prevent recipes known by the template from being applied to the // new character. The recipe list will be rebuilt when entering the game. - learned_recipes->clear(); + // Except if it is a character transfer template + if( points.limit != points_left::TRANSFER ) { + learned_recipes->clear(); + } tab = NEWCHAR_TAB_MAX; break; } @@ -491,6 +407,11 @@ bool avatar::create( character_type type, const std::string &tempname ) } werase( w ); wrefresh( w ); + + if( points.limit == points_left::TRANSFER ) { + tab = 6; + } + switch( tab ) { case 0: result = set_points( w, *this, points ); @@ -543,7 +464,11 @@ bool avatar::create( character_type type, const std::string &tempname ) return false; } - save_template( *this, _( "Last Character" ), points ); + if( points.limit == points_left::TRANSFER ) { + return true; + } + + save_template( _( "Last Character" ), points ); recalc_hp(); for( int i = 0; i < num_hp_parts; i++ ) { @@ -2275,7 +2200,8 @@ tab_direction set_description( const catacurses::window &w, avatar &you, const b wrefresh( w_stats ); mvwprintz( w_traits, point_zero, COL_HEADER, _( "Traits: " ) ); - std::vector current_traits = you.get_base_traits(); + std::vector current_traits = points.limit == points_left::TRANSFER ? you.get_mutations() : + you.get_base_traits(); if( current_traits.empty() ) { wprintz( w_traits, c_light_red, _( "None!" ) ); } else { @@ -2300,13 +2226,16 @@ tab_direction set_description( const catacurses::window &w, avatar &you, const b profession::StartingSkillList list_skills = you.prof->skills(); for( auto &elem : skillslist ) { int level = you.get_skill_level( elem->ident() ); - profession::StartingSkillList::iterator i = list_skills.begin(); - while( i != list_skills.end() ) { - if( i->first == ( elem )->ident() ) { - level += i->second; - break; + + if( points.limit != points_left::TRANSFER ) { + profession::StartingSkillList::iterator i = list_skills.begin(); + while( i != list_skills.end() ) { + if( i->first == ( elem )->ident() ) { + level += i->second; + break; + } + ++i; } - ++i; } if( level > 0 ) { @@ -2434,7 +2363,7 @@ tab_direction set_description( const catacurses::window &w, avatar &you, const b return tab_direction::NONE; } else if( action == "SAVE_TEMPLATE" ) { if( const auto name = query_for_template_name() ) { - ::save_template( you, *name, points ); + you.save_template( *name, points ); } // redraw after saving template draw_character_tabs( w, _( "DESCRIPTION" ) ); @@ -2607,7 +2536,7 @@ cata::optional query_for_template_name() } } -void save_template( const avatar &u, const std::string &name, const points_left &points ) +void avatar::save_template( const std::string &name, const points_left &points ) { std::string native = utf8_to_native( name ); #if defined(_WIN32) @@ -2632,10 +2561,10 @@ void save_template( const avatar &u, const std::string &name, const points_left jsout.member( "trait_points", points.trait_points ); jsout.member( "skill_points", points.skill_points ); jsout.member( "limit", points.limit ); - jsout.member( "start_location", u.start_location ); + jsout.member( "start_location", start_location ); jsout.end_object(); - u.serialize( jsout ); + serialize( jsout ); jsout.end_array(); }, _( "player template" ) );