diff --git a/doc/editor/Graphics.html b/doc/editor/Graphics.html index 7370abae..d3d46339 100644 --- a/doc/editor/Graphics.html +++ b/doc/editor/Graphics.html @@ -39,6 +39,14 @@ contains slots 0 to 9 (numbered left to right), the second row contains slots 11 and so on. Each sheet contains 100 slots; thus, slots 0 to 99 are on sheet 0, slots 100 to 199 or on sheet 1, and so forth.

+

If you don't like having to do the calculations to figure out which graphic is in which +slot, select Classify Custom Graphics from the Scenario menu, and go through your custom +graphics, telling the editor what purpose each one is intended for. If you want to use a +single graphic for multiple purposes (eg, an item and a monster), you'll still have to +occasionally calculate a graphic number, but by classifying them here, they'll +automatically show up in all the usual Choose Graphic lists, after all the preset +graphics.

+

How to Make Your Custom Sheets

Make your graphic in a painting program, and save it as 8-bit PNG (both indexed and RGB diff --git a/doc/editor/Monsters.html b/doc/editor/Monsters.html index 6d02ef41..5389e08f 100644 --- a/doc/editor/Monsters.html +++ b/doc/editor/Monsters.html @@ -141,6 +141,8 @@ aren't immune to fire. node as its action, which can do anything a special node can do. This can be used to create special abilities that are otherwise not supported - for example, you could use this to make a monster that can teleport. +

  • Special when hit (Advanced) - When you hit the monster with a weapon, a special +node is called.
  • Death Triggers Scenario Special (Advanced) - The extra value is the number of a scenario special node, which is called when a creature of this type is killed.
  • Custom Ability (Advanced) - Most of the abilities listed above are pre-made diff --git a/doc/editor/appendix/Examples.html b/doc/editor/appendix/Examples.html index b6417e43..6b308e93 100644 --- a/doc/editor/appendix/Examples.html +++ b/doc/editor/appendix/Examples.html @@ -12,7 +12,7 @@
    -

    Appendix 7 - Special Encounter Examples

    +

    Appendix 8 - Special Encounter Examples

    Extra examples can be very useful when making special encounters. Here are a few quick sets of instructions for making popular types of encounters.

    diff --git a/doc/editor/appendix/Items.html b/doc/editor/appendix/Items.html index 6bc36527..ea5f9ca1 100644 --- a/doc/editor/appendix/Items.html +++ b/doc/editor/appendix/Items.html @@ -11,7 +11,7 @@
    -

    Appendix 2 - Item Ability Types

    +

    Appendix 3 - Item Ability Types

    Each item type can have a special ability. The types of abilities available range from shooting fireballs to weapons doing special damage to dragons to items being ingredients diff --git a/doc/editor/appendix/Magic.html b/doc/editor/appendix/Magic.html index f071558b..ba363870 100644 --- a/doc/editor/appendix/Magic.html +++ b/doc/editor/appendix/Magic.html @@ -11,7 +11,7 @@

    -

    Appendix 6 - Spell and Alchemy Lists

    +

    Appendix 7 - Spell and Alchemy Lists

    You can set special encounter nodes to give the party mage spells, priest spells, and alchemical recipes. These lists contains the numbers you enter to give specific diff --git a/doc/editor/appendix/Messages.html b/doc/editor/appendix/Messages.html index 75706214..b96e609e 100644 --- a/doc/editor/appendix/Messages.html +++ b/doc/editor/appendix/Messages.html @@ -11,7 +11,7 @@

    -

    Appendix 5 - Text Messages

    +

    Appendix 6 - Text Messages

    You can edit the text messages for the scenario, a town, or an outdoor section. This gives you the chance to directly edit the adventure text, without going through the diff --git a/doc/editor/appendix/Monsters.html b/doc/editor/appendix/Monsters.html new file mode 100644 index 00000000..da07aec3 --- /dev/null +++ b/doc/editor/appendix/Monsters.html @@ -0,0 +1,216 @@ + + + + + + + +

    +
    + +

    Appendix 5 - Monster Abilities

    + +

    When adding a new monster ability, you may have noticed that there are fourteen custom +options to choose from at the end of the list. These options don't fill in the ability +parameters with a template; they just present you immediately with the full ability +dialog. You can also get there by editing an existing ability.

    +

    This appendix covers all the options you can get in the ability editing dialog.

    +

    Note: If you add a custom ability and want to cancel it, you have to click Delete, +rather than Cancel. Clicking Cancel will cause the ability to be added with default values +(generally all zeroes).

    + +

    The Basics

    + +

    When editing any monster ability, you'll see four informational fields.

    + +
      +
    • Monster - This is here to remind you what the monster is you're editing an +ability for.Display name - Shows the ability's name as it will be shown in-game, for +example when casting Scry Monster.
    • +
    • Ability Type - The type of ability. This was set when you added the ability and +cannot be changed. (If you want to change it, you'll have to add a new ability and delete +the old one.)
    • +
    • Action Points - Shows the number of action points the ability consumes when the +monster activates it. This is usually based on the type and subtype, but sometimes you can +set it directly.
    • +
    • Subtype - Most abilities allow you to choose a subtype. Click Choose to select +from a list. The options depend on the ability type.
    • +
    • Delete - You can delete the ability by clicking this button.
    • +
    + +

    Missile Abilities

    + +

    Monsters will not use most missile abilities when adjacent to their target. Missile +abilities offer the following "subtypes":

    + +
      +
    • Darts
    • +
    • Bow and Arrows
    • +
    • Spears
    • +
    • Rocks or Stones
    • +
    • Razordisks
    • +
    • Spines - can be used while adjacent
    • +
    • Knives
    • +
    • Crossbow and Bolts
    • +
    • Rocks or Boulders - uses an extra action point compared to stones
    • +
    • Rapid Bow and Arrows - uses one less action point than regular bow
    • +
    + +

    In addition, you can set the following properties:

    + +
      +
    • Missile Graphic - Click Choose to select your missile graphic from the +list.
    • +
    • Dice, Sides - This works the same as the monster's regular attacks.
    • +
    • Range - The maximum range at which the ability can function, in tiles.
    • +
    • Chance - The per mille chance (ie, out of 1000) that the monster chooses to use +this ability.
    • +
    • Skill - This is similar to the monster's skill level, affecting their chance of +hitting.
    • +
    + +

    General Abilities

    + +

    General abilities cover several categories with a variety of effects. Some of these are +obvious, others less so. Even when ranged, monsters will also use most of these abilities +while adjacent to their target. The following is a list of all ability types classified as +general, using the terminology from the Add Ability page:

    + +
      +
    • Damage - Deals direct damage. Ability strength indicates the number of dice; +the number of sides is determined by the subtype (ray/gaze - d6, breath - d8, touch/spit - +d10).
    • +
    • Damage (full attack) - Like damage, but takes 4 action points when used as a +breath weapon ability. If used as a touch attack, it's identical to damage. You get this +type by selecting a simple breath weapon (fire, ice, electricity, or darkness).
    • +
    • Status Effect - Afflicts the target with some status effect. Ability strength +is the amount inflicted.
    • +
    • Field - Places fields around the target. Ability strength indicates the size of +the affected area; press Choose to select one. Note that the open square will not damage +the target itself.
    • +
    • Petrification - Turns target to stone. Ability strength affects the chance of +resisting.
    • +
    • SP drain - Drains spell points. Ability strength is the percentage of spell +points drained.
    • +
    • XP drain - Drains experience. Draining experience does not affect your level, +but you'll need to regain the experience before you can gain a level. Ability strength is +the amount drained.
    • +
    • Death - Kills the target by dealing a very large amount of damage. Ability +strength indicates the number of d10's to roll (it's multiplied by 10, so a strength of 1 +means 10d10).
    • +
    • Steal food/gold - Drains your food or gold. Ability strength is the maximum +amount to drain (a random number less than or equal is taken).
    • +
    • Undead stat - This is similar to status effect, but is also negated by the +life-saving item ability.
    • +
    • Weapon stat - This is the same as status effect, except when used as a touch +attack. In that case, it can only occur on attack 1, never on attacks 2 or 3. You could +use this for a beast with a disease-ridden bite or an assassin with a poisoned blade.
    • +
    + +

    They offer the following "subtypes":

    + +
      +
    • Ray +
    • Touch - no range, occurs as part of normal melee attack
    • +
    • Gaze +
    • Breath +
    • Spit - can't be used while adjacent
    • +
    + +

    Note that a non-ranged attack could be made using Ray, Gaze, or Breath simply by +limiting the range to 1, but this still differs from a Touch attack because the latter +accompanies the plain damage of the monster's regular attack.

    + +

    In addition, you can set the following properties:

    + +
      +
    • Missile Graphic - Click Choose to select your missile graphic from the +list.
    • +
    • Range - The maximum range at which the ability can function, in tiles.
    • +
    • Chance - The per mille chance (ie, out of 1000) that the monster chooses to use +this ability.
    • +
    • Strength - This determines how powerful the ability is. Its exact effect +depends on the ability type.
    • +
    • Damage, Status, or Field Type - Additional information for some ability types; +the meaning is obvious. Click Choose to select one from a list.
    • +
    + +

    Radiate Field Abilities

    + +

    Monsters that radiate fields are automatically immune to their own fields. Since +radiate abilities are passive, they take no action points. A radiate ability provides no +subtypes; the only editable parameters are as follows:

    + +
      +
    • Field Type - The type of field to radiate. You can pick anything, but keep in +mind that when the radiation is activated, an area centred on the creature is filled with +fields, so generally something like barriers or forcecages is a poor choice.
    • +
    • Radiate Chance - The percent chance each turn of the ability activating.
    • +
    • Affected Area - The area to effect. Click Choose to select one.
    • +
    + +

    Summoning Abilities

    + +

    Since summon abilities are passive, they take no action points to activate. They do +have the following subtypes:

    + +
      +
    • Specific - Always summons a specific type of creature that you choose.
    • +
    • Strength - Summons a creature based on its summon strength, just like the +general summoning spells.
    • +
    • Race - Summons a creature based on its race. This form can potentially result +in wildly different levels, so use it with caution.
    • +
    + +

    You can specify the following parameters for a summoning ability:

    + +
      +
    • Monster, race, etc - Specify what sort of creature to summon by clicking the +Choose button.
    • +
    • Max, min - The number of monsters summoned will be a random number in this +range.
    • +
    • Duration - You can specify how long the summoned monsters will remain.
    • +
    • Chance - The percent chance each turn of summoning.
    • +
    + +

    Other Abilities

    + +

    There are several other abilities that can be added to monsters. They don't have a +dedicated "Custom Ability" option, but you can edit them after adding for some increased +customization. (The special node abilities assume you'll want to do this and automatically +open the edit window once you've saved the node.)

    + +
      +
    • Splits when hit - You can specify the per mille chance of the monster splitting +each time it is hit.
    • +
    • Permanent martyr's shield - You can specify the per mille chance of the +martyr's shield activating each time the monster is hit.
    • +
    • Absorb spells - You can specify the per mille chance of spells being absorbed +each time the monster is subject to them. Normally, the monster gains health proportional +to the damage that would have been dealt; however, some non-damaging effects can also be +absorbed, and in that case the monster gains a fixed amount of health that you specify +here.
    • +
    • Shoots webs - This is a legacy support type, but you can nevertheless specify +the range of the ability and the per mille chance of the monster deciding to use it. It +differs from a general field ability in that the monster will use it even if adjacent to +the target.
    • +
    • Heat ray - This is a legacy support type, but you can nevertheless specify the +range, strength (in number of d6's), and per mille chance of the monster deciding to use +it. It differs from a standard damage ray in that it costs only one action point. It does +not allow you to customize the damage type; it always deals fire damage.
    • +
    • Special action - This calls a special node as part of the monster's action. You +can specify how many action points it costs as well as the per mille chance of the monster +deciding to use it. Of course, from here you can also edit the special node that is +called.
    • +
    • Death/hit special - This calls a special node when the monster dies or when it +is hit. It does not have any options (other than specifying which node to call). Of +course, from here you can edit the special node that is called.
    • +
    + +
    + + diff --git a/doc/editor/appendix/Sounds.html b/doc/editor/appendix/Sounds.html index 99037c05..c0f7712b 100644 --- a/doc/editor/appendix/Sounds.html +++ b/doc/editor/appendix/Sounds.html @@ -11,7 +11,7 @@
    -

    Appendix 4 - Blades Sound Effects

    +

    Appendix 5 - Blades Sound Effects

    There are several places in the editor where you are asked for the number of a sound effect. These are the available Blades effects:

    diff --git a/doc/editor/appendix/Specials.html b/doc/editor/appendix/Specials.html index 8cb4b61e..942ee20d 100644 --- a/doc/editor/appendix/Specials.html +++ b/doc/editor/appendix/Specials.html @@ -11,7 +11,7 @@
    -

    Section 14: Appendix 1 - Special Encounter Node Types

    +

    Appendix 1 - Special Encounter Node Types

    These are the many types of special encounter nodes. They are divided into six different categories, and given with examples and descriptions of what the various editing diff --git a/doc/editor/appendix/Terrain.html b/doc/editor/appendix/Terrain.html index f2c97011..bfd9f8ea 100644 --- a/doc/editor/appendix/Terrain.html +++ b/doc/editor/appendix/Terrain.html @@ -11,11 +11,11 @@

    -

    Appendix 3 - Starting Terrain Types

    +

    Appendix 4 - Starting Terrain Types

    There are 256 premade terrain types (numbered 0 - 255) in the basic scenario you start with. Many of them have special properties, or need to be handled in different ways. -Remember that terrain types 0-90 cant be edited (except for the graphic).

    +This appendix also notes which terrain types are used by the default combat arenas.

    +

    Combat Arenas

    + +
      +
    1. Grassy field - uses terrain types 2, 3, 4, 112, 114, 115, sometimes 83
    2. +
    3. Ordinary cave - uses terrain types 0, 1, 93, 94, 95, 98, sometimes 82
    4. +
    5. Mountain - uses terrain types 36, 37, sometimes 83
    6. +
    7. Surface bridge - uses terrain types 50, 63, 64, 83
    8. +
    9. Cave bridge - uses terrain types 71, 74, 82
    10. +
    11. Rubble-strewn cave - uses terrain types 0, 84, 92, 95, 97, 98, sometimes 82
    12. +
    13. Cave forest - uses terrain types 0, 91, 92, 93, 95, 98, sometimes 82
    14. +
    15. Cave mushrooms - uses terrain types 0, 1, 92, 93, 94, 95, sometimes 82
    16. +
    17. Cave swamp - uses terrain types 0, 1, 92, 95, 96, 112, sometimes 82
    18. +
    19. Surface rocks - uses terrain types 2, 3, 87, 110, 113, 114, sometimes 83
    20. +
    21. Surface swamp - uses terrain types 2, 3, 4, 111, sometimes 83
    22. +
    23. Surface woods - uses terrain types 2, 3, 4, 112, 113, 114, sometimes 83
    24. +
    25. Surface shrubbery - uses terrain types 2, 3, 4, 112, 114, 115, sometimes 83
    26. +
    27. Stalagmites - uses terrain types 0, 1, 75, 76, 97, 98, sometimes 82
    28. +
    29. Cave fumarole - uses terrain types 0, 1, 75, 76, 94, 98
    30. +
    31. Surface fumarole - uses terrain types 36, 37, 75, 76
    32. +
    33. Cave camp - uses terrain types 0, 1, 92, 93, 94, 95, 98, 104, 105
    34. +
    35. Surface camp - uses terrain types 2, 3, 4, 104, 105, 112, 114, 115
    36. + +
    37. Cave crops - uses terrain types 0, 1, 84, 93, 97, 98, +plus the terrain type the arena was triggered from
    38. +
    39. Surface crops - uses terrain types 2, 3, 4, 87, 110, +plus the terrain type the arena was triggered from
    40. +
    + +

    In addition to the above, the walls (5-35) are used to make edges around the map and occasionally pillars in the middle.

    +
    diff --git a/doc/editor/nav.js b/doc/editor/nav.js index 18a47336..063b61a6 100644 --- a/doc/editor/nav.js +++ b/doc/editor/nav.js @@ -24,6 +24,7 @@ document.write('\
      \
    1. Special Encounter Node Types
    2. \
    3. Item Ability Types
    4. \ +
    5. Monster Ability Types
    6. \
    7. Starting Terrain Types
    8. \
    9. Blades Sound Effects
    10. \
    11. Text Messages
    12. \ diff --git a/rsrc/dialogs/edit-mabil-radiate.xml b/rsrc/dialogs/edit-mabil-radiate.xml index 5a64c817..af28c944 100644 --- a/rsrc/dialogs/edit-mabil-radiate.xml +++ b/rsrc/dialogs/edit-mabil-radiate.xml @@ -17,6 +17,10 @@ Radiate Chance: + Area Affected: + + + diff --git a/rsrc/strings/monster-abilities.txt b/rsrc/strings/monster-abilities.txt index 603acac2..7c983a07 100644 --- a/rsrc/strings/monster-abilities.txt +++ b/rsrc/strings/monster-abilities.txt @@ -96,6 +96,7 @@ Absorb spells Web missile Heat ray (costs 1 action point) Call special node +Call special when hit Death triggers special Radiate fields Summon aid @@ -106,17 +107,16 @@ Summon aid - Darts Bow/Arrows Spears -Rocks +Rocks/Stones Razordisks Spines Knives Crossbow/Bolts - - +Rocks/Boulders +Bow/Arrows (Rapid) Ray Touch Gaze @@ -160,7 +160,7 @@ Summon random creature of species Chance of Activating - +Chance of Activating Chance of Activating diff --git a/src/boe.combat.cpp b/src/boe.combat.cpp index ab9342bf..90b3639f 100644 --- a/src/boe.combat.cpp +++ b/src/boe.combat.cpp @@ -2545,8 +2545,35 @@ void do_monster_turn() { // Place fields for monsters that create them. Only done when monst sees foe if(target != 6 && can_see_light(cur_monst->cur_loc,targ_space,sight_obscurity) < 5) { - if(cur_monst->abil[eMonstAbil::RADIATE].active && get_ran(1,1,100) < cur_monst->abil[eMonstAbil::RADIATE].radiate.chance) - place_spell_pattern(square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + if(cur_monst->abil[eMonstAbil::RADIATE].active && get_ran(1,1,100) < cur_monst->abil[eMonstAbil::RADIATE].radiate.chance) { + switch(cur_monst->abil[eMonstAbil::RADIATE].radiate.pat) { + case PAT_SINGLE: + place_spell_pattern(single, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + case PAT_SMSQ: + place_spell_pattern(small_square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + case PAT_SQ: + place_spell_pattern(square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + case PAT_OPENSQ: + place_spell_pattern(open_square, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + case PAT_RAD2: + place_spell_pattern(radius2, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + case PAT_RAD3: + place_spell_pattern(radius3, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + case PAT_PLUS: + place_spell_pattern(t, cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + case PAT_WALL: + int dir = (cur_monst->direction + 6) % 8; + place_spell_pattern(field[dir], cur_monst->cur_loc, cur_monst->abil[eMonstAbil::RADIATE].radiate.type, 7); + break; + } + } if(cur_monst->abil[eMonstAbil::SUMMON].active && get_ran(1,1,100) < cur_monst->abil[eMonstAbil::SUMMON].summon.chance) { uAbility abil = cur_monst->abil[eMonstAbil::SUMMON]; mon_num_t what_summon = 0; diff --git a/src/boe.infodlg.cpp b/src/boe.infodlg.cpp index e516ed54..3f088ff2 100644 --- a/src/boe.infodlg.cpp +++ b/src/boe.infodlg.cpp @@ -356,7 +356,10 @@ static void put_monst_info(cDialog& me, const cCreature& store_m) { if(i > 4) break; // TODO: Support showing more than just the first four abilities if(!abil.second.active) continue; std::string id = "abil" + std::to_string(i); - me[id].setText(abil.second.to_string(abil.first)); + std::string name = abil.second.to_string(abil.first); + if(abil.first == eMonstAbil::SUMMON && abil.second.summon.type == eMonstSummon::TYPE) + name.replace(name.find("%s"), 2, univ.scenario.scen_monsters[abil.second.summon.what].m_name); + me[id].setText(name); i++; } diff --git a/src/classes/monster.cpp b/src/classes/monster.cpp index 06bf04db..facb3e91 100644 --- a/src/classes/monster.cpp +++ b/src/classes/monster.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "oldstructs.h" @@ -305,28 +306,28 @@ std::map::iterator cMonster::addAbil(eMonstAbilTemplate wha return abil.find(eMonstAbil::DEATH_TRIGGER); // Radiate abilities case eMonstAbilTemplate::RADIATE_FIRE: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_FIRE, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_FIRE, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); case eMonstAbilTemplate::RADIATE_ICE: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_ICE, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_ICE, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); case eMonstAbilTemplate::RADIATE_SHOCK: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_FORCE, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_FORCE, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); case eMonstAbilTemplate::RADIATE_ANTIMAGIC: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::FIELD_ANTIMAGIC, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::FIELD_ANTIMAGIC, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); case eMonstAbilTemplate::RADIATE_SLEEP: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::CLOUD_SLEEP, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::CLOUD_SLEEP, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); case eMonstAbilTemplate::RADIATE_STINK: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::CLOUD_STINK, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::CLOUD_STINK, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); case eMonstAbilTemplate::RADIATE_BLADE: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_BLADES, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::WALL_BLADES, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); case eMonstAbilTemplate::RADIATE_WEB: - abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::FIELD_WEB, param}; + abil[eMonstAbil::RADIATE].radiate = {true, eFieldType::FIELD_WEB, param, PAT_SQ}; return abil.find(eMonstAbil::RADIATE); // Advanced abilities case eMonstAbilTemplate::CUSTOM_MISSILE: @@ -772,7 +773,7 @@ std::string uAbility::to_string(eMonstAbil key) const { case eMonstAbil::DRAIN_XP: sout << "Draining"; break; case eMonstAbil::KILL: sout << "Death"; break; case eMonstAbil::STEAL_FOOD: sout << "Steals food"; break; - case eMonstAbil::STEAL_GOLD: sout << "Steals gold!"; break; + case eMonstAbil::STEAL_GOLD: sout << "Steals gold"; break; case eMonstAbil::FIELD: switch(gen.fld) { case eFieldType::SPECIAL_EXPLORED: @@ -878,7 +879,7 @@ std::string uAbility::to_string(eMonstAbil key) const { case eMonstAbilCat::SPECIAL: switch(key) { case eMonstAbil::SPLITS: - sout << "Splits when hit (" << special.extra1 << "% chance)"; + sout << "Splits when hit (" << std::fixed << std::setprecision(1) << double(special.extra1) / 10 << "% chance)"; break; case eMonstAbil::MARTYRS_SHIELD: sout << "Permanent martyr's shield"; @@ -893,9 +894,13 @@ std::string uAbility::to_string(eMonstAbil key) const { sout << "Heat ray (" << special.extra3 << "d6)"; break; case eMonstAbil::SPECIAL: + sout << "Unusual ability (active)"; + break; case eMonstAbil::DEATH_TRIGGER: + sout << "Unusual ability (death)"; + break; case eMonstAbil::HIT_TRIGGER: - sout << "Unusual ability"; + sout << "Unusual ability (passive)"; break; // Non-special abilities case eMonstAbil::STUN: case eMonstAbil::NO_ABIL: case eMonstAbil::RADIATE: case eMonstAbil::SUMMON: @@ -906,8 +911,49 @@ std::string uAbility::to_string(eMonstAbil key) const { } break; case eMonstAbilCat::SUMMON: - // TODO: Somehow look up the name of the monster to be summoned - sout << "Summons " << summon.min << '-' << summon.max << ' ' << "monst-name" << "s (" << summon.chance << "% chance)"; + sout << "Summons " << summon.min << '-' << summon.max << ' '; + switch(summon.type) { + case eMonstSummon::TYPE: + sout << "%s"; + break; + case eMonstSummon::SPECIES: + switch(eRace(summon.what)) { + case eRace::BEAST: sout << "beasts"; break; + case eRace::BIRD: sout << "birds"; break; + case eRace::BUG: sout << "bugs"; break; + case eRace::DEMON: sout << "demons"; break; + case eRace::DRAGON: sout << "dragons"; break; + case eRace::GIANT: sout << "giants"; break; + case eRace::GOBLIN: sout << "goblins"; break; + case eRace::HUMAN: sout << "humans"; break; + case eRace::HUMANOID: sout << "humanoids"; break; + case eRace::IMPORTANT: sout << "VIPs"; break; + case eRace::MAGE: sout << "mages"; break; + case eRace::MAGICAL: sout << "magical beings"; break; + case eRace::NEPHIL: sout << "nephilim"; break; + case eRace::PLANT: sout << "plants"; break; + case eRace::PRIEST: sout << "priests"; break; + case eRace::REPTILE: sout << "reptiles"; break; + case eRace::SKELETAL: sout << "skeletal undead"; break; + case eRace::SLIME: sout << "slimes"; break; + case eRace::SLITH: sout << "sliths"; break; + case eRace::STONE: sout << "mineral beings"; break; + case eRace::UNDEAD: sout << "undead"; break; + case eRace::UNKNOWN: sout << "monsters"; break; + case eRace::VAHNATAI: sout << "vahnatai"; break; + } + break; + case eMonstSummon::LEVEL: + switch(summon.what) { + case 0: sout << "cannon fodder"; break; + case 1: sout << "minor allies"; break; + case 2: sout << "allies"; break; + case 3: sout << "major allies"; break; + case 4: sout << "protectors"; break; + } + break; + } + sout << " (" << summon.chance << "% chance)"; break; case eMonstAbilCat::RADIATE: sout << "Radiates "; diff --git a/src/classes/monster.h b/src/classes/monster.h index 5b991aaf..2de80ec4 100644 --- a/src/classes/monster.h +++ b/src/classes/monster.h @@ -17,6 +17,7 @@ #include "simpletypes.h" #include "graphtool.hpp" #include "living.hpp" +#include "spell.hpp" namespace legacy { struct monster_record_type; @@ -90,6 +91,7 @@ union uAbility { bool active; eFieldType type; int chance; + eSpellPat pat; } radiate; struct { bool active; diff --git a/src/scenedit/scen.core.cpp b/src/scenedit/scen.core.cpp index 2218d8d8..26fe737f 100644 --- a/src/scenedit/scen.core.cpp +++ b/src/scenedit/scen.core.cpp @@ -874,7 +874,10 @@ static void put_monst_abils_in_dlog(cDialog& me, cMonster& monst) { me["abil-edit" + id].setText("Add"); } } else if(i % 4 == 3) abils.addPage(); - me["abil-name" + id].setText(abil.second.to_string(abil.first)); + std::string name = abil.second.to_string(abil.first); + if(abil.first == eMonstAbil::SUMMON && abil.second.summon.type == eMonstSummon::TYPE) + name.replace(name.find("%s"), 2, scenario.scen_monsters[abil.second.summon.what].m_name); + me["abil-name" + id].setText(name); me["abil-edit" + id].setText("Edit"); i++; } @@ -963,12 +966,15 @@ static short get_monst_abil_num(std::string prompt, int min, int max, cDialog& p static void fill_monst_abil_detail(cDialog& me, cMonster& monst, eMonstAbil abil, uAbility detail) { eMonstAbilCat cat = getMonstAbilCategory(abil); me["monst"].setText(monst.m_name); + std::string name = detail.to_string(abil); + if(abil == eMonstAbil::SUMMON && detail.summon.type == eMonstSummon::TYPE) + name.replace(name.find("%s"), 2, scenario.scen_monsters[detail.summon.what].m_name); me["name"].setText(detail.to_string(abil)); // These names start at line 80 in the strings file, but the first valid ability is ID 1, so add 79. me["type"].setText(get_str("monster-abilities", 79 + int(abil))); // Action points if(cat == eMonstAbilCat::MISSILE) { - if(detail.missile.type == eMonstMissile::ARROW || detail.missile.type == eMonstMissile::BOLT || detail.missile.type == eMonstMissile::SPINE || detail.missile.type == eMonstMissile::RAPID_ARROW) + if(detail.missile.type == eMonstMissile::ARROW || detail.missile.type == eMonstMissile::BOLT || detail.missile.type == eMonstMissile::SPINE || detail.missile.type == eMonstMissile::BOULDER) me["ap"].setTextToNum(3); else me["ap"].setTextToNum(2); } else if(cat == eMonstAbilCat::GENERAL) { @@ -1005,9 +1011,11 @@ static void fill_monst_abil_detail(cDialog& me, cMonster& monst, eMonstAbil abil me["missile"].show(); me["pick-missile"].show(); me["missile-pic"].show(); - me["missile-touch"].hide(); + if(cat != eMonstAbilCat::MISSILE) + me["missile-touch"].hide(); me["range"].show(); - me["range-touch"].hide(); + if(cat != eMonstAbilCat::MISSILE) + me["range-touch"].hide(); } miss_num_t missile; int range; @@ -1036,6 +1044,8 @@ static void fill_monst_abil_detail(cDialog& me, cMonster& monst, eMonstAbil abil if(abil == eMonstAbil::FIELD) me["extra"].setTextToNum(int(detail.gen.fld)); else me["field"].setTextToNum(int(detail.radiate.type)); + if(cat == eMonstAbilCat::RADIATE) + me["pat"].setTextToNum(int(detail.radiate.pat)); } // Other type-specific fields if(cat == eMonstAbilCat::MISSILE) { @@ -1101,6 +1111,7 @@ static void save_monst_abil_detail(cDialog& me, eMonstAbil abil, uAbility& detai detail.summon.chance = me["odds"].getTextAsNum(); } else if(cat == eMonstAbilCat::RADIATE) { detail.radiate.chance = me["odds"].getTextAsNum(); + detail.radiate.pat = eSpellPat(me["pat"].getTextAsNum()); } else if(cat == eMonstAbilCat::SPECIAL) { detail.special.extra1 = me["extra1"].getTextAsNum(); detail.special.extra2 = me["extra2"].getTextAsNum(); @@ -1167,10 +1178,10 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst return true; } } - size_t iShow = std::distance(monst.abil.begin(), iter); - dynamic_cast(me["abils"]).setPage(iShow / 4); if(tmpl < eMonstAbilTemplate::CUSTOM_MISSILE && tmpl != eMonstAbilTemplate::SPECIAL) { put_monst_abils_in_dlog(me, monst); + size_t iShow = std::distance(monst.abil.begin(), iter); + dynamic_cast(me["abils"]).setPage(iShow / 4); return true; } } else { @@ -1211,7 +1222,7 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst if(cat == eMonstAbilCat::MISSILE || cat == eMonstAbilCat::GENERAL || cat == eMonstAbilCat::SUMMON) { int first, last; - if(cat == eMonstAbilCat::MISSILE) first = 110, last = 117; + if(cat == eMonstAbilCat::MISSILE) first = 110, last = 119; else if(cat == eMonstAbilCat::GENERAL) first = 120, last = 124; else if(cat == eMonstAbilCat::SUMMON) first = 150, last = 152; abil_dlg["pick-subtype"].attachClickHandler([&,cat,first,last](cDialog& me,std::string,eKeyMod) -> bool { @@ -1270,17 +1281,26 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst fill_monst_abil_detail(me, monst, abil, abil_params); return true; }); + abil_dlg["pick-pat"].attachClickHandler([&](cDialog& me,std::string,eKeyMod) -> bool { + save_monst_abil_detail(me, abil, abil_params); + int i = abil_params.radiate.pat; + i = choose_text(STRT_SPELL_PAT, i, &me, "Which spell pattern?"); + abil_params.radiate.pat = eSpellPat(i); + fill_monst_abil_detail(me, monst, abil, abil_params); + return true; + }); } else if(cat == eMonstAbilCat::SUMMON) { abil_dlg["pick-summon"].attachClickHandler([&](cDialog& me,std::string,eKeyMod) -> bool { save_monst_abil_detail(me, abil, abil_params); int i = abil_params.summon.what; eStrType type; switch(abil_params.summon.type) { - case eMonstSummon::TYPE: type = STRT_MONST; break; + case eMonstSummon::TYPE: type = STRT_MONST; i--; break; case eMonstSummon::LEVEL: type = STRT_SUMMON; break; case eMonstSummon::SPECIES: type = STRT_RACE; break; } i = choose_text(type, i, &me, "Summon what?"); + if(type == STRT_MONST) i++; abil_params.summon.what = i; fill_monst_abil_detail(me, monst, abil, abil_params); return true; @@ -1312,6 +1332,8 @@ static bool edit_monst_abil_detail(cDialog& me, std::string hit, cMonster& monst if(abil_dlg.accepted()) iter->second = abil_params; put_monst_abils_in_dlog(me, monst); + size_t iShow = std::distance(monst.abil.begin(), iter); + dynamic_cast(me["abils"]).setPage(iShow / 4); return true; }