5e-database icon indicating copy to clipboard operation
5e-database copied to clipboard

Monster Data is missing Armor Type

Open Redmega opened this issue 4 years ago • 25 comments

The SRD includes armor types for monsters, such as:

Aboleth: 17 (natural armor)

Thug: 11 (leather armor)

The armor type is not provided along with the monster data. I propose we add it as an armor_type field.

Redmega avatar Sep 24 '20 00:09 Redmega

I think that sounds reasonable. It will be a lot of manual entry though. But I don't think that can be avoided.

bagelbits avatar Sep 24 '20 00:09 bagelbits

Might be a way to use pdf.js on the srd pdf and extract it by regex.

NAME HERE.*\n.*\n.*\n // Number of lines down should be uniform
/Armor.*Class:.*\d+.*(\(.*\))/

Redmega avatar Sep 24 '20 00:09 Redmega

I think it might be more complicated but if you want to take a shot, feel free!

bagelbits avatar Sep 24 '20 01:09 bagelbits

Let me take care of this.

txtsd avatar Oct 11 '20 21:10 txtsd

I'm going to repeat here what I wrote on the PR so it's on the original issue. I think all of these:

'10 in humanoid form, 11 (natural armor) in bear and hybrid form',
 '10 in humanoid form, 11 (natural armor) in boar or hybrid form',
 '11 in humanoid form, 12 (natural armor) in wolf or hybrid form',
 '14 (natural armor), 11 while prone',
 '15 with _mage armor_',
 '16 with _barkskin_',

mean that how we do AC is flawed for monsters.

bagelbits avatar Oct 12 '20 07:10 bagelbits

(Reposting here as well)

armor_type here seems to just imply "Whatever's in the parentheses after the armor class."

I think there's two paths this can take: Assume the above, and submit as is (so things like "armor_type": "12 with mage armor" will exist), or figure out an object structure.

My vote would be for something like this:

{
"armor_type": null,
"armor_special": "15 with mage armor"
},
{
"armor_class": 14,
"armor_type": "natural armor",
"armor_special": "11 while prone"
}

fwiw I don't think we should have markdown italics in this field as none of the other fields have markdown (language, senses, etc)

Redmega avatar Oct 12 '20 15:10 Redmega

fwiw I don't think we should have markdown italics in this field as none of the other fields have markdown (language, senses, etc)

Yeah. Markdown only really makes sense in the human readable description fields.

bagelbits avatar Oct 12 '20 18:10 bagelbits

I think that first one would end up being:

{
"armor_class": 12
"armor_type": "natural",
"armor_special": "15 with mage armor"
},

How would you want to handle multiple ACs like for Werebear: '10 in humanoid form, 11 (natural armor) in bear and hybrid form',

I'm kind of leaning towards @txtsd's original approach that they suggest in their PR. An alternative approach could be:

{
  "armor": [
    {
      "armor_class": 12,
    },
    {
      "armor_class": 15,
      "notes": "with mage armor"
    }
  ]
}

{
  "armor": [
    {
      "armor_class": 14,
      "armor_type": "natural armor"
    },
    {
      "armor_class": 11,
      "notes": "while prone"
    }
  ]
}

{
  "armor": [
    {
      "armor_class": 10,
      "notes": "in humanoid form"
    },
    {
      "armor_class": 11,
      "armor_type": "natural armor",
      "notes": "in bear form"
    },
    {
      "armor_class": 11,
      "armor_type": "natural armor",
      "notes": "in hybrid form"
    }
  ]
}

bagelbits avatar Oct 12 '20 18:10 bagelbits

Though I'm not 100% sold on it.

bagelbits avatar Oct 12 '20 18:10 bagelbits

So would a monster without an armor type look like this?

"armor": [
    {
      "armor_class": 14,
    }
]

I could get on board with that

Redmega avatar Oct 12 '20 18:10 Redmega

Yeah... I guess so.

bagelbits avatar Oct 12 '20 18:10 bagelbits

It feels the most shape consistent, but I'm honestly open to a better suggestion.

bagelbits avatar Oct 12 '20 18:10 bagelbits

Okay I removed the markdown.

I feel like the notes key is geared toward a human reader, whereas the way I had it structured would be geared toward programmatic access. If we ever add more OGL data that is not in the SRD, we'll be able to follow the same format for whatever "conditions" they have.

txtsd avatar Oct 13 '20 04:10 txtsd

@txtsd I think your way probably makes the most sense. It might get refined further though. But it feels like the right direction.

bagelbits avatar Oct 13 '20 20:10 bagelbits

Good. Is the armor_special key's name okay, or would you like to replace it with something else? EDIT: Okay I see your comment on the PR. We'll go with that.

