johreh / gloomycompanion

Gloomhaven monster app
https://johreh.github.io/gloomycompanion/
93 stars 82 forks source link

Include monster stats and scenario level #11

Open jthornsen opened 7 years ago

jthornsen commented 7 years ago

If this app also included monster stats, then we wouldn't need to use the big monster cards at all (just the sleeves for damage tracking)

Ideally the stats would appear on the left or right of the monster deck.

There would also need to be a scenario level drop down when selecting the scenario or cards list (in the left frame / pop-up menu)

Thanks so much for making this!

Stretch goal: if the app knows the monster stats, then it may also be viable to replace all instances of "Attack +0" with the actual attack/move/range value (i.e. The card would just say, "Move 3, Attack 4, Range 5")

mwinckler commented 7 years ago

I've started laying groundwork for an implementation of this. Currently I'm working on adding monster stats to this spreadsheet. I have obtained permission from Isaac to incorporate and share this info.

Right now I'm going through the boss stat cards and deciding how best to handle a number of their special abilities. After I'm done with the spreadsheet I'll put the data into JSON, post it publicly, and start thinking about how best to work it into the app.

GinoGalotti commented 7 years ago

Amazing idea! Feel free to ask for any help in the process. Maybe we can split the tasks: saving all the data in some in some kind of format, painting the real data in the cards (attack 7 instead of +3), dealing with the UI to select level/show hp..

mwinckler commented 7 years ago

Excellent. I have some ideas about how I was going to structure the JSON (making it relatively verbose so others can use it if desired), and right now I'm in the midst of the bosses and figuring out how best to handle their various special effects and such.

I also have some ideas for UI, but I haven't committed anything to paper yet. I'll try to get things organized next week, post my thoughts, and we can discuss/devise a plan of action to tackle it.

GinoGalotti commented 7 years ago

https://github.com/link0257/gloomycompanion/tree/master @Link0257 made a solution for this, although it's based on having 7 different decks for each monster. We could maybe extract the data out of it

johreh commented 7 years ago

I think it would be more elegant if we add some new data structures for each monster that contains which deck should be used and the base stats for each level of that monster instead of duplicating each deck multiple times.

GinoGalotti commented 7 years ago

Yes, completely agree. That solution doesn't look really scalable. But it's another source to double check the data that we've gathered!

mwinckler commented 7 years ago

Agreed. I totally respect @link0257's work (great job getting stuff done!), but I'd like to have the stats data independent of any particular implementation. I still need to finish up entering the bosses in the spreadsheet and then convert to JSON, but here's a prototype of the data structure I'm thinking of:

{
    "resources":{
        "elderDrake": {
            "attackArea": "some-kind-of-svg-representation-or-link-to-image"
        }
    },
    "monsters":[
        {
            "name":"Bandit Guard",
            "stats":[
                {
                    "level": 4,
                    "type": "elite",
                    "health": 11,
                    "move": 3,
                    "attack": 4,
                    "range": 0,
                    "attributes":[
                        {
                            "name": "Muddle",
                            "value": null
                        },
                        {
                            "name": "Shield",
                            "value": 2
                        }
                    ]
                }
            ]
        }
    ],
    "bosses": []
}

It's more verbose than strictly necessary (particularly attributes - I'm still debating about a more natural-reading strings-only approach like ["Muddle", "Shield 2"] instead of the objects, thereby requiring implementors to parse out the attr X attributes), but I think the verbosity pays for itself in clarity.

The resources node will have references to special-case resources such as area attack hex configurations. I'm hoping this could be embedded directly into the JSON as SVG, but I don't have any direct experience doing that so I'm not sure yet. At worst they can reference external resources, and of course implementors can always override them. I plan to reference resource nodes via mustache-like template syntax within monster properties, so for example the Elder Drake's Special 1 text would read Attack +0 {{resources.elderDrake.attackArea}}

Feel free to chime in with any feedback or suggestions regarding the data structure. I'm hoping to have the first cut done this week.

GinoGalotti commented 7 years ago

It looks promising! Bosses are, by far, the hardest to make. We should also think if we want all the text inside the card text, or we're going to write some paragraphs under explaining the skill (maybe even a mouseover of the card? Although mouseover sucks on mobile).

I tried to be smart generating the SVG from the code but in the end just gave up and create all the resources. I think that's more than OK, what would we need, 4 more images? That's ok.

Have we thought how to represent the HP of the monster (and the boss particularly, as it depends on the number of characters).

For the monsters, I was thinking something simpler. We already have the JSON with the scenario (I have a pending PR to support writing "Bandit Archer" instead of "Archer" in the scenario definition), and then, when we apply the decks, we should gather the monster data and send it to the deck (to write it on the text) from a structure like:

