diff --git a/data/json/player_activities.json b/data/json/player_activities.json index b5d385bf6c424..45e679aba5315 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -509,6 +509,15 @@ "based_on": "neither", "no_resume": true }, + { + "id": "ACT_STUDY_SPELL", + "type": "activity_type", + "stop_phrase": "Stop studying?", + "suspendable": false, + "rooted": true, + "based_on": "time", + "no_resume": true + }, { "id": "ACT_CONSUME_DRINK_MENU", "type": "activity_type", diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 4629f28939c29..a44ba45e9ab63 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -30,6 +30,7 @@ #include "iexamine.h" #include "itype.h" #include "iuse_actor.h" +#include "magic.h" #include "map.h" #include "map_iterator.h" #include "mapdata.h" @@ -143,7 +144,8 @@ activity_handlers::do_turn_functions = { { activity_id( "ACT_FERTILIZE_PLOT" ), fertilize_plot_do_turn }, { activity_id( "ACT_TRY_SLEEP" ), try_sleep_do_turn }, { activity_id( "ACT_ROBOT_CONTROL" ), robot_control_do_turn }, - { activity_id( "ACT_TREE_COMMUNION" ), tree_communion_do_turn } + { activity_id( "ACT_TREE_COMMUNION" ), tree_communion_do_turn }, + { activity_id( "ACT_STUDY_SPELL" ), study_spell_do_turn} }; const std::map< activity_id, std::function > @@ -204,7 +206,8 @@ activity_handlers::finish_functions = { { activity_id( "ACT_SHAVE" ), shaving_finish }, { activity_id( "ACT_HAIRCUT" ), haircut_finish }, { activity_id( "ACT_UNLOAD_MAG" ), unload_mag_finish }, - { activity_id( "ACT_ROBOT_CONTROL" ), robot_control_finish } + { activity_id( "ACT_ROBOT_CONTROL" ), robot_control_finish }, + { activity_id( "ACT_STUDY_SPELL" ), study_spell_finish } }; void messages_in_process( const player_activity &act, const player &p ) @@ -3537,3 +3540,43 @@ void activity_handlers::tree_communion_do_turn( player_activity *act, player *p p->add_msg_if_player( m_info, _( "The trees have shown you what they will." ) ); act->set_to_null(); } + +void activity_handlers::study_spell_do_turn( player_activity *act, player *p ) +{ + if( p->fine_detail_vision_mod() > 4 ) { + act->values[2] = -1; + act->moves_left = 0; + return; + } + if( act->get_str_value( 1 ) == "study" ) { + spell &studying = p->magic.get_spell( spell_id( act->name ) ); + if( act->get_str_value( 0 ) == "gain_level" ) { + if( studying.get_level() < act->get_value( 1 ) ) { + act->moves_left = 1000000; + } else { + act->moves_left = 0; + } + } + const int xp = roll_remainder( studying.exp_modifier( *p ) ); + act->values[0] += xp; + studying.gain_exp( xp ); + } + act->moves_left -= 100; +} + +void activity_handlers::study_spell_finish( player_activity *act, player *p ) +{ + act->set_to_null(); + + if( act->get_str_value( 1 ) == "study" ) { + p->add_msg_if_player( m_good, _( "You gained %i experience from your study session." ), + act->get_value( 0 ) ); + p->practice( skill_id( "spellcraft" ), act->get_value( 0 ) / 5, + p->magic.get_spell( spell_id( act->name ) ).get_difficulty() ); + } else if( act->get_str_value( 1 ) == "learn" && act->values[2] == 0 ) { + p->magic.learn_spell( act->name, *p ); + } + if( act->values[2] == -1 ) { + p->add_msg_if_player( m_bad, _( "It's too dark to read." ) ); + } +} diff --git a/src/activity_handlers.h b/src/activity_handlers.h index 3ccbeb380330d..2805711e8772f 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -95,6 +95,7 @@ void harvest_plot_do_turn( player_activity *act, player *p ); void try_sleep_do_turn( player_activity *act, player *p ); void robot_control_do_turn( player_activity *act, player *p ); void tree_communion_do_turn( player_activity *act, player *p ); +void study_spell_do_turn( player_activity *act, player *p ); // defined in activity_handlers.cpp extern const std::map< activity_id, std::function > @@ -150,6 +151,7 @@ void shaving_finish( player_activity *act, player *p ); void haircut_finish( player_activity *act, player *p ); void unload_mag_finish( player_activity *act, player *p ); void robot_control_finish( player_activity *act, player *p ); +void study_spell_finish( player_activity *act, player *p ); // defined in activity_handlers.cpp extern const std::map< activity_id, std::function > diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 03c161e3739e3..ca452b659d1e0 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -22,6 +22,7 @@ #include "cata_utility.h" #include "coordinate_conversions.h" #include "crafting.h" +#include "creature.h" #include "debug.h" #include "vpart_position.h" #include "effect.h" @@ -33,6 +34,7 @@ #include "item.h" #include "item_factory.h" #include "itype.h" +#include "magic.h" #include "map.h" #include "map_iterator.h" #include "map_selector.h" @@ -2116,6 +2118,107 @@ ret_val musical_instrument_actor::can_use( const player &p, const item &, return ret_val::make_success(); } +iuse_actor *learn_spell_actor::clone() const +{ + return new learn_spell_actor( *this ); +} + +void learn_spell_actor::load( JsonObject &obj ) +{ + spells = obj.get_string_array( "spells" ); +} + +void learn_spell_actor::info( const item &, std::vector &dump ) const +{ + std::string message; + if( spells.size() == 1 ) { + message = _( "This can teach you a spell." ); + } else { + message = _( "This can teach you a number of spells." ); + } + dump.emplace_back( "DESCRIPTION", message ); + dump.emplace_back( "DESCRIPTION", _( "Spells Contained:" ) ); + for( const std::string sp : spells ) { + dump.emplace_back( "SPELL", spell_id( sp ).obj().name ); + } +} + +long learn_spell_actor::use( player &p, item &, bool, const tripoint & ) const +{ + if( p.fine_detail_vision_mod() > 4 ) { + p.add_msg_if_player( _( "It's too dark to read." ) ); + return 0; + } + std::vector uilist_initializer; + bool know_it_all = true; + for( const std::string sp_id_str : spells ) { + const spell_id sp_id( sp_id_str ); + const std::string sp_nm = sp_id.obj().name; + uilist_entry entry( sp_nm ); + if( p.magic.knows_spell( sp_id ) ) { + const spell sp = p.magic.get_spell( sp_id ); + entry.ctxt = string_format( "Level %u", sp.get_level() ); + if( sp.is_max_level() ) { + entry.ctxt += _( " (Max)" ); + entry.enabled = false; + } else { + know_it_all = false; + } + } else { + if( p.magic.can_learn_spell( p, sp_id ) ) { + entry.ctxt = _( "Study to Learn" ); + know_it_all = false; + } else { + entry.ctxt = _( "Can't learn!" ); + entry.enabled = false; + } + } + uilist_initializer.emplace_back( entry ); + } + + if( know_it_all ) { + add_msg( m_info, _( "You already know everything this could teach you." ) ); + return 0; + } + + const int action = uilist( _( "Study a spell:" ), uilist_initializer ); + if( action < 0 ) { + return 0; + } + const bool knows_spell = p.magic.knows_spell( spells[action] ); + player_activity study_spell( activity_id( "ACT_STUDY_SPELL" ), + p.magic.time_to_learn_spell( p, spells[action] ) ); + study_spell.str_values = { + "", // reserved for "until you gain a spell level" option [0] + "learn" + }; // [1] + study_spell.values = { 0, 0, 0 }; + if( knows_spell ) { + study_spell.str_values[1] = "study"; + const int study_time = uilist( _( "Spend how long studying?" ), { + { 30000, true, -1, _( "30 minutes" ) }, + { 60000, true, -1, _( "1 hour" ) }, + { 120000, true, -1, _( "2 hours" ) }, + { 240000, true, -1, _( "4 hours" ) }, + { 480000, true, -1, _( "8 hours" ) }, + { 10100, true, -1, _( "Until you gain a spell level" ) } + } ); + if( study_time <= 0 ) { + return 0; + } + study_spell.moves_total = study_time; + } + study_spell.moves_left = study_spell.moves_total; + if( study_spell.moves_total == 10100 ) { + study_spell.str_values[0] = "gain_level"; + study_spell.values[0]; // reserved for xp + study_spell.values[1] = p.magic.get_spell( spell_id( spells[action] ) ).get_level() + 1; + } + study_spell.name = spells[action]; + p.assign_activity( study_spell, false ); + return 0; +} + iuse_actor *holster_actor::clone() const { return new holster_actor( *this ); diff --git a/src/iuse_actor.h b/src/iuse_actor.h index 9da946dd5552e..83e9fa3aa2ffe 100644 --- a/src/iuse_actor.h +++ b/src/iuse_actor.h @@ -657,6 +657,24 @@ class musical_instrument_actor : public iuse_actor iuse_actor *clone() const override; }; +/** + * Learn a spell + */ +class learn_spell_actor : public iuse_actor +{ + public: + // list of spell ids that can be learned from this item + std::vector spells; + + learn_spell_actor( const std::string &type = "learn_spell" ) : iuse_actor( type ) {} + + ~learn_spell_actor() override = default; + void load( JsonObject &jo ) override; + long use( player &p, item &, bool, const tripoint & ) const override; + iuse_actor *clone() const override; + void info( const item &, std::vector & ) const override; +}; + /** * Holster a weapon */