txtsd avatar Oct 14 '20 01:10 txtsd

@txtsd Sorry that we're talking in two places. I'm not sure if this will be the correct approach but it at least gets us to the right direction.

bagelbits avatar Oct 14 '20 07:10 bagelbits

That's alright. As long as it's in the right direction.

txtsd avatar Oct 14 '20 08:10 txtsd

Heya, Sorry for poping in since I'm fairly new here. In reading this I would suggest we consider how the API might be used in a program, so with that being said I am with txtsd in thinking that notes should be limited to "human readable" text that is not used to provide important data.

I like the idea of the armor key holding an array of document values that describte the armor attributes. I also recognize the need to indicate that some have an associated condition or state, especially with shape changes or fighting stances that are common to many creature types.

So I would propose that we add a new key to the main creature api document that might look something like

"states" : ["humanoid","bear form","hybrid form"]

I also considered implying a default state, but I think we can just leave it at state index 0 since the DM would choose this.

This state key would represent an enumeration type declaration. I would propose that ANY subdocument may use the

"required_state"

key and include an array of states that enable the use of this document.

"armor": [
    {
      "armor_class": 10,
      "armor_type" : "natural",
      "required_state" : [ "humaniod" ]
    },
    {
      "armor_class": 11,
      "armor_type": "natural",
      "required_state" : [ "bear form","hybrid form" ]
    }
  ]

This could also work to simplify the "actions" key too