var MONSTERS =
    { BANDIT_ARCHER:  
        { LEVEL_0 : 
            { NORMAL:
                {
                    HP: 20, ATTACK: 2, MOVE: 2
                }
            , ELITE:
                {
                    HP: 30, ATTACK: 3, MOVE: 3
                }
            },
            LEVEL_1 :
                { NORMAL:
                        ...

That way, if we know the level, we can just send the JSON inside LEVEL_0, and internally the deck will manage what to do with the normal/elite version and with the values. Does it make any sense?

mwinckler commented 7 years ago

Here's a first cut at the monster/boss stats JSON. I changed around the structure slightly to improve the calling syntax - monster level is now implicit in an array index (but also redundantly specified in a level property for human-readability). So to get a level 4 elite Bandit Archer's health, you can do:

stats.monsters.banditArcher.level[4].elite.health

I still need to pull together and/or create the necessary images for certain specials, but the resources node is stubbed out with null for all required properties.

I also need to finish writing up documentation for parsing out specials/immunities/etc. Some abilities will need to be parsed out (e.g. Attack +0, Range 3) - I left them as intact strings for now, as I think that makes more sense than trying to cover every edge case with an object structure.

I'll continue onto the resource images next, and once that's done I'll set up a PR to incorporate the data here.

GinoGalotti commented 7 years ago

Wow, looks almost done! Just a thing, is consume-day any different than use-element-light that we already have? In this case, we can use %light%%use_element%, as we're using in the card text (same for dark-night).

Just a rule question. If an elite has Range 2 as attribute, does it mean that we have to render Range 2 for EVERY card and say that elites will have it? We will need to find a good way to display information, then! It looks like a TON of work, but I think a lot of people will be pleased with this feature.

GinoGalotti commented 7 years ago

Wouldn't be easier if you save Health / Attack / Move as a number instead of a String? We'll probably use its numerical value anyway.

{ "Health" : 5

instead of

{"Health": "5"

mwinckler commented 7 years ago

Just a thing, is consume-day any different than use-element-light that we already have? In this case, we can use %light%%use_element%, as we're using in the card text (same for dark-night).

Yes, I think that will be the same image. For us, the value of resources.elements.consumeDay would be %light%%use_element%.

I'm not fully settled on the cleanest way to do the replacements, or even whether they should be done on the fly at all. Perhaps the resources abstraction is not worthwhile and apps using the stat JSON should just do a one-time resource string replacement when first incorporating the JSON. That makes updates more difficult down the road, but this data shouldn't change once the typos are worked out. My main goal here was to make this stat JSON as reusable/generic as possible, not just for us.

Just a rule question. If an elite has Range 2 as attribute, does it mean that we have to render Range 2 for EVERY card and say that elites will have it?

If a monster has an attribute such as "Retaliate 2, Range 2" listed on the stat card, then it is always in effect regardless of what monster ability card is drawn, yes. "Flying" is another, more common example of an attribute on stat cards which is always in effect.

In terms of UI, I haven't thought far enough ahead to have an opinion about how it should be displayed.

Wouldn't be easier if you save Health / Attack / Move as a number instead of a String? We'll probably use its numerical value anyway.

Not all Health is an integer. Boss health is based on number of characters, e.g. 10xC, and will need to be parsed and calculated by the app. If I recall correctly, each other attribute also has at least one such exception where it's not strictly a number (and not all of these are calculable - for instance, there's one boss whose attack value varies based on hexes he moved that turn, and I believe there's one whose movement is based on how many of a certain type of monster is presently on the board).

mwinckler commented 7 years ago

Updated the JSON to make all values integers where possible. I originally didn't want to do this because it means the objects have inconsistent property datatypes between some bosses, but I decided the tradeoff is probably worth it. Now if the property value is not an int, we know we have to parse something to calculate the true value to display (where possible).

GinoGalotti commented 7 years ago

It looks really good. We're getting there!

I agree that having a smart way to generate the resources is not that needed. We can assume that, in the near future, there are no plans for expansions or to add support for Fan generated content, and we should ALWAYS assume that the tool shouldn't be usable without purchasing the game itself, so it's ok that, if that happens, we refer to the game to see some skills till we create the images and include them.

Now that I've updated the Scenario definition to include the real name of the monster, having it as
(https://github.com/johreh/gloomycompanion/pull/24 )

"Bandit Guard": {
      "name": "Bandit Guard",
      "level": [

instead of

"banditGuard": {
      "name": "Bandit Guard",
      "level": [

will allow us to do things like load_monster(monsters[deck.name]);