diff --git a/data/mods/Xedra_Evolved/eocs/spell_learning_eoc.json b/data/mods/Xedra_Evolved/eocs/spell_learning_eoc.json index 52f7ea1bb4073..cca2ba9268807 100644 --- a/data/mods/Xedra_Evolved/eocs/spell_learning_eoc.json +++ b/data/mods/Xedra_Evolved/eocs/spell_learning_eoc.json @@ -1,41 +1,4 @@ [ - { - "type": "effect_on_condition", - "id": "EOC_XE_CASTED_A_SPELL", - "eoc_type": "EVENT", - "required_event": "character_casts_spell", - "condition": { - "or": [ - { "compare_string": [ "EATER", { "context_val": "school" } ] }, - { "compare_string": [ "DREAMER", { "context_val": "school" } ] } - ] - }, - "effect": [ - { - "math": [ - "debug_xe_spell_exp_diff = spell_exp_for_level(_spell, u_spell_level(_spell)+1) - spell_exp_for_level(_spell, u_spell_level(_spell))" - ] - }, - { "math": [ "debug_xe_spell_train_factor = (spell_train_factor(_cost))" ] }, - { - "math": [ - "debug_xe_spell_xp_give = (spell_exp_for_level(_spell, u_spell_level(_spell)+1) - spell_exp_for_level(_spell, u_spell_level(_spell)))/spell_train_factor(_cost)" - ] - }, - { "u_message": "school: ", "type": "debug" }, - { "u_message": "spell: ", "type": "debug" }, - { "u_message": "spell_exp_diff: ", "type": "debug" }, - { "u_message": "spell_train_factor: ", "type": "debug" }, - { "u_message": "amount of XP added: ", "type": "debug" }, - { - "math": [ - "u_spell_exp(_spell)", - "+=", - "(spell_exp_for_level(_spell, u_spell_level(_spell)+1) - spell_exp_for_level(_spell, u_spell_level(_spell)))/spell_train_factor(_cost)" - ] - } - ] - }, { "type": "effect_on_condition", "id": "EOC_XE_GOT_A_SPELL_LVL", diff --git a/data/mods/Xedra_Evolved/jmath.json b/data/mods/Xedra_Evolved/jmath.json index e8ae35e84a1c5..2f2a7793d2a3a 100644 --- a/data/mods/Xedra_Evolved/jmath.json +++ b/data/mods/Xedra_Evolved/jmath.json @@ -42,6 +42,24 @@ "num_args": 1, "return": "375 * 0.5 * _0 * (_0 + 1)" }, + { + "type": "jmath_function", + "id": "xedra_dream_formula_get_level", + "num_args": 1, + "return": "_0 / 300" + }, + { + "type": "jmath_function", + "id": "xedra_dream_formula_exp_for_level", + "num_args": 1, + "return": "_0 * 300" + }, + { + "type": "jmath_function", + "id": "xedra_dream_casting_xp_formula", + "num_args": 0, + "return": "100" + }, { "type": "jmath_function", "id": "spell_train_factor", diff --git a/data/mods/Xedra_Evolved/spells/classless_spells.json b/data/mods/Xedra_Evolved/spells/classless_spells.json index 990d6f0b7c3d0..083d5e8a712ab 100644 --- a/data/mods/Xedra_Evolved/spells/classless_spells.json +++ b/data/mods/Xedra_Evolved/spells/classless_spells.json @@ -4,6 +4,17 @@ "type": "magic_type", "energy_source": "MANA" }, + { + "id": "xedra_dream_magic", + "type": "magic_type", + "energy_source": "MANA", + "get_level_formula_id": "xedra_dream_formula_get_level", + "exp_for_level_formula_id": "xedra_dream_formula_exp_for_level", + "casting_xp_formula_id": "xedra_dream_casting_xp_formula", + "failure_cost_percent": 0.1, + "failure_exp_percent": 1, + "max_book_level": 0 + }, { "id": "night_sight", "type": "SPELL", diff --git a/data/mods/Xedra_Evolved/spells/dreamer_spells.json b/data/mods/Xedra_Evolved/spells/dreamer_spells.json index 520c3af18e684..35d4ab828623e 100644 --- a/data/mods/Xedra_Evolved/spells/dreamer_spells.json +++ b/data/mods/Xedra_Evolved/spells/dreamer_spells.json @@ -11,6 +11,7 @@ "valid_targets": [ "ally" ], "targeted_monster_ids": [ "mon_sapient_light", "mon_shifter", "mon_duplicator", "mon_ophanim" ], "flags": [ "NO_LEGS", "IGNORE_WALLS", "NO_FAIL" ], + "magic_type": "xedra_dream_magic", "min_aoe": 120, "max_aoe": 120 }, @@ -34,7 +35,7 @@ "skill": "deduction", "difficulty": 5, "max_level": { "math": [ "dreamer_level(1)" ] }, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "DREAMER" }, { @@ -57,7 +58,7 @@ "skill": "deduction", "difficulty": 3, "max_level": { "math": [ "dreamer_level(1)" ] }, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "DREAMER" }, { @@ -73,7 +74,7 @@ "effect_str": "armor_dreamdross", "shape": "blast", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "skill": "deduction", "difficulty": 7, "max_level": { "math": [ "dreamer_level(1)" ] }, @@ -104,7 +105,7 @@ "difficulty": 0, "max_level": { "math": [ "dreamer_level(1)" ] }, "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "flags": [ "PERMANENT_ALL_LEVELS", "NO_LEGS" ] }, { @@ -118,7 +119,7 @@ "shape": "blast", "targeted_monster_species": [ "NETHER" ], "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "damage_type": "pure", "skill": "deduction", "difficulty": 8, @@ -148,7 +149,7 @@ "shape": "blast", "targeted_monster_ids": [ "mon_darkman", "mon_shadow", "mon_shadow_snake" ], "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "damage_type": "pure", "skill": "deduction", "difficulty": 7, @@ -180,7 +181,7 @@ "flags": [ "SILENT", "RANDOM_TARGET", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "min_range": 1, "max_range": 1, "min_damage": 1, @@ -215,7 +216,7 @@ "flags": [ "SILENT", "RANDOM_TARGET", "NO_EXPLOSION_SFX", "NO_LEGS" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "min_range": 1, "max_range": 1, "min_damage": 9, @@ -246,7 +247,7 @@ "flags": [ "IGNORE_WALLS", "NO_LEGS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 8, "max_level": { "math": [ "dreamer_level(1)" ] }, "effect": "attack", @@ -277,7 +278,7 @@ "flags": [ "IGNORE_WALLS", "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 9, "max_level": { "math": [ "dreamer_level(1)" ] }, "effect": "attack", @@ -312,7 +313,7 @@ "effect": "summon", "effect_str": "mon_shifter", "shape": "blast", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 500, "base_casting_time": { "math": [ "spell_time(time(' 30 s'))" ] }, "final_casting_time": 100, @@ -340,7 +341,7 @@ "effect": "summon", "effect_str": "mon_sapient_light", "shape": "blast", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 600, "base_casting_time": { "math": [ "spell_time(time(' 30 s'))" ] }, "min_aoe": 2, @@ -376,7 +377,7 @@ "max_level": { "math": [ "dreamer_level(1)" ] }, "flags": [ "SOMATIC" ], "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "id": "constructed_hammer_attack", @@ -429,7 +430,7 @@ "effect": "summon", "effect_str": "mon_duplicator", "shape": "blast", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 800, "base_casting_time": 3000, "final_casting_time": 100, @@ -472,7 +473,7 @@ "base_casting_time": 200, "final_casting_time": 100, "casting_time_increment": -5, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 800, "final_energy_cost": 200, "energy_increment": -30 @@ -497,7 +498,7 @@ "base_casting_time": 400, "final_casting_time": 0, "casting_time_increment": -25, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 700, "final_energy_cost": 200, "energy_increment": -15 @@ -518,7 +519,7 @@ "effect": "summon", "effect_str": "mon_ophanim", "shape": "blast", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 1200, "final_energy_cost": 200, "energy_increment": -25, @@ -549,7 +550,7 @@ "base_energy_cost": 1200, "final_energy_cost": 200, "energy_increment": -25, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "flags": [ "SOMATIC", "VERBAL", "SILENT", "NO_EXPLOSION_SFX" ], "spell_class": "DREAMER", "difficulty": 7, @@ -573,7 +574,7 @@ "max_level": { "math": [ "dreamer_level(1)" ] }, "flags": [ "SILENT", "NO_EXPLOSION_SFX" ], "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "id": "dreamer_artifact", @@ -590,7 +591,7 @@ "effect": "effect_on_condition", "effect_str": "EOC_DREAMER_ARTIFACT_SPAWN", "shape": "blast", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 0, "base_casting_time": { "math": [ "spell_time(time(' 10 m'))" ] }, "final_casting_time": { "math": [ "spell_time(time(' 10 m'))" ] }, @@ -609,7 +610,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX", "NO_FAIL" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 8, "base_casting_time": { "math": [ "u_effect_intensity('effect_xedra_dreamer_generate_accelerated_time') > -1 ? 0 : 1500" ] }, "base_energy_cost": { "math": [ "u_effect_intensity('effect_xedra_dreamer_generate_accelerated_time') > -1 ? 0 : 200" ] }, @@ -630,7 +631,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX", "NO_FAIL" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 9, "base_casting_time": { "math": [ "u_has_trait('ethereal_wings') ? 0 : 2000" ] }, "base_energy_cost": { "math": [ "u_has_trait('ethereal_wings') ? 0 : 200" ] }, @@ -652,7 +653,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX", "NO_FAIL" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 10, "base_casting_time": { "math": [ "u_has_trait('karma_arms') ? 0 : 2000" ] }, "base_energy_cost": { "math": [ "u_has_trait('karma_arms') ? 0 : 200" ] }, @@ -673,7 +674,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX", "NO_FAIL" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 7, "base_casting_time": { "math": [ "u_has_trait('stalker_eyes') ? 0 : 1000" ] }, "base_energy_cost": { "math": [ "u_has_trait('stalker_eyes') ? 0 : 200" ] }, @@ -694,7 +695,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX", "NO_FAIL" ], "skill": "deduction", "spell_class": "DREAMER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 8, "base_casting_time": { "math": [ "u_has_trait('devil_tail') ? 0 : 1500" ] }, "base_energy_cost": { "math": [ "u_has_trait('devil_tail') ? 0 : 100" ] }, @@ -715,7 +716,7 @@ "shape": "blast", "base_casting_time": { "math": [ "u_has_trait('LUCID_DREAM') ? 0 : 1000" ] }, "base_energy_cost": { "math": [ "u_has_trait('LUCID_DREAM') ? 0 : 900" ] }, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "flags": [ "SOMATIC", "VERBAL", "SILENT", "NO_EXPLOSION_SFX" ], "spell_class": "DREAMER", "difficulty": 7, diff --git a/data/mods/Xedra_Evolved/spells/dreamsmithing.json b/data/mods/Xedra_Evolved/spells/dreamsmithing.json index 470f6aeb48e42..dfb6a4c7b2ee6 100644 --- a/data/mods/Xedra_Evolved/spells/dreamsmithing.json +++ b/data/mods/Xedra_Evolved/spells/dreamsmithing.json @@ -13,7 +13,7 @@ "effect_str": "xe_oneiric_hammer", "shape": "blast", "spell_class": "DREAMSMITH", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "base_energy_cost": 1000, "base_casting_time": 500 } diff --git a/data/mods/Xedra_Evolved/spells/eater_spells.json b/data/mods/Xedra_Evolved/spells/eater_spells.json index 58b0d9d8863b9..2ab220655cc5e 100644 --- a/data/mods/Xedra_Evolved/spells/eater_spells.json +++ b/data/mods/Xedra_Evolved/spells/eater_spells.json @@ -122,7 +122,7 @@ "effect": "attack", "shape": "blast", "flags": [ "VERBAL", "SOMATIC", "NO_LEGS", "NO_EXPLOSION_SFX", "NO_HANDS" ], - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "EATER", "skill": "deduction", "difficulty": 6, @@ -168,7 +168,7 @@ "spell_class": "EATER", "components": "spell_components_dreamdross2", "difficulty": 4, - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "id": "supercoffee", @@ -191,7 +191,7 @@ "spell_class": "EATER", "components": "spell_components_dreamdross2", "difficulty": 4, - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "type": "SPELL", @@ -217,7 +217,7 @@ "final_energy_cost": 100, "energy_increment": -10, "max_level": { "math": [ "eater_level(1)" ] }, - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "id": "spell_rage", @@ -232,7 +232,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "EATER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 2, "base_casting_time": 300, "final_casting_time": 50, @@ -268,7 +268,7 @@ "final_energy_cost": 500, "energy_increment": -15, "max_level": { "math": [ "eater_level(1)" ] }, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "EATER" }, { @@ -293,7 +293,7 @@ "final_energy_cost": 400, "energy_increment": -15, "max_level": { "math": [ "eater_level(1)" ] }, - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "id": "spell_dodge", @@ -308,7 +308,7 @@ "flags": [ "NO_LEGS", "SILENT", "NO_HANDS", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "EATER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 4, "base_casting_time": 1500, "final_casting_time": 500, @@ -333,7 +333,7 @@ "shape": "blast", "flags": [ "NO_LEGS", "SILENT", "NO_HANDS", "NO_EXPLOSION_SFX" ], "skill": "deduction", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "EATER", "difficulty": 5, "base_casting_time": 1500, @@ -369,7 +369,7 @@ "final_energy_cost": 500, "energy_increment": -50, "max_level": { "math": [ "eater_level(1)" ] }, - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "id": "spell_clairvoyance", @@ -380,7 +380,7 @@ "valid_targets": [ "hostile", "ground" ], "flags": [ "IGNORE_WALLS", "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "EATER", "difficulty": 9, "max_level": { "math": [ "eater_level(1)" ] }, @@ -412,7 +412,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "EATER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 7, "base_casting_time": 1500, "final_casting_time": 500, @@ -451,7 +451,7 @@ "base_energy_cost": 300, "final_energy_cost": 90, "energy_increment": -7, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "EATER", "min_aoe": 3, "max_aoe": 3 @@ -478,7 +478,7 @@ "final_energy_cost": 500, "energy_increment": -30, "max_level": { "math": [ "eater_level(1)" ] }, - "magic_type": "xedra_magic_generic" + "magic_type": "xedra_dream_magic" }, { "id": "spell_weak", @@ -493,7 +493,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "IGNORE_WALLS", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "EATER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 5, "min_aoe": 3, "max_aoe": 60, @@ -522,7 +522,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "EATER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 4, "base_casting_time": 300, "final_casting_time": 50, @@ -548,7 +548,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "EATER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 8, "base_casting_time": 300, "final_casting_time": 50, @@ -574,7 +574,7 @@ "flags": [ "NO_LEGS", "NO_HANDS", "SILENT", "NO_EXPLOSION_SFX" ], "skill": "deduction", "spell_class": "EATER", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 10, "base_casting_time": 600, "final_casting_time": 100, @@ -614,7 +614,7 @@ "effect": "attack", "shape": "blast", "flags": [ "SILENT", "SOMATIC", "NO_EXPLOSION_SFX" ], - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "spell_class": "EATER", "skill": "deduction", "difficulty": 4, diff --git a/data/mods/Xedra_Evolved/spells/inventor_spells.json b/data/mods/Xedra_Evolved/spells/inventor_spells.json index e433d9aff1595..978e0c6a54374 100644 --- a/data/mods/Xedra_Evolved/spells/inventor_spells.json +++ b/data/mods/Xedra_Evolved/spells/inventor_spells.json @@ -23,7 +23,7 @@ "final_energy_cost": 100, "energy_increment": -5, "spell_class": "INVENTOR", - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "extra_effects": [ { "id": "firmament_driver_damage" } ] }, { @@ -71,7 +71,7 @@ "base_energy_cost": 350, "final_energy_cost": 150, "energy_increment": -8, - "magic_type": "xedra_magic_generic", + "magic_type": "xedra_dream_magic", "difficulty": 4, "damage_type": "electric" } diff --git a/doc/JSON/MAGIC.md b/doc/JSON/MAGIC.md index 77e22b41abb57..2d6e470a767eb 100644 --- a/doc/JSON/MAGIC.md +++ b/doc/JSON/MAGIC.md @@ -670,6 +670,12 @@ Magic Type Example: "get_level_formula_id": "magic_test_func_get_level", "exp_for_level_formula_id": "magic_test_func_exp_for_level", "cannot_cast_flags": "NO_SPELLCASTING", + "cannot_cast_message": "you cannot cast test spells!", + "casting_xp_formula_id": "magic_test_xp_formula", // id of a jmath func that determines how much xp is gained by successful casts. + "max_book_level": 0, // associated spells can not be leveled by reading books, scrolls, etc above this level. + "failure_cost_percent": 1, // decimal value that decides how much energy is consumed by spells when they fail to cast. Defaults to 0. + "failure_exp_percent": 1, // decimal value that decides how much exp is gained when spells fail to cast compared to normal. Defaults to 0.2 . + "failure_eocs": "EOC_random_mutate" // EOC cast by the player if the spell fails to cast. }, { "id": "test_spell", diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 9d41ae1b0007b..1befe8def8852 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -3790,29 +3790,6 @@ void activity_handlers::tree_communion_do_turn( player_activity *act, Character act->set_to_null(); } -static void blood_magic( Character *you, int cost ) -{ - std::vector uile; - std::vector parts; - int i = 0; - for( const bodypart_id &bp : you->get_all_body_parts( get_body_part_flags::only_main ) ) { - const int hp_cur = you->get_part_hp_cur( bp ); - uilist_entry entry( i, hp_cur > cost, i + 49, body_part_hp_bar_ui_text( bp ) ); - - const std::pair &hp = get_hp_bar( hp_cur, you->get_part_hp_max( bp ) ); - entry.ctxt = colorize( hp.first, hp.second ); - uile.emplace_back( entry ); - parts.push_back( bp ); - i++; - } - int action = -1; - while( action < 0 ) { - action = uilist( _( "Choose part\nto draw blood from." ), uile ); - } - you->mod_part_hp_cur( parts[action], - cost ); - you->mod_pain( std::max( 1, cost / 3 ) ); -} - void activity_handlers::spellcasting_finish( player_activity *act, Character *you ) { act->set_to_null(); @@ -3843,10 +3820,19 @@ void activity_handlers::spellcasting_finish( player_activity *act, Character *yo _( "You lose your concentration!" ) ); if( !spell_being_cast.is_max_level( *you ) && level_override == -1 ) { // still get some experience for trying - spell_being_cast.gain_exp( *you, exp_gained / 5 ); - you->add_msg_if_player( m_good, _( "You gain %i experience. New total %i." ), exp_gained / 5, + exp_gained *= spell_being_cast.get_failure_exp_percent( *you ); + spell_being_cast.gain_exp( *you, exp_gained ); + you->add_msg_if_player( m_good, _( "You gain %i experience. New total %i." ), exp_gained, spell_being_cast.xp() ); } + if( act->get_value( 2 ) != 0 ) { + spell_being_cast.consume_spell_cost( *you ); + } + dialogue d( get_talker_for( you ), nullptr ); + std::vector failure_eocs = spell_being_cast.get_failure_eoc_ids(); + for( effect_on_condition_id failure_eoc : failure_eocs ) { + failure_eoc->activate( d ); + } get_event_bus().send( you->getID(), false, sp, spell_being_cast.spell_class(), spell_being_cast.get_difficulty( *you ), spell_being_cast.energy_cost( *you ), spell_being_cast.casting_time( *you ), @@ -3866,30 +3852,10 @@ void activity_handlers::spellcasting_finish( player_activity *act, Character *yo // spells with the components in hand. spell_being_cast.use_components( *you ); - // pay the cost. Allows ternaries based on having an effect or trait to calculate cost correctly - int cost = spell_being_cast.energy_cost( *you ); - spell_being_cast.cast_all_effects( *you, *target ); if( act->get_value( 2 ) != 0 ) { - switch( spell_being_cast.energy_source() ) { - case magic_energy_type::mana: - you->magic->mod_mana( *you, -cost ); - break; - case magic_energy_type::stamina: - you->mod_stamina( -cost ); - break; - case magic_energy_type::bionic: - you->mod_power_level( -units::from_kilojoule( static_cast( cost ) ) ); - break; - case magic_energy_type::hp: - blood_magic( you, cost ); - break; - case magic_energy_type::none: - default: - break; - } - + spell_being_cast.consume_spell_cost( *you ); } if( level_override == -1 ) { if( !spell_being_cast.is_max_level( *you ) ) { diff --git a/src/item.cpp b/src/item.cpp index 7532736bf9d57..aa440ae88f064 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -6425,8 +6425,11 @@ nc_color item::color_in_inventory( const Character *const ch ) const static_cast( iuse->get_actor_ptr() ); for( const std::string &spell_id_str : actor_ptr->spells ) { const spell_id sp_id( spell_id_str ); + std::optional max_book_level = sp_id->get_max_book_level(); if( player_character.magic->knows_spell( sp_id ) && - !player_character.magic->get_spell( sp_id ).is_max_level( player_character ) ) { + !player_character.magic->get_spell( sp_id ).is_max_level( player_character ) && + !( max_book_level.has_value() && + player_character.magic->get_spell( sp_id ).get_level() >= max_book_level.value() ) ) { ret = c_yellow; } if( !player_character.magic->knows_spell( sp_id ) && diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index e83d90c78dd1b..207cac9f876df 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -2425,7 +2425,8 @@ std::optional learn_spell_actor::use( Character *p, item &, const tripoint_ 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( *p ) ) { + if( sp.is_max_level( *p ) || ( sp.max_book_level().has_value() && + sp.get_level() >= sp.max_book_level() ) ) { entry.ctxt += _( " (Max)" ); entry.enabled = false; } else { diff --git a/src/magic.cpp b/src/magic.cpp index eb6422be5905e..de58631f08750 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -488,6 +488,7 @@ void spell_type::load( const JsonObject &jo, const std::string_view src ) optional( jo, was_loaded, "get_level_formula_id", get_level_formula_id ); optional( jo, was_loaded, "exp_for_level_formula_id", exp_for_level_formula_id ); optional( jo, was_loaded, "magic_type", magic_type ); + optional( jo, was_loaded, "max_book_level", max_book_level ); if( ( get_level_formula_id.has_value() && !exp_for_level_formula_id.has_value() ) || ( !get_level_formula_id.has_value() && exp_for_level_formula_id.has_value() ) ) { debugmsg( "spell id:%s has a get_level_formula_id or exp_for_level_formula_id but not the other! This breaks the calculations for xp/level!", @@ -635,6 +636,7 @@ void spell_type::serialize( JsonOut &json ) const json.member( "get_level_formula_id", get_level_formula_id ); json.member( "exp_for_level_formula_id", exp_for_level_formula_id ); json.member( "magic_type", magic_type ); + json.member( "max_book_level", max_book_level ); if( !learn_spells.empty() ) { json.member( "learn_spells" ); @@ -1710,6 +1712,22 @@ static constexpr double a = 6200.0; static constexpr double b = 0.146661; static constexpr double c = -62.5; +std::optional spell_type::get_max_book_level() const +{ + std::optional max_level; + if( max_book_level.has_value() ) { + max_level = max_book_level; + } else if( magic_type.has_value() ) { + max_level = magic_type.value()->max_book_level; + } + return max_level; +} + +std::optional spell::max_book_level() const +{ + return type->get_max_book_level(); +} + magic_energy_type spell_type::get_energy_source() const { if( energy_source.has_value() ) { @@ -1745,6 +1763,80 @@ std::optional spell_type::overall_exp_for_level_formula_id() cons } } +double spell::get_failure_cost_percent( Creature &caster ) const +{ + if( type->magic_type.has_value() ) { + const_dialogue d( get_const_talker_for( caster ), nullptr ); + return type->magic_type.value()->failure_cost_percent.evaluate( d ); + } else { + return 0.0f; + } +} + +double spell::get_failure_exp_percent( Creature &caster ) const +{ + if( type->magic_type.has_value() ) { + const_dialogue d( get_const_talker_for( caster ), nullptr ); + return type->magic_type.value()->failure_exp_percent.evaluate( d ); + } else { + return 0.2f; + } +} + +static void blood_magic( Character *you, int cost ) +{ + std::vector uile; + std::vector parts; + int i = 0; + for( const bodypart_id &bp : you->get_all_body_parts( get_body_part_flags::only_main ) ) { + const int hp_cur = you->get_part_hp_cur( bp ); + uilist_entry entry( i, hp_cur > cost, i + 49, body_part_hp_bar_ui_text( bp ) ); + + const std::pair &hp = get_hp_bar( hp_cur, you->get_part_hp_max( bp ) ); + entry.ctxt = colorize( hp.first, hp.second ); + uile.emplace_back( entry ); + parts.push_back( bp ); + i++; + } + int action = -1; + while( action < 0 ) { + action = uilist( _( "Choose part\nto draw blood from." ), uile ); + } + you->mod_part_hp_cur( parts[action], - cost ); + you->mod_pain( std::max( 1, cost / 3 ) ); +} + +void spell::consume_spell_cost( Character &caster ) const +{ + int cost = energy_cost( caster ) * get_failure_cost_percent( caster ); + switch( energy_source() ) { + case magic_energy_type::mana: + caster.magic->mod_mana( caster, -cost ); + break; + case magic_energy_type::stamina: + caster.mod_stamina( -cost ); + break; + case magic_energy_type::bionic: + caster.mod_power_level( -units::from_kilojoule( static_cast( cost ) ) ); + break; + case magic_energy_type::hp: + blood_magic( &caster, cost ); + break; + case magic_energy_type::none: + default: + break; + } +} + +std::vector spell::get_failure_eoc_ids() const +{ + if( type->magic_type.has_value() ) { + return type->magic_type.value()->failure_eocs; + } else { + return std::vector {}; + } +} + int spell::get_level() const { return type->get_level( experience ); @@ -1876,10 +1968,14 @@ float spell::exp_modifier( const Character &guy ) const int spell::casting_exp( const Character &guy ) const { - // the amount of xp you would get with no modifiers - const int base_casting_xp = 75; - - return std::round( guy.adjust_for_focus( base_casting_xp * exp_modifier( guy ) ) ); + if( type->magic_type.has_value() && type->magic_type.value()->casting_xp_formula_id.has_value() ) { + const_dialogue d( get_const_talker_for( guy ), nullptr ); + return std::round( type->magic_type.value()->casting_xp_formula_id.value()->eval( d, {} ) ); + } else { + // the amount of xp you would get with no modifiers + const int base_casting_xp = 75; + return std::round( guy.adjust_for_focus( base_casting_xp * exp_modifier( guy ) ) ); + } } std::string spell::enumerate_targets() const diff --git a/src/magic.h b/src/magic.h index 27fa92f724a4f..39c88f85606ad 100644 --- a/src/magic.h +++ b/src/magic.h @@ -383,6 +383,8 @@ class spell_type int exp_for_level( int level ) const; // returns the level of this spell type if the spell has the given experience. int get_level( int experience ) const; + // the maximum level of this spell that can be learned from a book. + std::optional get_max_book_level() const; private: // default values static const skill_id skill_default; @@ -438,6 +440,7 @@ class spell_type std::optional energy_source; std::optional get_level_formula_id; std::optional exp_for_level_formula_id; + std::optional max_book_level; }; class spell @@ -637,6 +640,12 @@ class spell int get_difficulty( const Creature &caster ) const; mod_id get_src() const; + std::optional max_book_level() const; + double get_failure_cost_percent( Creature &caster ) const; + double get_failure_exp_percent( Creature &caster ) const; + void consume_spell_cost( Character &caster ) const; + std::vector get_failure_eoc_ids() const; + // tries to create a field at the location specified void create_field( const tripoint_bub_ms &at, Creature &caster ) const; diff --git a/src/magic_type.cpp b/src/magic_type.cpp index 15c33096d2318..4d95b6330257f 100644 --- a/src/magic_type.cpp +++ b/src/magic_type.cpp @@ -1,5 +1,7 @@ #include "magic_type.h" +#include "condition.h" +#include "effect_on_condition.h" #include "debug.h" #include "generic_factory.h" #include "math_parser_jmath.h" @@ -41,6 +43,7 @@ void magic_type::load( const JsonObject &jo, const std::string_view src ) debugmsg( "magic_type id:%s has a get_level_formula_id or exp_for_level_formula_id but not the other! This breaks the calculations for xp/level!", id.c_str() ); } + optional( jo, was_loaded, "casting_xp_formula_id", casting_xp_formula_id ); optional( jo, was_loaded, "energy_source", energy_source ); if( jo.has_array( "cannot_cast_flags" ) ) { for( auto &cannot_cast_flag : jo.get_string_array( "cannot_cast_flags" ) ) { @@ -51,6 +54,20 @@ void magic_type::load( const JsonObject &jo, const std::string_view src ) cannot_cast_flags.insert( cannot_cast_flag ); } optional( jo, was_loaded, "cannot_cast_message", cannot_cast_message ); + optional( jo, was_loaded, "max_book_level", max_book_level ); + if( !was_loaded || jo.has_member( "failure_cost_percent" ) ) { + failure_cost_percent = get_dbl_or_var( jo, "failure_cost_percent", false, + 0.0f ); + } + if( !was_loaded || jo.has_member( "failure_exp_percent" ) ) { + failure_exp_percent = get_dbl_or_var( jo, "failure_exp_percent", false, + 0.2f ); + } + if( !was_loaded ) { + for( JsonValue jv : jo.get_array( "failure_eocs" ) ) { + failure_eocs.emplace_back( effect_on_conditions::load_inline_eoc( jv, src ) ); + } + } } void magic_type::serialize( JsonOut &json ) const @@ -62,9 +79,16 @@ void magic_type::serialize( JsonOut &json ) const json.member( "src_mod", src_mod ); json.member( "get_level_formula_id", get_level_formula_id ); json.member( "exp_for_level_formula_id", exp_for_level_formula_id ); + json.member( "casting_xp_formula_id", casting_xp_formula_id ); json.member( "energy_source", energy_source ); json.member( "cannot_cast_flags", cannot_cast_flags, std::set {} ); json.member( "cannot_cast_message", cannot_cast_message ); + json.member( "max_book_level", max_book_level ); + json.member( "failure_cost_percent", static_cast( failure_cost_percent.min.dbl_val.value() ), + 0.0f ); + json.member( "failure_exp_percent", static_cast( failure_exp_percent.min.dbl_val.value() ), + 0.2f ); + json.member( "failure_eocs", failure_eocs, std::vector {} ); json.end_object(); } @@ -79,6 +103,9 @@ void magic_type::check_consistency() if( m_t.get_level_formula_id.has_value() && m_t.get_level_formula_id.value()->num_params != 1 ) { debugmsg( "ERROR: %s get_level_formula_id has params that != 1!", m_t.id.c_str() ); } + if( m_t.casting_xp_formula_id.has_value() && m_t.casting_xp_formula_id.value()->num_params != 0 ) { + debugmsg( "ERROR: %s casting_xp_formula_id has params that != 0!", m_t.id.c_str() ); + } } } diff --git a/src/magic_type.h b/src/magic_type.h index 0647092506320..02b7280b5880e 100644 --- a/src/magic_type.h +++ b/src/magic_type.h @@ -6,6 +6,7 @@ #include #include +#include "dialogue_helpers.h" #include "enum_bitset.h" #include "type_id.h" @@ -43,9 +44,14 @@ class magic_type std::optional get_level_formula_id; std::optional exp_for_level_formula_id; + std::optional casting_xp_formula_id; std::optional energy_source; std::set cannot_cast_flags; // string flags std::optional cannot_cast_message; + std::optional max_book_level; + dbl_or_var failure_cost_percent; + dbl_or_var failure_exp_percent; + std::vector failure_eocs; static const std::vector &get_all(); static void check_consistency();