Skip to content

Instantly share code, notes, and snippets.

@ConstableBrew
Last active August 13, 2020 14:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ConstableBrew/3ec323bd3bd9d9bcfce11e239c11e16a to your computer and use it in GitHub Desktop.
Save ConstableBrew/3ec323bd3bd9d9bcfce11e239c11e16a to your computer and use it in GitHub Desktop.
Suggested Tactics for DnDB Encounter Builder
import React from 'react';
import PropTypes from 'prop-types';
const StealthIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.375 17.22v94.28l20.47 14.25 6.374-32.063 50.468 33.22L71.844 37.03l29.53 14.032L91.22 17.22H16.374zm119.72 0l19.843 42.03-42.032 46.72H155l4.656 39.718L180.686 120l10.408 61.938 38.75-56.72 25.22 32.657 24.186-91.156 33.78 21.124.595-50.78 47.656 24.436-3.03 67.5 28.438-41.813 12.78 22.126 60.157-64.25 1.188 44.187 33.875-42.625V17.22H136.094zM85.468 186.03c-16.63.038-34.107 1.616-52.345 4.376l-7.906 1.188-.064 7.97c-.35 40.24 4.84 67.31 16.125 85.936 11.287 18.627 29.17 27.313 48.626 29.938 6.937.935 14.17 1.266 21.656 1.218v-92.47c-18.67 4.285-32.593 20.996-32.593 40.97 0 12.76 5.68 24.195 14.655 31.906-.407-.048-.817-.102-1.22-.156-16.03-2.162-26.822-7.34-35.155-21.094-7.72-12.74-12.852-34.287-13.313-67.968 14.533-1.918 28.44-3.073 41.47-3.094 25.72-.042 48.403 3.894 67.593 12.78 27.768 12.86 49.145 36.087 63.406 75.626-23.567-1.313-45.838.293-65.906 1.97 7.748-7.628 12.563-18.238 12.563-29.97 0-20.04-14.044-36.785-32.813-41v91.75c29.98-2.004 63.35-7.024 98.438-3.187l13.968 1.53-4-13.47c-15.143-51.256-42.066-83.663-77.812-100.217-22.342-10.347-47.66-14.594-75.375-14.532zm347.624 0c-27.716-.06-53.034 4.186-75.375 14.532-35.748 16.555-62.67 48.962-77.814 100.22l-3.97 13.468 13.94-1.53c35.57-3.89 69.386 1.335 99.687 3.28v-92.125c-19.397 3.735-34.063 20.795-34.063 41.28 0 11.726 4.822 22.344 12.563 29.97-20.063-1.682-42.327-3.31-65.875-2 14.26-39.522 35.645-62.737 63.406-75.594 19.19-8.886 41.87-12.822 67.594-12.78 13.038.022 26.924 1.173 41.468 3.094-.46 33.68-5.624 55.228-13.344 67.97-8.333 13.753-19.093 18.93-35.125 21.092-.41.056-.832.107-1.25.156 8.984-7.71 14.688-19.137 14.688-31.906 0-19.525-13.332-35.927-31.375-40.656v92.156c7.04.004 13.86-.335 20.406-1.22 19.458-2.623 37.37-11.31 48.656-29.936 11.287-18.627 16.443-45.695 16.094-85.938l-.062-7.968-7.875-1.188c-18.24-2.76-35.747-4.338-52.376-4.375zm37.937 139.19l-2.717 35.124-80.407-4.063 78.375 66.25-39.874 9.44 68.28 27.03.002-105.406-23.657-28.375zm-131.124 12.03L274.5 422.53l-12.28-42.06-29.19 96.936-50.218-67.72-14.593 85.845h257.53l-6.406-34.467-53.156 19.875-9.344-53.157-16.938 13.44V337.25zm-300.78 15.188l-22.75 19.843v123.25h130.06l-16.186-76.5-13.875 16.72-24.688-54.125-33.28 54.344-19.282-83.533z"
/>
</svg>;
StealthIconSvg.propTypes = {
className: PropTypes.string,
};
StealthIconSvg.defaultProps = {
className: '',
};
export default StealthIconSvg;
diff --git a/js/analytics/constants.js b/js/analytics/constants.js
index 6d8d507..b029a88 100644
--- a/js/analytics/constants.js
+++ b/js/analytics/constants.js
@@ -183,6 +183,7 @@ export const EventActions = {
DDB_InfoTooltip_DifficultyViewed: 'Difficulty Viewed',
DDB_InfoTooltip_Viewed: 'Viewed',
DDB_InfoTooltip_XpMultiplierViewed: 'XP Multiplier Viewed',
+ DDB_InfoTooltip_TacticsViewed: 'Tactics Viewed',
DDB_Maintenance_BuildCharacterClicked: 'Build Character Clicked',
DDB_Maintenance_PageViewed: 'Maintenance Page Viewed',
diff --git a/js/analytics/index.js b/js/analytics/index.js
index 8c080f8..a281a32 100644
--- a/js/analytics/index.js
+++ b/js/analytics/index.js
@@ -1077,6 +1077,14 @@ const logMonsterListingSortClicked = ({ sortBy, sortOrder }) =
> {
});
};
+const logInfoTooltipTacticsViewed = monsterId => {
+ logEvent(SessionNames.DDB, {
+ category: EventCategories.DDB_InfoTooltip,
+ action: EventActions.DDB_InfoTooltip_TacticsViewed,
+ label: monsterId,
+ });
+};
+
const logNewFeatureClickedWhatsNewButton = viewed => {
logEvent(SessionNames.DDB, {
category: EventCategories.DDB_NewFeature,
@@ -1393,6 +1401,7 @@ export default {
logInfoTooltipDifficultyViewed,
logInfoTooltipViewed,
logInfoTooltipXpMultiplierViewed,
+ logInfoTooltipTacticsViewed,
logMaintenanceBuildCharacterClicked,
logMaintenancePageViewed,
import React from 'react';
import PropTypes from 'prop-types';
const BruteIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M448 36c-29.4 44.05-63.2 65.7-126.3 64.8A79.99 75.99 0 0 0 256 68.01a79.99 75.99 0 0 0-65.8 32.79c-63 .9-96.85-20.77-126.2-64.8-30.29 45.43 21.04 110.9 112.2 112.4a79.99 75.99 0 0 0 8.1 29.2C44.84 197.1 16.82 388.1 32 464h80c0-48 16-112 64-144l-16 144c0 16 64 16 64 0 0-32 16-64 32-64s32 32 32 64c0 16 64 16 64 0l-16-144c48 32 64 96 64 144h80c15.2-75.9-12.8-267-152.4-286.4a79.99 75.99 0 0 0 8.2-29.2C426.9 146.9 478.3 81.44 448 36zm-256 87.8c13.5 15.7 27.2 31.3 48 40.2 0 0-22.9 15.7-32 8.7-10.1-7.9-16-48.9-16-48.9zm128 0s-5.9 41-16 48.9c-9.1 7-32-8.7-32-8.7 20.8-8.9 34.5-24.5 48-40.2z"
/>
</svg>;
BruteIconSvg.propTypes = {
className: PropTypes.string,
};
BruteIconSvg.defaultProps = {
className: '',
};
export default BruteIconSvg;
import React from 'react';
import PropTypes from 'prop-types';
const CowardIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M45.215 19.162v438.443l18.687-27.334V19.163H45.215zm50.21 0v408.082h18.688V19.162H95.426zm54.07 0V294.11h3.21c4.71-5.543 9.902-10.77 15.48-15.684V19.162h-18.69zm50.21 0v236.98c6.047-3.43 12.296-6.582 18.69-9.44V19.16h-18.69zm50.21 0v216.033c6.18-1.787 12.42-3.34 18.69-4.625V19.162h-18.69zm48.923 0v207.262c5.177-.357 10.33-.54 15.432-.512 1.088.006 2.17.03 3.255.055V19.162h-18.687zm54.07 0v211.12c4.172.978 8.25 2.142 12.237 3.46 1.688-2.06 3.45-4.088 5.31-6.07.376-.4.763-.78 1.143-1.174V19.162h-18.69zm50.21 0v184.172c6.14-2.986 12.41-5.27 18.69-6.81V19.16h-18.69zm50.208 0v176.336c6.655 1.35 12.978 3.786 18.69 7.395V19.163h-18.69zM440.61 212.85c-3.508-.006-7.176.35-10.97 1.074-15.174 2.895-31.835 11.907-45.554 26.533-4.884 5.208-9.023 10.747-12.434 16.412-93.246-57.583-331.013 75.585-179.697 189.32H72.64l-26.453 47.744h257.165l-88.48-103.25c24.272-26.71 67.455-43.708 96.997-45.067 13.792 45.098 36.248 113.5 71.734 148.315h60.865c-43.9-47.444-77.84-111.502-82.236-183.94 1.887 5.67 4.938 10.597 9.137 14.55 9.046 8.518 22.192 11.497 37.366 8.603 15.175-2.895 31.838-11.905 45.557-26.532 13.72-14.626 21.666-31.854 23.61-47.22 1.943-15.367-1.85-28.35-10.895-36.868-6.784-6.387-15.875-9.66-26.4-9.675z"
/>
</svg>;
CowardIconSvg.propTypes = {
className: PropTypes.string,
};
CowardIconSvg.defaultProps = {
className: '',
};
export default CowardIconSvg;
import React from 'react';
import PropTypes from 'prop-types';
const HordeIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M64.865 21.293c-11.48 0-20.785 10.696-20.785 23.887 0 8.72 4.068 16.345 10.14 20.515H15.308v18.688h40.228v23.365l-24.56 49.422 16.736 8.318 17.17-34.547 17.167 34.548 16.736-8.318-24.56-49.422V84.383h40.228V65.695H75.51c6.072-4.17 10.14-11.796 10.14-20.515 0-13.193-9.305-23.887-20.785-23.887zm126.895 0c-11.48 0-20.785 10.696-20.785 23.887 0 8.72 4.068 16.345 10.14 20.515H142.2v18.688h40.23v23.365l-24.56 49.422 16.735 8.318 17.168-34.547 17.168 34.548 16.738-8.318-24.56-49.422V84.383h40.228V65.695h-38.942c6.073-4.17 10.14-11.796 10.14-20.515 0-13.193-9.305-23.887-20.784-23.887zm123.244 0c-11.48 0-20.785 10.696-20.785 23.887 0 8.72 4.067 16.345 10.14 20.515h-38.913v18.688h40.227v23.365l-24.56 49.422 16.736 8.318 17.17-34.55 17.168 34.55 16.734-8.318-24.56-49.418v-23.37h40.228V65.696h-38.94c6.073-4.17 10.14-11.796 10.14-20.515 0-13.193-9.307-23.887-20.786-23.887zm122.164 0c-11.48 0-20.785 10.696-20.785 23.887 0 8.72 4.068 16.345 10.14 20.515H387.61v18.688h40.228v23.365l-24.56 49.422 16.736 8.318 17.168-34.547 17.168 34.548 16.736-8.318-24.56-49.418v-23.37h40.228V65.696h-38.94c6.073-4.17 10.14-11.796 10.14-20.515 0-13.193-9.307-23.887-20.786-23.887zM64.865 187.153c-11.48 0-20.785 10.697-20.785 23.888 0 8.72 4.067 16.344 10.14 20.515H15.306v18.69h40.228v23.364l-24.56 49.42 16.736 8.318L64.88 296.8l17.167 34.548 16.736-8.317-24.56-49.42v-23.366h40.228v-18.69H75.513c6.07-4.17 10.138-11.794 10.138-20.513 0-13.192-9.305-23.886-20.785-23.886zm126.895 0c-11.48 0-20.785 10.697-20.785 23.888 0 8.72 4.067 16.344 10.138 20.515H142.2v18.69h40.23v23.364l-24.56 49.42 16.735 8.318 17.168-34.547 17.168 34.548 16.738-8.317-24.56-49.42v-23.366h40.228v-18.69h-38.94c6.072-4.17 10.14-11.794 10.14-20.513 0-13.192-9.307-23.886-20.786-23.886zm123.244 0c-11.48 0-20.785 10.697-20.785 23.888 0 8.72 4.066 16.344 10.137 20.515h-38.91v18.69h40.227v23.364l-24.56 49.42 16.736 8.318 17.17-34.547 17.168 34.548 16.734-8.317-24.56-49.417v-23.37h40.228v-18.688h-38.938c6.072-4.17 10.14-11.795 10.14-20.514 0-13.192-9.31-23.886-20.788-23.886zm122.164 0c-11.48 0-20.785 10.697-20.785 23.888 0 8.72 4.067 16.344 10.138 20.515h-38.91v18.69h40.228v23.364l-24.56 49.42 16.736 8.318 17.168-34.547 17.168 34.548 16.736-8.317-24.56-49.417v-23.37h40.228v-18.688h-38.938c6.072-4.17 10.14-11.795 10.14-20.514 0-13.192-9.31-23.886-20.788-23.886zM64.865 352.43c-11.48 0-20.785 10.695-20.785 23.886 0 8.72 4.067 16.345 10.14 20.516H15.306v18.69h40.228v23.365l-24.56 49.422 16.736 8.315 17.17-34.547 17.167 34.547 16.736-8.316-24.56-49.423V415.52h40.228v-18.688H75.513c6.07-4.17 10.138-11.797 10.138-20.516 0-13.192-9.305-23.886-20.785-23.886zm126.895 0c-11.48 0-20.785 10.695-20.785 23.886 0 8.72 4.067 16.345 10.138 20.516H142.2v18.69h40.23v23.365l-24.56 49.422 16.735 8.315 17.168-34.547 17.168 34.547 16.738-8.316-24.56-49.423V415.52h40.228v-18.688h-38.94c6.072-4.17 10.14-11.797 10.14-20.516 0-13.192-9.307-23.886-20.786-23.886zm123.244 0c-11.48 0-20.785 10.695-20.785 23.886 0 8.72 4.066 16.345 10.137 20.516h-38.91v18.69h40.227v23.365l-24.56 49.422 16.736 8.315 17.17-34.547 17.168 34.547 16.734-8.316-24.56-49.42v-23.37h40.228v-18.688h-38.938c6.072-4.17 10.14-11.797 10.14-20.516 0-13.192-9.31-23.886-20.788-23.886zm122.164 0c-11.48 0-20.785 10.695-20.785 23.886 0 8.72 4.067 16.345 10.138 20.516h-38.91v18.69h40.228v23.365l-24.56 49.422 16.736 8.315 17.168-34.547 17.168 34.547 16.736-8.316-24.56-49.42v-23.37h40.228v-18.688h-38.938c6.072-4.17 10.14-11.797 10.14-20.516 0-13.192-9.31-23.886-20.788-23.886z"
/>
</svg>;
HordeIconSvg.propTypes = {
className: PropTypes.string,
};
HordeIconSvg.defaultProps = {
className: '',
};
export default HordeIconSvg;
import React from 'react';
import PropTypes from 'prop-types';
const RangedIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M492.656 20.406l-118.594 56.22L413.875 86l-86.97 86.97-305.5 259.374.69.687 104.75-47.467-46.376 105.843.905.906 272.5-319.875 73.22-73.218 9.342 39.81 56.22-118.624zm-473.25.063c-1.347 23.43 5 39.947 16.563 52.218l24.093 302.28 17.562-14.874-21.72-272.438c57.975 31.954 169.096 25.165 216.907 106.72l66.625-56.564 1.22-1.218C292.74 38.666 86.01 99.716 19.406 20.47zm359.531 151.56l-1.156 1.157-57.25 67.188c82.006 47.945 75.587 159.267 107.283 218.03l-272.157-24.5-14.812 17.408 301.562 27.125c12.48 12.283 29.4 19.084 53.688 17.687-79.95-67.2-18.36-275.754-117.156-324.094z"
/>
</svg>;
RangedIconSvg.propTypes = {
className: PropTypes.string,
};
RangedIconSvg.defaultProps = {
className: '',
};
export default RangedIconSvg;
import React from 'react';
import PropTypes from 'prop-types';
const SkirmishIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M241.844 28.625l-21.188 5.063L33.25 78.53l-9.594 2.282 2.813 9.47 54.718 184.03 6.156 20.782 10.875-18.75 36.624-63.125 39.344 22.655 9.375-16.188-47.47-27.312L128 187.72l-4.656 8.06-30.406 52.47-45.75-153.844 156.625-37.47-30.344 52.345-4.69 8.126 8.126 4.656L332.75 211.75l-17.594 30.344 16.22 9.312 22.25-38.375 4.687-8.124-8.125-4.656-155.844-89.688 36.594-63.093 10.906-18.845zm-28.25 176.47l-57.438 99.31 155.22 89.5 8.093 4.658-4.69 8.093-44.06 76.25 218.81-52.5-63.874-215.47-44.094 76.25-4.656 8.064-8.094-4.656-155.218-89.5z"
/>
</svg>;
SkirmishIconSvg.propTypes = {
className: PropTypes.string,
};
SkirmishIconSvg.defaultProps = {
className: '',
};
export default SkirmishIconSvg;
import React from 'react';
import PropTypes from 'prop-types';
const StabbyIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M180.75 22.97l-17.72 5.968 25.345 75.406 17.72-6.03-25.345-75.345zm73.906 78.56L149.97 137.157l10.155 29.875c8.316-9.75 17.78-17.498 28.125-23.374 7.232 27.494 16.666 54.12 27.813 79.906 13-5.56 26.423-10.197 39.906-13.718-6.967-26.727-15.822-53.187-26.345-79.313 11.295-1.24 23.1-.91 35.22.94l-10.19-29.94zm191.688 88.22c-19.464-.103-42.28 9.843-60.875 28.438-5.778 5.776-10.684 11.978-14.75 18.343-28.734-17.313-69.766-18.263-110.22-7.968-.02-.093-.04-.187-.063-.28-12.357 3.134-24.726 7.248-36.687 12.312l.188.406c-87.328 37.506-151.902 123.99-48.032 202.063H46.03L17.25 495.03h279.875l-96.28-112.374c26.414-29.067 73.41-47.584 105.56-49.062C321.42 382.674 345.85 457.11 384.47 495h66.25c-46.88-50.664-83.318-118.734-89.19-195.75 1.76 3.523 4.022 6.77 6.845 9.594 20.312 20.312 60.906 12.657 90.656-17.094 29.752-29.75 37.407-70.344 17.095-90.656-7.617-7.617-18.103-11.282-29.78-11.344z"
/>
</svg>;
StabbyIconSvg.propTypes = {
className: PropTypes.string,
};
StabbyIconSvg.defaultProps = {
className: '',
};
export default StabbyIconSvg;
import React from 'react';
import PropTypes from 'prop-types';
const TacticsIconSvg = ({ className }) =>
<svg
className={className}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M458.125 19.125c-9.603 0-17.188 7.585-17.188 17.188 0 9.602 7.585 17.187 17.188 17.187 9.603 0 17.188-7.585 17.188-17.188 0-9.602-7.585-17.187-17.188-17.187zM377.78 40.938c-160.32.806-218.11 217.493-362.936 96.28 3.244 36.77 88.5 78.407 88.5 78.407-26.103 19.995-34.85 24.705-72.063 25.438 104.168 86.748 338.695-99.8 408.408 40.093l-2.25-38.312-45.47-46.563 42.75.657-.593-10.28-50.75-32.188 48.406-7.22-.655-11.063-50.03-27.25L429 100.094l-.625-10.688-52.156-22.75 50.467-5.844-.812-13.906c-17.043-4.2-33.02-6.044-48.094-5.968zm70.845 29.968l20.188 428 18.656-.875L467.31 71c-2.933.784-6.018 1.188-9.187 1.188-3.283 0-6.472-.442-9.5-1.282zm-93.438 221.97l-43.562 70.03 21.25 25.03L317.22 493h18.874l15.25-102.28 27.937-20.22-24.092-77.625zM198.47 310.78l-28.814 69.783 20.53 20.218 2.22 92.22h18.688l-2.22-92.97 19.595-19.936-30-69.313zm-158.157 5l-16.407 61.876 17.657 13.28L56.406 493H75.28L60.063 388.25l13.22-17.594-32.938-54.875h-.032zm86 7.44l-28.25 57.467 15.156 16.97L106.5 493h18.75l6.72-95.28 15.905-14.19-21.563-60.31zm136.843 32.31L243.72 423.94l19.405 15.125L268.938 493h18.812l-5.875-54.28 16.813-21.595-35.532-61.594zm143.656.064l-18.906 68.562 20.313 15.563 6.155 53.28h18.813l-6.375-55.188 16.03-20.906-36.03-61.312z"
/>
</svg>;
TacticsIconSvg.propTypes = {
className: PropTypes.string,
};
TacticsIconSvg.defaultProps = {
className: '',
};
export default TacticsIconSvg;
diff --git a/js/monster/EncounterMonster.jsx b/js/monster/EncounterMonster.jsx
index 22ed983..2e53f22 100644
--- a/js/monster/EncounterMonster.jsx
+++ b/js/monster/EncounterMonster.jsx
@@ -7,6 +7,7 @@ import InputStepper from 'input/InputStepper';
import MonsterUtils from './MonsterUtils';
import SecretSelectors from 'secret/SecretSelectors';
import MonsterDetails from './MonsterDetails';
+import MonsterTactics from 'tactics/MonsterTactics';
import ChevronUpSvg from 'icons/ChevronUpSvg';
import ChevronDownSvg from 'icons/ChevronDownSvg';
import Button from '../input/Button';
@@ -119,6 +120,16 @@ export class EncounterMonster extends PureComponent {
<span className="difficulty__label" title="Experience Points">XP: </span>
<span className="difficulty__value">{MonsterUtils.getXp(this.props.monster)}</span>
</div>
+ {MonsterUtils.canAccess(this.props.monster) &&
+ <div>
+ <span className="difficulty__label" title="Tactics">Tactics: </span>
+ <span className="difficulty__value">
+ <MonsterTactics
+ monster={this.props.monster}
+ />
+ </span>
+ </div>
+ }
</div>
</div>
{this.props.isReadOnly ?
diff --git a/js/monster/EncounterMonster.scss b/js/monster/EncounterMonster.scss
index 9eb07df..de92b69 100644
--- a/js/monster/EncounterMonster.scss
+++ b/js/monster/EncounterMonster.scss
@@ -96,6 +96,25 @@ $quantity-width: 34px;
}
}
}
+
+ .tactics {
+ color: $text-dark;
+
+ svg {
+ width: 1.5em;
+ height: 1.5em;
+ margin-right: 0em;
+ fill: $text-dark;
+ }
+
+ .dark-mode-enabled & {
+ color: $text-light;
+
+ svg {
+ fill: $text-light;
+ }
+ }
+ }
}
&__quantity {
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import './MonsterTactics.scss';
import analytics from 'analytics';
import TacticsIconSvg from 'icons/TacticsIconSvg';
import InfoTooltip from 'tooltip/InfoTooltip';
import CitationLink from 'utils/CitationLink';
import {
HorizontalPosition,
VerticalPosition,
} from 'utils/Popover';
import { getTactics } from 'tactics/MonsterTacticsUtils';
class MonsterTactics extends PureComponent {
static propTypes = {
monster: PropTypes.object.isRequired,
};
render() {
const tactics = getTactics(this.props.monster);
return (
<React.Fragment>
{tactics.map(({ icon: Icon }) => Icon && <Icon className="monster-tactics__icon"/> || null)}
<InfoTooltip
analyticsLabel="Suggested Tactics"
className='monster-tactics__tooltip'
onMount={() => analytics.logInfoTooltipTacticsViewed(this.props.monster.id)}
title="Suggested Tactics"
tooltipProps={{
horizontalPosition: HorizontalPosition.Right,
verticalPosition: VerticalPosition.Center,
offsetY: 20,
}}
>
<div
className="monster-tactics__body"
>
<ul>
{tactics.map(({ icon: Icon, text }) => <li>{Icon && <Icon className="monster-tactics__icon"/> || null} {text}</li>)}
</ul>
<CitationLink
className="encounter-suggested-tactics__citation"
href="https://www.themonstersknow.com/why-these-tactics/"
>
(The Monsters Know What They&apos;re Doing: Why These Tactics)
</CitationLink>
</div>
</InfoTooltip>
</React.Fragment>
);
}
}
export default MonsterTactics;
@import '_variables';
.monster-tactics {
&__icon {
width: 1.5em;
height: 1.5em;
margin: 0 0.2em -0.3em 0;
fill: $text-dark;
.dark-mode-enabled & {
fill: $text-light;
}
}
&__body {
ul {
list-style: initial;
padding: 15px;
}
}
}
import MonsterUtils from 'monster/MonsterUtils';
import BruteIconSvg from 'icons/BruteIconSvg';
import CowardIconSvg from 'icons/CowardIconSvg';
import HordeIconSvg from 'icons/HordeIconSvg';
import RangedIconSvg from 'icons/RangedIconSvg';
import SkirmishIconSvg from 'icons/SkirmishIconSvg';
import StabbyIconSvg from 'icons/StabbyIconSvg';
import StealthIconSvg from 'icons/StealthIconSvg';
import TacticsIconSvg from 'icons/TacticsIconSvg';
// What row of the tactics intelligence premises table to use based on ability score
const TacticsPremisesIntelligenceIndex = {
SevenOrLess: 0,
EightToEleven: 1,
TwelveToThirteen: 2,
FourteenOrMore: 3,
};
// Intelligence tactics premises from The Monsters Know What They're Doing
// https://www.themonstersknow.com/why-these-tactics/
const TacticsPremisesIntelligence = [
'A creature with Intelligence of 7 or less operates purely from instinct. That doesn’t mean it uses its features ineffectively, only that it has one preferred modus operandi and isn’t going to be able to adjust if it stops working.',
'A creature with Intelligence of 8 to 11 is unsophisticated in its tactics and largely lacking in strategy, but it can tell when things are going wrong and adjust to some degree.',
'A creature with Intelligence of 12 or higher can come up with a good plan and coordinate with others; it probably also has multiple ways of attacking and/or defending and knows which is better in which situation.',
'A creature with Intelligence of 12 or higher can come up with a good plan and coordinate with others; it probably also has multiple ways of attacking and/or defending and knows which is better in which situation; it accurately assess its enemies’ weaknesses and target accordingly.',
];
// Determines what row to use for a given intelligence
const getIndexOffsetTacticsIntelligence = intelligence => {
if (intelligence <= 7) {
return TacticsPremisesIntelligenceIndex.SevenOrLess;
}
if (intelligence <= 11) {
return TacticsPremisesIntelligenceIndex.EightToEleven;
}
if (intelligence <= 13) {
return TacticsPremisesIntelligenceIndex.TwelveToThirteen;
}
return TacticsPremisesIntelligenceIndex.FourteenOrMore;
};
// Gets icon and message for generic tactical behavior governed by intelligence
export const getTacticsIntelligence = monster => {
const intelligence = MonsterUtils.getIntelligenceValue(monster);
const indexOffset = getIndexOffsetTacticsIntelligence(intelligence);
return {
icon: null,
text: TacticsPremisesIntelligence[indexOffset],
};
};
// What row of the tactics wisdom premises table to use based on ability score
const TacticsPremisesWisdomIndex = {
SevenOrLess: 0,
EightToEleven: 1,
TwelveToThirteen: 2,
FourteenOrMore: 3,
};
// Wisdom tactics premises from The Monsters Know What They're Doing
// https://www.themonstersknow.com/why-these-tactics/
const TacticsPremisesWisdom = [
'A creature with Wisdom of 7 or less has an underdeveloped survival instinct and may wait too long to flee.',
'A creature with Wisdom of 8 to 11 knows when to flee but is indiscriminate in choosing targets.',
'A creature with Wisdom of 12 or higher will choose targets carefully and may even refrain from combat in favor of parley if it recognizes that it’s outmatched.',
'A creature with Wisdom of 14 or higher chooses its battles carefully and fights only when it’s sure it will win (or will be killed if it doesn’t fight).',
];
// Determines what row to use for a given wisdom
const getIndexOffsetTacticsWisdom = wisdom => {
if (wisdom <= 7) {
return TacticsPremisesWisdomIndex.SevenOrLess;
}
if (wisdom <= 11) {
return TacticsPremisesWisdomIndex.EightToEleven;
}
if (wisdom <= 13) {
return TacticsPremisesWisdomIndex.TwelveToThirteen;
}
return TacticsPremisesWisdomIndex.FourteenOrMore;
};
// Gets icon and message for generic tactical behavior governed by wisdom
export const getTacticsWisdom = monster => {
const wisdom = MonsterUtils.getWisdomValue(monster);
const indexOffset = getIndexOffsetTacticsWisdom(wisdom);
return {
icon: null,
text: TacticsPremisesWisdom[indexOffset]
};
};
// What row of the tactics physical premises table to use based on ability scores
const TacticsPremisesPhysicalIndex = {
LowStr: 0,
LowCon: 1,
LowDex: 2,
Brute: 3,
Stabby: 4,
Skirmish: 5,
Ranged: 6,
Coward: 7,
};
// Physical tactics premises from The Monsters Know What They're Doing
// https://www.themonstersknow.com/why-these-tactics/
const TacticsPremisesPhysical = [
'Low-Strength creatures will always try to compensate with numbers; if their numbers are reduced enough, they’ll scatter.',
'Low-Constitution creatures will prefer to attack from hiding.',
'Low-Dexterity creatures will need to choose their battles carefully: since their ability to avoid damage is poor, they’ll want some sort of compensatory advantage.',
'High-Strength, high-Constitution, low-Dexterity creatures are brutes that will welcome a close-quarters slugfest.',
'High-Strength, high-Dexterity, low-Constitution creatures will use stealth and go for big-damage sneak attacks.',
'High-Dexterity, high-Constitution, low-Strength creatures are scrappy skirmishers.',
'High-Dexterity, low-Strength, low-Constitution creatures will snipe at range.',
'If all three physical abilities are low, a creature will seek to avoid fighting altogether unless it has some sort of advantage; if it’s intelligent, it may lay traps.',
];
const TacticsIconsPhysical = [
HordeIconSvg,
StealthIconSvg,
TacticsIconSvg,
BruteIconSvg,
StabbyIconSvg,
SkirmishIconSvg,
RangedIconSvg,
CowardIconSvg,
];
// Determines what row(s) to use for a given set of physical abilities
const getIndexOffsetTacticsPhysical = (strength, dexterity, constitution) => {
const indexOffsets = [];
if (strength <= 9) {
indexOffsets.push(TacticsPremisesPhysicalIndex.LowStr);
}
if (constitution <= 9) {
indexOffsets.push(TacticsPremisesPhysicalIndex.LowCon);
}
if (dexterity <= 9) {
indexOffsets.push(TacticsPremisesPhysicalIndex.LowDex);
}
const strCon = Math.min(strength, constitution);
if (strCon >= 12 && dexterity < strCon) {
indexOffsets.push(TacticsPremisesPhysicalIndex.Brute);
}
const strDex = Math.min(strength, dexterity);
if (strDex >= 12 && constitution < strDex) {
indexOffsets.push(TacticsPremisesPhysicalIndex.Stabby);
}
const dexCon = Math.min(dexterity, constitution);
if (dexCon >= 12 && strength < dexCon) {
indexOffsets.push(TacticsPremisesPhysicalIndex.Skirmish);
}
if (strCon <= dexterity && dexterity >= 12) {
indexOffsets.push(TacticsPremisesPhysicalIndex.Ranged);
}
if (strength <= 11 && dexterity <= 11 && constitution <= 11) {
indexOffsets.push(TacticsPremisesPhysicalIndex.Coward);
}
if (!indexOffsets.length) {
indexOffsets.push(TacticsPremisesWisdomIndex.Coward);
}
return indexOffsets;
};
// Gets icon and message for generic tactical behavior governed by physical attributes
const getTacticsPhysical = monster => {
const strength = MonsterUtils.getStrengthValue(monster);
const dexterity = MonsterUtils.getDexterityValue(monster);
const constitution = MonsterUtils.getConstitutionValue(monster);
const indexOffsets = getIndexOffsetTacticsPhysical(strength, dexterity, constitution);
return indexOffsets.map(i => ({
icon: TacticsIconsPhysical[i],
text: TacticsPremisesPhysical[i],
}));
};
export const getTactics = monster => {
if (!monster) {
return [];
}
return [
getTacticsIntelligence(monster),
getTacticsWisdom(monster),
...getTacticsPhysical(monster),
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment