5180 fichiers 38.5 Mo 0 téléchargements 218 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / api / quests / RewardTrackLayout.ts
RewardTrackLayout.ts 7.2 Ko Télécharger le fichier
/**
 * The geometry of the official reward track window (reward_track_main_xml, 1103x722), ported from
 * RewardTrackPrizeLayout / RewardTrackPrizeTrackView / RewardTrackMainProgressBarView.
 *
 * Prizes and point indicators are placed along the track by their required points: one pixel scale
 * (distance per point) for the whole track, a page every `pagePointSpan` points, and the prev / next
 * buttons step through the pages. Every x is relative to the prize layer (`prize_content`, 598 wide).
 */

/** prize_content / points_indicator width. */
export const REWARD_TRACK_VISIBLE_WIDTH = 598;
/** prize_template width (also point_indicator_template). */
export const REWARD_TRACK_PRIZE_WIDTH = 80;
/** RewardTrackPrizeTrackView.MIN_PRIZE_SPACING. */
export const REWARD_TRACK_MIN_PRIZE_SPACING = 15;
/** The main loading_bar of the track container. */
export const REWARD_TRACK_BAR_WIDTH = 602;

const PAGE_BOUNDARY_EPSILON = 1e-4;

export interface RewardTrackLayoutPrize {
    premium: boolean;
    requiredPoints: number;
}

export interface RewardTrackLayout {
    distancePerPoint: number;
    pageCount: number;
    pagePointSpan: number;
    zeroOffset: number;
    /** The x of a points value on a page. */
    xFor: (points: number, page: number) => number;
    /** The page a points value belongs to. */
    pageOf: (points: number) => number;
}

const minRequiredPoints = (prizes: RewardTrackLayoutPrize[]): number => {
    let min = -1;

    for (const prize of prizes) if (min === -1 || prize.requiredPoints < min) min = prize.requiredPoints;

    return Math.max(0, min);
};

const maxRequiredPoints = (prizes: RewardTrackLayoutPrize[]): number => prizes.reduce((max, prize) => Math.max(max, prize.requiredPoints), 0);

/** The smallest positive gap between two prizes of the same tier, in list order. */
const tierMinimumGap = (prizes: RewardTrackLayoutPrize[], premium: boolean): number => {
    let previous = -1;
    let gap = 0;

    for (const prize of prizes) {
        if (prize.premium !== premium) continue;

        if (previous !== -1) {
            const delta = prize.requiredPoints - previous;

            if (delta > 0 && (gap === 0 || delta < gap)) gap = delta;
        }

        previous = prize.requiredPoints;
    }

    return gap;
};

const minimumGap = (prizes: RewardTrackLayoutPrize[]): number => {
    const free = tierMinimumGap(prizes, false);
    const premium = tierMinimumGap(prizes, true);

    if (free <= 0) return premium;

    if (premium <= 0) return free;

    return Math.min(free, premium);
};

const pageIndexOf = (points: number, span: number): number => (points <= 0 ? 0 : Math.max(0, Math.ceil((points - PAGE_BOUNDARY_EPSILON) / span) - 1));

