feat: add money converter dialog, bump version to 1.3.36

Extract currency conversion out of character/NPC sheets into a
dedicated dialog with live NBP exchange rates, covering both
real-world (PLN/EUR/USD) and in-world currencies (Thrakka, Sol-3
Federation Credit, Silver Talent, Gold Crown, Platinum Lingot).
This commit is contained in:
Octoturge
2026-08-17 21:01:56 +02:00
parent c238f61cfc
commit 0fe9dd02d6
7 changed files with 254 additions and 206 deletions
+31
View File
@@ -573,6 +573,37 @@
"zakazana-wiedza-dziedzina": "Forbidden Knowledge (Discipline)",
"zawisanie-w-powietrzu": "Hovering",
"naturalny-lot": "Natural Flight"
},
"moneyConverter": {
"liveRates": "📡 Rates from NBP ({date})",
"defaultRates": "📡 Default rates (no connection to NBP)",
"actorCash": "Actor cash:",
"campaignYear": "Campaign year: {year}",
"calculator": "Calculator",
"resetToCash": "Reset to cash",
"exchangeRatesTable": "Exchange rates table",
"currency": "Currency",
"ratePln": "Rate (PLN)",
"onePlnEquals": "1 PLN =",
"note": "Note",
"currencies": {
"PLN": "PLN (Zloty)",
"EUR": "EUR (Euro)",
"USD": "USD (US Dollar)",
"TH": "TH (Thrakka)",
"FC": "FC (Sol-3 Federation Credit)",
"ST": "ST (Silver Talent)",
"ZK": "ZK (Gold Crown)",
"PL": "PL (Platinum Lingot)"
},
"notes": {
"nbp": "NBP {date}",
"th": "1:1 with EUR",
"fc": "fixed rate 2 PLN = 1 FC",
"st": "1 ST = 240 PLN",
"zk": "1 ZK = 12 ST",
"pl": "1 PL = 12 ZK"
}
}
}
}
+31
View File
@@ -576,6 +576,37 @@
"zakazana-wiedza-dziedzina": "Zakazana wiedza (Dziedzina)",
"zawisanie-w-powietrzu": "Zawisanie w powietrzu",
"naturalny-lot": "Naturalny lot"
},
"moneyConverter": {
"liveRates": "📡 Kursy z NBP ({date})",
"defaultRates": "📡 Domyślne kursy (brak połączenia z NBP)",
"actorCash": "Gotówka aktora:",
"campaignYear": "Rok kampanii: {year}",
"calculator": "Kalkulator",
"resetToCash": "Przywróć gotówkę",
"exchangeRatesTable": "Tabela kursów wymiany",
"currency": "Waluta",
"ratePln": "Kurs (PLN)",
"onePlnEquals": "1 PLN =",
"note": "Uwaga",
"currencies": {
"PLN": "PLN (Złoty)",
"EUR": "EUR (Euro)",
"USD": "USD (Dolar)",
"TH": "TH (Thrakka)",
"FC": "FC (Kredyt Federacji Sol-3)",
"ST": "ST (Srebrny Talent)",
"ZK": "ZK (Złota Korona)",
"PL": "PL (Platynowy Lingot)"
},
"notes": {
"nbp": "NBP {date}",
"th": "1:1 z EUR",
"fc": "stały kurs 2 PLN = 1 FC",
"st": "1 ST = 240 PLN",
"zk": "1 ZK = 12 ST",
"pl": "1 PL = 12 ZK"
}
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hbm-rpg-v3-foundry",
"version": "1.3.30",
"version": "1.3.36",
"description": "Foundry VTT system for Homebrew Magic: Role Playing Game v3",
"private": true,
"type": "module",
+2 -95
View File
@@ -11,6 +11,7 @@ import { askApplyDamage } from '../dice/damage-dialog';
import { applyDamage } from '../logic/damage';
import { rest } from '../logic/rest';
import { ATTRIBUTES, AttributeKey, SKILL_KEYS, getMagicPowerEntry } from '../constants';
import { showMoneyConverter } from './money-converter-dialog';
const { ActorSheetV2 } = foundry.applications.sheets as unknown as {
ActorSheetV2: typeof foundry.applications.sheets.ActorSheetV2;
@@ -482,101 +483,7 @@ export class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
static async _onRecalculateMoney(this: CharacterSheet, _event: PointerEvent, _target: HTMLElement) {
const actor = (this as unknown as { actor: any }).actor;
const pln = actor.system.details?.money ?? 0;
const currentYear = actor.system.details?.currentYear ?? 2026;
const currentRealYear = new Date().getFullYear();
const fetchNBP = async (currency: string, year: number): Promise<{ rate: number; date: string } | null> => {
if (year >= 2002 && year <= currentRealYear) {
for (let day = 1; day <= 7; day++) {
const d = `${year}-06-${String(day).padStart(2, '0')}`;
try {
const res = await fetch(`https://api.nbp.pl/api/exchangerates/rates/a/${currency}/${d}/?format=json`);
if (res.ok) { const data = await res.json(); const r = data?.rates?.[0]?.mid; if (typeof r === 'number') return { rate: r, date: d }; }
} catch (_) { /* ignore */ }
}
}
try {
const res = await fetch(`https://api.nbp.pl/api/exchangerates/rates/a/${currency}/?format=json`);
if (res.ok) { const data = await res.json(); const r = data?.rates?.[0]?.mid; const dt = data?.rates?.[0]?.effectiveDate || ''; if (typeof r === 'number') return { rate: r, date: dt }; }
} catch (_) { /* ignore */ }
return null;
};
const [eurData, usdData] = await Promise.all([fetchNBP('eur', currentYear), fetchNBP('usd', currentYear)]);
const eurRate = eurData?.rate ?? 4.35;
const usdRate = usdData?.rate ?? 4.00;
const rateDate = eurData?.date ?? usdData?.date ?? 'default';
const isLive = !!(eurData || usdData);
const CURRENCIES: Record<string, { label: string; toPln: number; note: string }> = {
PLN: { label: 'PLN (Złoty)', toPln: 1, note: '' },
EUR: { label: 'EUR (Euro)', toPln: eurRate, note: `NBP ${rateDate}` },
USD: { label: 'USD (Dolar)', toPln: usdRate, note: `NBP ${rateDate}` },
TH: { label: 'TH (Thrakka)', toPln: eurRate, note: '1:1 z EUR' },
FC: { label: 'FC (Kredyt Federacji Sol-3)', toPln: 2.0, note: 'stały kurs 2 PLN = 1 FC' },
ST: { label: 'ST (Srebrny Talent)', toPln: 240, note: '1 ST = 240 PLN' },
ZK: { label: 'ZK (Złota Korona)', toPln: 2880, note: '1 ZK = 12 ST' },
PL: { label: 'PL (Platynowy Lingot)', toPln: 34560, note: '1 PL = 12 ZK' },
};
const currKeys = Object.keys(CURRENCIES);
const optHtml = (sel: string) => currKeys.map(k =>
`<option value="${k}"${k === sel ? ' selected' : ''}>${CURRENCIES[k].label}</option>`
).join('');
const ratesRows = currKeys.map(k => {
const c = CURRENCIES[k];
const fromPln = k === 'PLN' ? '1.0000' : (1 / c.toPln).toPrecision(4);
return `<tr><td>${c.label}</td><td style="text-align:right;font-family:monospace;">${c.toPln === 1 ? '1.0000' : c.toPln.toFixed(4)}</td><td style="text-align:right;font-family:monospace;">${fromPln}</td><td style="color:#777;font-size:0.75rem;">${c.note}</td></tr>`;
}).join('');
const ratesJson = JSON.stringify(Object.fromEntries(currKeys.map(k => [k, CURRENCIES[k].toPln])));
const content = `
<div class="hbm money-converter" style="padding:10px;font-family:'Signika',sans-serif;display:flex;flex-direction:column;gap:12px;">
<p style="margin:0;font-size:0.78rem;color:${isLive ? '#2a7a3e' : '#888'};">
<em>📡 ${isLive ? `Kursy z NBP (${rateDate})` : 'Domyślne kursy (brak połączenia z NBP)'}</em>
</p>
<div style="background:var(--hbm-card-bg,#1a1a2e);border:1px solid var(--hbm-border,#333);border-radius:6px;padding:8px 12px;">
<span style="font-weight:bold;">Gotówka aktora:</span>
<span style="font-family:monospace;font-size:1.1rem;margin-left:6px;">${pln} PLN</span>
<small style="color:#888;margin-left:6px;">(Rok kampanii: ${currentYear})</small>
</div>
<div style="background:var(--hbm-card-bg,#1a1a2e);border:1px solid var(--hbm-accent,#6060c0);border-radius:6px;padding:10px 12px;display:flex;flex-direction:column;gap:8px;">
<strong style="font-size:0.9rem;">Kalkulator</strong>
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
<input id="hbm-conv-left" type="number" value="1" min="0" step="any"
style="width:6.5rem;font-size:1rem;text-align:right;padding:3px 6px;background:transparent;color:var(--hbm-fg,#eee);border:1px solid var(--hbm-border,#555);border-radius:4px;"
oninput="(function(v){var r=window._hbmRates;var lk=document.getElementById('hbm-sf').value;var rk=document.getElementById('hbm-st').value;var res=v*r[lk]/r[rk];document.getElementById('hbm-conv-right').value=isFinite(res)?res.toFixed(4):'';})(this.value)" />
<select id="hbm-sf" style="flex:1;min-width:8rem;"
onchange="(function(){var v=parseFloat(document.getElementById('hbm-conv-left').value)||0;var r=window._hbmRates;var lk=this.value;var rk=document.getElementById('hbm-st').value;var res=v*r[lk]/r[rk];document.getElementById('hbm-conv-right').value=isFinite(res)?res.toFixed(4):'';}).call(this)">
${optHtml('PLN')}
</select>
<span style="font-size:1.2rem;color:var(--hbm-accent,#8080ff);">⇄</span>
<input id="hbm-conv-right" type="number" value="" min="0" step="any"
style="width:6.5rem;font-size:1rem;text-align:right;padding:3px 6px;background:transparent;color:var(--hbm-fg,#eee);border:1px solid var(--hbm-border,#555);border-radius:4px;"
oninput="(function(v){var r=window._hbmRates;var lk=document.getElementById('hbm-sf').value;var rk=document.getElementById('hbm-st').value;var res=v*r[rk]/r[lk];document.getElementById('hbm-conv-left').value=isFinite(res)?res.toFixed(4):'';}).call(this)" />
<select id="hbm-st" style="flex:1;min-width:8rem;"
onchange="(function(){var v=parseFloat(document.getElementById('hbm-conv-right').value)||0;var r=window._hbmRates;var lk=document.getElementById('hbm-sf').value;var rk=this.value;var res=v*r[rk]/r[lk];document.getElementById('hbm-conv-left').value=isFinite(res)?res.toFixed(4):'';}).call(this)">
${optHtml('EUR')}
</select>
</div>
</div>
<details>
<summary style="cursor:pointer;font-size:0.85rem;color:var(--hbm-accent-dim,#999);">▸ Tabela kursów wymiany</summary>
<table style="width:100%;border-collapse:collapse;font-size:0.8rem;margin-top:8px;">
<thead><tr style="border-bottom:1px solid var(--hbm-border,#555);">
<th style="text-align:left;">Waluta</th><th style="text-align:right;">Kurs (PLN)</th><th style="text-align:right;">1 PLN =</th><th>Uwaga</th>
</tr></thead>
<tbody>${ratesRows}</tbody>
</table>
</details>
</div>
<script>window._hbmRates=${ratesJson};</script>`;
await (foundry.applications.api as any).DialogV2.prompt({
window: { title: game.i18n.localize('HBM.ui.moneyConverterTitle') },
content,
rejectClose: false,
});
return showMoneyConverter(actor);
}
}
+185
View File
@@ -0,0 +1,185 @@
const { DialogV2 } = foundry.applications.api as unknown as {
DialogV2: any;
};
class MoneyConverterDialog extends DialogV2 {
rates: Record<string, number>;
constructor(options: any, rates: Record<string, number>) {
super(options);
this.rates = rates;
}
protected override _onRender(context: any, options: any) {
super._onRender(context, options);
const html = this.element;
const leftInput = html.querySelector('#hbm-conv-left') as HTMLInputElement;
const rightInput = html.querySelector('#hbm-conv-right') as HTMLInputElement;
const leftSelect = html.querySelector('#hbm-sf') as HTMLSelectElement;
const rightSelect = html.querySelector('#hbm-st') as HTMLSelectElement;
if (!leftInput || !rightInput || !leftSelect || !rightSelect) return;
const calcRight = () => {
const v = parseFloat(leftInput.value);
if (Number.isNaN(v)) {
rightInput.value = '';
return;
}
const lk = leftSelect.value;
const rk = rightSelect.value;
const res = v * this.rates[lk] / this.rates[rk];
rightInput.value = isFinite(res) ? res.toFixed(4) : '';
};
const calcLeft = () => {
const v = parseFloat(rightInput.value);
if (Number.isNaN(v)) {
leftInput.value = '';
return;
}
const lk = leftSelect.value;
const rk = rightSelect.value;
const res = v * this.rates[rk] / this.rates[lk];
leftInput.value = isFinite(res) ? res.toFixed(4) : '';
};
leftInput.addEventListener('input', calcRight);
leftSelect.addEventListener('change', calcRight);
rightInput.addEventListener('input', calcLeft);
rightSelect.addEventListener('change', calcRight);
}
}
export async function showMoneyConverter(actor: any): Promise<void> {
const pln = actor.system.details?.money ?? 0;
const currentYear = actor.system.details?.currentYear ?? 2026;
const currentRealYear = new Date().getFullYear();
const fetchNBP = async (currency: string, year: number): Promise<{ rate: number; date: string } | null> => {
if (year >= 2002 && year <= currentRealYear) {
for (let day = 1; day <= 7; day++) {
const d = `${year}-06-${String(day).padStart(2, '0')}`;
try {
const res = await fetch(`https://api.nbp.pl/api/exchangerates/rates/a/${currency}/${d}/?format=json`);
if (res.ok) {
const data = await res.json();
const r = data?.rates?.[0]?.mid;
if (typeof r === 'number') return { rate: r, date: d };
}
} catch (_) { /* ignore */ }
}
}
try {
const res = await fetch(`https://api.nbp.pl/api/exchangerates/rates/a/${currency}/?format=json`);
if (res.ok) {
const data = await res.json();
const r = data?.rates?.[0]?.mid;
const dt = data?.rates?.[0]?.effectiveDate || '';
if (typeof r === 'number') return { rate: r, date: dt };
}
} catch (_) { /* ignore */ }
return null;
};
const [eurData, usdData] = await Promise.all([fetchNBP('eur', currentYear), fetchNBP('usd', currentYear)]);
const eurRate = eurData?.rate ?? 4.35;
const usdRate = usdData?.rate ?? 4.00;
const rateDate = eurData?.date ?? usdData?.date ?? 'default';
const isLive = !!(eurData || usdData);
const CURRENCIES: Record<string, { labelKey: string; toPln: number; noteKey: string; noteParam?: Record<string, any> }> = {
PLN: { labelKey: 'HBM.moneyConverter.currencies.PLN', toPln: 1, noteKey: '' },
EUR: { labelKey: 'HBM.moneyConverter.currencies.EUR', toPln: eurRate, noteKey: 'HBM.moneyConverter.notes.nbp', noteParam: { date: rateDate } },
USD: { labelKey: 'HBM.moneyConverter.currencies.USD', toPln: usdRate, noteKey: 'HBM.moneyConverter.notes.nbp', noteParam: { date: rateDate } },
TH: { labelKey: 'HBM.moneyConverter.currencies.TH', toPln: eurRate, noteKey: 'HBM.moneyConverter.notes.th' },
FC: { labelKey: 'HBM.moneyConverter.currencies.FC', toPln: 2.0, noteKey: 'HBM.moneyConverter.notes.fc' },
ST: { labelKey: 'HBM.moneyConverter.currencies.ST', toPln: 240, noteKey: 'HBM.moneyConverter.notes.st' },
ZK: { labelKey: 'HBM.moneyConverter.currencies.ZK', toPln: 2880, noteKey: 'HBM.moneyConverter.notes.zk' },
PL: { labelKey: 'HBM.moneyConverter.currencies.PL', toPln: 34560, noteKey: 'HBM.moneyConverter.notes.pl' },
};
const currKeys = Object.keys(CURRENCIES);
const optHtml = (sel: string) => currKeys.map(k => {
const label = game.i18n.localize(CURRENCIES[k].labelKey);
return `<option value="${k}"${k === sel ? ' selected' : ''}>${label}</option>`;
}).join('');
const ratesRows = currKeys.map(k => {
const c = CURRENCIES[k];
const label = game.i18n.localize(c.labelKey);
const note = c.noteKey ? game.i18n.format(c.noteKey, c.noteParam || {}) : '';
const fromPln = k === 'PLN' ? '1.0000' : (1 / c.toPln).toPrecision(4);
return `<tr>
<td>${label}</td>
<td style="text-align:right;font-family:monospace;">${c.toPln === 1 ? '1.0000' : c.toPln.toFixed(4)}</td>
<td style="text-align:right;font-family:monospace;">${fromPln}</td>
<td style="color:#777;font-size:0.75rem;">${note}</td>
</tr>`;
}).join('');
const rates = Object.fromEntries(currKeys.map(k => [k, CURRENCIES[k].toPln]));
const liveText = isLive
? game.i18n.format('HBM.moneyConverter.liveRates', { date: rateDate })
: game.i18n.localize('HBM.moneyConverter.defaultRates');
const initialEur = isFinite(pln / eurRate) ? (pln / eurRate).toFixed(4) : '';
const content = `
<div class="hbm money-converter" style="padding:10px;font-family:'Signika',sans-serif;display:flex;flex-direction:column;gap:12px;">
<p style="margin:0;font-size:0.78rem;color:${isLive ? '#2a7a3e' : '#888'};">
<em>${liveText}</em>
</p>
<div style="background:var(--hbm-card-bg,#1a1a2e);border:1px solid var(--hbm-border,#333);border-radius:6px;padding:8px 12px;">
<span style="font-weight:bold;">${game.i18n.localize('HBM.moneyConverter.actorCash')}</span>
<span style="font-family:monospace;font-size:1.1rem;margin-left:6px;">${pln} PLN</span>
<small style="color:#888;margin-left:6px;">(${game.i18n.format('HBM.moneyConverter.campaignYear', { year: currentYear })})</small>
</div>
<div style="background:var(--hbm-card-bg,#1a1a2e);border:1px solid var(--hbm-accent,#6060c0);border-radius:6px;padding:10px 12px;display:flex;flex-direction:column;gap:8px;">
<strong style="font-size:0.9rem;">${game.i18n.localize('HBM.moneyConverter.calculator')}</strong>
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
<input id="hbm-conv-left" type="number" value="${pln}" min="0" step="any"
style="width:6.5rem;font-size:1rem;text-align:right;padding:3px 6px;background:transparent;color:var(--hbm-fg,#eee);border:1px solid var(--hbm-border,#555);border-radius:4px;" />
<select id="hbm-sf" style="flex:1;min-width:8rem;">
${optHtml('PLN')}
</select>
<span style="font-size:1.2rem;color:var(--hbm-accent,#8080ff);">⇄</span>
<input id="hbm-conv-right" type="number" value="${initialEur}" min="0" step="any"
style="width:6.5rem;font-size:1rem;text-align:right;padding:3px 6px;background:transparent;color:var(--hbm-fg,#eee);border:1px solid var(--hbm-border,#555);border-radius:4px;" />
<select id="hbm-st" style="flex:1;min-width:8rem;">
${optHtml('EUR')}
</select>
</div>
</div>
<details>
<summary style="cursor:pointer;font-size:0.85rem;color:var(--hbm-accent-dim,#999);">▸ ${game.i18n.localize('HBM.moneyConverter.exchangeRatesTable')}</summary>
<table style="width:100%;border-collapse:collapse;font-size:0.8rem;margin-top:8px;">
<thead><tr style="border-bottom:1px solid var(--hbm-border,#555);">
<th style="text-align:left;">${game.i18n.localize('HBM.moneyConverter.currency')}</th>
<th style="text-align:right;">${game.i18n.localize('HBM.moneyConverter.ratePln')}</th>
<th style="text-align:right;">${game.i18n.localize('HBM.moneyConverter.onePlnEquals')}</th>
<th>${game.i18n.localize('HBM.moneyConverter.note')}</th>
</tr></thead>
<tbody>${ratesRows}</tbody>
</table>
</details>
</div>
`;
const dialog = new MoneyConverterDialog({
window: { title: game.i18n.localize('HBM.ui.moneyConverterTitle') },
content,
buttons: [
{
action: 'ok',
label: game.i18n.localize('HBM.ui.confirm') || 'Ok',
default: true
}
],
rejectClose: false
}, rates);
dialog.render(true);
}
+2 -108
View File
@@ -6,6 +6,7 @@ import { applyDamage } from '../logic/damage';
import { castSpell } from '../logic/spell-cast';
import { HbmTSRoll } from '../dice/ts-roll';
import { ATTRIBUTES, AttributeKey, SKILL_KEYS, TS_DEFAULT_REQUIRED, TS_DEFAULT_THRESHOLD, getMagicPowerEntry } from '../constants';
import { showMoneyConverter } from './money-converter-dialog';
const { ActorSheetV2 } = foundry.applications.sheets as unknown as {
ActorSheetV2: typeof foundry.applications.sheets.ActorSheetV2;
@@ -338,113 +339,6 @@ export class NpcSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
static async _onRecalculateMoney(this: NpcSheet, event: PointerEvent, target: HTMLElement) {
const actor = (this as unknown as { actor: any }).actor;
const pln = actor.system.details?.money ?? 0;
const currentYear = actor.system.details?.currentYear ?? 2026;
let eurRate = 4.35;
let usdRate = 4.00;
let rateSource = "Domyślne przeliczniki (brak połączenia lub rok poza zakresem API NBP)";
let isLive = false;
const currentRealYear = new Date().getFullYear();
// Helper function to fetch rate from NBP
const fetchNBP = async (currency: string, year: number): Promise<{ rate: number; date: string } | null> => {
if (year >= 2002 && year <= currentRealYear) {
// Try first 7 days of June to find a working business day (NBP doesn't publish on weekends)
for (let day = 1; day <= 7; day++) {
const dateString = `${year}-06-${String(day).padStart(2, '0')}`;
try {
const response = await fetch(`https://api.nbp.pl/api/exchangerates/rates/a/${currency}/${dateString}/?format=json`);
if (response.ok) {
const data = await response.json();
const rate = data?.rates?.[0]?.mid;
if (typeof rate === 'number') {
return { rate, date: dateString };
}
}
} catch (e) {
// Ignore and try next
}
}
}
// Fallback to latest rate
try {
const response = await fetch(`https://api.nbp.pl/api/exchangerates/rates/a/${currency}/?format=json`);
if (response.ok) {
const data = await response.json();
const rate = data?.rates?.[0]?.mid;
const date = data?.rates?.[0]?.effectiveDate || "";
if (typeof rate === 'number') {
return { rate, date };
}
}
} catch (e) {
// Ignore
}
return null;
};
// Fetch rates in parallel
const [eurData, usdData] = await Promise.all([
fetchNBP("eur", currentYear),
fetchNBP("usd", currentYear)
]);
if (eurData && usdData) {
eurRate = eurData.rate;
usdRate = usdData.rate;
rateSource = `Pobrane z NBP (EUR z ${eurData.date}, USD z ${usdData.date})`;
isLive = true;
} else if (eurData) {
eurRate = eurData.rate;
rateSource = `Częściowo pobrane z NBP (EUR z ${eurData.date})`;
isLive = true;
} else if (usdData) {
usdRate = usdData.rate;
rateSource = `Częściowo pobrane z NBP (USD z ${usdData.date})`;
isLive = true;
}
// Conversion rates
const eur = (pln / eurRate).toFixed(2);
const th = (pln / eurRate).toFixed(2); // 1:1 with EUR
const usd = (pln / usdRate).toFixed(2);
const fc = (pln / 2.0).toFixed(2); // 2 PLN = 1 Credit
const st = (pln / 240).toFixed(2);
const zk = (pln / 2880).toFixed(2);
const pl = (pln / 34560).toFixed(2);
const content = `
<div class="hbm money-converter" style="padding: 10px; font-family: 'Signika', sans-serif;">
<p><strong>Bieżąca gotówka:</strong> ${pln} PLN (Rok kampanii: ${currentYear})</p>
<p style="font-size: 0.8rem; color: ${isLive ? '#2a7a3e' : '#888'}; margin-top: -5px;">
<em>Kursy: ${rateSource}</em><br/>
(1 EUR = ${eurRate.toFixed(4)} PLN, 1 USD = ${usdRate.toFixed(4)} PLN)
</p>
<hr style="border-top: 1px dashed var(--hbm-border, #ccc); margin: 10px 0;" />
<h4 style="margin: 5px 0;">Waluty Ziemskie i Międzyświatowe</h4>
<ul style="list-style: none; padding: 0; margin: 5px 0; display: flex; flex-direction: column; gap: 4px;">
<li><strong>FC (Kredyt Federacji Sol-3):</strong> ${fc} Credits <small style="color: #666;">(Waluta Federacji Sol-3, stały kurs 2 PLN = 1 Credit)</small></li>
<li><strong>EUR (Euro):</strong> ${eur} €</li>
<li><strong>USD (Dolar):</strong> ${usd} $</li>
<li><strong>Thrakka (TH):</strong> ${th} TH <small style="color: #666;">(krasnoludzka waluta rozliczeniowa, 1:1 z EUR)</small></li>
</ul>
<hr style="border-top: 1px dashed var(--hbm-border, #ccc); margin: 10px 0;" />
<h4 style="margin: 5px 0;">Krasnoludzkie Monety Klanowe (System Dwunastkowy)</h4>
<ul style="list-style: none; padding: 0; margin: 5px 0; display: flex; flex-direction: column; gap: 4px;">
<li><strong>ST (Srebrny Talent):</strong> ${st} ST <small style="color: #666;">(1 ST = 240 PLN / 55 TH)</small></li>
<li><strong>ZK (Złota Korona):</strong> ${zk} ZK <small style="color: #666;">(1 ZK = 2880 PLN / 660 TH)</small></li>
<li><strong>PL (Platynowy Lingot):</strong> ${pl} PL <small style="color: #666;">(1 PL = 34560 PLN / 7920 TH)</small></li>
</ul>
</div>
`;
await (foundry.applications.api as any).DialogV2.prompt({
window: { title: game.i18n.localize('HBM.ui.moneyConverterTitle') },
content: content,
rejectClose: false,
});
return showMoneyConverter(actor);
}
}
+2 -2
View File
@@ -2,9 +2,9 @@
"id": "hbm-rpg-v3",
"title": "Homebrew Magic: RPG v3",
"description": "System Foundry VTT dla Homebrew Magic: Role Playing Game v3 - autorska gra fabularna z mechaniką puli kości d6 (TS - Trudność:Sukcesy).",
"version": "1.3.30",
"version": "1.3.36",
"manifest": "https://vtt-content.octoturge.com/packages/system/hbm-rpg-v3.json",
"download": "https://vtt-content.octoturge.com/packages/system/hbm-rpg-v3-v1.3.30.zip",
"download": "https://vtt-content.octoturge.com/packages/system/hbm-rpg-v3-v1.3.36.zip",
"compatibility": {
"minimum": "13",
"verified": "14"