foundryvtt / dnd5e

An implementation of the 5th Edition game system for Foundry Virtual Tabletop (http://foundryvtt.com).
MIT License
300 stars 206 forks source link

Embeddable actor stat blocks #3798

Open Kikimor-rec opened 2 months ago

Kikimor-rec commented 2 months ago

At the moment, the embed extension displays the actor's biography in the journal. Maybe it makes sense to output a short card about an actor instead?

I made a not very good but working script for my journals to show how it could look like.

image

Hooks.once('ready', () => {
    initEnhancedActorEmbed();
});

function initEnhancedActorEmbed() {
    const originalEnrichHTML = TextEditor.enrichHTML;

    const sizeTranslations = {
        "tiny": "Tiny",
        "sm": "Small",
        "med": "Medium",
        "lg": "Large",
        "huge": "Huge",
        "grg": "Gargantuan"
    };

    function translateCR(cr) {
        const crMap = {
            "0.125": "1/8",
            "0.25": "1/4",
            "0.5": "1/2"
        };
        return crMap[cr] || cr;
    }

    TextEditor.enrichHTML = async function (content, options = {}) {

        if (content === null || content === undefined) {
            return content;
        }

        // Regex for dearch @embed[Actor...] or @embed[Compendium.actor...]
        const embedRegex = /@embed\[([^\]]+)\](?:{([^}]*)})?/g;

        const embedPromises = [];
        content = content.replace(embedRegex, (match, actorRef, label) => {
            const placeholder = `__ACTOREMBED_${embedPromises.length}__`;
            embedPromises.push(processActorEmbed(actorRef, label || actorRef));
            return placeholder;
        });

        const enriched = await originalEnrichHTML.call(this, content, options);

        const embedResults = await Promise.all(embedPromises);
        return embedResults.reduce((acc, embed, index) => {
            return acc.replace(`__ACTOREMBED_${index}__`, embed);
        }, enriched);
    };

    async function processActorEmbed(actorRef, label) {
        let actor;

        try {
            actor = await fromUuid(actorRef);
        } catch (error) {
            console.error("Error fetching actor:", error);
        }

        if (!actor) {
            console.warn(`Actor not found: ${actorRef}`);
            return `[Actor not found: ${actorRef}]`;
        }

        return createEnhancedActorEmbed(actor, label);
    }

    function createEnhancedActorEmbed(actor, label) {

        //  Get the creature's type and size, translate them.
        const type = actor.system.details.type.value || 'Unknown type';

        const size = actor.system.traits.size || 'Unknown size';
        const translatedSize = sizeTranslations[size.toLowerCase()] || size; // Use the translation if there is one

        // convert CR
        const cr = translateCR(actor.system.details.cr?.toString() || 'Unknown CR');

        const abilityScores = ['dex', 'str', 'con', 'int', 'wis', 'cha'];

        let content = `
        <section class="fvtt advice">
            <figure class="icon">
                <img src="${actor.img}" alt="${actor.name}" class="round">
            </figure>
            <article>
                <h2><cite>${actor.toAnchor().outerHTML}</cite></h2>
                <p><strong>${actor.system.details.type.value}, ${translatedSize}, CR ${cr}</strong></p>
                <hr>
                <div>
                    <strong>Armor Class:</strong> ${actor.system.attributes.ac.value || 'N/A'}
                    <strong>Health Point:</strong> ${actor.system.attributes.hp.value || 'N/A'}
                    <strong>Speed:</strong> ${actor.system.attributes.movement?.walk || '0'} ft
                </div>
                <hr>
                <div style="display: grid; grid-template-columns: repeat(6, 1fr); text-align: center;">
                    ${abilityScores.map(ability => `
                         <div>
                            <strong>${ability.toUpperCase()}</strong><br>
                            ${actor.system.abilities[ability].value} (${actor.system.abilities[ability].mod})
                        </div>
                    `).join('')}
                </div>
            </article>
        </section>
        `;

        return content;
    }

}
revilowaldow commented 1 month ago

Maybe something for saves as well that takes inspiration from the new D&D statblock format with the mod/save table? Certainly I would do, <Size> <type> rather than <type>, <Size> to follow standard statblock layout though.

This statblock released publicly. image