"actions": [
		{
			"name": "Multiattack",
			"desc": "In bear form, the werebear makes two claw attacks. In humanoid form, it makes two greataxe attacks. In hybrid form, it can attack like a bear or a humanoid.",
			"options": {
				"choose": 1,
				"from": [
					[
						{
							"name": "Claw",
							"required_state" : [ "bear form","hybrid form" ],
							"count": 2,
							"type": "melee"
						}
					],
					[
						{
							"name": "Greataxe",
							"required_state" : [ "bear form","hybrid form" ],
							"count": 2,
							"type": "melee"
						}
					],
					[
						{
							"name": "Greataxe",
							"required_state" : [ "hybrid form" ],
							"count": 1,
							"type": "melee"
						},
						{
							"name": "Claw",
							"required_state" : [ "hybrid form" ],
							"count": 1,
							"type": "melee"
						}
					]
				]
			},
			"damage": []
		}

This design choice would also be useful for things like fighting styles, and can help a programmer handle monsters with various states by detecting if the state array length. It can be implicit that if the states key has no value, then we don't need to check for conditions.

Just a thought, and thanks for hearing me out!

Drazev avatar Dec 17 '20 16:12 Drazev

Heya, Sorry for poping in since I'm fairly new here.

Please don't apologize! All opinions are welcome.

bagelbits avatar Dec 17 '20 23:12 bagelbits

@drazev the approach you're describing makes sense, I think most of us agree with a lot of what you're saying. Chris is right, don't be sorry for putting your opinions out there and helping us out!

I don't know if you've read the PR linked to this issue, #297? If not, it might give you some more food for thought.

I prefer the solution we were arriving at in the #297, summarised by my last comment on the PR. I think it's more flexible and a fair bit more computer-friendly. What are your thoughts?

fergcb avatar Dec 18 '20 00:12 fergcb

@drazev Thank you, this is indeed a good idea, and it was something that I thought about when I faced the issue, but I fear that the API would be too cluttered with "required_states" field, as you already showed. To me, it adds complexity to both the data and a data application that would use it: you'd need to filter every single field based on the form of the monster. This is why I created #304 "Split lycanthropes into each forms", so that it's basically just a new monster with just a few references to its other forms. It's much, much easier to make links between monsters than adding filters to each field.

ogregoire avatar Dec 18 '20 09:12 ogregoire

@ogregoire Actually I think I like #304 for the same reasons. It can allow a program to just get the version they need. Your idea for a forms key would be ideal if it links to the other document versions the program would also need to pull for that monster.

However, I still think you have something on API clutter too that would not necessarly be solved by taking that approach. Ideally it would be nice if the API used the same approach for any conditional form/state/style/etc. As an example this solution works for the specific problem of multi form monsters. However, monsters that just have different spell or style options that change different attributes would require a seperate solution. I just thought it may be good to take this as an opportunity to step back and review a higher level approach to a common problem of conditioal effects which could affect any element on the stat sheet

I do not know exactly what that could look like exactly. However, taking my prevous idea as a point of reference it might look something like this. Maybe take a look at what I am getting at and see if there is anything ideas that can be used?

I just took the werebear API pull and did a remodel to illustrate how things could look if some elements of the state concept were adopted to be uinversal.

The concept I was going for would generally place documents in lists, and only allow at most three levels of this.

  1. First level keys determine the high level attribute. Only unchanging values like CR will be a value.
  2. The values will generally be a list of documents. Those documents represent individual configurations or options related to that key
  3. Values to second level key will either be a base date type, or a list of documents. This list-document level is the last tier.
  4. If the second level key has a list of documents, those documents should be references to another document.
{
	"index": "werebear",
	"name": "Werebear",
	"size": "Medium",
	"type": "humanoid",
	"subtype": "human",
	"alignment": "neutral good",
	"armor_class": 10,
	"hit_points": 135,
    "hit_dice": "18d8",
    "states" : ["humanoid form","bear form","hybrid form"],
    "speed": 
        [
            {
                "required_state" : [ "humanoid form"],
                "walk": "30 ft."
            },
            {
                "required_state" : ["hybrid form"],
                "walk": "40 ft.",
                "climb": "30 ft."
            }
            ,
            {
                "required_state" : ["bear form"],
                "walk": "40 ft.",
                "climb": "30 ft."            
            }
        ],
    "stat_blocks" : 
        [
            { //No required state here means it has no condition
                "strength": 19,
                "dexterity": 10,
                "constitution": 17,
                "intelligence": 11,
                "wisdom": 12,
                "charisma": 12
            }
        ],
    "proficiencies": 
        [
            {
                "value": 7,
                "proficiency": {
                    "index": "skill-perception",
                    "name": "Skill: Perception",
                    "url": "/api/proficiencies/skill-perception"
                }
            }
        ],
    "resistance_levels" :
        [
            {   //If we wanted to make this state based, we could add "required_state" key and then list multiple
                //We should not list a key if it's empty. All libraries allow for checking if a key exists
                "damage_vulnerabilities": [],
                "damage_resistances": [],
                "damage_immunities": 
                    [
                        "bludgeoning, piercing, and slashing from nonmagical attacks not made with silvered weapons"
                    ],
                "condition_immunities": []
            }
        ],
    "senses": 
        [
            {
                "passive_perception": 17
            }
        ],
    "languages": 
        [
            {
                "required_state" : [ "hybrid form","humanoid"],
                "language": ["Common"]
            }
        ],
	"challenge_rating": 5,
	"xp": 1800,
	"special_abilities": [
		{
			"name": "Shapechanger",
			"desc": "The werebear can use its action to polymorph into a Large bear-humanoid hybrid or into a Large bear, or back into its true form, which is humanoid. Its statistics, other than its size and AC, are the same in each form. Any equipment it is wearing or carrying isn't transformed. It reverts to its true form if it dies."
		},
		{
			"name": "Keen Smell",
			"desc": "The werebear has advantage on Wisdom (Perception) checks that rely on smell."
		}
	],
	"actions": [
        {
            "required_state" : [ "bear form","hybrid form"],
            "name": "Multiattack",
            "desc": "In bear form, the werebear makes two claw attacks. In humanoid form, it makes two greataxe attacks. In hybrid form, it can attack like a bear or a humanoid.",
            "attack_options" : 
                [
                    {
                        "action" : "Claw",
                        "count" : 2
                    }   
                ]
        },
        {
            "required_state" : [ "humanoid form","hybrid form"],
            "name": "Multiattack",
            "desc": "In bear form, the werebear makes two claw attacks. In humanoid form, it makes two greataxe attacks. In hybrid form, it can attack like a bear or a humanoid.",
            "attack_options" : 
                [
                    {
                        "action" : "Greataxe",
                        "count" : 2
                    }   
                ]
        },
		{
            "required_state" : [ "bear form","hybrid form"],
            "name": "Bite (Bear or Hybrid Form Only)",
			"desc": "Melee Weapon Attack: +7 to hit, reach 5 ft., one target. Hit: 15 (2d10 + 4) piercing damage. If the target is a humanoid, it must succeed on a DC 14 Constitution saving throw or be cursed with werebear lycanthropy.",
			"attack_bonus": 7,
			"damage": [
				{
					"damage_type": {
						"index": "piercing",
						"name": "Piercing",
						"url": "/api/damage-types/piercing"
					},
					"damage_dice": "2d10+4"
				}
			]
		},
		{
            "required_state" : [ "bear form","hybrid form"],
			"name": "Claw (Bear or Hybrid Form Only)",
			"desc": "Melee Weapon Attack: +7 to hit, reach 5 ft., one target. Hit: 13 (2d8 + 4) slashing damage.",
			"attack_bonus": 7,
			"damage": [
				{
					"damage_type": {
						"index": "slashing",
						"name": "Slashing",
						"url": "/api/damage-types/slashing"
					},
					"damage_dice": "2d8+4"
				}
			]
		},
		{
            "required_state" : [ "humanoid form","hybrid form"],
            "name": "Greataxe (Humanoid or Hybrid Form Only)",
			"desc": "Melee Weapon Attack: +7 to hit, reach 5 ft., one target. Hit: 10 (1d12 + 4) slashing damage.",
			"attack_bonus": 7,
			"damage": [
				{
					"damage_type": {
						"index": "slashing",
						"name": "Slashing",
						"url": "/api/damage-types/slashing"
					},
					"damage_dice": "1d12+4"
				}
			]
		}
	],
	"url": "/api/monsters/werebear"
}

Drazev avatar Dec 18 '20 19:12 Drazev

I understand what you mean, but the main drawback is that everything is now an array to be filtered. A monster has two forms and each has a different AC? Bam, AC is now an array for all monsters. A monster with two forms has two sets of ability scores? Bam, the ability scores and the derivative stats must now be arrays for all monsters.

Plus there seem to have very special wording like the multiattack in your example which requires weird filters and subfilters.

I understand your desire to introduce conditional data, and we already have this kind of structure in other file such as the race or the class one.

The first question we should have is what condition are we expecting, and then only we can try to find answers to those questions. But I have to admit that as of now, I don't see many conditional structures, except for lycanthropes, and I believe that my suggestion in #304 is superior.

ogregoire avatar Dec 18 '20 20:12 ogregoire

I'm mostly on the same page as @ogregoire for this as well. I feel like trying to solve problems on an outlier which cause us to completely change our structure smells like premature abstraction to me. I think splitting the lycanthropes feels like a more reasonable step forward.

bagelbits avatar Dec 18 '20 20:12 bagelbits

Fair enough! #304 is a great solution for this particular problem. I do see your point that right now conditionals are not a big problem. I guess we can revisit it in the future if further expansions make them more abundant.

I see your solution also gets rid of using notes for data which may need to be used by a program which is also great.

Drazev avatar Dec 18 '20 21:12 Drazev

@fergcb Circling back to this based on #297, would this be the type shape that we'd want now that monsters have been split into their forms?

type ArmorClass = (ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition)[];
type ArmorClassNatural = {
  type: "natural";
  value: number;
  desc?: string;
}
type ArmorClassArmor = {
  type: "armor";
  value: number;
  armor_type: APIReference; // Equipment
  desc?: string;
}
type ArmorClassSpell = {
  type: "spell";
  value: number;
  spell: APIReference; // Spell
  desc?: string;
}
type ArmorClassCondition = {
  type: "condition";
  value: number;
  condition: APIReference; // Condition
  desc?: string;
}

bagelbits avatar Dec 08 '22 23:12 bagelbits

would this be the type shape that we'd want now that monsters have been split into their forms?

@bagelbits Yeah, this looks good!

fergcb avatar Dec 10 '22 09:12 fergcb

Slight adjustment:

type ArmorClass = (ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition)[];
type ArmorClassNatural = {
  type: "natural";
  value: number;
  desc?: string;
}
type ArmorClassArmor = {
  type: "armor";
  value: number;
  armor: APIReference[]; // Equipment
  desc?: string;
}
type ArmorClassSpell = {
  type: "spell";
  value: number;
  spell: APIReference; // Spell
  desc?: string;
}
type ArmorClassCondition = {
  type: "condition";
  value: number;
  condition: APIReference; // Condition
  desc?: string;
}

bagelbits avatar Dec 13 '22 01:12 bagelbits

I realized that some monsters don't have natural armor, they just have their DEX mod. So that might make more sense as a base AC:

type ArmorClass = (ArmorClassDex | ArmorClassNatural | ArmorClassArmor | ArmorClassSpell | ArmorClassCondition)[];
type ArmorClassDex = {
  type: "dex";
  value: number;
};
type ArmorClassNatural = {
  type: "natural";
  value: number;
  desc?: string;
}
type ArmorClassArmor = {
  type: "armor";
  value: number;
  armor: APIReference[]; // Equipment
  desc?: string;
}
type ArmorClassSpell = {
  type: "spell";
  value: number;
  spell: APIReference; // Spell
  desc?: string;
}
type ArmorClassCondition = {
  type: "condition";
  value: number;
  condition: APIReference; // Condition
  desc?: string;
}

bagelbits avatar Dec 13 '22 01:12 bagelbits

I started a branch and am slowly converting monsters over. Feel free to cut PRs against this branch if you want to help out. Try to keep them in reviewable sized chunks.

bagelbits avatar Dec 13 '22 01:12 bagelbits