/** RewardTrackPrizeLayout.rebuild(). */
export const buildRewardTrackLayout = (
    prizes: RewardTrackLayoutPrize[],
    visibleWidth = REWARD_TRACK_VISIBLE_WIDTH,
    prizeWidth = REWARD_TRACK_PRIZE_WIDTH,
    spacing = REWARD_TRACK_MIN_PRIZE_SPACING
): RewardTrackLayout => {
    const firstOffset = prizeWidth / 2 + spacing;
    const minPoints = minRequiredPoints(prizes);
    const zeroOffsetFor = (distance: number) => Math.max(0, firstOffset - minPoints * distance);
    const usableWidth = (distance: number) => Math.max(1, visibleWidth - firstOffset - zeroOffsetFor(distance));
    const fits = (distance: number, span: number) => span * distance <= usableWidth(distance) + PAGE_BOUNDARY_EPSILON;

    let gap = minimumGap(prizes);

    if (gap <= 0) gap = Math.max(1, maxRequiredPoints(prizes));

    let distancePerPoint = (prizeWidth + spacing) / gap;

    if (distancePerPoint <= 0) distancePerPoint = 1;

    const pagePointSpan = Math.max(1, Math.max(1, Math.floor(usableWidth(distancePerPoint) / distancePerPoint / gap)) * gap);

    // findMaxDistancePerPoint(): stretch the scale as far as one page still fits.
    let low = distancePerPoint;
    let high = distancePerPoint;

    for (let step = 0; step < 32; step++) {
        high *= 2;

        if (!fits(high, pagePointSpan)) break;

        low = high;
    }

    for (let step = 0; step < 24; step++) {
        const middle = (low + high) / 2;

        if (fits(middle, pagePointSpan)) low = middle;
        else high = middle;
    }

    distancePerPoint = low;

    const zeroOffset = zeroOffsetFor(distancePerPoint);
    const pageCount = Math.max(1, prizes.reduce((max, prize) => Math.max(max, pageIndexOf(prize.requiredPoints, pagePointSpan)), 0) + 1);

    return {
        distancePerPoint,
        pageCount,
        pagePointSpan,
        zeroOffset,
        xFor: (points: number, page: number) => zeroOffset + (points - Math.max(0, page * pagePointSpan)) * distancePerPoint,
        pageOf: (points: number) => Math.max(0, Math.min(pageCount - 1, pageIndexOf(points, pagePointSpan)))
    };
};

export interface RewardTrackPage<T extends RewardTrackLayoutPrize> {
    free: T[];
    premium: T[];
    /** The distinct point values of the page's prizes, one indicator each. */
    points: number[];
}

/** RewardTrackPrizeTrackView.buildPageData(): the prizes and indicator values of every page, in prize order. */
export const buildRewardTrackPages = <T extends RewardTrackLayoutPrize>(prizes: T[], layout: RewardTrackLayout): RewardTrackPage<T>[] => {
    const pages: RewardTrackPage<T>[] = [];

    for (let index = 0; index < layout.pageCount; index++) pages.push({ free: [], premium: [], points: [] });

    for (const prize of prizes) {
        const page = pages[layout.pageOf(prize.requiredPoints)];

        (prize.premium ? page.premium : page.free).push(prize);

        if (page.points.indexOf(prize.requiredPoints) === -1) page.points.push(prize.requiredPoints);
    }

    return pages;
};

/** A prize / indicator window is centred on its x: `window.x = round(x - width / 2)`. */
export const getRewardTrackSlotLeft = (x: number, width = REWARD_TRACK_PRIZE_WIDTH): number => Math.round(x - width / 2);

/**
 * RewardTrackMainProgressBarView.refreshByX() + render(): the `progress` container gets
 * `round(602 * trunc(x) / 602)` px and the green fill shape 4px more (the whole bar once it is near the end).
 */
export const getRewardTrackBarWidths = (x: number, barWidth = REWARD_TRACK_BAR_WIDTH): { fill: number; progress: number } => {
    const ratio = Math.max(0, Math.min(1, Math.trunc(x) / barWidth));
    const progress = Math.max(0, Math.min(barWidth, Math.round(barWidth * ratio)));

    return { fill: progress >= barWidth - 4 ? barWidth : progress + 4, progress };
};

/** The red counters on the page buttons: claimable prizes on the earlier / later pages. */
export const countRewardTrackUnclaimed = <T extends RewardTrackLayoutPrize>(
    prizes: T[],
    layout: RewardTrackLayout,
    page: number,
    isClaimable: (prize: T) => boolean
): { next: number; previous: number } => {
    let previous = 0;
    let next = 0;

    for (const prize of prizes) {
        if (!isClaimable(prize)) continue;

        const prizePage = layout.pageOf(prize.requiredPoints);

        if (prizePage === page) continue;

        if (prizePage < page) previous++;
        else next++;
    }

    return { next, previous };
};

/** A task / level mini bar: `progress` width in px (rounded like RewardTrackProgressBarViewBase.render()). */
export const getRewardTrackMiniBarWidth = (ratio: number, width: number): number => Math.max(0, Math.min(width, Math.round(width * Math.max(0, Math.min(1, ratio)))));
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser