'
+'
Attack Master API v'+version+'
'
+'
and later
'
+'
'
+'
New: in this Help Handout
'
- +'
Updated: Advantage, Disadvantage and Movement modifiers added to --set-mods command
'
- +'
Updated: Saving Throw priority can now prioritise magic item effects for best saving throws
'
- +'
New: Magic resistance can be specified for characters and creatures and will interact with saves
'
- +'
New: --turn-undead command displays a dialog to support turning undead as speified in the Class and Creature databases
'
+ +'
New: Added ability to override auto-management of worn items
'
+ +'
New: Added Called Shots including attack penalty.
'
+ +'
New: Added support for body parts with different armour classes.
'
+ +'
New: Added new surprise modifier option for --set-mods command.
'
+'
AttackMaster API provides functions to manage weapons, armour & shields, including taking weapons in hand and using them to attack. It uses rules (defined in the RPGMaster Library) to the full extent, taking into account: ranged weapon ammo management with ranges varying appropriately and range penalties/bonuses applied; Strength & Dexterity bonuses where appropriate; any magic bonuses to attacks that are in effect (if used with RoundMaster API effects); penalties & bonuses for non-proficiency, proficiency, specialisation & mastery; penalties for non-Rangers attacking with two weapons; use of 1-handed, 2-handed or many-handed weapons and restrictions on the number of weapons & shields that can be held at the same time; support for Fighting Styles as defined in The Complete Fighter\'s Handbook; plus many other features. This API works best with the databases provided with the RPGMaster series APIs (or added by yourself in custom databases), which hold the data for automatic definition of weapons and armour. However, some attack commands will generally work with manual entry of weapons onto the character sheet. The CommandMaster API can be used by the GM to easily manage weapon proficiencies.
'
+'
Specification for weapons, armour & shields are implemented as ability macros in specific database character sheets. This API comes with a wide selection of weapon and armour macros, held in databases in the RPGMaster Library for the specific game version you are playing. If the MagicMaster API is also loaded, it provides many more specifications for standard and magic items that are beneficial to melee actions and armour class. The GM can add to the provided items in the databases using standard Roll20 Character Sheet editing, following the instructions provided in the relevant Database Help handout.
'
@@ -328,7 +340,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+'
Adjustments to Attack Calculations
'
+'
The APIs automatically calculate the Thac0 they expect a particular character or creature to have. If the Thac0 is also allocated to a Token Circle, the calculated expected Thac0 will be compared to that set on the Token and any differences highlighted by showing a "Thac0 Bar" on the Token. This difference may be due to current magical effects or other circumstantial aspects (the token circle can be altered manually by the player or GM at any time). The difference is highlighted so that the player and GM can investigate the difference.
'
- +'
When combined with the RPGM MagicMaster and RoundMaster APIs, magical effects of spells and magic items can automatically adjust the calculated expected Thac0 depending on the properties of the spell or item, such as spells like Prayer or magic items like a Luck Stone. To date, these effects have been hidden in the attack calculations. In order to see an explanation of how these modifiers are calculated, and for the GM to manually adjust these modifiers, the AttackMaster API now provides a dialog under the Attk Menu > Check Thac0 & HP Mods action button menu option for each token, or via the !attk --check-mods command. This dialog also allows the player to select whether to prioritise the best AC, Thac0 (to-hit likelihood), Damage or HP when selecting which magical effects to apply when conflicts arrise.
'
+ +'
When combined with the RPGM MagicMaster and RoundMaster APIs, magical effects of spells and magic items can automatically adjust the calculated expected Thac0 depending on the properties of the spell or item, such as spells like Prayer or magic items like a Luck Stone. To date, these effects have been hidden in the attack calculations. In order to see an explanation of how these modifiers are calculated, and for the GM to manually adjust these modifiers, the AttackMaster API now provides a dialog under the Attk Menu > Check Thac0 & HP Mods action button menu option for each token, or via the !attk --check-mods command. This dialog also allows the player to select whether to prioritise the best AC, Thac0 (to-hit likelihood), Damage, HP or Saving Throws when selecting which magical effects to apply when conflicts arrise.
'
+'
Ammunition
'
+'
The system handles both Ranged weapons that take ammunition, such as bows and slings, and also "self-ammoed" Ranged weapons like daggers, that can be thrown at a target. The quantity of ammunition or self-ammoed weapons is managed by the system: as they are used in attacks, the quantity in the Characters Item table decreases. A menu can be called to recover ammunition, in agreement with the DM - the same menu can be used to add or remove quantities of ammunition for other reasons (such as being purchased). Some types of ammo always breaks and can\'t be recovered (for example glass arrows) - this is charged ammo.
'
@@ -341,16 +353,48 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+'
Armour Class management
'
+'
The system continually checks the Armour Class of each Character / NPC / creature by examining the information on the Character Sheet and the items in the Item table. Armour and Shields can be placed in the Items table which will be discovered, and the specifications from the Armour database used to calculate the appropriate AC under various conditions and display them to the Player. The process the system made to achieve the calculated AC will be shown.
'
+'
Many magic items have AC qualities, such as Bracers of Defence and Rings of Protection, and if the MagicMaster API is used these are also taken into account - invalid combinations will also be prevented, such as Rings of Protection with magical armour. If allocated to a Token Circle, the calculated AC is compared to the displayed Token AC and any difference highlighted - this may be due to magical effects currently in place, for instance - the highlight allows the Player to review why this might be.
'
+ +'
New: It is now possible to override the decisions that the APIs make about what armour, robes, cloaks, bracers, shields etc are being worn. Type in the name of an item into the appropriate place of the "Equipment Worn" table on the Equipment tab of the Charater Sheet and it will be used in preference to others of a similar type. Use these fields:
'
+ +'
'
+ +'| Head | Helm, Hat |
'
+ +'| Eyes | Glasses, Eyes, & similar |
'
+ +'| Cloak | Cloak |
'
+ +'| Chest | Robe |
'
+ +'| Wrists | Bracers |
'
+ +'| Hands | Gauntlets, Gloves |
'
+ +'| Rings | NOT USED: use Attk Menu > Change Weapon to wear rings |
'
+ +'| Armor | Armor, Barding |
'
+ +'| Belt | Girdle |
'
+ +'| Feet | Boots |
'
+ +'| Weapons | NOT USED: use Attk Menu > Change Weapon to hold weapons |
'
+ +'| Shield | NOT USED: use Attk Menu > Change Weapon to hold shields |
'
+ +'
'
+ +'
Other types of item do not conflict and can be held simultaniously, except where magic item descriptions state otherwise. You can check the impact of your choices using the Attk Menu > Check AC or Attk Menu > Check Thac0/HP dialogs.
'
+'
When combined with the RPGM MagicMaster and RoundMaster APIs, magical effects of spells and magic items can automatically adjust armour class depending on the properties of the spell or item and these modifications can be viewed in the Attk Menu > Check AC dialog or via the !attk --checkac command.
'
- +'
New:Magic Resistance & Saves
'
- +'
The corollary to attacks is saves and New: magic resistance. The system provides three menus: one to make magic resistance checks, one to access, review, update and make saving throws and the appropriate modifiers; and one to make attribute checks, again with the appropriate modifiers.
'
+
+ +'
New: Called Shots
'
+ +'
As per the rules for attacks in the DMG, a Character, NPC or Creature can declair they want to make a Called Shot when they specify their action for the next round as part of initiative. The RoundMaster Suite of APIs now support this. See the InitiativeMaster Help handout for details.
'
+ +'
When making an attack, if the token represents a Character / NPC / Creature that has stated when doing initiative that they were attempting a called shot, the attack dialog will state the Called Shot previously selected. All appropriate adjustments will be made to the attack calculations, including the appropriate armour class adjustments for the body part targeted, if any. The attack calculation can be seen by hovering the cursor over the AC Hit value.
'
+ +'
If circumstances have changed, a Change hyperlink exists next to the Called Shot name on the Attack dialog. Selecting this will present a new dialog on which a different called shot, or None, can be selected. Note: The target for the Called Shot cannot be changed from that selected during initiative. If a different foe is attacked, the Called Shot will be ignored (but will remain for any further attacks by that token on that foe for the duration of the round).
'
+
+ +'
New: Parrying
'
+ +'
A specific Called Shot is a Parry. The RPGMaster APIs support Parrying using either the rules in the DMG, page 61, or alternatively using the rules in The Complete Fighter\'s Handbook, by selecting the appropriate Parry option in the RPGM configuration dialog using the GM macro button (or the !attk --config command).
'
+ +'
If using the DMG rules, the armour class of the character/NPC/Creature selecting Parry for initiative will be set appropriately for the round, reverting automatically. The effect can be seen in the Attk Menu > Check AC dialog. This AC is valid against all attacks in that round, but the parryer can not undertake any other actions.
'
+ +'
If the optional rules for Parrying from The Complete Fighter\'s Handbook are selected, the Parry is selected as a Called Shot. No penalties are applied to initiative or attack calculations for Parrying. When the target selected to parry against has their turn in a round, a message will be displayed to player(s) that control the Character / NPC / Creature who selected to Parry to make a "Counter Attack". As per the rules in The Complete Fighter\'s Handbook if the Parrying attack succeeds, any attack by the previously selected Called Shot target (assumed to be the frontal attacker) against the token doing the Parrying is deemed to fail. The Parrying attack is also deemed not to do any damage. Note that each Parry takes one of the Actions during the round, but the token doing the Parry will still have their turn(s) announced - ignore one announcement for each Parry performed (or yet to be performed).
'
+
+ +'
New: Body Part AC
'
+ +'
Different body parts of a Race or Creature can have different armour classes. See the Race & Creature Database Help handout for details of how to specify this. This means that Called Shots against different body parts may get better (or worse) armour classes to overcome. The AC of the foe for the called shot selected during Initiative will be changed for the duration of the attack, reflected in the foe\'s Token Circle if mapped to AC. The AC reverts for future attacks.
'
+ +'
Even if not making a called shot, it might be that an attack can randomly hit a vulnerable spot. Relative chances of hitting different body parts can be specified in the Creature definition. If so specified, before each attack the API will determine which body part will randomly be the target of the next attack based on the relative chances specified (body parts / called shots with a chance of zero will never be so selected). As an example, see the definition of a Beholder in the Monsterous Manual and extracted from the Race-DB-Creatures-A-E creatures database.
'
+ +'
The GM can override the random selection of body parts that take the next attack by selecting the token representing the creature and using the Attk Menu > Check AC (or the !attk --check-ac command). If the creature has multiple body parts a Change hyperlink will appear next to the base AC value on the dialog displayed. Selecting this will offer a choice of fixed body part ACs, or reverting to Random selection. This choice does not affect Called Shots which continue to operate as normal, and will last until changed again by the GM.
'
+
+ +'
Magic Resistance & Saves
'
+ +'
The corollary to attacks is saves and magic resistance. The system provides three menus: one to make magic resistance checks, one to access, review, update and make saving throws and the appropriate modifiers; and one to make attribute checks, again with the appropriate modifiers.
'
+'
Magic Resistance is a property of some creatures specified in the Monstrous Manual: not only as a straight overall percentage resistance to all types of magic, but also as specific full or percentage immunities to certain types of magic (such as Charm magic or Sleep spells). Some PC/NPC races can also have resistances, such as Elves & Half-Elves (others, such as Dwarves and Halflings gain adjustments to saving throws, not magic resistance per se). These resistances and immunities are set within the creature and race databases (see the Race & Creature Database Help handout for details). Very rarely, magic items will confer magic resistance or impact the magic resistance of foes (such as the Robe of the Archmagi).
'
+'
If a magical attack is made against a creature or character with any form of magic resistance, a prompt will be made to make an appropriate check - this might be just for rersistance, or for resistance followed by a saving throw if failed as appropriate. For creatures/characters with magic resistance the Other Actions menu will show buttons to check magic resistance & saving throws, or just magic resistance. On the magic resistance menu, the appropriate type of resistance can be selected which will roll the correct dice. If failed and a saving throw is relevant, the saving throws menu will be shown. Otherwise, the magial effect applies.
'
+'
For each menu, the initial menu presented shows the magic resistance, saving throw and attribute tables from the Character Sheet (inluding the Monster tab magic resistance if relevant, but saving throws from the Character tab rather than the Monster Tab - monster saving throws should be copied to both). Each type of magic resistance, save or attribute check has a button to make the throw: the system (or the player - see below) will perform the roll and display the result with an indication of success or failure. The menu also shows buttons to add a situational adjustment (as per the AD&D 2e PHB) and for saves to modify the saving throw table, either automatically (taking into account race, class, level and magic items) or manually.
'
+'
You can change the way the roll is made using the [PC Rolls] and [You Roll] buttons at the bottom of the saving throw and attribute check menus. If [You Roll] is selected, the player will be presented with a Roll20 Roll Query to enter the result of the roll (though just submitting the dice specification displayed will roll the dice). The option chosen is remembered between game sessions within that campaign.
'
+'
The easiest way to set the correct saving throws for each type of save, based on class, level & race, is to use the CommandMaster API Character Sheet setup commands.
'
+'
When combined with the RPGM MagicMaster and RoundMaster APIs, magical effects of spells and magic items can automatically adjust saving throws depending on the properties of the spell or item, and these adjustments can be seen on the Other Menu > Saving Throws > Auto-check Saving Throws dialog.
'
- +'
New:Turning Undead
'
+ +'
Turning Undead
'
+'
Most classes of priest have a power to be able to turn undead. Also some other classes of character can turn undead, but perhaps not as well as clerics. The Class and Creature databases now have data tags that can specify the ability to turn or be turned, and any adjustment to the level or number of hit dice considered on the turning table.
'
+'
A new AttackMaster command exists to enact turning of undead as determined by the database entries and the turning table, with all possible outomes enated.
'
+'
Rogue Skill Checks
'
@@ -364,7 +408,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+'
Finally, as for saving throws and attribute checks, buttons are provided for [PC Rolls] and [You Roll] which have the same effects of allowing an automatic roll to take place or for the player to make each roll.
'
+'
Note: There are additional commands provided in the MagicMaster API to find traps and to loot containers or other creatures which will prompt "find traps", "remove traps" and "pick pockets" rolls as required. If using these functions on Drag & Drop creatures and containers, the appropriate processes and checks will occur (including asking the GM to make checks as appropriate if the GM has set that RPGMaster configuration) without having to use this dialog.
'
- +'
New:Advantage & Disadvantage
'
+ +'
Advantage & Disadvantage
'
+'
A D&D5e concept has been implemented as a useful addition to the AD&D2e game, for instance as a possible outcome for a fumble roll (disadvantage on the next attack(s). Having Advantage means two dice of the same type are rolled instead of one, and the more favouable value (either higher or lower) is taken. Having Disadvantage again means two dice instead of one, but the least favourable is then taken. Advantage or Disadvantage can be applied on one or more tokens selected by the DM, or some items (such as drinking an alcoholic potion) will apply Advantage or Disadvantage when used.
'
+'
'
@@ -390,20 +434,20 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+'
5. Armour Class, Saving Throws and Skill Checks
'
+'
--edit-armour [token_id]
'
+'--checkac [token_id] | [ SILENT ] | [SADJ / PADJ / BADJ]
'
- +'New:--resist[-no-save] [token_id] | [situation-mod]
'
- +'Updated:--save[-no-resist] [token_id] | [situation-mod]
'
+ +'--resist[-no-save] [token_id] | [situation-mod]
'
+ +'--save[-no-resist] [token_id] | [situation-mod]
'
+'--build-save [token_id] | save-type | save-value | [macro-name]
'
+'--attr-check [token_id] | [situation-mod] | [message] | [DCval]
'
- +'Updated:--set-mods [token_id / character_id]|cmd|name|spell|mod_spec|[(unused)]|[round_count]
'
- +'Updated: --set-mod-priority [token_id]|mod_type[|disp_menu]
'
+ +'--set-mods [token_id / character_id]|cmd|name|spell|mod_spec|[(unused)]|[round_count]
'
+ +'--set-mod-priority [token_id]|mod_type[|disp_menu]
'
+'--check-mods [token_id]|[QUIET/SILENT]
'
- +'--set-savemod [token_id]|cmd|name|spell|save_spec|[save_count]|[round_count]|[fail_cmd]
'
+ +'Updated: --set-savemod [token_id]|cmd|name|spell|save_spec|[save_count]|[round_count]|[fail_cmd]
'
+'--savemod-count [token_id]|[+/-]count
'
+'--theive [token_id]
'
+'--check-thieving [token_id]|[SILENT]
'
+'--set-thieving [token_id]
'
- +'New:--turn-undead token_id|number of undead|[creature name]|[roll]
'
- +'New:--advantage token_id|[duration]
'
+ +'--turn-undead token_id|number of undead|[creature name]|[roll]
'
+ +'--advantage token_id|[duration]'
+'
6. Other Commands
'
+'
--help
'
+'--config [PROF/ALL-WEAPS/WEAP-CLASS/ALL-ARMOUR/MASTER-RANGE/DM-TARGET] | [TRUE/FALSE]
'
@@ -553,7 +597,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+'This command presents a menu which can be used to perform attribute checks for the character. The menu displays the character\'s attribute values and the currently applicable modifiers for attribute checks. Each line has a button which will run the Attribute Check roll and display success or failure. As for the Saving Throw table, buttons also exist to set a situational modifier and to check the modifiers against current magic items in use and magic in effect.
'
+'A DC value parameter is provided to emulate attribute check modifiers for D&D 3e and later, though as these checks and modifiers work very differently this is not a direct equivalence. If a DC value is set as a parameter, 10 minus the DC value is added to all the modifiers.
'
- +'Updated: 5.7 Set AC / Thac0 / Damage / HP / Movement Modifiers
'
+ +'Updated: 5.7 Set AC / Thac0 / Damage / HP / Movement / Surprise Modifiers
'
+'--set-mods [TOKEN_ID / CHARACTER_ID]|cmd|name|spell/item|mod_spec|(unused)|[round_count]|[VERBOSE / SILENT]
'
+'Takes an optional token ID or character ID (defaults to the character ID represented by the selected token), a mandatory command, a mandatory mod name, a mandatory spell or item name, a mandaroty modifier specification, an optional number of rounds duration, and an optional verbose or silent specifier (defaults to silent).
'
+'Adds, modifies or deletes one or any number of named AC, thac0, damage, HP or movement modifiers that can apply to a limited number of rounds, or just continue indefinately until cancelled.
'
@@ -568,17 +612,19 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+'| hpt+:[=][+/-]# | Temporary HP, when ends substracted from ending current HP (even to below 0), + being beneficial and - being a penalty |
'
+'| hpp+:[=][+/-]# | Semi-permanent HP, when ends returns to minimum of ending current HP and starting maximum HP, + being beneficial and - being a penalty |
'
+'| hpm+:[=][+/-]# | Modify Maximum HP, when ends reverses the change by the mod amount |
'
- +'| move:[=][+/-/*//]# | New:Modify Movement of a character or creature, + being beneficial. Full maths allowed. |
'
- +'| adall:[=][+/-]# | New:Set Advantage or Disadvantage for all dice rolls, + for Advantage, - for Disadvantage |
'
- +'| admwa:[=][+/-]# | New:Set Advantage or Disadvantage for melee weapon attacks, + for Advantage, - for Disadvantage |
'
- +'| adrwa:[=][+/-]# | New:Set Advantage or Disadvantage for ranged weapon attacks, + for Advantage, - for Disadvantage |
'
- +'| addam:[=][+/-]# | New:Set Advantage or Disadvantage for damage rolls, + for Advantage, - for Disadvantage |
'
- +'| adsav:[=][+/-]# | New:Set Advantage or Disadvantage for saving throws, + for Advantage, - for Disadvantage |
'
- +'| adatr:[=][+/-]# | New:Set Advantage or Disadvantage for attribute checks, + for Advantage, - for Disadvantage |
'
- +'| adrog:[=][+/-]# | New:Set Advantage or Disadvantage for rouge skill checks, + for Advantage, - for Disadvantage |
'
+ +'| sme+:[+/-]# | New: Modify any roll for surprise when surprised, +ve being beneficial |
'
+ +'| syou+:[+/-]# | New: Modify any roll for surprise when attempting to surprise others, +ve being beneficial |
'
+ +'| move:[=][+/-/*//]# | Modify Movement of a character or creature, + being beneficial. Full maths allowed. |
'
+ +'| adall:[=][+/-]# | Set Advantage or Disadvantage for all dice rolls, + for Advantage, - for Disadvantage |
'
+ +'| admwa:[=][+/-]# | Set Advantage or Disadvantage for melee weapon attacks, + for Advantage, - for Disadvantage |
'
+ +'| adrwa:[=][+/-]# | Set Advantage or Disadvantage for ranged weapon attacks, + for Advantage, - for Disadvantage |
'
+ +'| addam:[=][+/-]# | Set Advantage or Disadvantage for damage rolls, + for Advantage, - for Disadvantage |
'
+ +'| adsav:[=][+/-]# | Set Advantage or Disadvantage for saving throws, + for Advantage, - for Disadvantage |
'
+ +'| adatr:[=][+/-]# | Set Advantage or Disadvantage for attribute checks, + for Advantage, - for Disadvantage |
'
+ +'| adrog:[=][+/-]# | Set Advantage or Disadvantage for rouge skill checks, + for Advantage, - for Disadvantage |
'
+''
- +'Updated: 5.8 Set Modifier Priorities
'
+ +'5.8 Set Modifier Priorities
'
+'--set-mod-priority [token_id]|mod_type[|CHECKAC / CHECKMODS]
'
+'Takes an optional token_id (defaults to selected token), a mandatory modifier type specifier, and an optional menu type to display.
'
+'Defines which type of modifier takes priority when automatically assessed. This is relevant when more than one item owned and carried by a character of the same type (e.g. two worn rings, or two miscellaneous items, or similar) have two conflicting effects. The API can be requested to prioritise and take into account mods that favour the best outcome for armour class (AC), Thac0 (thac0), Damage (dam), HP (hp), or Saves (saves). If no conflicts exist, all possible modification effects will be optimised.
'
@@ -720,7 +766,9 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
var classAllowedWeaps;
var classAllowedArmour;
var weapMultiAttks;
+ var singleItems;
var punchWrestle;
+ var table57;
var encumberDef;
/*
@@ -773,6 +821,9 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
MON_ATTACK: 'MON_ATTACK',
MON_INNATE: 'MON_INNATE',
MON_MELEE: 'MON_MELEE',
+ SET_SHOT: 'SET_SHOT',
+ CALLED_SHOT: 'CALLED_SHOT',
+ PARRY_MSG: 'PARRY_MSG',
BACKSTAB: 'BACKSTAB',
MELEE: 'MELEE',
MW_DMGSM: 'MW_DMGSM',
@@ -836,6 +887,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
CTRL_SELECTED: 'CTRL_SELECTED',
CTRL_CONFIRMED: 'CTRL_CONFIRMED',
ADV_DURATION: 'ADV_DURATION',
+ ATTK_MOD: 'ATTK_MOD',
+ ATTK_TABLE51: 'ATTK_TABLE51',
});
const reIgnore = /[\s\-\_\(\)]*/gi;
@@ -940,10 +993,12 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
const disTxt = ' Disadvantage';
const withAdv = ' with Advantage';
const withDis = ' with Disadvantage';
+ const table51AttkMods = [['Attack from height',1,false],['Defender invisible','auto',false],['Defender off balance',2,false],['Defender Sleeps/Held',20,false],['Defender stunned/prone',4,false],['Defender surprised','auto',false],['Range mods','auto',false],['Rear Attack',2,false],['Backstab','auto',false]];
let apiCommands = {};
let apiDBs = {magic:false,attk:false};
let msg_orig = {};
+ let attkMods = {};
let flags = {
feedbackName: 'AttackMaster',
@@ -957,50 +1012,6 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
notifyLibErr: true,
noWaitMsg: true,
};
-/*
- const attackMaster_tmp = (function() {
- const templates = {
- button: _.template('<%= text %>'),
- confirm_box: _.template(''
- + '
'
- + '<%= message %>'
- + '
'
- + '
'
- + ''
- + '| '
- + '<%= confirm_button %>'
- + ' | '
- + ''
- + '<%= reject_button %>'
- + ' | '
- + '
'
- + '
'
- + '
')
- };
-
- return {
- getTemplate: function(tmpArgs, type) {
- var retval;
-
- retval = _.find(templates, function(e,i) {
- if (type === i) {
- {return true;}
- }
- })(tmpArgs);
-
- return retval;
- },
-
- hasTemplate: function(type) {
- if (!type)
- {return false;}
- return !!_.find(_.keys(templates), function(elem) {
- {return (elem === type);}
- });
-
- }
- };
- }());
/**
* Init
@@ -1034,6 +1045,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
{state.attackMaster.thieveCrit = 0;}
if (_.isUndefined(state.attackMaster.attrRoll))
{state.attackMaster.attrRoll = false;}
+ if (_.isUndefined(state.attackMaster.parry))
+ {state.attackMaster.parry = true;}
if (_.isUndefined(state.attackMaster.touchAC))
{state.attackMaster.touchAC = true;}
if (!state.MagicMaster.playerConfig)
@@ -1069,7 +1082,9 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
classAllowedWeaps = RPGMap.classAllowedWeaps;
classAllowedArmour = RPGMap.classAllowedArmour;
weapMultiAttks = RPGMap.weapMultiAttks;
+ singleItems = RPGMap.singleItems;
punchWrestle = RPGMap.punchWrestle;
+ table57 = RPGMap.table57;
reClassSpecs = RPGMap.reClassSpecs;
reSpellSpecs = RPGMap.reSpellSpecs;
reWeapSpecs = RPGMap.reWeapSpecs;
@@ -1385,14 +1400,15 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
* Deal with legacy magical hit and damage bonuses
*/
- async function updateHitDmgBonus(args) {
+ async function updateHitDmgBonus(args=[]) {
+ const cmd = (args[0] || '').toLowerCase(),
+ senderId = findTheGM();
+
try {
if (!args) args = [];
- const cmd = (args[0] || '').toLowerCase(),
- senderId = findTheGM();
if (_.isUndefined(state.attackMaster.updateMsg)) state.attackMaster.updateMsg = true;
if (!!cmd.length) state.attackMaster.updateMsg = cmd !== 'hide';
@@ -1504,7 +1520,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
clearTimeout(ref);
tokens = tokens.filter((token) => (csVer(token.charObj) < 3.6)).map((t) => t.tokenObj);
- log('updateHitDmgBonus: done token list, length = '+tokens.length);
+// log('updateHitDmgBonus: done token list, length = '+tokens.length);
if (!tokens.length) {
if (state.attackMaster.updateMsg) sendFeedback('&{template:'+fields.messageTemplate+'}{{name=All Effects Converted}}{{desc=No tokens require converting}}');
@@ -2028,33 +2044,6 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
});
}
- /*
- * Determine if the character has an item in-hand
- */
-
- const itemInHand = function( charCS, itemTrueName ) {
- const inHandTable = getTableField( charCS, {}, fields.InHand_table, fields.InHand_trueName );
- return !_.isUndefined(inHandTable.tableFind( fields.InHand_trueName, itemTrueName ));
- }
-
- /*
- * Determine if the character has a shield in-hand
- */
-
- const shieldInHand = function( charCS, shieldTrueName ) {
- return itemInHand( charCS, shieldTrueName );
- }
-
- /*
- * Determine if the character is wearing a particular ring
- */
-
- const ringOnHand = function( charCS, ringTrueName ) {
- const leftRing = attrLookup( charCS, fields.Equip_leftTrueRing ) || '-',
- rightRing = attrLookup( charCS, fields.Equip_rightTrueRing ) || '-';
- return [leftRing,rightRing].includes(ringTrueName);
- }
-
/*
* Determine the number of attacks per round for a weapon,
* using the type, superType or class (melee/ranged) of
@@ -2163,7 +2152,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
try {
let itemTable = getTableGroupField( charCS, {}, fieldGroups.MI, 'name' ),
- weaponList = (type == 'ring') ? ['-,-'] : ['-,-','Touch,-2','Punch-Wrestle,-2.5'],
+ weaponList = (type == 'ring') ? ['-,-'] : [(handed > 1 ? 'Wrestle,-2.5' : 'Punch,-2.5'),'Touch,-2'],
spellFields = {mu:{table:fields.MUSpellNo_table,spells:fields.MUSpellNo_memable,spec:fields.MUSpellNo_specialist,misc:fields.MUSpellNo_misc},
pr:{table:fields.PRSpellNo_table,spells:fields.PRSpellNo_memable,spec:fields.PRSpellNo_wisdom,misc:fields.PRSpellNo_misc}},
itemList = [],
@@ -2272,7 +2261,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
*/
const proficient = function( charCS, wname, wt, wst ) {
-
+
wname = wname ? wname.dbName() : '-';
wt = wt ? wt.dbName() : '';
wst = wst ? wst.dbName() : '';
@@ -2709,9 +2698,10 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
innate = weapon[2].toLowerCase().includes('innate');
weapData = parseData( toHit, reWeapSpecs );
attk2H = noOfHands == 2 ? 1 : 0;
- weapType = weapData.type || wt || weapon[1];
+ weapType = wt || weapData.type || weapon[1];
weapSuperType = wst || weapData.superType || weapon[4];
proficiency = innate ? 0 : proficient( charCS, weaponTrueName, weapType, weapSuperType );
+// log('addWeapon: proficient called with trueName = '+weaponTrueName+', type = '+weapType+', super-type = '+weapSuperType+', result = '+proficiency);
if (!levelTest( charCS, weapData )) continue;
@@ -2883,27 +2873,6 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
return Quiver;
};
- /*
- * Find the named weapons in the character's Magic Item
- * bag and return their current index.
- */
-/*
- var findWeapon = function( charCS, ...weapons ) {
-
- var i = 0,
- MagicItems = getTableGroupField( charCS, {}, fieldGroups.MI, 'name' ),
- itemName,
- index = [];
-
- index.length = weapons.length;
- index.fill(NaN);
-
- while (!_.isUndefined(itemName = tableGroupLookup( MagicItems, 'name', i, false ))) {
- index[weapons.indexOf(itemName)] = i;
- i++;
- }
- return index;
- }
/*
* Function to check the number of rows in the weapons InHand table
@@ -2972,157 +2941,13 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
* of each type
*/
- const scanForModifiers = function( tokenID, charCS, scanType='', dmgType='nadj', priority='ac' ) {
+ const scanForModifiers = function( tokenID, charCS, scanType='', dmgType='nadj', priority='ac' ) { //getTableGroupField
let armourMsg = [],
noDex = false,
- acValues = {armour:{name:'Clothes',magic:false,specs:['','Clothes','armour','0H','cloth'],data:{ac:10,adj:0,dexBonus:1,madj:0,thac0adj:0,hpadj:0,rules:'',ppa:0,ola:0,rta:0,msa:0,hsa:0,dna:0,cwa:0,rla:0,iba:0,adall:0,admw:0,adrw:0,addam:0,adsave:0,adattr:0,adrogue:0},savAvg:0}};
-
- const dexBonus = parseInt(attrLookup( charCS, fields.Dex_acBonus ) || 0);
-
- const calcDiff = function( priority, curBest, thisData, thisSaves, dmgType ) {
-
- const ac = parseInt(thisData.ac || 10),
- adj = (parseInt(thisData.adj || 0) + (dmgType !== 'nadj' ? parseInt(thisData[dmgType] || 0) : 0)),
- dexAdj = Math.floor(dexBonus * parseFloat(Math.max(thisData.dexBonus,0))),
- curDexAdj = Math.floor(dexBonus * parseFloat(Math.max(curBest.data.dexBonus,0))), // acValues.armour.data.dexBonus
- acDiff = ((curBest.data.ac || 0) - (curBest.data.adj || 0) - curDexAdj) - (ac - adj - dexAdj);
- let diff;
-
- switch (priority) {
- case 'thac0':
- diff = acDiff + ((parseInt(thisData.thac0adj) || 0) - (curBest.data.thac0adj || 0));
- break;
- case 'hp':
- diff = acDiff + (((parseInt(thisData.hpadj) || 0) + (parseInt(thisData.hptemp) || 0) + (parseInt(thisData.hpmax) || 0)) - ((curBest.data.hpadj || 0) + (curBest.data.hptemp || 0)));
- break;
- case 'dmg':
- diff = acDiff + ((parseInt(thisData.dmgadj) || 0) - (curBest.data.dmgadj || 0));
- break;
- case 'saves':
- diff = acDiff + ((_.reduce(thisSaves, (t,v) => t+(v || 0), 0) / _.size(thisSaves)) - curBest.savAvg);
- break;
- default:
- diff = acDiff;
- break;
- };
- return diff;
- };
-
- const assessItem = function( itemName, itemTrueName, itemCharge, itemSpecs, itemData ) {
-
- let ac, acData, acRules, isMod, data,
- itemType, itemClass, itemHands, itemSuperType, itemAC, itemAdj, itemCursed, itemRules,
- diff, classCursed, acSavData,
- totalFlag = false;
-
- for (let i=0; i r.replace(/\-/g,(match,i,s)=>(i>0?'':match)));
- itemType = itemSpecs[i][1].dbName();
- itemClass = itemSpecs[i][2].dbName();
- itemHands = itemSpecs[i][3].toUpperCase();
- itemSuperType = itemSpecs[i][4].dbName();
- isMod = itemClass.includes('modifiers');
-
- if ((isMod && acData.ac.length) || itemClass.includes('armor') || itemClass.includes('armour')) itemClass = 'armour';
- if (itemClass.includes('shield')) itemClass = 'shield';
- if (itemClass.includes('helm')) itemClass = 'helm';
-
- if (!isMod && !state.attackMaster.weapRules.allowArmour && !classAllowedItem(charCS, itemName, itemType, itemSuperType, 'ac')) {
- armourMsg.push(itemName+' is not of a usable type');
- } else if (itemClass === 'shield' && itemHands != '0H' && !shieldInHand(charCS,itemTrueName)) {
- armourMsg.push(itemName+' is not currently in hand');
- } else if (itemClass.includes('ring') && itemHands != '0H' && !ringOnHand(charCS,itemTrueName)) {
- armourMsg.push(itemName+' is not currently worn');
- } else if (acRules.includes('+inhand') && itemHands != '0H' && !itemInHand(charCS,itemTrueName)) {
- armourMsg.push(itemName+' is not currently in hand');
- } else {
- if (itemSpecs[i][2].includes('totalac')) {
- itemClass = 'armour';
- if (totalFlag) {
- diff = calcDiff( priority, acValues.armour, acData, acSavData, dmgType );
- } else {
- _.each( acValues, e => armourMsg.push(e.name+' is overridden by another item'));
- acValues = {};
- diff = 1;
- totalFlag = true;
- }
- if (diff > 0) noDex = (parseInt(acData.dexBonus) <= 0);
- } else if (!totalFlag) {
- protectionMI: {
- if (acRules.includes('-magic') && acValues.armour.magic) {
- armourMsg.push(itemName+' does not add to magical armour');
- break protectionMI;
- }
- if (acRules.includes('-'+acValues.armour.specs[4].dbName()) || (acRules.includes('-acall') && !acRules.includes('+'+acValues.armour.specs[4].dbName()))) {
- armourMsg.push(itemName+' will not combine with '+acValues.armour.name);
- break protectionMI;
- }
- if (acRules.includes('-shield') && !!acValues.shield) {
- armourMsg.push(itemName+' does not combine with shields of any type');
- break protectionMI;
- }
-
- if (_.isUndefined(acValues[itemClass])) {
- diff = 1;
- } else {
- diff = calcDiff( priority, acValues[itemClass], acData, acSavData, dmgType );
- }
- }
- } else {
- armourMsg.push(itemName+' is overridden by another item');
- diff = undefined;
- }
- if (!_.isUndefined(diff)) {
- itemCursed = (itemCharge || '').includes('cursed');
- classCursed = acValues[itemClass] && (acValues[itemClass].charge || '').includes('cursed');
- if (diff < 0 && (!itemCursed || classCursed)) {
- armourMsg.push(itemName+' is not the best '+itemClass+' available');
- } else if (diff == 0 && (!itemCursed || classCursed)) {
- armourMsg.push(itemName+' is no better than other '+itemClass+'s');
- } else if (acValues[itemClass] && !classCursed && itemCursed && diff <= 0) {
- armourMsg.push('Oh! You do not seem to be wearing '+acValues[itemClass].name+'...');
- }
- if ((!classCursed && itemCursed) || diff > 0) {
- if (diff > 0 && acValues[itemClass] && acValues[itemClass].name) {
- armourMsg.push(acValues[itemClass].name+' is not the best '+itemClass+' available');
- }
-
- acValues[itemClass] = {name:itemName, trueName:itemTrueName, row:i, specs:itemSpecs[i], charge:(itemCharge || ''), data:acData, savAvg:(_.reduce(acSavData, (t,v) => t+(v || 0), 0) / _.size(acSavData))};
-
- if (itemClass === 'armour') {
- acValues.armour.magic = parseInt(acData.adj||0)!==0;
- }
- acValues = _.omit( acValues, function(item,iClass) {
- itemRules = item.data.rules.toLowerCase().replace(/[_\s]/g, '').split('|').map(r => r.replace(/\-/g,(match,i,s)=>(i>0?'':match)));
-
- if (itemClass === 'armour' && acValues.armour.magic && itemRules.includes('-magic')) {
- armourMsg.push(item.name+' cannot be used alongside magical armour');
- return true;
- }
- if (itemClass === 'armour' && (itemRules.includes('-'+itemSuperType) || (itemRules.includes('-acall') && !itemRules.includes('+'+itemSuperType)))) {
- armourMsg.push(item.name+' cannot be used alongside '+acValues.armour.specs[4]);
- return true;
- }
- if (itemRules.includes('-'+itemClass)) {
- armourMsg.push(item.name+' cannot be used alongside '+itemName);
- return true;
- }
- return false;
- });
- }
- }
- }
- }
- return;
- };
-
- let i = -1,
- itemName, itemTrueName,
- itemDef, itemSpecs, itemData, itemCharge;
+ i = -1,
+ acValues = {armour:{name:'Clothes',magic:false,specs:['','Clothes','armour','0H','cloth'],data:{ac:10,adj:0,dexBonus:1,madj:0,thac0adj:0,hpadj:0,rules:'',ppa:0,ola:0,rta:0,msa:0,hsa:0,dna:0,cwa:0,rla:0,iba:0,adall:0,admw:0,adrw:0,addam:0,adsave:0,adattr:0,adrogue:0,adsurprise:0},savAvg:0,worn:false}},
+ itemName, itemTrueName, itemDef, itemSpecs, itemData, itemCharge;
const monsterAC = attrLookup( charCS, fields.MonsterAC, {def:false} );
@@ -3133,29 +2958,11 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
}
if ((attrLookup( charCS, fields.Gender ) || '').toLowerCase() === 'container') return {acValues: acValues, msgs: armourMsg, dexFlag: !noDex};
- let Items = getTableGroupField( charCS, {}, fieldGroups.MI, 'trueName' );
- Items = getTableGroupField( charCS, Items, fieldGroups.MI, 'name' );
- Items = getTableGroupField( charCS, Items, fieldGroups.MI, 'type' );
- Items = getTableGroupField( charCS, Items, fieldGroups.MI, 'trueType' );
- while (!_.isUndefined(itemName = tableGroupLookup( Items, 'name', ++i, false ))) {
- itemTrueName = tableGroupLookup( Items, 'trueName', i ) || itemName;
- if (itemName.length && itemName != '-') {
- itemCharge = (tableGroupLookup( Items, 'trueType', i ) || tableGroupLookup( Items, 'type', i ) || '').toLowerCase();
- itemDef = abilityLookup( fields.MagicItemDB, itemTrueName, charCS, true );
- if (itemDef.obj) {
- let miIndex, miRowID, miTable;
- [miIndex,miTable,miRowID] = tableGroupIndex( Items, i );
- itemSpecs = itemDef.specs(/}}\s*Specs\s*=(.*?(?:armou?r|shield|helm|barding|protection|modifiers).*?){{/im) || [];
- itemData = resolveData( itemTrueName, fields.MagicItemDB, reItemData, charCS, reACSpecs, {row:miIndex, rowID:miRowID} ).raw;
- assessItem( itemName, itemTrueName, itemCharge, itemSpecs, itemData );
- }
- }
- }
let Mods = getTable( charCS, fieldGroups.MODS ),
curRound, toRound, diff, modTokenID, modToken,
hpBar, currentHP, maxHP, thac0Bar, currentThac0,
hpBase, hpMax, modType;
-
+
for (let modRow=Mods.table[1]; modRow (''+(toggle ? '\u2705' : '\u2B1C')+'');
+
+ let content = ((attkMods[tokenID].mods.length && (ticks || attkMods[tokenID].mods.some(m => m[2]))) ? ('**'+(ticks ? 'Possible ' : '')+'1-off Attack Modifiers**\n') : '');
+ _.each( attkMods[tokenID].mods, (m,i) => {content += ((ticks || m[2]) ? ('| '+m[0]+' | '+(ticks ? (String(m[1]).dbName()!=='dexac' && isNaN(m[1])?'auto':(tickBox(tokenID,i,m[0],m[2]))) : (String(m[1]).dbName()!=='dexac' ? m[1] : 'No Target Dex Bonus'))+' |
' ) : '')});
+ if (content.length) content += '
';
+ if (ticks && !attkMods[tokenID].table51) content += '_Show PHB Table51 mods_(!attk --button '+BT.ATTK_TABLE51+'|'+args.join('|')+')';
+ if (content.length) content = '{{desc5='+content+'}}';
+ return content;
+ };
+
+ /*
+ * Get Punch/Wrestle armour penalty
+ */
+
+ const pwArmourPenalty = function( charCS ) {
+ const armourName = attrLookup( charCS, fields.Armor_trueName ),
+ armourObj = abilityLookup( fields.MagicItemDB, armourName, charCS );
+// log('pwArmourPenalty: armourName = '+armourName+', armourObj '+(!armourObj || !armourObj.obj[1] ? 'undefined' : 'exists'));
+ if (!armourObj || !armourObj.obj) return '0';
+ const armourSpecs = armourObj.specs();
+// log('pwArmourPenalty: specs = '+armourSpecs+', name = '+armourSpecs[0][1]+', group = '+armourSpecs[0][4]);
+ if (!armourSpecs || !armourSpecs.length) return '0';
+ let acClass = armourSpecs[0][1].dbName(),
+ penalty = table57.find( p => p.armour.some( a => a.includes(acClass) ));
+ if (_.isUndefined(penalty)) {
+ acClass = armourSpecs[0][4].dbName(),
+ penalty = table57.find( p => p.armour.some( a => a.includes(acClass) ));
+ };
+// log('pwArmourPenalty: acClass = '+acClass+', penalty = '+penalty);
+ return (_.isUndefined(penalty) ? '0' : penalty.mod);
+ };
+
+
+ /*
+ * Extract attackData from Attack macro and parse it
+ */
+
+ const parseAttkData = attkMacro => parseData( attkMacro.match(/}}\s*AttackData\s*=\s*\[.+?\]\s*{{/im),
+ {
+ targetDmg: {field:'targetdmg',def:'0',re:/[\[,\s]targetdmg:([10])[,\s\]]/i},
+ attkDice: {field:'attkdice',def:'',re:/[\[,\s]attkdice:(\d+)\|(\d+)[,\s\]]/i},
+ dmgSMdice: {field:'dmgSMdice',def:'',re:/[\[,\s]dmgsmdice:(\d+)\|(\d+)[,\s\]]/i},
+ dmgLdice: {field:'dmgLdice',def:'',re:/[\[,\s]dmgldice:(\d+)\|(\d+)[,\s\]]/i},
+ } );
+
/*
* Create a message explaining attack & damage advantage or
* disadvantage dice rolls achieved.
*/
const advantageDiceRolls = function( attkMacro, attkAdv, dmgAdv, dmgGap=0 ) {
- const reAttkData = {
- attkDice: {field:'attkdice',def:'',re:/[\[,\s]attkdice:(\d+)\|(\d+)[,\s\]]/i},
- dmgSMdice: {field:'dmgSMdice',def:'',re:/[\[,\s]dmgsmdice:(\d+)\|(\d+)[,\s\]]/i},
- dmgLdice: {field:'dmgLdice',def:'',re:/[\[,\s]dmgldice:(\d+)\|(\d+)[,\s\]]/i},
- };
-
- const attkData = attkMacro.match(/}}\s*AttackData\s*=\s*\[.+?\]\s*{{/im);
- if (!attkData || !attkData.length) return '';
- const attkParsed = parseData( attkData, reAttkData );
+ const attkParsed = parseAttkData( attkMacro );
let advArray = [];
if (attkAdv && attkParsed.attkdice && attkParsed.attkdice.length) advArray.push('**'+(attkAdv > 0 ? 'Advantage' : 'Disadvantage')+'** attack dice were $[['+attkParsed.attkdice[0]+']] & $[['+attkParsed.attkdice[1]+']]');
if (dmgAdv && attkParsed.dmgSMdice && attkParsed.dmgSMdice.length) advArray.push('**'+(dmgAdv > 0 ? 'Advantage' : 'Disadvantage')+'** S/M damage dice were $[['+(parseInt(attkParsed.dmgSMdice[0])+dmgGap)+']] & $[['+(parseInt(attkParsed.dmgSMdice[1])+dmgGap)+']]');
@@ -3418,13 +3292,16 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
tokenAC = (tokenACname ? ('[[0+@{Target|Select Target|'+tokenACname+'}&{noerror}]]') : ''),
tokenHPname = getTokenValue( curToken, fields.Token_HP, fields.HP, null, fields.Thac0_base ).barName,
tokenHP = (tokenHPname ? ('[[0+@{Target|Select Target|'+tokenHPname+'}&{noerror}]]') : ''),
- attkAdvantage = (parseInt(attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////'.split('/')[0]) || 0),
- dmgAdvantage = (parseInt(attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////'.split('/')[2]) || 0),
+ shotData = attrLookup( charCS, [fields.ShotCalled[0]+tokenID,fields.ShotCalled[1],fields.ShotCalled[2]] ).split(':'),
+ shot = shotData[0] || '',
+ shotPen = ((_.isUndefined(shotData[2]) || isNaN(parseInt(shotData[2])) || !shotData[2].length) ? -4 : String(shotData[2])),
+ attkAdvantage = (parseInt(attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////'.split('/')[0]) || 0),
+ dmgAdvantage = (parseInt(attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////'.split('/')[2]) || 0),
attkImpact = attkAdvantage > 0 ? 'kh1' : (attkAdvantage < 0 ? 'kl1' : ''),
dmgImpact = dmgAdvantage > 0 ? 'kh1' : (dmgAdvantage < 0 ? 'kl1' : ''),
attkADtxt = attkAdvantage > 0 ? withAdv : (attkAdvantage < 0 ? withDis : ''),
dmgADtxt = dmgAdvantage > 0 ? withAdv : (dmgAdvantage < 0 ? withDis : '');
-
+
const attkMonObj = {
towho: sendToWho(charCS,senderId,false),
towhopublic: sendToWho(charCS,senderId,true),
@@ -3437,6 +3314,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
targetid: '@{Target|Select Target|token_id}',
monstercrithit: String(parseInt(attrLookup( charCS, fields.MonsterCritHit )) || 20),
monstercritmiss:String(parseInt(attrLookup( charCS, fields.MonsterCritMiss )) || 1),
+ shotpenalty: String((!shot || !shot.length || !shotPen.length) ? 0 : (shotPen || 0)),
+ attkmod: String((attkMods[tokenID].mods.filter(m => !isNaN(parseInt(m[1]))).reduce( (t,m) => m[2] ? (t + parseInt(m[1])) : t, 0 )) || 0) + (attkMods[tokenID].mods.some(m => m[2] && String(m[1]).dbName()==='dexac') ? ('+[[0-(@{Target|Select Target|'+fields.Dex_acBonus[0]+'}&{noerror})]]') : ''),
thac0: thac0,
magicattkadj: String((parseInt(attrLookup( charCS, fields.Magical_hitAdj )) || 0)
+ thac0 - (parseInt(getTokenValue(curToken,fields.Token_Thac0,fields.Thac0_base,fields.MonsterThac0,fields.Thac0_base).val) || 20)),
@@ -3444,6 +3323,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
acvsnomodstxt: 'No Mods',
targetacfield: tokenAC,
targetac: tokenAC,
+ targetacextra: '@{target|Select Target|'+fields.MonsterAC[0]+'}&{noerror}@{target|Select Target|'+fields.ACnotes[0]+'}',
acfield: tokenACname,
magicdmgadj: String((parseInt(attrLookup( charCS, [fields.Magical_dmgAdj[0]+tokenID,fields.Magical_dmgAdj[1],undefined],{def:false}) || attrLookup( charCS, fields.Magical_dmgAdj )) || 0)
+ (parseInt(attrLookup( charCS, fields.Legacy_dmgAdj ))) || 0),
@@ -3524,6 +3404,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
monsterdmgmacro2:(charName+'|'+dmgSMmacroName),
monsterdmgmacro3:(charName+'|'+dmgSMmacroName),
advdice: advExplain,
+ modstable: (attkMods[tokenID].mods.length ? addAttkModBoxes( args, tokenID ) : ''),
};
return attkMacro.replace( /\^\^([-\+\w]+?)\^\^/img, (m, t) => attkMonVars[t.toLowerCase()] || '' ).replace( /,/gi , ',' );
};
@@ -3555,6 +3436,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (!(errFlag = (!attkMacroDef.obj || !attkMacroDef.obj[1]))) {
attkMacro = attkMacroDef.obj[1].body;
}
+ if (abilityType == Attk.TARGET && parseAttkData( attkMacro ).targetdmg == '1') abilityType = Attk.TO_HIT;
if (abilityType != Attk.TARGET && !errFlag) {
dmgSMmacroDef = abilityLookup( fields.AttacksDB, 'Mon-DmgSM'+qualifier, charCS, silent );
if (!dmgSMmacroDef.obj) {
@@ -3587,6 +3469,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
monAttk = (i==1 ? attk1 : (i==2 ? attk2 : attk3));
if (monAttk) {
+ if (shot) monAttk += ' for a Called Shot of '+shot;
monDmg = (i==1 ? monDmg1 : (i==2 ? monDmg2 : monDmg3));
dmgType = (i==1 ? monsterDmg1 : (i==2 ? monsterDmg2 : monsterDmg3 ));
attkPlus = parseInt(dmgType[4]) || 0;
@@ -3639,7 +3522,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
miName = tableInfo.MELEE.tableLookup( fields.MW_miName, mwIndex );
let hitCharges = tableInfo.MELEE.tableLookup( fields.MW_charges, mwIndex ),
- weapDmgCmd = parseStr(tableInfo.DMG.tableLookup( fields.Dmg_cmd, mwIndex ) || '');
+ weapDmgCmd = parseStr(tableInfo.DMG.tableLookup( fields.Dmg_cmd, mwIndex ) || ''),
+ InHandTable = getTableField( charCS, {}, fields.InHand_table, fields.InHand_index );
const tokenID = args[1],
attkType = args[2],
@@ -3652,8 +3536,11 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
thac0 = parseInt(handleGetBaseThac0(charCS)) || 20,
mwNumber = mwIndex + (fields.MW_table[1]==0 ? 1 : 2),
weaponName= tableInfo.MELEE.tableLookup( fields.MW_name, mwIndex ),
- miRowref = attrLookup( charCS, fields.InHand_index, {tableDef:fields.InHand_table, row:tableInfo.MELEE.tableLookup( fields.MW_hand, mwIndex )} ),
+ miRowref = InHandTable.tableLookup( fields.InHand_index, tableInfo.MELEE.tableLookup( fields.MW_hand, mwIndex )),
dancing = tableInfo.MELEE.tableLookup( fields.MW_dancing, mwIndex ),
+ shotData = attrLookup( charCS, [fields.ShotCalled[0]+tokenID,fields.ShotCalled[1],fields.ShotCalled[2]] ).split(':'),
+ shot = (shotData[0] || ''),
+ shotPen = ((_.isUndefined(shotData[2]) || isNaN(parseInt(shotData[2])) || !shotData[2].length) ? -4 : String(shotData[2])),
mwType = tableInfo.MELEE.tableLookup( fields.MW_type, mwIndex ),
mwSuperType = tableInfo.MELEE.tableLookup( fields.MW_superType, mwIndex ),
slashWeap = parseInt(tableInfo.MELEE.tableLookup( fields.MW_slash, mwIndex )),
@@ -3681,7 +3568,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
arVal = '[[0+(@{Target|Select Target|'+fields.BaseAC[0]+(fields.BaseAC[1] === 'max' ? '|max' : '')+'}&{noerror})]]',
tokenACname= getTokenValue( curToken, fields.Token_AC, fields.AC, fields.MonsterAC, fields.Thac0_base ).barName,
tokenHPname= getTokenValue( curToken, fields.Token_HP, fields.HP, null, fields.Thac0_base ).barName;
-
+
const attkMWobj = {
towho: sendToWho(charCS,senderId,false),
towhopublic: sendToWho(charCS,senderId,true),
@@ -3699,11 +3586,14 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
profpenalty: String(isNaN(proficiency) ? 0 : Math.min(proficiency,0)),
specprof: (proficiency == 2 ? '1' : '0'),
masterprof: (proficiency > 2 ? '1' : '0'),
+ wrestleacpen: pwArmourPenalty( charCS ),
racebonus: String(raceMods( charCS, mwType, mwSuperType ) || 0),
magicattkadj: String(thac0Bar.name && thac0Bar.barName.startsWith('bar') ? (thac0 - thac0Bar.val) : ((attrLookup( charCS, fields.Magical_hitAdj ) || 0) + (attrLookup( charCS, [fields.Magical_hitAdj[0]
+ tokenID, fields.Magical_hitAdj[1], fields.Magical_hitAdj[2]] ) || 0) + (attrLookup( charCS, fields.Legacy_hitAdj ) || 0))),
twoweappenalty: String((ranger || primeWeapon < 1) ? 0 : (-1*(((mwIndex*2)+(fields.MW_table[1]==0?1:3)) == primeWeapon ? Math.floor(twPen) : Math.floor((10*twPen)%10)))),
- encumbrance: String(encumberDef.attack[parseInt(attrLookup(charCS,fields.Encumbrance)) || 0] || 0),
+ shotpenalty: String((!shot || !shot.length || !shotPen.length) ? 0 : (shotPen || 0)),
+ attkmod: String((attkMods[tokenID].mods.filter(m => !isNaN(parseInt(m[1]))).reduce( (t,m) => m[2] ? (t + parseInt(evalAttr(m[1]))) : t, 0 )) || 0) + (attkMods[tokenID].mods.some(m => m[2] && String(m[1]).dbName()==='dexac') ? ('+[[0-(@{Target|Select Target|'+fields.Dex_acBonus[0]+'}&{noerror})]]') : ''),
+ encumbrance: state.MagicMaster.encumbrance ? String(encumberDef.attack[parseInt(attrLookup(charCS,fields.Encumbrance)) || 0] || 0) : '0',
weapdmgadj: (tableInfo.DMG.tableLookup( fields.Dmg_adj, mwIndex ) || '0'),
weapstyledmgadj:tableInfo.DMG.tableLookup( fields.Dmg_styleAdj, mwIndex ),
magicdmgadj: String((parseInt(attrLookup(charCS,[fields.Magical_dmgAdj[0]+tokenID,fields.Magical_dmgAdj[1],undefined],{def:false}) || attrLookup( charCS, fields.Magical_dmgAdj )) || 0)
@@ -3712,7 +3602,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
backstab: (backstab ? '1' : '0'),
roguelevel: String(parseInt(attrLookup( charCS, fields.Rogue_level ) || 0)
+ (((attrLookup( charCS, fields.Wizard_class ) || '').toUpperCase() == 'BARD') ? parseInt(attrLookup( charCS, fields.Wizard_level ) || 0) : 0)),
- weapon: tableInfo.MELEE.tableLookup( fields.MW_name, mwIndex ),
+ weapon: tableInfo.MELEE.tableLookup( fields.MW_name, mwIndex ) + ((!shot || !shot.length) ? '' : ' for a Called Shot of '+shot),
thac0: String(parseInt(handleGetBaseThac0( charCS, tableInfo.MELEE.tableLookup( fields.MW_magicThac0, mwIndex ) || thac0))),
slashweap: slashWeap,
pierceweap: pierceWeap,
@@ -3738,6 +3628,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
targetacfield: (tokenACname ? ('[[0+(@{Target|Select Target|'+tokenACname+'}&{noerror})]]') : ''),
targetac: (tokenACname ? ('[[0+(@{Target|Select Target|'+tokenACname+'}&{noerror})]]') : ''),
targettouchac: '[[0+(@{Target|Select Target|'+fields.Armour_touch[0]+(fields.Armour_touch[1] === 'max'?'|max':'')+'}&{noerror})]]',
+ targetacextra: '@{target|Select Target|'+fields.MonsterAC[0]+'}&{noerror}@{target|Select Target|'+fields.ACnotes[0]+'}',
dmgname: (tableInfo.DMG.tableLookup( fields.Dmg_name, mwIndex ) || miName),
weapstyledmgsm: tableInfo.DMG.tableLookup( fields.Dmg_styleSM, mwIndex ),
weapstrdmg: (tableInfo.DMG.tableLookup( fields.Dmg_strBonus, mwIndex ) || 1),
@@ -3746,12 +3637,13 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
targethp: (tokenHPname ? ('[[0+(@{Target|Select Target|'+tokenHPname+'}&{noerror})]]') : ''),
targetmaxhp: (tokenHPname ? ('[[0+(@{Target|Select Target|'+tokenHPname+'|max}&{noerror})]]') : ''),
hpfield: tokenHPname,
- mwsmdmgmacro: (charName+'|Do-not-use-DmgSM-MW'+mwNumber),
- mwlhdmgmacro: (charName+'|Do-not-use-DmgL-MW'+mwNumber),
+ mwsmdmgmacro: (charCS.id+'|Do-not-use-DmgSM-MW'+mwNumber),
+ mwlhdmgmacro: (charCS.id+'|Do-not-use-DmgL-MW'+mwNumber),
};
let arTable = '',
- arAdjust= (tableInfo.MELEE.tableLookup( fields.MW_aradj, mwIndex ) || '0|0|0|0|0|0|0|0|0').split('|');
+ arAdjust= (tableInfo.MELEE.tableLookup( fields.MW_aradj, mwIndex ) || '0|0|0|0|0|0|0|0|0').split('|'),
+ abilityType = attkType.toUpperCase();
for (let i=2; i'+i+'';
arTable += '
';
@@ -3761,15 +3653,14 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
for (let j=arAdjust.length; j<=10; j++) arAdjust.unshift(arAdjust[0]);
arAdjust = arAdjust.join('|');
- const abilityType = attkType.toUpperCase(),
- abilityRoot = 'MW-' + (abilityType == Attk.TARGET ? 'Targeted-Attk' : 'ToHit'),
+ const abilityRoot = 'MW-' + (abilityType == Attk.TARGET ? 'Targeted-Attk' : 'ToHit'),
arSlash = slashWeap ? ('[[('+arVal+'[AR Adjust='+arAdjust+'])]]') : '',
arPierce = pierceWeap ? ('[[('+arVal+'[AR Adjust='+arAdjust+'])]]') : '',
arBludgeon = bludgeonWeap ? ('[[('+arVal+'[AR Adjust='+arAdjust+'])]]') : '';
const parseMWattkMacro = function( args, charCS, attkType, macro ) {
- const attkAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////').split('/')[0]) || 0,
- dmgAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////').split('/')[2]) || 0,
+ const attkAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////').split('/')[0]) || 0,
+ dmgAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////').split('/')[2]) || 0,
attkRoll = getToHitRoll( macro ),
minMaxRoll = maxDiceRoll(attkRoll),
critHit = String(Math.min((tableInfo.MELEE.tableLookup( fields.MW_critHit, mwIndex ) || minMaxRoll.max),(tableInfo.MELEE.tableLookup( fields.MW_styleCH, mwIndex )||minMaxRoll.max))),
@@ -3780,7 +3671,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
dmgADtxt = dmgAdvantage > 0 ? withAdv : (dmgAdvantage < 0 ? withDis : ''),
targetADtxt = (attkADtxt || dmgADtxt ? '\n' : '')+(attkADtxt ? 'Attack '+attkADtxt : '') + (attkADtxt && dmgADtxt ? ' and ' : '') + (dmgADtxt ? 'Damage'+dmgADtxt : ''),
dmgPosAdj = (abilityType == Attk.TARGET ? ((slashWeap?1:0)+(pierceWeap?1:0)+(bludgeonWeap?1:0)) : 0),
- advExplain = (!attkImpact && !dmgImpact) ? '' : advantageDiceRolls( macro, attkAdvantage, dmgAdvantage, dmgPosAdj );
+ advExplain = (!attkImpact && !dmgImpact) ? '' : advantageDiceRolls( macro, attkAdvantage, dmgAdvantage, dmgPosAdj ),
+ modsExplain = addAttkModBoxes( args, tokenID );
let toHitRoll = attkRoll,
dmgSMroll = dmgSM,
@@ -3819,6 +3711,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
case 'weapdmgsm': return dmgSMroll;
case 'weapdmgl': return dmgLroll;
case 'advdice': return advExplain;
+ case 'modstable': return modsExplain;
default: return attkMWobj[t] || '';
};
};
@@ -3855,6 +3748,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
qualifier = '';
}
if (!(errFlag = !attkMacroDef.obj)) {
+ if (abilityType == Attk.TARGET && parseAttkData( attkMacroDef.obj[1].body ).targetdmg == '1') abilityType = Attk.TO_HIT;
if (abilityType != Attk.TARGET) {
let dmgMacroDef = abilityLookup( fields.AttacksDB, (backstab ? ('MW-Backstab-DmgSM'+qualifier) : ('MW-DmgSM'+qualifier)), charCS, silent );
if (!dmgMacroDef.obj) dmgMacroDef = abilityLookup( fields.AttacksDB, (backstab ? 'MW-Backstab-DmgSM' : 'MW-DmgSM'), charCS );
@@ -3910,16 +3804,17 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
* Build ranged weapon attack macro, one for each
* of the 6 possible ranges: Near, PB, S, M, L, Far
*/
-
+
const buildRWattkMacros = function( args, senderId, charCS, tableInfo, ranges ) {
let dmgMsg = parseStr(args[5] || attrLookup( charCS, fields.Dmg_specials ) || ''),
attkMsg = parseStr(args[5] || attrLookup( charCS, fields.Attk_specials ) || ''),
+ abilityType = args[2].toUpperCase(),
+ InHandTable = getTableField( charCS, {}, fields.InHand_table, fields.InHand_index ),
errFlag = false;
const tokenID = args[1],
attkType = args[2],
- abilityType = attkType.toUpperCase(),
abilityRoot = 'RW-' + (abilityType == Attk.TARGET ? 'Targeted-Attk' : 'ToHit'),
rwIndex = parseInt(args[3]),
ammoIndex = parseInt(args[4]),
@@ -3931,8 +3826,11 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
rwNumber = rwIndex + (fields.RW_table[1]==0 ? 1 : 2),
weaponName = tableInfo.RANGED.tableLookup( fields.RW_name, rwIndex ),
miName = tableInfo.RANGED.tableLookup( fields.RW_miName, rwIndex ),
- miRowref = attrLookup( charCS, fields.InHand_index, {tableDef:fields.InHand_table, row:tableInfo.RANGED.tableLookup( fields.RW_hand, rwIndex )} ),
+ miRowref = InHandTable.tableLookup( fields.InHand_index, tableInfo.RANGED.tableLookup( fields.RW_hand, rwIndex )),
dancing = tableInfo.RANGED.tableLookup( fields.RW_dancing, rwIndex ),
+ shotData = attrLookup( charCS, [fields.ShotCalled[0]+tokenID,fields.ShotCalled[1],fields.ShotCalled[2]] ).split(':'),
+ shot = (shotData[0] || ''),
+ shotPen = ((_.isUndefined(shotData[2]) || isNaN(parseInt(shotData[2])) || !shotData[2].length) ? -4 : String(shotData[2])),
rwType = tableInfo.RANGED.tableLookup( fields.RW_type, rwIndex ),
rwSuperType = tableInfo.RANGED.tableLookup( fields.RW_superType, rwIndex ),
touchWeap = tableInfo.RANGED.tableLookup( fields.RW_touch, rwIndex ) === '1',
@@ -3974,13 +3872,13 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
tokenHPname = getTokenValue( curToken, fields.Token_HP, fields.HP, null, fields.Thac0_base ).barName,
tokenHP = (tokenHPname ? ('[[0+(@{Target|Select Target|'+tokenHPname+'})]]') : ''),
missileACnoMods= '[[(0+(@{Target|Select Target|'+fields.StdAC[0]+'|max}&{noerror}))+((0+(@{Target|Select Target|'+tokenACname+'}&{noerror}))-(0+(@{Target|Select Target|'+fields.StdAC[0]+'}&{noerror})))]]';
-
+
let attkAdj = (tableInfo.RANGED.tableLookup( fields.RW_adj, rwIndex ) || '0'),
weapCmd = parseStr(tableInfo.RANGED.tableLookup( fields.RW_cmd, rwIndex ) || ''),
hitCharges = tableInfo.RANGED.tableLookup( fields.RW_charges, rwIndex ),
styleRangeMods=tableInfo.RANGED.tableLookup(fields.RW_styleRangeMods, rwIndex ),
arAdjust = (tableInfo.RANGED.tableLookup( fields.RW_aradj, rwIndex ) || '0|0|0|0|0|0|0|0|0').split('|');
-
+
const weapDmgAdj= /^[+-][+-]/.test(attkAdj);
const attkRWobj = {
towho: sendToWho(charCS,senderId,false),
@@ -4000,17 +3898,19 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
weapstrhit: tableInfo.RANGED.tableLookup( fields.RW_strBonus, rwIndex ),
profpenalty: String(Math.min(proficiency,0)),
specprof: String(proficiency == 2 ? 1 : 0),
+ shotpenalty: String((!shot || !shot.length || !shotPen.length) ? 0 : (shotPen || 0)),
+ attkmod: String(attkMods[tokenID].mods.filter(m => !isNaN(parseInt(m[1]))).reduce( (t,m) => m[2] ? (t + parseInt(m[1])) : t, 0 ) || 0),
racebonus: String(raceMods( charCS, rwType, rwSuperType )),
magicattkadj: String(thac0Bar.name && thac0Bar.barName.startsWith('bar') ? (thac0 - thac0Bar.val) : ((attrLookup( charCS, fields.Magical_hitAdj ) || 0) + (attrLookup( charCS, [fields.Magical_hitAdj[0]
+tokenID,fields.Magical_hitAdj[1], fields.Magical_hitAdj[2]] ) || 0) + (attrLookup( charCS, fields.Legacy_hitAdj ) || 0))),
twoweappenalty: String((ranger || primeWeapon < 1) ? 0 : (-1*(((rwIndex*2)+(fields.RW_table[1]==0?2:4)) == primeWeapon ? Math.floor(twPen) : Math.floor((10*twPen)%10)))),
- encumbrance: String(encumberDef.attack[parseInt(attrLookup(charCS,fields.Encumbrance)) || 0] || 0),
+ encumbrance: state.MagicMaster.encumbrance ? String(encumberDef.attack[parseInt(attrLookup(charCS,fields.Encumbrance)) || 0] || 0) : '0',
ammodmgadj: String((tableInfo.AMMO.tableLookup( fields.Ammo_adj, ammoIndex ) + (weapDmgAdj ? attkAdj : ''))),
ammostyledmgadj: tableInfo.AMMO.tableLookup( fields.Ammo_styleAdj, ammoIndex ),
magicdmgadj: String((parseInt(attrLookup( charCS, [fields.Magical_dmgAdj[0]+tokenID,fields.Magical_dmgAdj[1],undefined],{def:false}) || attrLookup( charCS, fields.Magical_dmgAdj )) || 0)
+ (parseInt(attrLookup( charCS, fields.Legacy_dmgAdj )) || 0)),
strdmgbonus: String(parseInt(attrLookup( charCS, fields.Strength_dmg )) || '0'),
- weapon: weaponName,
+ weapon: weaponName + ((!shot || !shot.length) ? '' : ' for a Called Shot of '+shot),
thac0: thac0,
slashweap: slashWeap,
pierceweap: pierceWeap,
@@ -4049,6 +3949,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
targetacfield: tokenAC,
targetac: tokenAC,
targetacmissile: missileACnoMods,
+ targetacextra: '@{target|Select Target|'+fields.MonsterAC[0]+'}&{noerror}@{target|Select Target|'+fields.ACnotes[0]+'}',
distpb: (ranges.length >= 4 ? ranges[0] : Math.min(ranges[0],30) ),
dists: (ranges.length >= 4 ? ranges[1] : ranges[0]),
distm: (ranges.length >= 4 ? ranges[2] : ranges[Math.min(1,ranges.length-1)]),
@@ -4095,8 +3996,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
dmgLroll2 = dmgLroll,
rangeMod;
- const attkAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////').split('/')[1]),
- dmgAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////').split('/')[2]),
+ const attkAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////').split('/')[1]),
+ dmgAdvantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////').split('/')[2]),
minMaxRoll = maxDiceRoll(toHitRoll),
critHit = String(Math.min((tableInfo.RANGED.tableLookup( fields.RW_critHit, rwIndex )||minMaxRoll.max),(tableInfo.RANGED.tableLookup( fields.RW_styleCH, rwIndex )||minMaxRoll.max))),
critMiss = String(Math.max((tableInfo.RANGED.tableLookup( fields.RW_critMiss, rwIndex )||minMaxRoll.min),(tableInfo.RANGED.tableLookup( fields.RW_styleCM, rwIndex )||minMaxRoll.min))),
@@ -4157,9 +4058,10 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
rangesmlf: String((range != 'N' && range != 'PB') ? 1 : 0),
ammodmgsm: dmgSMroll,
ammodmgl: dmgLroll,
- rwsmdmgmacro: (charName+'|Do-not-use-DmgSM-RW'+rwNumber+'-'+range),
- rwlhdmgmacro: (charName+'|Do-not-use-DmgL-RW'+rwNumber+'-'+range),
+ rwsmdmgmacro: (charCS.id+'|Do-not-use-DmgSM-RW'+rwNumber+'-'+range),
+ rwlhdmgmacro: (charCS.id+'|Do-not-use-DmgL-RW'+rwNumber+'-'+range),
advdice: advExplain,
+ modstable: (attkMods[tokenID].mods.length ? addAttkModBoxes( args, tokenID ) : ''),
};
const rePlaceRW = (m,t) => attkRWvars[t.toLowerCase()] || '';
return attkMacro.replace( /\^\^([-\+\w]+?)\^\^/img, rePlaceRW ).replace( /,/gi , ',' );
@@ -4252,6 +4154,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (!attkMacroDef.obj) {
errFlag = true;
} else {
+ if (abilityType == Attk.TARGET && parseAttkData( attkMacroDef.obj[1].body ).targetdmg == '1') abilityType = Attk.TO_HIT;
attkMacro = attkMacroDef.obj[1].body.replace( /\^\^([-\+\w]+?)\^\^/img, reInitRW );
let dmgSMmacroDef = abilityLookup( fields.AttacksDB, 'RW-DmgSM'+qualifier+'-'+ammoName, charCS, silent );
@@ -4297,7 +4200,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
saveMod = parseInt(attrLookup( charCS, saveObj.mod ) || 0),
saveAdj = parseInt(attrLookup( charCS, fields.MagicSaveAdj ) || 0),
calcResult = attr ? (save+saveMod+sitMod+DCval+saveAdj) : (save-saveMod-sitMod-saveAdj),
- advantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////').split('/')[(!attr?3:4)]) || 0,
+ advantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////').split('/')[(!attr?3:4)]) || 0,
roll2Txt = !advantage ? '' : (advantage > 0 ? advTxt : disTxt),
adTitle = !advantage ? '' : (advantage > 0 ? withAdv : withDis),
keepCmd = ((attr ? advantage < 0 : advantage > 0) ? 'kh1' : 'kl1'),
@@ -4310,7 +4213,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
const content = (isGM ? '/w gm ' : '')
+ '&{template:'+fields.menuTemplate+'}{{name='+name+' Save vs '+saveType.dispName()+adTitle+'}}'
- + '{{Saving Throw=Rolling [[([['+roll+'cf<'+(calcResult-1)+'cs>'+calcResult+']][Dice Roll])]] vs. [[([[0+'+calcResult+']][Target])]] target}}'
+ + '{{Saving Throw=Rolling [[([['+roll+']][Dice Roll])]] vs. [[([[0+'+calcResult+']][Target])]] target}}'
+ '{{Result=Saving Throw'+(attr ? '<=' : '>=')+calcResult+'}}'
+ (failCmd ? ('{{failcmd='+failCmd+'}}{{fumblecmd='+failCmd+'}}') : '')
+ '{{desc=**'+name+'\'s target**[[0+'+save+']] base save vs. '+saveType.dispName()+' with [[0+'+saveMod+']] improvement from race, class & Magic Items, '
@@ -4373,7 +4276,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
sitMod = parseInt(sitMod);
target = Math.max(state.attackMaster.thieveCrit,(sitMod+target));
const name = getObj('graphic',tokenID).get('name'),
- advantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'//////').split('/')[5]) || 0,
+ advantage = parseInt((attrLookup(charCS,[fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1]])+'///////').split('/')[5]) || 0,
roll2Txt = !advantage ? '' : (advantage > 0 ? advTxt : disTxt),
adTitle = !advantage ? '' : (advantage > 0 ? withAdv : withDis),
keepCmd = (advantage < 0 ? 'kh1' : 'kl1'),
@@ -4413,7 +4316,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
return new Promise(resolve => {
- try { // ammoIndex
+ try {
var content = '',
wname;
@@ -4484,36 +4387,36 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
content += disabled ? ('') : '[';
farRange = Math.max(1,Math.min(6,(ranges[0]-2),specRange-2));
content += ranges.length ? ('Near: 0 to '+(farRange-1)) : 'Near';
- content += disabled ? '' : ('](~'+charName+'|Do-not-use-Attk-RW'+weaponIndex+'-N)');
+ content += disabled ? '' : ('](~'+charCS.id+'|Do-not-use-Attk-RW'+weaponIndex+'-N)');
const shortRange = ranges.length ? ranges[0] : specRange;
if (specialist) {
content += disabled ? ('') : '[';
content += ranges.length ? ('PB'+(shortRange <= specRange ? '&S' : '')+': '+farRange+' to '+specRange) : 'Point Blank' ;
farRange = specRange;
- content += disabled ? '' : ('](~'+charName+'|Do-not-use-Attk-RW'+weaponIndex+'-PB)');
+ content += disabled ? '' : ('](~'+charCS.id+'|Do-not-use-Attk-RW'+weaponIndex+'-PB)');
}
farRange = ranges.length ? (ranges[0]) : farRange;
if (!specialist || farRange > specRange) {
content += disabled ? ('') : '[';
content += ranges.length ? ('S: '+Math.min((ranges.length ? ranges[0] : farRange),(specialist ? (specRange+1) : Math.max(1,Math.min(6,(ranges[0]-2),specRange-2))))+' to '+farRange) : 'Short';
- content += disabled ? '' : ('](~'+charName+'|Do-not-use-Attk-RW'+weaponIndex+'-S)');
+ content += disabled ? '' : ('](~'+charCS.id+'|Do-not-use-Attk-RW'+weaponIndex+'-S)');
};
if (ranges.length != 1) {
content += disabled ? ('') : '[';
farRange = ranges.length ? (ranges[1]) : farRange;
content += ranges.length ? ('M: '+((ranges[0])+1)+' to '+farRange) : 'Medium';
- content += disabled ? '' : ('](~'+charName+'|Do-not-use-Attk-RW'+weaponIndex+'-M)');
+ content += disabled ? '' : ('](~'+charCS.id+'|Do-not-use-Attk-RW'+weaponIndex+'-M)');
}
if (!ranges.length || ranges.length > 2) {
content += disabled ? ('') : '[';
farRange = ranges.length ? (ranges[2]) : farRange;
content += ranges.length ? ('L: '+((ranges[1])+1)+' to '+farRange) : 'Long';
- content += disabled ? '' : ('](~'+charName+'|Do-not-use-Attk-RW'+weaponIndex+'-L)');
+ content += disabled ? '' : ('](~'+charCS.id+'|Do-not-use-Attk-RW'+weaponIndex+'-L)');
}
content += disabled ? ('') : '[';
content += ranges.length ? ('Far: beyond '+(farRange)) : 'Far';
- content += disabled ? '' : ('](~'+charName+'|Do-not-use-Attk-RW'+weaponIndex+'-F)');
+ content += disabled ? '' : ('](~'+charCS.id+'|Do-not-use-Attk-RW'+weaponIndex+'-F)');
} catch (e) {
log('AttackMaster makeRangeButtons: JavaScript '+e.name+': '+e.message+' while processing weapon '+wname);
@@ -4537,7 +4440,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
async function makeAttackMenu( args, senderId, submitted ) {
try {
-
+ const start = Date.now();
const backstab = (args[0] == BT.BACKSTAB),
tokenID = args[1],
weaponButton = args[3] || null,
@@ -4569,7 +4472,14 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
charName = charCS.get('name'),
targetStr = (attkType === Attk.TARGET) ? '@{target|Select Opponent|token_id}' : '',
diceRoll = (attkType === Attk.ROLL) ? ('?{Roll To Hit|'+fields.ToHitRoll+'}') : fields.ToHitRoll,
+ shotCalled = attrLookup( charCS, [fields.ShotCalled[0]+tokenID,fields.ShotCalled[1],fields.ShotCalled[2]] ),
content = '&{template:'+fields.menuTemplate+'}{{name=How is ' + tokenName + ' attacking?}}';
+
+ if (shotCalled && shotCalled.length) {
+ let shotData = shotCalled.split(':'),
+ targetID = attrLookup( charCS, [fields.CalledTarget[0]+tokenID,fields.CalledTarget[1],fields.CalledTarget[2]] );
+ content += '{{Called Shot='+shotData[0]+' _change_('+fields.initMaster+' '+senderId+' --button '+BT.CALLED_SHOT+'|'+tokenID+'|'+targetID+'||attk)}}';
+ };
if ( monsterAttk1 || monsterAttk2 || monsterAttk3 ) {
@@ -4578,13 +4488,13 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
}
content += '{{Section1=**Monster Attacks**\n';
if (monsterAttk1) {
- content += '[' + monsterAttk1 + '](~'+charName+'|Do-not-use-Monster-Attk-1)';
+ content += '[' + monsterAttk1 + '](~'+charCS.id+'|Do-not-use-Monster-Attk-1)';
}
if (monsterAttk2) {
- content += '[' + monsterAttk2 + '](~'+charName+'|Do-not-use-Monster-Attk-2)';
+ content += '[' + monsterAttk2 + '](~'+charCS.id+'|Do-not-use-Monster-Attk-2)';
}
if (monsterAttk3) {
- content += '[' + monsterAttk3 + '](~'+charName+'|Do-not-use-Monster-Attk-3)';
+ content += '[' + monsterAttk3 + '](~'+charCS.id+'|Do-not-use-Monster-Attk-3)';
}
content += '}}';
}
@@ -4602,8 +4512,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
// build the Weapon Powers list
let magicList = {},
- weaponName;
- let Weapons = getTableField( charCS, {}, fields.MW_table, fields.MW_name );
+ weaponName,
+ Weapons = getTableField( charCS, {}, fields.MW_table, fields.MW_name );
Weapons = getTableField( charCS, Weapons, fields.MW_table, fields.MW_miName );
const Items = getTableGroup( charCS, fieldGroups.MI );
const Magic = getTable( charCS, fieldGroups.MAGIC );
@@ -4657,6 +4567,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
magicWeaps += cmdStr;
}
if (magicCmd) {
+// magicWeaps += '
'+parseStr(magicCmd).replace(/(?:@|@){selected\|MIrowref}/ig,String(itemIndex)).replace(/:/g,'\\clon;');
magicWeaps += '
'+parseStr(magicCmd).replace(/(?:@|@){selected\|MIrowref}/ig,String(itemIndex));
}
if (magicName && magicLib.obj && !magicPower) {
@@ -4706,9 +4617,9 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
};
weaponName = ((weapCharged && !enabling) ? miQty+' ' : '')+weaponName;
if (tableInfo.MELEE.tableLookup( fields.MW_dancing, weaponIndex ) == '1') {
- dancingMeleeWeaps += '['+weaponName+'](~'+charName+'|Do-not-use-Attk-MW'+(weaponIndex+weaponOffset)+') ';
+ dancingMeleeWeaps += '['+weaponName+'](~'+charCS.id+'|Do-not-use-Attk-MW'+(weaponIndex+weaponOffset)+') ';
} else {
- meleeWeaps += '['+weaponName+'](~'+charName+'|Do-not-use-Attk-MW'+(weaponIndex+weaponOffset)+') ';
+ meleeWeaps += '['+weaponName+'](~'+charCS.id+'|Do-not-use-Attk-MW'+(weaponIndex+weaponOffset)+') ';
}
}
}
@@ -4729,20 +4640,24 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
tableInfo.AMMO = getTable( charCS, fieldGroups.AMMO ),
title = true;
}
- let miName = tableInfo.RANGED.tableLookup( fields.RW_miName, weaponIndex ),
+ let miName = tableInfo.RANGED.tableLookup( fields.RW_miName, weaponIndex ).dbName(),
itemIndex = indexTableGroup( Items, tableGroupFind( Items, 'trueName', miName)),
charges = tableInfo.RANGED.tableLookup( fields.RW_charges, weaponIndex ),
weapObj = abilityLookup( fields.WeaponDB, miName, charCS, true ),
weapCharged = (weapObj.obj ? !(['uncharged','cursed','cursed+uncharged','single-uncharged','enable','disable'].includes(weapObj.obj[1].charge.toLowerCase())) : false),
miQty = (_.isUndefined(itemIndex) || !weapCharged) ? charges : tableGroupLookup( Items, 'qty', itemIndex ),
- valid = (!weapCharged || ((miQty-charges) >= 0));
+ valid = (!weapCharged || ((miQty-charges) >= 0)),
+ selfAmmoed = weapObj.obj ? reAmmoData.test(weapObj.obj[1].body) : false,
+ ammoTrueName;
weapButton = '{{'+(weapCharged ? '**'+miQty+'** ' : '')+weaponName+'=';
weaponType = tableInfo.RANGED.tableLookup( fields.RW_type, weaponIndex ).dbName();
weaponSuperType = tableInfo.RANGED.tableLookup( fields.RW_superType, weaponIndex ).dbName();
ammoIndex = fields.Ammo_table[1]-1;
while (!_.isUndefined(ammoName = tableInfo.AMMO.tableLookup( fields.Ammo_name, ++ammoIndex, false ))) {
ammoType = tableInfo.AMMO.tableLookup( fields.Ammo_type, ammoIndex ).dbName();
- if (ammoName != '-' && (!ammoType ? (weaponName.includes((ammoName.split(',')||['none',''])[0])) : (weaponType.split('|').includes(ammoType) || weaponSuperType.split('|').includes(ammoType) || weaponName.dbName().includes(ammoType)))) {
+ ammoTrueName = tableInfo.AMMO.tableLookup( fields.Ammo_miName, ammoIndex ).dbName();
+ if (ammoName == '-' || (selfAmmoed && ammoTrueName !== miName && ammoTrueName !== weaponName.dbName())) continue;
+ if (!ammoType ? (weaponName.includes((ammoName.split(',')||['none',''])[0])) : (weaponType.split('|').includes(ammoType) || weaponSuperType.split('|').includes(ammoType) || weaponName.dbName().includes(ammoType))) {
ammoQty = tableInfo.AMMO.tableLookup( fields.Ammo_qty, ammoIndex );
weapButton += (weaponIndex == weaponButton && ammoIndex == ammoButton) ? ('')
: ((ammoQty <= 0 || !valid) ? ('') : '[');
@@ -4786,15 +4701,17 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (rangeButtons) {
content += rangeButtons;
}
+ content += addAttkModBoxes(args,tokenID,true);
let argString = args.join('|');
let target = !state.attackMaster.weapRules.dmTarget || playerIsGM(senderId);
let width = target ? '33%' : '50%';
- content += '{{desc1=| **Change Dice Action** |
'
- + '| '+((attkType == Attk.TO_HIT) ? ('') : '[') + 'PC rolls' + ((attkType == Attk.TO_HIT) ? '' : '](!attk --set-attk-type '+senderId+'|'+Attk.TO_HIT+'|'+argString+') | ')
- + ''+((attkType == Attk.ROLL) ? ('') : '[') + 'You roll' + ((attkType == Attk.ROLL) ? '' : '](!attk --set-attk-type '+senderId+'|'+Attk.ROLL+'|'+argString+') | ')
- + (target ? (''+((attkType == Attk.TARGET) ? ('') : '[') + 'Targeted' + ((attkType == Attk.TARGET) ? '' : '](!attk --set-attk-type '+senderId+'|'+Attk.TARGET+'|'+argString+') | ')) : '')
+ content += '{{desc9=| **Change Dice Action** |
'
+ + '| '+((attkType == Attk.TO_HIT) ? ('') : '_') + 'PC rolls' + ((attkType == Attk.TO_HIT) ? '' : '_(!attk --set-attk-type '+senderId+'|'+Attk.TO_HIT+'|'+argString+') | ')
+ + ''+((attkType == Attk.ROLL) ? ('') : '_') + 'You roll' + ((attkType == Attk.ROLL) ? '' : '_(!attk --set-attk-type '+senderId+'|'+Attk.ROLL+'|'+argString+') | ')
+ + (target ? (''+((attkType == Attk.TARGET) ? ('') : '_') + 'Targeted' + ((attkType == Attk.TARGET) ? '' : '_(!attk --set-attk-type '+senderId+'|'+Attk.TARGET+'|'+argString+') | ')) : '')
+ '
}}';
sendResponse( charCS, content, senderId,flags.feedbackName,flags.feedbackImg,tokenID );
+ measureTime('makeAttackMenu', start);
} catch (e) {
sendCatchError('AttackMaster',msg_orig[senderId],e);
}
@@ -4938,7 +4855,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
async function makeChangeWeaponMenu( args, senderId, msg='' ) {
try { // hands
-
+ const start = Date.now();
const tokenID = args[1],
spellsMI = args[4],
isGM = playerIsGM(senderId),
@@ -5018,6 +4935,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (auto) {content += '}}';}
sendResponse( charCS, content, senderId,flags.feedbackName,flags.feedbackImg,tokenID );
+ measureTime('makeChangeWeaponMenu',start);
return;
} catch (e) {
log('AttackMaster makeChangeWeaponMenu: JavaScript '+e.name+': '+e.message);
@@ -5198,12 +5116,13 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
thac0Mods = '',
dmgMods = '',
hpMods = '',
+ surpMods = '',
magicAdj = '',
legacyMods = false,
isWarrior = false,
HPbonus = attrMods.con.hpadj.data[con];
- content += '{{Section1=';
+ content += '{{Section=';
if (tokenThac0 != currentThac0) {
content += '**Current Thac0 is '+tokenThac0+'**'
+ '\n(calculated as '+currentThac0+')';
@@ -5225,32 +5144,37 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
content += '{{Constitution '+con+'=HP bonus '+((HPbonus > 0 && HPbonus[0] !== '+') ? '+' : '')+HPbonus+' per level}}';
}
- let modObj,thac0Adj,hpAdj,dmgAdj,isMod,removeLink,objType;
+ let modObj,thac0Adj,hpAdj,dmgAdj,surpAdj,isMod,surpriseMod,removeLink,objType;
_.each( modValues, (e,k) => {
modObj = abilityLookup( fields.MagicItemDB, e.name, charCS, silent ),
- thac0Adj = (parseInt(e.data.thac0adj) || 0),
- hpAdj = (parseInt(e.data.hpadj) || 0) + (parseInt(e.data.hptemp) || 0) + (parseInt(e.data.hpmax) || 0),
- dmgAdj = (parseInt(e.data.dmgadj) || 0),
+ thac0Adj = (parseInt(evalAttr(e.data.thac0adj)) || 0),
+ hpAdj = (parseInt(evalAttr(e.data.hpadj)) || 0) + (parseInt(evalAttr(e.data.hptemp)) || 0) + (parseInt(evalAttr(e.data.hpmax)) || 0),
+ dmgAdj = (parseInt(evalAttr(e.data.dmgadj)) || 0),
+ surpAdj = (parseInt(evalAttr((e.data.surpriseme || '').split('=')[1] || 0)) || 0) + (parseInt(evalAttr((e.data.surpriseyou || '').split('=')[1] || 0) || 0)),
isMod = e.specs[2].toLowerCase().includes('modifiers'),
removeLink = ((isGM && isMod) ? (' _Remove_(!attk --set-mods '+tokenID+'|del|'+e.name+'|'+e.trueName+'|thac0|||verbose)') : ''),
objType = (modObj.obj ? getShownType( modObj, e.row ) : e.specs[4].replace(/\|/g,'/')).dispName();
if (thac0Adj) thac0Mods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? ((thac0Adj >= 0 ? ' +' : ' ') + thac0Adj) : '') + removeLink +'}}';
- if (dmgAdj) dmgMods += '{{<'+(rowNo++)+'>'+objType+' ='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? ((dmgAdj >= 0 ? ' +' : ' ') + dmgAdj) : '') + removeLink +'}}';
- if (hpAdj) hpMods += '{{<'+(rowNo++)+'>'+objType+' ='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? ((hpAdj >= 0 ? ' +' : ' ') + hpAdj) : '') + removeLink+'}}';
- if (e.data.adall) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': All '+(e.data.adall >= 0 ? advTxt : disTxt)) : '') + removeLink +'}}';
- if (e.data.admw) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': Melee '+(e.data.admw >= 0 ? advTxt : disTxt)) : '') + removeLink +'}}';
- if (e.data.adrw) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': Ranged '+(e.data.adrw >= 0 ? advTxt : disTxt)) : '') + removeLink +'}}';
- if (e.data.addam) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': Damage '+(e.data.addam >= 0 ? advTxt : disTxt)) : '') + removeLink +'}}';
- if (e.data.adsave) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': Saves '+(e.data.adsave >= 0 ? advTxt : disTxt)) : '') + removeLink +'}}';
- if (e.data.adattr) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': Attribute check '+(e.data.adattr >= 0 ? advTxt : disTxt)) : '') + removeLink +'}}';
- if (e.data.adrogue) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': Rogue skill '+(e.data.adAtk >= 0 ? AdvTxt : disTxt)) : '') + removeLink +'}}';
+ if (dmgAdj) dmgMods += '{{<'+(rowNo++)+'>'+objType+' ='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? ((dmgAdj >= 0 ? ' +' : ' ') + dmgAdj) : '') + removeLink.replace('|thac0|','|dmg|') +'}}';
+ if (hpAdj) hpMods += '{{<'+(rowNo++)+'>'+objType+' ='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? ((hpAdj >= 0 ? ' +' : ' ') + hpAdj) : '') + removeLink.replace('|thac0|','|hp|')+'}}';
+ if (e.data.surpriseme || e.data.surpriseyou) surpMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? ((surpAdj >= 0 ? ' +' : ' ') + surpAdj) : '') + removeLink.replace('|thac0|','|surprise|') +'}}';
+ if (e.data.adall) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'all')?'All':'')+(evalAttr(e.data.adall) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|adall:|') +'}}';
+ if (e.data.admw) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'melee')?'Melee':'')+(evalAttr(e.data.admw) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|admwa:|') +'}}';
+ if (e.data.adrw) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'ranged')?'Ranged':'')+(evalAttr(e.data.adrw) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|adrwa:|') +'}}';
+ if (e.data.addam) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'damage')?'Damage':'')+(evalAttr(e.data.addam) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|addam:|') +'}}';
+ if (e.data.adsave) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'saves')?'Saves':'')+(evalAttr(e.data.adsave) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|adsav:|') +'}}';
+ if (e.data.adattr) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'attributecheck')?'Attribute Check':'')+(evalAttr(e.data.adattr) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|adatr:|') +'}}';
+ if (e.data.adrogue) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'rogueskill')?'All':'')+(evalAttr(e.data.adRogue) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|adrog:|') +'}}';
+ if (e.data.adsurprise) adMods += '{{<'+(rowNo++)+'>'+objType+'='+e.name.dispName()+(!(/[+-]\d+?/.test(e.name)) ? (': '+((e.name.dbName() !== 'surprise')?'Surprise ':'')+'Roll '+(evalAttr(e.data.adsurprise) >= 0 ? advTxt : disTxt)) : '') + removeLink.replace('|thac0|','|adsur:|') +'}}';
legacyMods = legacyMods || objType.dbName() === 'legacymods';
});
- content += '{{Section2=**Thac0 Modifiers** (\'+\' is beneficial)}}' + (thac0Mods || '{{Section3=None}}');
- content += '{{Section4=**Damage Modifiers** (\'+\' is beneficial)}}' + (dmgMods || '{{Section5=None}}');
- content += '{{Section6=**HP Modifiers** (\'+\' is beneficial)}}' + (hpMods || '{{Section7=None}}');
- content += '{{Section8=**Advantages**}}' + (adMods || '{{Section9=None}}');
+
+ content += '{{Section1=**Thac0 Modifiers** (\'+\' is beneficial)}}' + (thac0Mods || '{{Section2=None}}');
+ content += '{{Section3=**Damage Modifiers** (\'+\' is beneficial)}}' + (dmgMods || '{{Section4=None}}');
+ content += '{{Section5=**HP Modifiers** (\'+\' is beneficial)}}' + (hpMods || '{{Section6=None}}');
+ content += '{{Section7=**Surprise Modifiers** (\'+\' is beneficial)}}' + (surpMods || '{{Section8=None}}');
+ content += '{{Section9=**Advantages**}}' + (adMods || '{{Section0=None}}');
content += modScanPriority( modsPriority, tokenID, 'checkmods' );
if (modMsgs && modMsgs.length) {
@@ -5296,6 +5220,17 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
acNotes = attrLookup( charCS, fields.ACnotes ) || '',
isGM = playerIsGM( senderId );
+ let calledShots = attrLookup( charCS, fields.SavedShots ) || attrLookup( charCS, fields.CalledShots ),
+ shotQuery = '',
+ options = '';
+
+ if (isGM && calledShots && calledShots.length) {
+ let options = calledShots.split('/').map(cs => cs.split(':')).filter(cs => !!cs[3] && cs[3].length);
+ options = options.map( cs => ('Fix as '+cs[0]+' AC'+cs[3]+','+cs[3]+'['+cs[0]+']'));
+ options.push('Random,Random');
+ shotQuery = options.length > 1 ? ('_change_('+fields.attackMaster+' --button '+BT.SET_SHOT+'|'+tokenID+'|?{Select a shot|'+options.join('|')+'})') : '';
+ };
+
let content = '&{template:'+fields.menuTemplate+'}{{name=Current Armour for '+tokenName+'}}';
if (currentAC != finalAC) {
@@ -5318,21 +5253,19 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (monSpecial && monSpecial.length) content += '\n'+monSpecial;
if (acNotes && acNotes.length) content += '\n'+acNotes;
content += '}}'
- + (acValues.armour ? '{{Armour='+acValues.armour.name+' AC'+(parseInt(acValues.armour.data.ac||10)-parseInt(acValues.armour.data.adj||0)-parseInt(acValues.armour.data[dmgType]||0))+'}}' : '')
+ + (acValues.armour ? '{{Armour='+acValues.armour.name+' AC'+(parseInt(!String(acValues.armour.data.ac).length ? 10 : acValues.armour.data.ac)-parseInt(acValues.armour.data.adj||0)-parseInt(acValues.armour.data[dmgType]||0))+' '+shotQuery+'}}' : '')
+ (acValues.shield ? '{{Shield='+acValues.shield.name+'}}' : '');
let acObj, acAdj, isMod;
_.each( acValues, (e,k) => {
- if (k != 'armour' && k != 'shield') {
- acObj = abilityLookup( fields.MagicItemDB, e.name, charCS, silent ),
- acAdj = (parseInt(e.data.adj) || 0) + (isSlash?(parseInt(e.data.sadj) || 0):(isPierce?(parseInt(e.data.padj) || 0):(isBash?(parseInt(e.data.badj) || 0):0))),
- isMod = e.specs[2].toLowerCase().includes('modifiers');
- if (!!acAdj) {
- content += '{{'+(acObj.obj ? getShownType( acObj, e.row ) : e.specs[4].replace(/\|/g,'/'))+'='+e.name.dispName();
- if (!(/[+-]\d+?/.test(e.name))) content += (acAdj >= 0 ? ' +' : ' ') + acAdj;
- content += ((isGM && isMod) ? (' _Remove_(!attk --set-mods '+tokenID+'|del|'+e.name+'|'+e.trueName+'|ac|||verbose)') : '');
- content += '}}';
- };
+ acObj = abilityLookup( fields.MagicItemDB, e.name, charCS, silent );
+ acAdj = (parseInt(evalAttr(e.data.adj)) || 0) + (isSlash?(parseInt(evalAttr(e.data.sadj)) || 0):(isPierce?(parseInt(evalAttr(e.data.padj)) || 0):(isBash?(parseInt(evalAttr(e.data.badj)) || 0):0)));
+ isMod = e.specs[2].toLowerCase().includes('modifiers');
+ if (!!acAdj && (isMod || (k != 'armour' && k != 'shield'))) {
+ content += '{{'+(acObj.obj ? getShownType( acObj, e.row ) : e.specs[4].replace(/\|/g,'/'))+'='+e.name.dispName();
+ if (!(/[+-]\d+?/.test(e.name))) content += (acAdj >= 0 ? ' +' : ' ') + acAdj;
+ content += ((isGM && isMod) ? (' _Remove_(!attk --set-mods '+tokenID+'|del|'+e.name+'|'+e.trueName+'|armour|||verbose)') : '');
+ content += '}}';
}
});
content += modScanPriority( (attrLookup( charCS, fields.ModPriority ) || 'ac'), tokenID, 'checkac' );
@@ -5387,7 +5320,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
* Make a display explaining how weight, encumbrance and move are calculated
*/
- const makeMoveDisplay = function( args, senderId ) { // itemSlotData
+ const makeMoveDisplay = function( args, senderId ) {
const tokenID = args[1],
charCS = getCharacter( tokenID ),
@@ -5397,7 +5330,6 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
strength = attrLookup( charCS, fields.Strength ),
weights = (attrLookup( charCS, fields.Weight_forStr ) || ',,,,').split(','),
baseMove = attrLookup( charCS, fields.Move_base ),
-// moves = encumberDef.moves[encumberDef.moves.findIndex(m => m[0] >= baseMove)],
Items = getTableGroup( charCS, fieldGroups.MI ),
rowStyles = ['','background-color: #eeeeee;','background-color: #ffff00;'],
tableStyle = 'style="border-collapse:collapse;border: 2px solid rgb(140 140 140);"',
@@ -5430,7 +5362,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (otherWt > 0 || otherEnc > 0) content += '
| Others | '+Math.ceil(otherWt)+' | '+Math.ceil(otherEnc)+' |
';
content += '| Total | '+weight+' | '+encWeight+' |
'
content += '
}}';
- content += '{{Section2=Encumbrance}}{{Section3=
| | Strength = | '+strength+' |
'
+ content += '{{Section2=Encumbrance'+(state.MagicMaster.encumbrance ? '' : ' (ignored)')+'}}{{Section3=| | Strength = | '+strength+' |
'
+ '| Move | Max lbs |
'
+ '| Unencumbered | '+baseMove+' | '+weights[0]+' |
'
+ '| Lightly | '+Math.round(baseMove*2/3)+' | '+weights[1]+' |
'
@@ -5500,6 +5432,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+ '| Saving Throws | [Disadvantage](!rounds --target multi|'+tokenID+'|Disadvantage-Saves|'+duration+'|'+direction+'|Suffering disadvantage on saving throws|lightning-helix) | [Stop](!rounds --target multi|'+tokenID+'|Stop-Saves|'+1+'|-1|Dis/advantage on saving throws is ending|lightning-helix) | [Advantage](!rounds --target multi|'+tokenID+'|Advantage-Saves|'+duration+'|'+direction+'|Gained advantage on saving throws|lightning-helix) |
'
+ '| Attribute Checks | [Disadvantage](!rounds --target multi|'+tokenID+'|Disadvantage-Attribute|'+duration+'|'+direction+'|Suffering disadvantage on attribute checks|lightning-helix) | [Stop](!rounds --target multi|'+tokenID+'|Stop-Attributes|'+1+'|-1|Dis/advantage on attribute checks is ending|lightning-helix) | [Advantage](!rounds --target multi|'+tokenID+'|Advantage-Attributes|'+duration+'|'+direction+'|Gained advantage on attribute checks|lightning-helix) |
'
+ '| Rogue Skills | [Disadvantage](!rounds --target multi|'+tokenID+'|Disadvantage-Rogue|'+duration+'|'+direction+'|Suffering disadvantage on rogue skill checks|lightning-helix) | [Stop](!rounds --target multi|'+tokenID+'|Stop-Rogue|'+1+'|-1|Dis/advantage on rogue skill checks is ending|lightning-helix) | [Advantage](!rounds --target multi|'+tokenID+'|Advantage-Rogue|'+duration+'|'+direction+'|Gained advantage on rogue skill checks|lightning-helix) |
'
+ + '| Surprise | [Disadvantage](!rounds --target multi|'+tokenID+'|Disadvantage-Surprise|'+duration+'|'+direction+'|Suffering disadvantage on surprise checks|lightning-helix) | [Stop](!rounds --target multi|'+tokenID+'|Stop-Surprise|'+1+'|-1|Dis/advantage on surprise checks is ending|lightning-helix) | [Advantage](!rounds --target multi|'+tokenID+'|Advantage-Surprise|'+duration+'|'+direction+'|Gained advantage on surprise checks|lightning-helix) |
'
+ '| Everything | [Disadvantage](!rounds --target multi|'+tokenID+'|Disadvantage-All|'+duration+'|'+direction+'|Suffering disadvantage on attacks, damage, and checks|lightning-helix) | [Stop](!rounds --target multi|'+tokenID+'|Stop-All|'+1+'|-1|Dis/advantage on attacks, damage, and checks is ending|lightning-helix) | [Advantage](!rounds --target multi|'+tokenID+'|Advantage-All|'+duration+'|'+direction+'|Gained advantage on attacks, damage, and checks|lightning-helix) |
|---|
}}';
sendFeedback( content );
@@ -5588,7 +5521,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
};
if (canFail && !allModCmds.length) allModCmds.push(targetArgs);
buildResistRoll( tokenID, charCS, resName, tag, resistance, mod, sitMod, isGM, whoRolls, allModCmds.join('
'), !_.isUndefined(modRow) );
- buttons += '| ['+resName+'](~'+name+'|Do-not-use-'+resName.hyphened()+'-resist) | [['+resistance+']] | [['+mod+']] | '+(notes)+' |
';
+ buttons += '| ['+resName+'](~'+charCS.id+'|Do-not-use-'+resName.hyphened()+'-resist) | [['+resistance+']] | [['+mod+']] | '+(notes)+' |
';
tag = 'spe';
} while (++i < ResTable.sortKeys.length);
@@ -5655,7 +5588,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
_.each( saveFormat.Saves, (saveObj,save) => {
if (saveType && saveType.length && save.dbName() !== saveType) return;
content += ''
- + '| ['+save+'](~'+charName+'|Do-not-use-'+save+'-save) | '
+ + '['+save+'](~'+charCS.id+'|Do-not-use-'+save+'-save) | '
+ '[[0+'+attrLookup(charCS,saveObj.save)+']] | '
+ '[[0+'+attrLookup(charCS,saveObj.mod)+'+'+sitMod+']] | '
+ '
';
@@ -5685,7 +5618,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
modCmd = SaveMods.tableLookup( fields.Mods_cmd, modRow ) || saveCmd;
modCmd = !!modCmd && modCmd.length ? (targetArgs && targetArgs.length ? (modCmd + '
' + targetArgs) : modCmd) : targetArgs;
content += ''
- + '| ['+modName+'](~'+charName+'|Do-not-use-'+modName.hyphened()+'-save) | '
+ + '['+modName+'](~'+charCS.id+'|Do-not-use-'+modName.hyphened()+'-save) | '
+ '[[0+'+attrLookup(charCS,save)+']] | '
+ '[[0+'+attrLookup(charCS,mod)+'+'+sitMod+']] | '
+ '
';
@@ -5702,7 +5635,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
+'|Fire or acid attack,?{Enter your magical armour plusses|0}|Fire or acid'
+'|DM adjustment,?{Ask DM for value of adjustment|0}|DM adjustment'
+'|None of the above,0})'
- + 'such as ***Wisdom adjustment, Dexterity adjustment, fire or acid** etc. before making the roll (+ is beneficial)}}'
+ + 'such as ***Wisdom adjustment, Dexterity adjustment, fire or acid*** etc. before making the roll (+ is beneficial)}}'
+ '{{desc2=['+(!manUpdate ? '' : '')+'Auto-check Saving Throws'+(!manUpdate ? '' : '')+'](!attk --button '+BT.CHECK_SAVES+'|'+tokenID+') to set saves using Race, Class, Level & MI data, or\n'
+ '['+(manUpdate ? '' : '')+'Manually check Saving Throws'+(manUpdate ? '' : '') + '](!attk --setSaves '+tokenID+'|||save) to manually change numbers}}';
@@ -5712,8 +5645,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
content += '{{desc3=**Notes**\n'+saveNotes+'}}';
}
content += '{{desc4=| **Change Dice Action** |
'
- + '| '+((whoRolls === SkillRoll.PCROLLS) ? ('') : '[') + 'PC rolls' + ((whoRolls === SkillRoll.PCROLLS) ? '' : '](!attk --button '+BT.SET_SAVE_ROLL+'|'+senderId+'|'+SkillRoll.PCROLLS+'|'+argString+') | ')
- + ''+((whoRolls === SkillRoll.YOUROLL) ? ('') : '[') + 'You roll' + ((whoRolls === SkillRoll.YOUROLL) ? '' : '](!attk --button '+BT.SET_SAVE_ROLL+'|'+senderId+'|'+SkillRoll.YOUROLL+'|'+argString+') | ')
+ + ''+((whoRolls === SkillRoll.PCROLLS) ? ('') : ' _') + 'PC rolls' + ((whoRolls === SkillRoll.PCROLLS) ? '' : '_(!attk --button '+BT.SET_SAVE_ROLL+'|'+senderId+'|'+SkillRoll.PCROLLS+'|'+argString+')')+' | '
+ + ''+((whoRolls === SkillRoll.YOUROLL) ? ('') : ' _') + 'You roll' + ((whoRolls === SkillRoll.YOUROLL) ? '' : '_(!attk --button '+BT.SET_SAVE_ROLL+'|'+senderId+'|'+SkillRoll.YOUROLL+'|'+argString+')')+' | '
+ '
}}';
sendResponse( charCS, content, senderId,flags.feedbackName,flags.feedbackImg,tokenID );
@@ -5756,7 +5689,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
mod = parseInt(attrLookup(charCS,saveObj.mod)) || 0;
target = parseInt(attrLookup(charCS,saveObj.save)) || 0;
txt += ''
- + '| ['+save.dispName()+'](~'+charName+'|Do-not-use-'+save+'-save) | '
+ + '['+save.dispName()+'](~'+charCS.id+'|Do-not-use-'+save+'-save) | '
+ '[['+target+']] | '
+ '[['+mod+'+'+sitMod+'+'+DCval+']] | '
+ '
';
@@ -5785,8 +5718,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
content += '{{desc8=**Notes**\n'+saveNotes+'}}';
}
content += '{{desc9=| **Change Dice Action** |
'
- + '| '+((whoRolls === SkillRoll.PCROLLS) ? ('') : '[') + 'PC rolls' + ((whoRolls === SkillRoll.PCROLLS) ? '' : '](!attk --button '+BT.SET_ATTR_ROLL+'|'+senderId+'|'+SkillRoll.PCROLLS+'|'+argString+') | ')
- + ''+((whoRolls === SkillRoll.YOUROLL) ? ('') : '[') + 'You roll' + ((whoRolls === SkillRoll.YOUROLL) ? '' : '](!attk --button '+BT.SET_ATTR_ROLL+'|'+senderId+'|'+SkillRoll.YOUROLL+'|'+argString+') | ')
+ + ''+((whoRolls === SkillRoll.PCROLLS) ? ('') : '_') + 'PC rolls' + ((whoRolls === SkillRoll.PCROLLS) ? '' : '_(!attk --button '+BT.SET_ATTR_ROLL+'|'+senderId+'|'+SkillRoll.PCROLLS+'|'+argString+')')+' | '
+ + ''+((whoRolls === SkillRoll.YOUROLL) ? ('') : '_') + 'You roll' + ((whoRolls === SkillRoll.YOUROLL) ? '' : '_(!attk --button '+BT.SET_ATTR_ROLL+'|'+senderId+'|'+SkillRoll.YOUROLL+'|'+argString+')')+' | '
+ '
}}';
sendResponse( charCS, content, senderId,flags.feedbackName,flags.feedbackImg,tokenID );
@@ -5821,7 +5754,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
_.each( rogueSkills, (skillObj) => {
content += ''
- + '| ['+skillObj.name.dispName()+'](~'+charName+'|Do-not-use-'+skillObj.name.replace(/_/g,'-')+'-check) | '
+ + '['+skillObj.name.dispName()+'](~'+charCS.id+'|Do-not-use-'+skillObj.name.replace(/_/g,'-')+'-check) | '
+ '[[0+'+attrLookup(charCS,skillObj.save)+']] | '
+ '
';
});
@@ -5847,8 +5780,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
let argString = args.join('|');
content += '{{desc9=| **Change Dice Action** |
'
- + '| '+((whoRolls === SkillRoll.PCROLLS) ? ('') : '[') + 'PC rolls' + ((whoRolls === SkillRoll.PCROLLS) ? '' : '](!attk --button '+BT.SET_SKILL_ROLL+'|'+senderId+'|'+SkillRoll.PCROLLS+'|'+argString+') | ')
- + ''+((whoRolls === SkillRoll.YOUROLL) ? ('') : '[') + 'You roll' + ((whoRolls === SkillRoll.YOUROLL) ? '' : '](!attk --button '+BT.SET_SKILL_ROLL+'|'+senderId+'|'+SkillRoll.YOUROLL+'|'+argString+') | ')
+ + ''+((whoRolls === SkillRoll.PCROLLS) ? ('') : '_') + 'PC rolls' + ((whoRolls === SkillRoll.PCROLLS) ? '' : '_(!attk --button '+BT.SET_SKILL_ROLL+'|'+senderId+'|'+SkillRoll.PCROLLS+'|'+argString+') | ')
+ + ''+((whoRolls === SkillRoll.YOUROLL) ? ('') : '_') + 'You roll' + ((whoRolls === SkillRoll.YOUROLL) ? '' : '_(!attk --button '+BT.SET_SKILL_ROLL+'|'+senderId+'|'+SkillRoll.YOUROLL+'|'+argString+') | ')
+ '
}}';
sendResponse( charCS, content, senderId, flags.feedbackName, flags.feedbackImg, tokenID );
@@ -6207,7 +6140,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
async function handleChangeWeapon ( args, senderId, silent=false, noCurse=false ) { // InHand_attkCount
try {
-
+ const start = Date.now();
const cmd = (args[0] || '').replace('-NOCURSE',''),
tokenID = args[1],
row = args[3],
@@ -6272,7 +6205,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (selection == -2) {
weapon = trueWeapon = 'Touch';
} else if (selection == -2.5) {
- weapon = trueWeapon = 'Punch-Wrestle';
+ weapon = trueWeapon = twoHanded ? 'Wrestle' : 'Punch';
} else if (selection == -3) {
await handleChangeWeapon( [BT.BOTH,tokenID,'-',2], senderId, true );
weapon = trueWeapon = 'Lend-a-Hand';
@@ -6435,6 +6368,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
setAttr( charCS, [fields.Init_hand[0]+'1',fields.Init_hand[1]], '' );
setAttr( charCS, [fields.Init_hand[0]+'2',fields.Init_hand[1]], '' );
+ measureTime('handleChangeWeapon',start);
return;
} catch (e) {
sendCatchError('AttackMaster',msg_orig[senderId],e);
@@ -7206,6 +7140,99 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
});
};
+ /*
+ * Handle fixing or unfixing a target body part
+ * for a creature with multiple strikeable body
+ * parts specified.
+ */
+
+ const handleFixBodyPart = function( args, senderId ) {
+
+ const tokenID = args[1],
+ AC = args[2] || '',
+ charCS = getCharacter( tokenID );
+
+ if (!charCS) {
+ sendError('Invalid tokenID specified');
+ return;
+ }
+
+ let savedShots = attrLookup( charCS, fields.SavedShots );
+ let calledShots = attrLookup( charCS, fields.CalledShots );
+
+ if (AC.toLowerCase() !== 'random' && !isNaN(parseInt(AC))) {
+ if (!savedShots && !savedShots.length && calledShots && calledShots.length) {
+ setAttr( charCS, fields.SavedShots, calledShots );
+ }
+ setAttr( charCS, fields.CalledShots, '' );
+ setAttr( charCS, fields.MonsterAC, AC );
+ } else {
+ if (savedShots.length) {
+ setAttr( charCS, fields.CalledShots, savedShots );
+ } else if (calledShots.length) {
+ setAttr( charCS, fields.SavedShots, calledShots );
+ savedShots = calledShots;
+ };
+
+ let shotData = savedShots.split('/').map(cs => cs.split(':'));
+ let diceVal = shotData.reduce((a,b) => a+parseInt(b[4] || 0),0);
+ if (diceVal > 0) {
+ let roll = Math.floor(Math.random()*(diceVal+1));
+ let diceRoll = roll;
+ let row = shotData.find(cs => {roll = roll - (cs[4] || 0); return roll <= 0});
+ if (!_.isUndefined(row) && !_.isUndefined(row[4]) && !isNaN(parseInt(row[4]))) {
+ let targetAC = attrLookup( charCS, fields.MonsterAC, {def:false} );
+ if (!_.isUndefined(targetAC) && targetAC.length) {
+ targetAC = targetAC.split('[');
+ targetAC[0] = row[3];
+ targetAC[1] = row[0]+']';
+ setAttr( charCS, fields.MonsterAC, targetAC.join('[') );
+ };
+ state.initMaster.randomShots.push(tokenID);
+ };
+ };
+ };
+ doCheckAC( [tokenID,'','',senderId], senderId, [] );
+ };
+
+ /*
+ * Handle the toggling of an attack modifier from PHB Table 51 or
+ * as defined by a rae, class or creature definition.
+ */
+
+ const handleToggleAttkMod = function( args=[], senderId ) {
+
+ const tokenID = args[1],
+ index = args[2],
+ flag = args[3];
+
+ if (_.isUndefined( getObj('graphic',tokenID) )) {
+ sendError('Invalid tokenID in command');
+ return;
+ };
+ attkMods[tokenID].mods[index][2] = flag==='true';
+ makeAttackMenu( args.slice(4), senderId, false );
+
+ };
+
+ /*
+ * Handle adding Table 51 attack mods to the attack mods
+ * list of tick boxes
+ */
+
+ const handleTable51 = function( args=[], senderId ) {
+
+ const tokenID = args[2];
+ if (_.isUndefined( getObj('graphic',tokenID) )) {
+ sendError('Invalid tokenID in command');
+ return;
+ };
+ attkMods[tokenID].table51 = true;
+ attkMods[tokenID].mods = fetchTickMods( tokenID, 'attkmods', JSON.parse(JSON.stringify(table51AttkMods)) );
+ makeAttackMenu( args.slice(1), senderId, false );
+ };
+
+
// ------------------------------------------------------------- Command Action Functions ---------------------------------------------
/**
@@ -7356,8 +7383,10 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
args[0] = encoders.reduce((m, rep) => m.replace(rep[0], rep[1]), args[0]);
}
- creatureAttkDefs( charCS );
+ attkMods[tokenID] = {mods:fetchTickMods( tokenID, 'attkmods', [] ),table51:false};
+ creatureAttkDefs( charCS );
+
if (!args[1] && (mAttk = (attrLookup( charCS, fields.Monster_dmg1 ) || '')).length) {
args[1] = mAttk.split(',');
args[1] = (args[1].length < 6) ? (args[1].length > 1 && reDiceRollSpec.test(args[1][0]) ? args[1][1] : args[1][0]) : '';
@@ -7372,6 +7401,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
}
args = ['',tokenID,attkType,null,null].concat(args);
makeAttackMenu( args, senderId, false );
+ sendAPI( fields.initMaster + ' --button '+BT.PARRY_MSG+'|'+tokenID );
return;
};
@@ -7689,6 +7719,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
async function doCheckMods( args, senderId, selected, silent=false, forceMod=false ) { // val
try {
+ const start = Date.now();
if (!args) args=[];
if (!args[0] && selected && selected.length) {
@@ -7721,6 +7752,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
adSave = 0,
adAttr = 0,
adRogue = 0,
+ adSurprise = 0,
ResistTab = getTable( charCS, fieldGroups.RESIST ),
moveIsFixed = false,
movMod = '',
@@ -7729,29 +7761,35 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
ResistTab = checkRaceResistance( ResistTab, senderId );
_.each( modValues, (e,k) => {
- thac0Mod += parseInt(e.data.thac0adj) || 0;
- dmgMod += parseInt(e.data.dmgadj) || 0;
- adAll += parseInt(e.data.adall) || 0;
- adMW += parseInt(e.data.admw) || 0;
- adRW += parseInt(e.data.adrw) || 0;
- adDmg += parseInt(e.data.addam) || 0;
- adSave += parseInt(e.data.adsave) || 0;
- adAttr += parseInt(e.data.adattr) || 0;
- adRogue += parseInt(e.data.adrogue) || 0;
+ thac0Mod += parseInt(evalAttr(e.data.thac0adj)) || 0;
+ dmgMod += parseInt(evalAttr(e.data.dmgadj)) || 0;
+ adAll += parseInt(evalAttr(e.data.adall)) || 0;
+ adMW += parseInt(evalAttr(e.data.admw)) || 0;
+ adRW += parseInt(evalAttr(e.data.adrw)) || 0;
+ adDmg += parseInt(evalAttr(e.data.addam)) || 0;
+ adSave += parseInt(evalAttr(e.data.adsave)) || 0;
+ adAttr += parseInt(evalAttr(e.data.adattr)) || 0;
+ adRogue += parseInt(evalAttr(e.data.adrogue)) || 0;
+ adSurprise += parseInt(evalAttr(e.data.adsurprise)) || 0;
if (!_.isUndefined(e.data.move) && (e.data.move || '').length) {
fixedMove = e.data.move[0] === '=';
mathMove = '+*-/'.includes(e.data.move[0]);
- movMod = moveIsFixed ? (fixedMove ? Math.max(movMod,(parseFloat(e.data.move.slice(1))||0)) : movMod) : (fixedMove ? e.data.move.slice(1) : (movMod+(!mathMove ? '+' : '')+e.data.move));
+ movMod = moveIsFixed ? (fixedMove ? Math.max(movMod,(parseFloat(evalAttr(e.data.move.slice(1)))||0)) : movMod) : (fixedMove ? evalAttr(e.data.move.slice(1)) : (movMod+(!mathMove ? '+' : '')+evalAttr(e.data.move)));
moveIsFixed = moveIsFixed || fixedMove;
};
+// if ((e.data.surpriseme && String(e.data.surpriseme).length) || (e.data.surpriseyou && String(e.data.surpriseyou).length)) {
+// doSetMod( [tokenID,'add','Surprise',e.data.name,(e.data.surpriseme ? ('sme+:'+e.data.surpriseme) : '') + ' ' +(e.data.surpriseyou ? ('syou+:'+e.data.surpriseyou) : '')], null, senderId );
+// };
ResistTab = scanForResistance( ResistTab, e.data );
});
+
adMW += adAll;
adRW += adAll;
adDmg += adAll;
adSave += adAll;
adAttr += adAll;
adRogue += adAll;
+ adSurprise += adAll;
if (moveIsFixed) movMod = '='+movMod;
@@ -7769,13 +7807,14 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
setAttr( charCS, fields.MoveMod, movMod );
setAttr( charCS, [fields.Magical_hitAdj[0]+tokenID,fields.Magical_hitAdj[1],fields.Magical_hitAdj[2],fields.Magical_hitAdj[3]], thac0Mod );
setAttr( charCS, [fields.Magical_dmgAdj[0]+tokenID,fields.Magical_dmgAdj[1],fields.Magical_dmgAdj[2],fields.Magical_dmgAdj[3]], dmgMod );
- setAttr( charCS, [fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1],fields.Magical_advantages[2],fields.Magical_advantages[3]], `${adMW}/${adRW}/${adDmg}/${adSave}/${adAttr}/${adRogue}`);
+ setAttr( charCS, [fields.Magical_advantages[0]+tokenID,fields.Magical_advantages[1],fields.Magical_advantages[2],fields.Magical_advantages[3]], `${adMW}/${adRW}/${adDmg}/${adSave}/${adAttr}/${adRogue}/${adSurprise}`);
if ((silentCmd !== 'quiet') && (!silent || newThac0 !== currentThac0)) {
makeModsDisplay( args, senderId, (baseThac0 - thac0Mod), currentHP, modValues, modMsgs );
} else {
sendWait(senderId,0,'Attk doCheckMods');
};
+ measureTime('doCheckMods',start);
return false;
} catch (e) {
sendCatchError('AttackMaster',msg_orig[senderId],e);
@@ -7792,11 +7831,10 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
* if there are no effects set the AC_current to this, otherwise
* if the two are different turn on the AC bar to indicate difference
*/
-// doCheckAC( [obj.id], findTheGM(), [], true );
-
async function doCheckAC( args, senderId, selected, silent = false, forceAC = false ) {
try {
+ const start = Date.now();
if (!args) args=[];
if (!args[0] && selected && selected.length) {
@@ -7804,7 +7842,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
}
const tokenID = args[0],
- silentCmd = args[1] || '',
+ silentCmd = (args[1] || '').toLowerCase(),
dmgType = (args[2] || 'nadj').toLowerCase(),
curToken = getObj('graphic',tokenID);
@@ -7816,7 +7854,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
if (!['sadj','padj','badj','nadj'].includes(dmgType))
{throw {name:'AttackMaster error', message:'Invalid damage type provided.'};}
- silent = silent || (silentCmd.toLowerCase().trim() == 'silent');
+ silent = silent || (silentCmd.toLowerCase().trim() == 'silent') || (silentCmd == 'quiet');
senderId = args[3] || senderId;
if (silent) sendWait(senderId,0,'Attk doCheckAC early');
@@ -7830,6 +7868,9 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
aless:{adj:0,madj:0,sadj:0,padj:0,badj:0,nadj:0}},
magicArmour = acValues.armour.magic,
isCreatureAC = acValues.armour.name.toLowerCase() === 'monster';
+
+ acValues.armour.data.ac = evalAttr(acValues.armour.data.ac);
+ acValues.armour.data.adj = evalAttr(acValues.armour.data.adj);
let prevAC = parseInt(attrLookup( charCS, fields.Armour_normal, {def:false} )),
dexBonus = !armourInfo.dexFlag ? 0 : parseInt(attrLookup( charCS, fields.Dex_acBonus ) || 0) * -1,
@@ -7845,19 +7886,21 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
_.each( acValues, (e,k) => {
if (k !== 'armour') {
- dmgAdj.armoured = _.mapObject(dmgAdj.armoured, (d,a) => {return d + parseInt(e.data[a] || 0)});
+ dmgAdj.armoured = _.mapObject(dmgAdj.armoured, (d,a) => d + parseInt(evalAttr(e.data[a] || 0)));
armouredDexBonus *= parseFloat(e.data.db || 1);
if (k === 'shield') {
- dmgAdj.armoured.adj += parseInt(e.data.ac || 1);
+ dmgAdj.armoured.adj += parseInt(evalAttr(evalAttr(e.data.ac || 1)));
} else {
- dmgAdj.sless = _.mapObject(dmgAdj.sless, (d,a) => {return d + parseInt(e.data[a] || 0)});
+ dmgAdj.sless = _.mapObject(dmgAdj.sless, (d,a) => d + parseInt(evalAttr(e.data[a] || 0)));
shieldlessDexBonus *= parseFloat(e.data.db || 1);
- dmgAdj.aless = _.mapObject(dmgAdj.aless, (d,a) => {;return d + parseInt(e.data[a] || 0)});
+ dmgAdj.aless = _.mapObject(dmgAdj.aless, (d,a) => d + parseInt(evalAttr(e.data[a] || 0)));
armourlessDexBonus *= parseFloat(e.data.db || 1);
};
}
if (is1e) {
+ e.data.ac = parseInt(evalAttr(e.data.ac || 10));
+ e.data.adj = parseInt(evalAttr(e.data.adj || 0));
switch (k) {
case 'armour':
setAttr( charCS, fields.Armor_name, e.name );
@@ -7891,8 +7934,8 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
dmgAdj.armoured.adj += dmgAdj.armoured[dmgType];
dmgAdj.sless.adj += dmgAdj.sless[dmgType];
- baseAC -= parseInt(acValues.armour.data[dmgType] || 0);
- dmgAdj.armoured.madj += parseInt(acValues.armour.data.madj || 0);
+ baseAC -= parseInt(evalAttr(acValues.armour.data[dmgType] || 0));
+ dmgAdj.armoured.madj += parseInt(evalAttr(acValues.armour.data.madj || 0));
dexBonus = !armourInfo.dexFlag ? 0 : Math.floor(armouredDexBonus * parseFloat(acValues.armour.data.db || 1));
const armourlessAC = isCreatureAC ? baseAC : 10,
touchAC = ((!isCreatureAC && state.attackMaster.touchAC) ? (armourlessAC-acMagicAdj) : baseAC);
@@ -7927,9 +7970,9 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
setAttr( charCS, fields.Armourless_back, (armourlessAC - dmgAdj.aless.adj) );
setAttr( charCS, fields.Armourless_head, (armourlessAC - dmgAdj.aless.adj - armourlessDexBonus - styleBonus - 4) );
- dmgAdj.armoured.sadj += parseInt(acValues.armour.data.sadj || 0);
- dmgAdj.armoured.padj += parseInt(acValues.armour.data.padj || 0);
- dmgAdj.armoured.badj += parseInt(acValues.armour.data.badj || 0);
+ dmgAdj.armoured.sadj += parseInt(evalAttr(acValues.armour.data.sadj || 0));
+ dmgAdj.armoured.padj += parseInt(evalAttr(acValues.armour.data.padj || 0));
+ dmgAdj.armoured.badj += parseInt(evalAttr(acValues.armour.data.badj || 0));
// set token circles & bars
@@ -7986,6 +8029,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
} else {
sendWait(senderId,0,'Attk doCheckAC');
}
+ measureTime('doCheckAC',start);
return false;
} catch (e) {
sendCatchError('AttackMaster',msg_orig[senderId],e);
@@ -8092,11 +8136,10 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
* to a spell
*/
- const doSetMod = function( args, selected, senderId, silent=true ) {
+ const doSetMod = function( args = [], selected, senderId, silent=true ) {
let selToken,
delToken = '';
- if (!args) args = [];
if (!args[0] && selected && selected.length) {
args[0] = selToken = selected[0]._id;
} else if (!args[0]) {
@@ -8120,13 +8163,13 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
targetCmd = args.slice(7).join('|'),
curToken = getObj('graphic',tokenID),
linkedToken = false,
- hpField, hpMaxField, modRow;
+ hpField, hpMaxField;
const name = args[2] || '-',
spell = args[3] || '',
- spec = parseStr(args[4] || '').replaceAll(';',':').toLowerCase(),
- saves = parseInt(evalAttr(args[5])) || NaN,
- rounds = parseInt(evalAttr(args[6])) || NaN,
+ specList = parseStr(args[4] || '').toLowerCase(),
+ saves = (args[5] === '0') ? 0 : parseInt(evalAttr(args[5])) || NaN,
+ rounds = (args[6] === '0') ? 0 : parseInt(evalAttr(args[6])) || NaN,
charCS = getCharacter(selToken),
fieldIndex = _.isUndefined(state.RPGMaster.tokenFields) ? -1 : state.RPGMaster.tokenFields.indexOf( fields.HP[0] );
@@ -8146,18 +8189,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
linkedToken = (fieldIndex >= 0 && curToken.get('bar'+(fieldIndex+1)+'_link').length);
};
- let Mods = getTable( charCS, fieldGroups.MODS ),
- tokenRows = Mods.tableFindAll( fields.Mods_tokenID, [(tokenID || charCS.id),''] ),
- type = /(?:sv[a-z0-9]{3}[;:]|save)/i.test(spec) ? 'save' :
- /(?:mr[a-z0-9]{3}[;:])/i.test(spec) ? 'resist' :
- (spec.includes('thac0') ? 'thac0' :
- (spec.includes('dmg') ? 'dmg' :
- (/(?:ad[a-z0-9]{3}[;:]|adv)/i.test(spec) ? 'adv' :
- (spec.includes('hp') ? 'hp' :
- (spec.includes('move') ? 'move' :
- (/(?:ac)?\+[;:]/i.test(spec) ? 'ac' : '')))))),
- modType = type,
- saveUpdate = false,
+ let saveUpdate = false,
mrUpdate = false,
thac0Update = false,
dmgUpdate = false,
@@ -8165,127 +8197,159 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
acUpdate = false,
hpUpdate = false,
moveUpdate = false,
+ surpriseUpdate = false,
updated = false,
- nameRows = Mods.tableFindAll( fields.Mods_name, name ) || [];
+ quietMods = false,
+ saveMenu = false,
+ quietAC = false,
+ Mods = getTable( charCS, fieldGroups.MODS ),
+ tokenRows = Mods.tableFindAll( fields.Mods_tokenID, [(tokenID || charCS.id),''] ) || [],
+ nameRows = Mods.tableFindAll( fields.Mods_name, name ) || [],
+ foundRows = _.intersection(tokenRows,nameRows),
+ modRows = foundRows.filter( r => Mods.tableLookup( fields.Mods_spellName, r ) === spell);
+
+ if (!curToken && !!selToken) curToken = getObj('graphic',selToken);
+ if (!!curToken) {
+ hpField = getTokenValue(curToken,fields.Token_HP,fields.HP,null);
+ hpMaxField = getTokenValue(curToken,fields.Token_HPmax,fields.MaxHP,null);
+ }
+
+ let type = /(?:sv[a-z0-9]{3}[;:]|save)/i.test(specList) ? 'save' :
+ /(?:mr[a-z0-9]{3}[;:]|resist)/i.test(specList) ? 'resist' :
+ /(?:surprise|(?:sme|syou)\+[;:])/i.test(specList) ? 'surprise' :
+ (specList.includes('thac0') ? 'thac0' :
+ (specList.includes('dmg') ? 'dmg' :
+ (/(?:ad[a-z0-9]{3}[;:]|adv)/i.test(specList) ? 'adv' :
+ (specList.includes('hp') ? 'hp' :
+ (specList.includes('move') ? 'move' :
+ (/(?:armour|(?:ac)?\+[;:])/i.test(specList) ? 'ac' : '')))))),
+ modType = type;
- if (type === 'resist' && !checkMagicResist( charCS ) && targetCmd && targetCmd.length) {
- if (targetCmd[0] !== '!') targetCmd = '!rounds --addTargetStatus '+tokenID+'|'+targetCmd;
- sendAPI( parseStr(targetCmd), senderId );
- return;
- } else if (targetCmd && targetCmd.length && (type === 'resist' || type === 'save')) {
- const typeText = (type !== 'save' ? 'Resistance Check' : 'Saving Throw');
- sendResponse( charCS, '&{template:'+fields.warningTemplate+'}{{title='+typeText+' Required}}{{desc=A '+typeText+' is required from '+(!curToken ? charCS.get('name') : curToken.get('name'))+'}}', senderId );
- };
-
if (cmd === 'delall' && (!_.isUndefined(tokenRows))) {
- for (let i=0; i < tokenRows.length; i++) {
- modType = Mods.tableLookup( fields.Mods_modType, tokenRows[i] ) || '';
+ tokenRows.forEach( i => {
+ modType = Mods.tableLookup( fields.Mods_modType, i ) || '';
if (!modType.length || !type.length || modType === type) {
- updated = true;
- Mods = Mods.tableSet( fields.Mods_round, tokenRows[i], -1 );
+ Mods = Mods.tableSet( fields.Mods_round, i, -1 );
+ updated = updated || true;
};
- };
+ });
} else if (cmd === 'delspell' && !_.isUndefined(tokenRows)) {
- for (let i=0; i < tokenRows.length; i++) {
- modType = Mods.tableLookup( fields.Mods_modType, tokenRows[i] ) || '';
- if ((!type.length || !modType.length || modType === type) && Mods.tableLookup( fields.Mods_spellName, tokenRows[i] ) === spell) {
- Mods = Mods.tableSet( fields.Mods_round, tokenRows[i], -1 );
- updated = true;
+ tokenRows.forEach( i => {
+ modType = Mods.tableLookup( fields.Mods_modType, i ) || '';
+ if ((!type.length || !modType.length || modType === type) && Mods.tableLookup( fields.Mods_spellName, i ) === spell) {
+ Mods = Mods.tableSet( fields.Mods_round, i, -1 );
+ updated = updated || true;
};
- };
- } else {
- if (curToken || selToken) {
- if (!curToken) curToken = getObj('graphic',selToken);
- }
- if (!!curToken) {
- hpField = getTokenValue(curToken,fields.Token_HP,fields.HP,null);
- hpMaxField = getTokenValue(curToken,fields.Token_HPmax,fields.MaxHP,null);
- }
- if (!_.isUndefined(tokenRows) && tokenRows.length) {
- let foundRows = _.intersection(tokenRows,nameRows);
- if (foundRows && foundRows.length) modRow = foundRows.find( r => Mods.tableLookup( fields.Mods_spellName, r ) === spell );
- };
- if (cmd === 'del') {
- if (!_.isUndefined(modRow)) {
- modType = type || Mods.tableLookup( fields.Mods_modType, modRow );
- Mods = Mods.tableSet( fields.Mods_round, modRow, -1 );
- updated = true;
+ });
+ } else if (cmd === 'del' && !_.isUndefined(foundRows)) {
+ modRows.forEach( i => {
+ modType = Mods.tableLookup( fields.Mods_modType, i ) || '';
+ if (!type.length || !modType.length || modType === type) {
+ Mods = Mods.tableSet( fields.Mods_round, i, -1 );
+ updated = updated || true;
};
- } else {
- if (_.isUndefined(modRow)) modRow = Mods.tableFind( fields.Mods_name, '-' );
- let specVals = _.mapObject(parseData( '['+spec+']', reModSpecs ), v => evalAttr(v,charCS) ),
- hpMaxVal = (parseInt(specVals.hpmax) || 0),
- hpChange = (parseInt(specVals.hpadj) || 0) + (parseInt(specVals.hptemp) || 0) + (parseInt(specVals.hpperm) || 0) + hpMaxVal;
-
- if (hpChange && curToken) {
- if (!linkedToken && hpField && hpField.name && hpField.barName.startsWith('bar')) {
- if (specVals.hpmax) {
- curToken.set(hpMaxField.barName+'_max',(hpMaxField.val + hpMaxVal));
- } else {
- curToken.set(hpField.barName+'_value',(hpField.val + hpChange));
- };
+ });
+ } else _.each( specList.replace(/\s/g,',').split(','), spec => {
+
+ type = /(?:sv[a-z0-9]{3}[;:]|save)/i.test(spec) ? 'save' :
+ /(?:mr[a-z0-9]{3}[;:])/i.test(spec) ? 'resist' :
+ /(?:sme|syou)\+[;:]/i.test(spec) ? 'surprise' :
+ (spec.includes('thac0') ? 'thac0' :
+ (spec.includes('dmg') ? 'dmg' :
+ (/(?:ad[a-z0-9]{3}[;:]|adv)/i.test(spec) ? 'adv' :
+ (spec.includes('hp') ? 'hp' :
+ (spec.includes('move') ? 'move' :
+ (/(?:ac)?\+[;:]/i.test(spec) ? 'ac' : '')))))),
+ modType = type;
+
+ if (type === 'resist' && !checkMagicResist( charCS ) && targetCmd && targetCmd.length) {
+ if (targetCmd[0] !== '!') targetCmd = '!rounds --addTargetStatus '+tokenID+'|'+targetCmd;
+ sendAPI( parseStr(targetCmd), senderId );
+ return;
+ } else if (targetCmd && targetCmd.length && (type === 'resist' || type === 'save')) {
+ const typeText = (type !== 'save' ? 'Resistance Check' : 'Saving Throw');
+ sendResponse( charCS, '&{template:'+fields.warningTemplate+'}{{title='+typeText+' Required}}{{desc=A '+typeText+' is required from '+(!curToken ? charCS.get('name') : curToken.get('name'))+'}}', senderId );
+ };
+
+ let specVals = _.mapObject(parseData( '['+spec+']', reModSpecs ), v => evalAttr(v,charCS) ),
+ hpMaxVal = (parseInt(specVals.hpmax) || 0),
+ hpChange = (parseInt(specVals.hpadj) || 0) + (parseInt(specVals.hptemp) || 0) + (parseInt(specVals.hpperm) || 0) + hpMaxVal;
+
+ if (hpChange && curToken) {
+ if (!linkedToken && hpField && hpField.name && hpField.barName.startsWith('bar')) {
+ if (specVals.hpmax) {
+ curToken.set(hpMaxField.barName+'_max',(hpMaxField.val + hpMaxVal));
} else {
- if (specVals.hpmax) {
- setAttr( charCS, fields.MaxHP, ((parseInt(attrLookup( charCS, fields.MaxHP )) || 0) + hpMaxVal));
- } else {
- setAttr( charCS, fields.HP, ((parseInt(attrLookup( charCS, fields.HP )) || 0) + hpChange ));
- };
+ curToken.set(hpField.barName+'_value',(hpField.val + hpChange));
+ };
+ } else {
+ if (specVals.hpmax) {
+ setAttr( charCS, fields.MaxHP, ((parseInt(attrLookup( charCS, fields.MaxHP )) || 0) + hpMaxVal));
+ } else {
+ setAttr( charCS, fields.HP, ((parseInt(attrLookup( charCS, fields.HP )) || 0) + hpChange ));
};
- if (specVals.hpmax) hpUpdate = true;
};
+ if (specVals.hpmax) hpUpdate = hpUpdate || true;
+ };
- if (cmd === 'mod' && !_.isUndefined(modRow) && Mods.tableLookup( fields.Mods_name, modRow ) !== '-') {
- let existingVals = parseData( '['+Mods.tableLookup( fields.Mods_saveSpec, modRow )+']', reModSpecs );
+ if (cmd === 'mod' && !!modRows && !!modRows.length) {
+ modRows.forEach( i => {
+ let existingVals = parseData( '['+Mods.tableLookup( fields.Mods_saveSpec, i )+']', reModSpecs );
_.each( reModSpecs, mod => {
if (mod.field === 'name' || mod.field === 'rules' || !specVals[mod.field]) return;
existingVals[mod.field] = String(evalAttr(('(' + existingVals[mod.field] + (isNaN(specVals[mod.field][0]) ? '' : '+') + specVals[mod.field] + ')'),charCS));
- updated = true;
+ updated = updated || true;
});
- Mods.tableSet( fields.Mods_saveSpec, modRow, _.chain(existingVals).omit(v => !v || v == 0).pairs().map(v => reModSpecs[v[0]].tag+':'+v[1]).value().join(',') );
- } else {
- let prefix = fieldGroups.MODS.prefix,
- values = initValues( prefix );
- values.valLine( prefix, 'name', name )
- .valLine( prefix, 'tokenID', tokenID )
- .valLine( prefix, 'modType', type )
- .valLine( prefix, 'spellName', spell )
- .valLine( prefix, 'saveSpec', spec )
- .valLine( prefix, 'modCount', saves || '' )
- .valLine( prefix, 'baseCount', ((parseInt(specVals.hptemp) != 0) ? 0 : (parseInt(hpField.val) || 0)) )
- .valLine( prefix, 'maxHP', ((hpMaxVal == 0) ? 0 : parseInt(hpMaxField.val) || 0) )
- .valLine( prefix, 'round', (Number.isNaN(rounds) ? '' : (state.initMaster.round + rounds)) )
- .valLine( prefix, 'curRound', (Number.isNaN(rounds) ? '' : state.initMaster.round) )
- .valLine( prefix, 'basis', '' )
- .valLine( prefix, 'cmd', targetCmd )
- if (cmd.includes('=')) {
- cmd = cmd.split('=');
- let basis = _.find(saveFormat.Saves, f => f.tag === cmd[1]);
- if (basis) {
- values.valLine( prefix, 'basis', cmd[1] )
- .valLine( prefix, 'tag', cmd[0] )
- .valLine( prefix, 'modField', cmd[0]+'mod' )
- .valLine( prefix, 'saveField', basis.save[0] )
- .valLine( prefix, 'index', basis.index )
- .valLine( prefix, 'roll', basis.roll );
- }
+ Mods.tableSet( fields.Mods_saveSpec, i, _.chain(existingVals).omit(v => !v || v == 0).pairs().map(v => reModSpecs[v[0]].tag+':'+v[1]).value().join(',') );
+ });
+ } else {
+ let prefix = fieldGroups.MODS.prefix,
+ values = initValues( prefix );
+ values.valLine( prefix, 'name', name )
+ .valLine( prefix, 'tokenID', tokenID )
+ .valLine( prefix, 'modType', type )
+ .valLine( prefix, 'spellName', spell )
+ .valLine( prefix, 'saveSpec', spec )
+ .valLine( prefix, 'modCount', saves || '' )
+ .valLine( prefix, 'baseCount', ((parseInt(specVals.hptemp) != 0) ? 0 : (parseInt(hpField.val) || 0)) )
+ .valLine( prefix, 'maxHP', ((hpMaxVal == 0) ? 0 : parseInt(hpMaxField.val) || 0) )
+ .valLine( prefix, 'round', (Number.isNaN(rounds) ? '' : (state.initMaster.round + rounds)) )
+ .valLine( prefix, 'curRound', (Number.isNaN(rounds) ? '' : state.initMaster.round) )
+ .valLine( prefix, 'basis', '' )
+ .valLine( prefix, 'cmd', targetCmd );
+ if (cmd.includes('=')) {
+ cmd = cmd.split('=');
+ let basis = _.find(saveFormat.Saves, f => f.tag === cmd[1]);
+ if (basis) {
+ values.valLine( prefix, 'basis', cmd[1] )
+ .valLine( prefix, 'tag', cmd[0] )
+ .valLine( prefix, 'modField', cmd[0]+'mod' )
+ .valLine( prefix, 'saveField', basis.save[0] )
+ .valLine( prefix, 'index', basis.index )
+ .valLine( prefix, 'roll', basis.roll );
}
- Mods = Mods.addTableRow( modRow, values );
- };
- saveUpdate = /(?:sv[a-z0-9]{3}[;:]|save)/i.test(spec);
- mrUpdate = /(?:mr[a-z0-9]{3}[;:]|save)/i.test(spec);
- thac0Update = spec.includes('thac0');
- dmgUpdate = spec.includes('dmg');
- advUpdate = /(?:ad[a-z0-9]{3}[;:]|adv)/i.test(spec);
- moveUpdate = spec.includes('move');
- acUpdate = /(?:ac)\+[:;]/i.test(spec);
-
+ }
+ let newRow = Mods.tableFind( fields.Mods_name, '-' );
+ Mods = Mods.addTableRow( newRow, values );
};
- }
+ saveUpdate = saveUpdate || /(?:sv[a-z0-9]{3}[;:]|save)/i.test(spec);
+ mrUpdate = mrUpdate || /(?:mr[a-z0-9]{3}[;:]|save)/i.test(spec);
+ thac0Update = thac0Update || spec.includes('thac0');
+ dmgUpdate = dmgUpdate || spec.includes('dmg');
+ advUpdate = advUpdate || /(?:ad[a-z0-9]{3}[;:]|adv)/i.test(spec);
+ moveUpdate = moveUpdate || spec.includes('move');
+ acUpdate = acUpdate || /(?:ac)\+[:;]/i.test(spec);
+ surpriseUpdate = surpriseUpdate || /(?:sme|syou)\+[;:]/i.test(spec);
+
+ quietMods = quietMods || ['save','resist','ac'].includes(modType);
+ saveMenu = saveMenu || (modType === 'save');
+ quietAC = quietAC || (modType !== 'ac');
+ });
if (!delToken) {
const playerConfig = getSetPlayerConfig( senderId );
- if (updated || mrUpdate || thac0Update || dmgUpdate || advUpdate || hpUpdate || moveUpdate) doCheckMods( [selToken,((silent || ['save','resist','ac'].includes(modType))?'quiet':'')], senderId, [] );
- if ((updated || saveUpdate) && (!playerConfig || !playerConfig.manualCheckSaves)) setTimeout( () => handleCheckSaves( [selToken,'',((silent || modType !== 'save') ? 'nomenu' : '')], senderId, selected, (silent || modType !== 'save') ), 250);
- if (updated || acUpdate) setTimeout( () => doCheckAC( [selToken,((silent || modType !== 'ac')?'quiet':'')], senderId, [] ), 500);
+ if (updated || mrUpdate || thac0Update || dmgUpdate || advUpdate || hpUpdate || moveUpdate) doCheckMods( [selToken,((silent || quietMods)?'quiet':'')], senderId, [] );
+ if ((updated || saveUpdate) && (!playerConfig || !playerConfig.manualCheckSaves)) setTimeout( () => handleCheckSaves( [selToken,'',((silent || !saveMenu) ? 'nomenu' : '')], senderId, selected, (silent || !saveMenu) ), 250);
+ if (updated || acUpdate) setTimeout( () => doCheckAC( [selToken,((silent || quietAC)?'quiet':'')], senderId, [] ), 500);
};
return;
};
@@ -8759,6 +8823,11 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
msg = value ? 'Roll NPC attributes if not already rolled' : 'Do not roll NPC attributes automatically';
break;
+ case 'parry':
+ state.attackMaster.parry = value;
+ msg = value ? 'Parrying uses DMG rules' : 'Parrying uses Complete Fighter Handbook rules';
+ break;
+
case 'touchac':
state.attackMaster.touchAC = value;
msg = value ? 'Touch attacks ignore base AC of armour' : 'Touch attacks do not ignore base AC of armour';
@@ -8928,6 +8997,18 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
handleTurnCheck( args, selected, senderId );
break;
+ case BT.SET_SHOT :
+ handleFixBodyPart( args, senderId );
+ break;
+
+ case BT.ATTK_MOD :
+ handleToggleAttkMod( args, senderId );
+ break;
+
+ case BT.ATTK_TABLE51 :
+ handleTable51( args, senderId );
+ break;
+
default :
sendError('Invalid attackMaster syntax');
};
@@ -9044,7 +9125,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
try {
if (!sendGMquery( 'attk', arg, senderId )) {
-
+ const startCmd = Date.now();
cmd = (i<0 ? arg : arg.substring(0,i)).trim().toLowerCase();
argString = (i<0 ? '' : arg.substring(i+1).trim());
arg = argString.split('"');
@@ -9060,6 +9141,7 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
} else {
arg = argString.split('|');
}
+ let measure = true;
switch (cmd) {
case 'display-ability':
@@ -9226,19 +9308,23 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
case 'hsq':
case 'handshake':
doHsQueryResponse(arg);
+ measure = false;
break;
case 'hsr':
doHandleHsResponse(arg);
+ measure = false;
break;
case 'handout':
case 'handouts':
if (isGM) updateHandouts(handouts,false,senderId);
+ measure = false;
break
case 'button':
doButton(arg,isGM,senderId,selected);
break;
case 'help':
showHelp();
+ measure = false;
break;
case 'relay':
if (isGM) doRelay(argString,senderId);
@@ -9248,15 +9334,19 @@ var attackMaster = (function() { // eslint-disable-line no-unused-vars
doSetDebug(argString,senderId);
break;
case 'nowaitmsg':
+ measure = false;
break;
default:
+ measure = false;
showHelp();
sendFeedback('Invalid command " '+msg.content+' "',flags.feedbackName,flags.feedbackImg);
}
+ if (measure) measureTime( 'Total Cmd', startCmd );
}
} catch (err) {
sendCatchError('AttackMaster',msg_orig[senderId],err,('!attk --'+e));
}
+ if (!!state.attackMaster.debug) reportTimes('!attk --'+e);
};
diff --git a/AttackMaster/script.json b/AttackMaster/script.json
index b58e9bb879..4b6cf37764 100644
--- a/AttackMaster/script.json
+++ b/AttackMaster/script.json
@@ -2,8 +2,8 @@
"$schema": "https://github.com/DameryDad/roll20-api-scripts/blob/AttackMaster/AttackMaster/Script.json",
"name": "AttackMaster",
"script": "attackMaster.js",
- "version": "5.3.2",
- "previousversions": ["1.036","1.038","1.039","1.041","1.042","2.046","1.3.00","1.3.01","1.3.02","1.3.03","1.4.01","1.4.02","1.4.03","1.4.04","1.4.05","1.4.06","1.4.07","1.5.01","1.5.02","1.5.03","2.1.0","2.2.0","2.3.0","2.3.1","2.3.2","2.3.3","3.0.0","3.1.2","3.2.0","3.2.1","3.3.0","3.4.0","3.5.0","4.0.1","4.0.2","4.0.3","4.0.4","5.0.0","5.0.1","5.0.2","5.0.3","5.0.4","5.0.5","5.0.6","5.3.0","5.3.1"],
+ "version": "5.4.2",
+ "previousversions": ["1.036","1.038","1.039","1.041","1.042","2.046","1.3.00","1.3.01","1.3.02","1.3.03","1.4.01","1.4.02","1.4.03","1.4.04","1.4.05","1.4.06","1.4.07","1.5.01","1.5.02","1.5.03","2.1.0","2.2.0","2.3.0","2.3.1","2.3.2","2.3.3","3.0.0","3.1.2","3.2.0","3.2.1","3.3.0","3.4.0","3.5.0","4.0.1","4.0.2","4.0.3","4.0.4","5.0.0","5.0.1","5.0.2","5.0.3","5.0.4","5.0.5","5.0.6","5.3.0","5.3.1","5.3.2"],
"description": "AttackMaster API for AD&D 2E provides functions to manage weapons, armour & shields, including taking weapons in hand and using them to attack, ranged weapon range and ammo management; penalties & bonuses for non-proficiency, proficiency, specialisation & mastery; 1-handed, 2-handed or many-handed weapons, and multi-weapon attacks.\n[AttackMaster Documentation](https://wiki.roll20.net/Script:AttackMaster) \n\n### Related APIs\nThis API works best with the RPGMaster series of APIs\n[RPGMaster Documentation](https://wiki.roll20.net/RPGMaster) \n\n### Getting Started\n* After installation, add the commands `!attk --menu` and `!attk --other-menu` as Ability Macros on Character Sheets of Characters, NPCs & Monsters that will use the API, and tick 'Show as Token Action'. These menus will then be available to Players controlling those sheets and give access to all common commands used in game-play.",
"authors": "Richard E.",
"roll20userid": "6497708",