From fb41a48a2143f5391c4d9c9f34e27682095b2dad Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Sun, 8 Jun 2025 09:29:55 +0200 Subject: [PATCH 01/12] Create README.md --- Calendar and Weather/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 Calendar and Weather/README.md diff --git a/Calendar and Weather/README.md b/Calendar and Weather/README.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/Calendar and Weather/README.md @@ -0,0 +1 @@ + From 835c2437e20a9d09098989b7ee93c044969f54d0 Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Sun, 8 Jun 2025 09:30:35 +0200 Subject: [PATCH 02/12] Add files via upload --- Calendar and Weather/Calendar and Weather.js | 809 +++++++++++++++++++ Calendar and Weather/script.json | 12 + 2 files changed, 821 insertions(+) create mode 100644 Calendar and Weather/Calendar and Weather.js create mode 100644 Calendar and Weather/script.json diff --git a/Calendar and Weather/Calendar and Weather.js b/Calendar and Weather/Calendar and Weather.js new file mode 100644 index 000000000..fd853c6f9 --- /dev/null +++ b/Calendar and Weather/Calendar and Weather.js @@ -0,0 +1,809 @@ +on('ready', () => { + // Chat Style - Customize here + const chatStyle = 'border:1px solid #000; background-color:#926239; padding:5px; border-radius:5px; height: fit-content;'; + + // 🌦️ Weather Config with all climates - You can customize if you have your own matrice + const WeatherConfig = { + windForce: { + "1": { speed: [0, 11], chance: 55, name: "Slight Breeze" }, + "2": { speed: [12, 38], chance: 25, name: "Nice Breeze" }, + "3": { speed: [39, 88], chance: 12, name: "Strong Wind" }, + "4": { speed: [89, 102], chance: 5, name: "Storm" }, + "5": { speed: [103, 117], chance: 2, name: "Violent Storm" }, + "6": { speed: [118, 200], chance: 1, name: "Hurricane" } + }, + precipitationStrength: { + rain: { light: 55, moderate: 25, heavy: 15, torrential: 5 }, + snow: { light: 65, moderate: 25, snowstorm: 10 }, + thunderstorm: { slight: 55, moderate: 25, strong: 15, severe: 5 } + }, + climates: { + temperate: { + humidity: [40, 60], + windChances: { north: 10, west: 20, east: 65, south: 5 }, + temperature: { + spring: [[5,10],[10,15],[15,20],[20,30]], + summer: [[10,15],[15,20],[20,30],[30,40]], + fall: [[0,5],[5,10],[10,15],[15,20]], + winter: [[-5,0],[0,5],[5,10],[10,15]] + }, + precipitation: { + spring: { clear: 40, rain: 53, thunderstorm: 7 }, + summer: { clear: 42, rain: 46, thunderstorm: 12 }, + fall: { clear: 43, rain: 53, thunderstorm: 4 }, + winter: { clear: 35, rain: 60, thunderstorm: 5 } + } + }, + desert: { + humidity: [5, 15], + windChances: { north: 5, west: 10, east: 20, south: 65 }, + temperature: { + spring: [[15,20],[20,25],[25,35],[35,45]], + summer: [[20,25],[25,35],[35,45],[45,55]], + fall: [[10,15],[15,20],[20,25],[25,35]], + winter: [[-5,0],[0,5],[5,10],[10,15]] + }, + precipitation: { + spring: { clear: 66, rain: 13, thunderstorm: 21 }, + summer: { clear: 75, rain: 0, thunderstorm: 25 }, + fall: { clear: 73, rain: 11, thunderstorm: 16 }, + winter: { clear: 81, rain: 18, thunderstorm: 1 } + } + }, + jungle: { + humidity: [70, 90], + windChances: { north: 5, west: 10, east: 65, south: 20 }, + temperature: { + spring: [[10,15],[15,20],[20,30],[30,40]], + summer: [[15,20],[20,30],[30,40],[40,50]], + fall: [[5,10],[10,15],[15,20],[20,30]], + winter: [[0,5],[5,10],[10,15],[15,20]] + }, + precipitation: { + spring: { clear: 21, rain: 51, thunderstorm: 28 }, + summer: { clear: 74, rain: 15, thunderstorm: 11 }, + fall: { clear: 12, rain: 44, thunderstorm: 44 }, + winter: { clear: 15, rain: 48, thunderstorm: 37 } + } + }, + cold: { + humidity: [35, 55], + windChances: { north: 65, west: 20, east: 10, south: 5 }, + temperature: { + spring: [[-5,0],[0,5],[5,10],[10,15]], + summer: [[0,5],[5,10],[10,15],[15,20]], + fall: [[-10,-5],[-5,0],[0,5],[5,10]], + winter: [[-20,-10],[-10,-5],[-5,0],[0,5]] + }, + precipitation: { + spring: { clear: 75, rain: 22, thunderstorm: 3 }, + summer: { clear: 44, rain: 49, thunderstorm: 7 }, + fall: { clear: 35, rain: 63, thunderstorm: 2 }, + winter: { clear: 87, rain: 12, thunderstorm: 1 } + } + } + } + }; + + // 📅 Calendar Config - You can customized it + const CalendarConfig = { + days: ["Rilmor", "Eretor", "Nauri", "Neldir", "Veltor", "Eltor", "Mernach"], + months: [ + { name: "Juras", length: 31 }, + { name: "Fevnir", length: 28 }, + { name: "Morsir", length: 31 }, + { name: "Avalis", length: 30 }, + { name: "Maï", length: 31 }, + { name: "Jurn", length: 30 }, + { name: "Jullirq", length: 31 }, + { name: "Aors", length: 31 }, + { name: "Septibir", length: 30 }, + { name: "Octors", length: 31 }, + { name: "Noval", length: 30 }, + { name: "Devenir", length: 31 } + ], + seasons: [ //If you want to modify the season name, do it further down in the code in the translations + { name: "spring", months: [2, 3, 4] }, + { name: "summer", months: [5, 6, 7] }, + { name: "fall", months: [8, 9, 10] }, + { name: "winter", months: [11, 0, 1] } + ] + }; + + // 🌘 Moon Config - You can customized it (you can add moons respecting the defined format) + const MoonConfig = { + moons: [ + { + name: "Lunara", + cycle: 28, + phases: ["New", "Crescent", "First Quarter", "Gibbous", "Full", "Gibbous Waning", "Last Quarter", "Crescent Waning"] + }, + { + name: "Virell", + cycle: 35, + phases: ["New", "First Quarter", "Full", "Last Quarter"] + } + ] + }; + + // Localization + helpers remain unchanged from earlier (t, tClimate, etc.) + // Localization Strings + const i18n = { + en: { + climateNames: { + temperate: "Temperate", + desert: "Desert", + jungle: "Jungle", + cold: "Cold" + }, + moonPhases: { + "New": "New", + "Crescent": "Crescent", + "First Quarter": "First Quarter", + "Gibbous": "Gibbous", + "Full": "Full", + "Gibbous Waning": "Gibbous Waning", + "Last Quarter": "Last Quarter", + "Crescent Waning": "Crescent Waning" + }, + windDirections: { + north: "North", + south: "South", + east: "East", + west: "West" + }, + windForces: { + "Slight Breeze": "Slight Breeze", + "Nice Breeze": "Nice Breeze", + "Strong Wind": "Strong Wind", + "Storm": "Storm", + "Violent Storm": "Violent Storm", + "Hurricane": "Hurricane" + }, + seasonNames: { // Modify the seasons names here for english + spring: "Spring", + summer: "Summer", + fall: "Autumn", + winter: "Winter" + }, + date: "Date", + season: "Season", + moon: "Moon Phases", + weather: "Weather Report", + climate: "Climate", + temperature: "Temperature", + humidity: "Humidity", + wind: "Wind", + precipitation: "Precipitation", + clear: "Clear", + rain: "Rain", + snow: "Snow", + thunderstorm: "Thunderstorm", + windFrom: "from", + manual: "Manual Weather Mode", + generate: "Generate Weather", + setDay: "Set Day", + setYear: "Set Year", + saveProfile: "Save Current", + loadProfile: "Load", + exportProfile: "Export to Handout" + }, + fr: { + climateNames: { + temperate: "Tempéré", + desert: "Désertique", + jungle: "Jungle", + cold: "Froid" + }, + moonPhases: { + "New": "Nouvelle lune", + "Crescent": "Premier croissant", + "First Quarter": "Premier quartier", + "Gibbous": "Gibbeuse croissante", + "Full": "Pleine lune", + "Gibbous Waning": "Gibbeuse décroissante", + "Last Quarter": "Dernier quartier", + "Crescent Waning": "Dernier croissant" + }, + windDirections: { + north: "Nord", + south: "Sud", + east: "Est", + west: "Ouest" + }, + windForces: { + "Slight Breeze": "Brise légère", + "Nice Breeze": "Brise agréable", + "Strong Wind": "Vent fort", + "Storm": "Tempête", + "Violent Storm": "Tempête violente", + "Hurricane": "Ouragan" + }, + seasonNames: { // Modify the seasons names here for french + spring: "Floreas", + summer: "Solarios", + fall: "Mornevent", + winter: "Hilveris" + }, + date: "Date", + season: "Saison", + moon: "Phases lunaires", + weather: "Météo du jour", + climate: "Climat", + temperature: "Température", + humidity: "Humidité", + wind: "Vent", + precipitation: "Précipitations", + clear: "Clair", + rain: "Pluie", + snow: "Neige", + thunderstorm: "Orage", + windFrom: "de", + manual: "Mode météo manuel", + generate: "Générer la météo", + setDay: "Définir le jour", + setYear: "Définir l'année", + saveProfile: "Sauvegarder", + loadProfile: "Charger", + exportProfile: "Exporter vers un handout" + } + }; + + // Language Helpers + const lang = () => state.WeatherMod?.language || 'en'; + const t = (key) => i18n[lang()]?.[key] || key; + const tClimate = (key) => i18n[lang()].climateNames?.[key] || key; + const tPhase = (phase) => i18n[lang()].moonPhases?.[phase] || phase; + const tWindDir = (dir) => i18n[lang()].windDirections?.[dir] || dir; + const tWindForce = (force) => i18n[lang()].windForces?.[force] || force; + const tSeason = (season) => i18n[lang()].seasonNames?.[season] || season; + + const formatDate = () => { + const c = state.WeatherMod.calendar; + const dayName = CalendarConfig.days[(c.day - 1) % CalendarConfig.days.length]; + const monthName = CalendarConfig.months[c.month].name; + + if (lang() === 'fr') { + return `${dayName} ${c.day} ${monthName} ${c.year}`; + } else { + return `${monthName} ${c.day}, ${c.year} (${dayName})`; + } + }; + + // 🌍 Climate icons + const climateIcons = { + temperate: "🌳", + desert: "🏜️", + jungle: "🌴", + cold: "❄️" + }; + + // 🍂 Season icons + const seasonIcons = { + spring: "🌼", + summer: "☀️", + fall: "🍂", + winter: "❄️" + }; + + // 🌘 Moon phase icons + const moonIcons = { + "New": "🌑", + "Crescent": "🌒", + "First Quarter": "🌓", + "Gibbous": "🌔", + "Full": "🌕", + "Gibbous Waning": "🌖", + "Last Quarter": "🌗", + "Crescent Waning": "🌘" + }; + + // ☀️ Sky/weather condition icons + const skyIcons = { + clear: "☀️", + rain: "🌧️", + snow: "❄️", + thunderstorm: "⛈️" + }; + + // 🌡️ Temperature icon by interval + const tempIcon = (temp) => { + if (temp < -10) return "🧊"; + if (temp < 0 && temp >= -10) return "🥶"; + if (temp < 10 && temp >= 0) return "❄️"; + if (temp < 20 && temp >= 10) return "🌤️"; + if (temp < 30 && temp >= 20) return "☀️"; + if (temp < 40 && temp >= 30) return "🔥"; + if (temp >= 40) return "🌋"; + return "❓"; + }; + + // 💧 Humidity icon by interval + const humidityIcon = (humidity) => { + if (humidity < 20) return "🌵"; + if (humidity < 40 && humidity >= 20) return "💨"; + if (humidity < 60 && humidity >= 40) return "🌤️"; + if (humidity < 80 && humidity >= 60) return "💧"; + if (humidity >= 80) return "🌫️"; + return "❓"; + }; + + // 💨 Wind speed icon by interval + const windSpeedIcon = (speed) => { + if (speed <= 5 && speed >= 0) return "🌬️"; + if (speed <= 20 && speed > 5) return "🍃"; + if (speed <= 40 && speed > 20) return "💨"; + if (speed <= 70 && speed > 40) return "🌪️"; + if (speed <= 100 && speed > 70) return "🌬️🌩️"; + if (speed > 100) return "🌀"; + return "❓"; + }; + + // Clears old GM messages by inserting a visual separator + const clearOldWeatherMessages = () => { + sendChat("WeatherMod", "/w gm
"); + }; + + // Initialize default campaign state + if (!state.WeatherMod) { + state.WeatherMod = { + language: 'fr', + selectedClimate: 'temperate', + calendar: { + day: 17, + month: 10, + year: 3533, + totalDays: 0 + }, + settings: { + useManualWeather: false, + manualWeather: { + type: "clear", + windDirection: "north", + temperature: 20, + windSpeed: 10 + } + }, + profiles: {} + }; + } + + // Advance the calendar by one day + const advanceDay = () => { + const c = state.WeatherMod.calendar; + c.day++; + c.totalDays++; + const monthData = CalendarConfig.months[c.month]; + if (c.day > monthData.length) { + c.day = 1; + c.month++; + if (c.month >= CalendarConfig.months.length) { + c.month = 0; + c.year++; + } + } + }; + + // Determine the current season from the month + const getSeason = () => { + const monthIndex = state.WeatherMod.calendar.month; + const season = CalendarConfig.seasons.find(s => s.months.includes(monthIndex)); + return season ? season.name : 'spring'; // valeur par défaut + }; + + + // Calculate the current moon phases + const getMoonPhases = (day) => { + return MoonConfig.moons.map(moon => { + const phaseIndex = Math.floor((day % moon.cycle) / moon.cycle * moon.phases.length); + return `${moon.name}: ${tPhase(moon.phases[phaseIndex])}`; + }); + }; + + // Select a weighted random key from a table + const randomWeighted = (table) => { + const total = Object.values(table).reduce((a, b) => a + b, 0); + let roll = randomInteger(total); + for (let key in table) { + roll -= table[key]; + if (roll <= 0) return key; + } + return Object.keys(table)[0]; + }; + + // Pick a random number from a list of min-max ranges + const randomRangeFromList = (ranges) => { + const [min, max] = ranges[Math.floor(Math.random() * ranges.length)]; + return randomInteger(max - min + 1) + min - 1; + }; + + // 🌦 Generate the weather report (manual or randomized) + const generateWeather = () => { + const s = state.WeatherMod.settings; + const season = getSeason(); + const climate = WeatherConfig.climates[state.WeatherMod.selectedClimate]; + if (!climate) return '⚠ Invalid climate'; + + if (s.useManualWeather) { + return ` + ${t('temperature')}: ${s.manualWeather.temperature}°C...or after a marker) + let jsonMatch = notes.match(/
([\s\S]+?)<\/pre>/) || notes.match(/([\s\S]+)$/);
+ let json;
+ if (jsonMatch) {
+ try {
+ json = JSON.parse(jsonMatch[1]);
+ } catch (e) {
+ sendChat('WeatherMod', `/w gm Error: Invalid JSON in handout / JSON invalide dans le handout.`);
+ return;
}
- return Object.keys(table)[0];
- };
-
- // Pick a random number from a list of min-max ranges
- const randomRangeFromList = (ranges) => {
- const [min, max] = ranges[Math.floor(Math.random() * ranges.length)];
- return randomInteger(max - min + 1) + min - 1;
- };
-
- // 🌦 Generate the weather report (manual or randomized)
- const generateWeather = () => {
- const s = state.WeatherMod.settings;
- const season = getSeason();
- const climate = WeatherConfig.climates[state.WeatherMod.selectedClimate];
- if (!climate) return '⚠ Invalid climate';
-
- if (s.useManualWeather) {
- return `
- ${t('temperature')}: ${s.manualWeather.temperature}°C
- ${t('wind')}: ${s.manualWeather.windSpeed} km/h ${t('windFrom')} ${tWindDir(s.manualWeather.windDirection)}
- ${t('precipitation')}: ${t(s.manualWeather.type)}
- `;
+ } else {
+ sendChat('WeatherMod', `/w gm No JSON found in handout / Aucun JSON trouvé dans le handout.`);
+ return;
+ }
+ // Save imported profile
+ state.WeatherMod.profiles[name] = json;
+ sendChat('WeatherMod', `/w gm Profile "${name}" imported from handout / Profil "${name}" importé depuis le handout.`);
+ showGMMainMenu();
+ });
+ };
+
+ // Export profile to handout
+ const exportProfileToHandout = (name) => {
+ const profile = state.WeatherMod.profiles[name];
+ if (!profile) return;
+
+ // Save JSON in for easy import
+ const html = `
+ ${t('profiles')}: ${name}
+ ${t('climate')}: ${tClimate(profile.selectedClimate)}
+ ${t('date')}: ${profile.calendar.day} ${CalendarConfig.months[profile.calendar.month].name} ${profile.calendar.year}
+ ${t('language')}: ${profile.language}
+ ${t('manualMode')}: ${profile.settings.useManualWeather ? t('yes') : t('no')}
+ ${profile.settings.useManualWeather ? `
+ ${t('type')}: ${t(profile.settings.manualWeather.type)}
+ ${t('temp')}: ${profile.settings.manualWeather.temperature}°C
+ ${t('windSpeed')}: ${profile.settings.manualWeather.windSpeed} km/h ${t('windFrom')} ${tWindDir(profile.settings.manualWeather.windDirection)}
+ ${t('humidityShort')}: ${profile.settings.manualWeather.humidity !== undefined ? profile.settings.manualWeather.humidity : 50}%
+ ` : ''}
+
+ JSON:
+ ${JSON.stringify(profile, null, 2)}
+ ${JSON.stringify(profile)}
+ `;
+
+ let handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0];
+ if (!handout) {
+ handout = createObj("handout", { name: `WeatherProfile_${name}` });
}
-
- const humidity = randomInteger(climate.humidity[1] - climate.humidity[0]) + climate.humidity[0];
- const windOrigin = randomWeighted(climate.windChances);
- const windForceKey = randomWeighted(
- Object.fromEntries(Object.entries(WeatherConfig.windForce).map(([k, v]) => [k, v.chance]))
- );
- const windForce = WeatherConfig.windForce[windForceKey];
- const windSpeed = randomInteger(windForce.speed[1] - windForce.speed[0]) + windForce.speed[0];
- const temperature = randomRangeFromList(climate.temperature[season]);
- const precipType = randomWeighted(climate.precipitation[season]);
-
- let precipStrength = '';
- if (precipType === 'rain') {
- precipStrength = (temperature <= 0)
- ? `❄️ ${t('snow')} (${randomWeighted(WeatherConfig.precipitationStrength.snow)})`
- : `🌧️ ${t('rain')} (${randomWeighted(WeatherConfig.precipitationStrength.rain)})`;
- } else if (precipType === 'thunderstorm') {
- precipStrength = `⛈️ ${t('thunderstorm')} (${randomWeighted(WeatherConfig.precipitationStrength.thunderstorm)})`;
- } else {
- precipStrength = `☀️ ${t('clear')}`;
+ handout.set({ notes: html });
+ };
+
+ // Advance one day
+ const advanceDay = () => {
+ const c = state.WeatherMod.calendar;
+ c.day++;
+ c.totalDays++;
+ const max = CalendarConfig.months[c.month].length;
+ if (c.day > max) {
+ c.day = 1;
+ c.month++;
+ if (c.month >= CalendarConfig.months.length) {
+ c.month = 0;
+ c.year++;
+ }
}
-
- return `
- ${t('climate')}: ${climateIcons[state.WeatherMod.selectedClimate] || ""} ${tClimate(state.WeatherMod.selectedClimate)}
- ${t('season')}: ${seasonIcons[season] || ""} ${tSeason(season)}
- ${t('temperature')}: ${temperature}°C
- ${t('humidity')}: ${humidity}%
- ${t('wind')}: ${tWindForce(windForce.name)} (${windSpeed} km/h) ${t('windFrom')} ${tWindDir(windOrigin)}
- ${t('precipitation')}: ${precipStrength}
- `;
+ };
+
+ // State initialization
+ if (!state.WeatherMod) {
+ state.WeatherMod = {
+ language: 'fr',
+ selectedClimate: 'temperate',
+ calendar: { day: 1, month: 0, year: 1000, totalDays: 0 },
+ settings: {
+ useManualWeather: false,
+ manualWeather: { type: "clear", windDirection: "north", temperature: 20, windSpeed: 10, humidity: 50 }
+ },
+ profiles: {}
};
-
- const buildFullWeatherReportHTML = () => {
- const c = state.WeatherMod.calendar;
- const dayName = CalendarConfig.days[(c.day - 1) % CalendarConfig.days.length];
- const monthName = CalendarConfig.months[c.month].name;
- const season = getSeason();
- const s = state.WeatherMod.settings;
- const climate = WeatherConfig.climates[state.WeatherMod.selectedClimate];
-
- let temperature, humidity, windSpeed, windOrigin, windForceKey, windForce, precipType, precipStrength;
-
- if (s.useManualWeather) {
- temperature = s.manualWeather.temperature;
- windSpeed = s.manualWeather.windSpeed;
- windOrigin = s.manualWeather.windDirection;
- precipType = s.manualWeather.type;
- humidity = "-";
- windForce = { name: tWindForce("Manual") };
- precipStrength = `${skyIcons[precipType] || ""} ${t(precipType)}`;
+ }
+
+ // Chat commands
+ on('chat:message', (msg) => {
+ if (msg.type !== 'api' || !playerIsGM(msg.playerid)) return;
+
+ const args = msg.content.trim().split(" ");
+ const command = args[0];
+ const subcommand = args[1];
+ const value = args.slice(2).join(" ");
+
+ if (command !== '!weather') return;
+
+ switch (subcommand) {
+ case 'report': displayFullReport(); break;
+ case 'showplayers': showWeatherToPlayers(); break;
+ case 'menu': showGMMainMenu(); break;
+ case 'menu-date': showDateMenu(); break;
+ case 'menu-manual': showManualWeatherMenu(); break;
+ case 'menu-profiles': showProfilesMenu(); break;
+
+ case 'next':
+ case 'next-day':
+ advanceDay();
+ displayFullReport();
+ break;
+
+ case 'lang':
+ if (['en', 'fr'].includes(args[2])) {
+ state.WeatherMod.language = args[2];
+ sendChat("WeatherMod", `/w gm ${t('language')} : ${args[2].toUpperCase()}`);
} else {
- humidity = randomInteger(climate.humidity[1] - climate.humidity[0]) + climate.humidity[0];
- windOrigin = randomWeighted(climate.windChances);
- windForceKey = randomWeighted(
- Object.fromEntries(Object.entries(WeatherConfig.windForce).map(([k, v]) => [k, v.chance]))
- );
- windForce = WeatherConfig.windForce[windForceKey];
- windSpeed = randomInteger(windForce.speed[1] - windForce.speed[0]) + windForce.speed[0];
- if (climate.temperature && climate.temperature[season]) {
- temperature = randomRangeFromList(climate.temperature[season]);
- } else {
- log(`⚠️ Température manquante pour le climat "${state.WeatherMod.selectedClimate}" et la saison "${season}"`);
- temperature = 0;
- }
- precipType = randomWeighted(climate.precipitation[season]);
-
- if (precipType === 'rain') {
- precipStrength = (temperature <= 0)
- ? `❄️ ${t('snow')} (${randomWeighted(WeatherConfig.precipitationStrength.snow)})`
- : `🌧️ ${t('rain')} (${randomWeighted(WeatherConfig.precipitationStrength.rain)})`;
- } else if (precipType === 'thunderstorm') {
- precipStrength = `⛈️ ${t('thunderstorm')} (${randomWeighted(WeatherConfig.precipitationStrength.thunderstorm)})`;
- } else {
- precipStrength = `☀️ ${t('clear')}`;
- }
+ sendChat("WeatherMod", `/w gm ${t('language')}: en, fr`);
}
+ break;
- const moon = getMoonPhases(c.totalDays)
- .map(m => {
- const [name, phase] = m.split(": ");
- return `${moonIcons[phase] || "🌑"} ${name}: ${tPhase(phase)}`;
- })
- .join("
");
-
- let output = ``;
- output += `📅 ${t('date')}: ${lang() === 'fr' ? `${dayName} ${c.day} ${monthName} ${c.year}` : `${monthName} ${c.day}, ${c.year} (${dayName})`}`;
- output += `🗓 ${t('season')}: ${seasonIcons[season] || ""} ${tSeason(season)}`;
- output += `
🌘 ${t('moon')}:
${moon}`;
- output += `
🌦 ${t('weather')}:`;
- output += `${t('climate')}: ${climateIcons[state.WeatherMod.selectedClimate] || ""} ${tClimate(state.WeatherMod.selectedClimate)}`;
- output += `${t('season')}: ${seasonIcons[season] || ""} ${tSeason(season)}`;
- output += `${t('temperature')}: ${temperature}°C ${tempIcon(temperature)}`;
- output += `${t('humidity')}: ${humidity}${humidity !== "-" ? "%" : ""} ${humidity !== "-" ? humidityIcon(humidity) : ""}`;
- output += `${t('wind')}: ${tWindForce(windForce.name)} (${windSpeed} km/h) ${t('windFrom')} ${tWindDir(windOrigin)} ${windSpeedIcon(windSpeed)}`;
- output += `${t('precipitation')}: ${precipStrength}`;
- output += ``;
-
- return output;
- };
-
- // Display full weather report to the GM
- const displayFullReport = () => {
- clearOldWeatherMessages();
- const output = buildFullWeatherReportHTML();
- sendChat("WeatherMod", `/w gm ${output}`);
- };
-
- // Display report for players
- const showWeatherToPlayers = () => {
- const output = buildFullWeatherReportHTML();
- sendChat("WeatherMod", output); // public message
- };
-
- // GM Menu: Weather Control Panel
- const showGMMenu = () => {
- clearOldWeatherMessages();
-
+ case 'setgm': {
+ const param = args[2];
+ const val = args.slice(3).join(" ");
const s = state.WeatherMod;
- const c = s.calendar;
- const set = s.settings;
- const manual = set.manualWeather;
-
- // Buttons
- const climates = Object.keys(WeatherConfig.climates).map(climate =>
- `[${climateIcons[climate] || ""} ${tClimate(climate)}](!weather setgm climate ${climate})`
- ).join(" ");
-
- const months = CalendarConfig.months.map((m, i) =>
- `[${m.name}](!weather setgm month ${i})`
- ).join(" ");
-
- const weatherTypes = ['clear', 'rain', 'snow', 'thunderstorm'].map(type =>
- `[${skyIcons[type] || ""} ${t(type)}](!weather setgm weathertype ${type})`
- ).join(" ");
-
- const windDirs = ['north', 'east', 'south', 'west'].map(dir =>
- `[${tWindDir(dir)}](!weather setgm winddir ${dir})`
- ).join(" ");
-
- // Message build
- let output = ``;
- output += `⚙️ GM Weather Menu
`;
+ const manual = s.settings.manualWeather;
- // Date
- output += `📅 ${t('date')}: ${lang() === 'fr'
- ? `${CalendarConfig.days[(c.day - 1) % CalendarConfig.days.length]} ${c.day} ${CalendarConfig.months[c.month].name} ${c.year}`
- : `${CalendarConfig.months[c.month].name} ${c.day}, ${c.year} (${CalendarConfig.days[(c.day - 1) % CalendarConfig.days.length]})`}
`;
-
- output += `
- [${t('setDay')}](!weather setgm day ?{${t('setDay')}|${c.day}})
- [${t('setYear')}](!weather setgm year ?{${t('setYear')}|${c.year}})
- `;
-
- output += `${months}
`;
-
- // Climate
- output += `🌍 ${t('climate')}: ${climateIcons[s.selectedClimate] || ""} ${tClimate(s.selectedClimate)}
`;
- output += `${climates}
`;
-
- // Manual mode
- output += `🛠 ${t('manual')}: [${set.useManualWeather ? "🟢 On" : "🔴 Off"}](!weather setgm manual ${set.useManualWeather ? "off" : "on"})
`;
-
- if (set.useManualWeather) {
- output += `
☁️ ${t('precipitation')}:`;
- output += `${weatherTypes}`;
-
- output += `🌡 ${t('temperature')}:
- [${tempIcon(manual.temperature)} ${manual.temperature}°C](!weather setgm temp ?{${t('temperature')}|${manual.temperature}})
`;
-
- output += `💨 ${t('wind')}:
- [${windSpeedIcon(manual.windSpeed)} ${manual.windSpeed} km/h](!weather setgm windspeed ?{${t('wind')}|${manual.windSpeed}})
`;
-
- output += `${t('windFrom')}:
- ${windDirs}`;
+ switch (param) {
+ case 'climate':
+ if (val in WeatherConfig.climates) s.selectedClimate = val;
+ break;
+ case 'manual':
+ s.settings.useManualWeather = (val === 'on');
+ break;
+ case 'weathertype':
+ manual.type = val;
+ break;
+ case 'winddir':
+ manual.windDirection = val;
+ break;
+ case 'temp':
+ const tval = parseInt(val, 10);
+ if (!isNaN(tval)) manual.temperature = tval;
+ break;
+ case 'windspeed':
+ const wval = parseInt(val, 10);
+ if (!isNaN(wval)) manual.windSpeed = wval;
+ break;
+ case 'humidity':
+ const hval = parseInt(val, 10);
+ if (!isNaN(hval)) manual.humidity = hval;
+ break;
+ case 'day':
+ const d = parseInt(val, 10);
+ if (!isNaN(d)) s.calendar.day = d;
+ break;
+ case 'month':
+ const m = parseInt(val, 10);
+ if (!isNaN(m)) s.calendar.month = m;
+ break;
+ case 'year':
+ const y = parseInt(val, 10);
+ if (!isNaN(y)) s.calendar.year = y;
+ break;
}
+ showGMMainMenu();
+ break;
+ }
- // Profiles
- output += `
💾 Profiles:`;
- output += `
- [${t('saveProfile')}](!weather save ?{Profile name})
- [${t('loadProfile')}](!weather load ?{Profile name})
- [${t('exportProfile')}](!weather export ?{Profile name})
- `;
-
- // Language
- output += `
- 🌐 Language: [EN](!weather lang en) [FR](!weather lang fr)
- `;
-
- // Generate
- output += `
- [🌦 ${t('generate')}](!weather report)
- `;
-
- output += `
- [📣 ${t('weather')} → Players](!weather showplayers)
- `;
-
- output += ``;
+ case 'save':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('saveProfile')} : !weather save MonProfil`);
+ } else {
+ saveWeatherProfile(value.trim());
+ sendChat('WeatherMod', `/w gm ${t('saveProfile')}: ${value.trim()}`);
+ }
+ break;
- sendChat("WeatherMod", `/w gm ${output}`);
- };
+ case 'load':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('loadProfile')} : !weather load MonProfil`);
+ } else {
+ loadWeatherProfile(value.trim());
+ sendChat('WeatherMod', `/w gm ${t('loadProfile')}: ${value.trim()}`);
+ showGMMainMenu();
+ }
+ break;
+ case 'export':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('exportProfile')} : !weather export MonProfil`);
+ } else {
+ exportProfileToHandout(value.trim());
+ sendChat('WeatherMod', `/w gm ${t('exportProfile')}: WeatherProfile_${value.trim()}`);
+ }
+ break;
- // Save the current weather setup
- const saveWeatherProfile = (name) => {
- if (!name) return;
- state.WeatherMod.profiles[name] = {
- language: state.WeatherMod.language,
- selectedClimate: state.WeatherMod.selectedClimate,
- calendar: { ...state.WeatherMod.calendar },
- settings: JSON.parse(JSON.stringify(state.WeatherMod.settings))
- };
- };
-
- // Load a saved weather profile
- const loadWeatherProfile = (name) => {
- if (!name || !state.WeatherMod.profiles[name]) return;
- const data = state.WeatherMod.profiles[name];
- state.WeatherMod.language = data.language;
- state.WeatherMod.selectedClimate = data.selectedClimate;
- state.WeatherMod.calendar = { ...data.calendar };
- state.WeatherMod.settings = JSON.parse(JSON.stringify(data.settings));
- };
-
- // Export a profile to a Roll20 handout
- const exportProfileToHandout = (name) => {
- const profile = state.WeatherMod.profiles[name];
- if (!profile) return;
-
- const html = `
- 📋 Weather Profile: ${name}
- 🌍 Climate: ${tClimate(profile.selectedClimate)}
- 📆 Date: ${profile.calendar.day} / ${CalendarConfig.months[profile.calendar.month].name} / ${profile.calendar.year}
- 🌐 Language: ${profile.language}
- ⚙️ Manual Weather: ${profile.settings.useManualWeather ? "Yes" : "No"}
- ${profile.settings.useManualWeather ? `
- 🌡 Temp: ${profile.settings.manualWeather.temperature}°C
- 💨 Wind: ${profile.settings.manualWeather.windSpeed} km/h from ${tWindDir(profile.settings.manualWeather.windDirection)}
- ☁️ Type: ${t(profile.settings.manualWeather.type)}
- ` : ''}
- `;
-
- let handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0];
- if (!handout) {
- handout = createObj("handout", { name: `WeatherProfile_${name}` });
- }
- handout.set({ notes: html });
- };
-
- // Handle weather-related API commands
- on('chat:message', (msg) => {
- if (msg.type !== 'api' || !playerIsGM(msg.playerid)) return;
-
- const args = msg.content.trim().split(" ");
- const command = args[0];
- const subcommand = args[1];
-
- if (command !== '!weather') return;
-
- switch (subcommand) {
- case 'report':
- displayFullReport();
- break;
-
- case 'next':
- case 'next-day':
- advanceDay();
- displayFullReport();
- break;
-
- case 'menu':
- showGMMenu();
- break;
-
- case 'lang': {
- const langCode = args[2];
- if (langCode && ['en', 'fr'].includes(langCode)) {
- state.WeatherMod.language = langCode;
- sendChat('WeatherMod', `/w gm 🌐 Language set to: ${langCode === 'fr' ? 'Français' : 'English'}`);
- } else {
- sendChat('WeatherMod', `/w gm ⚠️ Invalid language. Use 'en' or 'fr'.`);
- }
- break;
- }
-
- case 'setgm': {
- const param = args[2];
- const value = args.slice(3).join(" ");
- const s = state.WeatherMod;
- const manual = s.settings.manualWeather;
-
- switch (param) {
- case 'climate':
- if (value in WeatherConfig.climates) s.selectedClimate = value;
- break;
- case 'manual':
- s.settings.useManualWeather = (value === 'on');
- break;
- case 'weathertype':
- manual.type = value;
- break;
- case 'winddir':
- manual.windDirection = value;
- break;
- case 'temp': {
- const temp = parseInt(value, 10);
- if (!isNaN(temp)) manual.temperature = temp;
- break;
- }
- case 'windspeed': {
- const wind = parseInt(value, 10);
- if (!isNaN(wind)) manual.windSpeed = wind;
- break;
- }
- case 'day': {
- const day = parseInt(value, 10);
- if (!isNaN(day)) s.calendar.day = day;
- break;
- }
- case 'month': {
- const month = parseInt(value, 10);
- if (!isNaN(month)) s.calendar.month = month;
- break;
- }
- case 'year': {
- const year = parseInt(value, 10);
- if (!isNaN(year)) s.calendar.year = year;
- break;
- }
- }
-
- showGMMenu();
- break;
- }
-
- case 'save': {
- const saveName = args.slice(2).join(" ").trim();
- if (!saveName) {
- sendChat('WeatherMod', `/w gm ⚠️ Provide a name to save: !weather save MyScene`);
- } else {
- saveWeatherProfile(saveName);
- sendChat('WeatherMod', `/w gm ✅ Saved profile: ${saveName}`);
- }
- break;
- }
-
- case 'load': {
- const loadName = args.slice(2).join(" ").trim();
- if (!loadName) {
- sendChat('WeatherMod', `/w gm ⚠️ Provide a name to load: !weather load MyScene`);
- } else {
- loadWeatherProfile(loadName);
- sendChat('WeatherMod', `/w gm 📂 Loaded profile: ${loadName}`);
- showGMMenu();
- }
- break;
- }
-
- case 'export': {
- const exportName = args.slice(2).join(" ").trim();
- if (!exportName) {
- sendChat('WeatherMod', `/w gm ⚠️ Provide a name to export: !weather export MyScene`);
- } else {
- exportProfileToHandout(exportName);
- sendChat('WeatherMod', `/w gm 📝 Exported to handout: WeatherProfile_${exportName}`);
- }
- break;
- }
-
- case 'showplayers':
- showWeatherToPlayers();
- break;
+ case 'import':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('importProfile')} : !weather import MonProfil`);
+ } else {
+ importProfileFromHandout(value.trim());
+ showGMMainMenu();
}
- });
-});
\ No newline at end of file
+ break;
+ }
+ });
+});
From 2f7452a61f5a4bf781387265d8bdb4e5794dc5ad Mon Sep 17 00:00:00 2001
From: mailare49 <57602257+mailare49@users.noreply.github.com>
Date: Tue, 10 Jun 2025 11:22:25 +0200
Subject: [PATCH 10/12] Update script.json
---
Calendar and Weather/script.json | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/Calendar and Weather/script.json b/Calendar and Weather/script.json
index 2fe87a0b0..ddea56859 100644
--- a/Calendar and Weather/script.json
+++ b/Calendar and Weather/script.json
@@ -2,11 +2,10 @@
"name": "Calendar and Weather",
"script": "Calendar and Weather.js",
"version": "1.0",
- "previousversions": "",
- "description": "An entirely customizable weather and calendar mod in english and french",
+ "description": "An entirely customizable weather and calendar mod in english and french / Un mod pour le calendrier et la météo entièrement personnalisable en anglais et en français.",
"authors": "Maïlare",
"roll20userid": "3234089",
"dependencies": "",
"modifies": "",
"conflicts": ""
-}
\ No newline at end of file
+}
From 1282ff4239c7754cd69001cdfb35ecb4332fc200 Mon Sep 17 00:00:00 2001
From: mailare49 <57602257+mailare49@users.noreply.github.com>
Date: Wed, 11 Jun 2025 08:14:18 +0200
Subject: [PATCH 11/12] Create Calendar and weather.js
---
.../1.0/Calendar and weather.js | 581 ++++++++++++++++++
1 file changed, 581 insertions(+)
create mode 100644 Calendar and Weather/1.0/Calendar and weather.js
diff --git a/Calendar and Weather/1.0/Calendar and weather.js b/Calendar and Weather/1.0/Calendar and weather.js
new file mode 100644
index 000000000..171e86dc7
--- /dev/null
+++ b/Calendar and Weather/1.0/Calendar and weather.js
@@ -0,0 +1,581 @@
+on('ready', () => {
+ // Styles
+ const chatStyle = 'background-color:#926239; border:1px solid #000; border-radius:8px; padding:8px; width:100%; height:fit-content; font-size:1.1em;';
+ const hr = '
';
+ const styleTitle = 'text-align:center; font-size:1.5em; font-weight:bold;';
+ const styleSection = 'font-size:1.3em; font-weight:bold; margin-top:8px;';
+ const styleCenter = 'text-align:center;';
+ const btnStyle = 'background-color:#574530; border:1px solid #352716; border-radius:4px; padding:2px 8px; font-size:0.9em; color:#fff; text-decoration:none; margin:2px; display:inline-block;';
+ const btnGroup = (html) => `${html}`;
+
+ // Configurations
+ const WeatherConfig = {
+ windForce: {
+ "1": { speed: [0, 11], chance: 55, name: "Slight Breeze" },
+ "2": { speed: [12, 38], chance: 25, name: "Nice Breeze" },
+ "3": { speed: [39, 88], chance: 12, name: "Strong Wind" },
+ "4": { speed: [89, 102], chance: 5, name: "Storm" },
+ "5": { speed: [103, 117], chance: 2, name: "Violent Storm" },
+ "6": { speed: [118, 200], chance: 1, name: "Hurricane" }
+ },
+ precipitationStrength: {
+ rain: { light: 55, moderate: 25, heavy: 15, torrential: 5 },
+ snow: { light: 65, moderate: 25, snowstorm: 10 },
+ thunderstorm: { slight: 55, moderate: 25, strong: 15, severe: 5 }
+ },
+ climates: {
+ temperate: {
+ humidity: [40, 60],
+ windChances: { north: 10, west: 20, east: 65, south: 5 },
+ temperature: {
+ spring: [[5,10],[10,15],[15,20],[20,30]],
+ summer: [[10,15],[15,20],[20,30],[30,40]],
+ fall: [[0,5],[5,10],[10,15],[15,20]],
+ winter: [[-5,0],[0,5],[5,10],[10,15]]
+ },
+ precipitation: {
+ spring: { clear: 40, rain: 53, thunderstorm: 7 },
+ summer: { clear: 42, rain: 46, thunderstorm: 12 },
+ fall: { clear: 43, rain: 53, thunderstorm: 4 },
+ winter: { clear: 35, rain: 60, thunderstorm: 5 }
+ }
+ },
+ desert: {
+ humidity: [5, 15],
+ windChances: { north: 5, west: 10, east: 20, south: 65 },
+ temperature: {
+ spring: [[15,20],[20,25],[25,35],[35,45]],
+ summer: [[20,25],[25,35],[35,45],[45,55]],
+ fall: [[10,15],[15,20],[20,25],[25,35]],
+ winter: [[-5,0],[0,5],[5,10],[10,15]]
+ },
+ precipitation: {
+ spring: { clear: 66, rain: 13, thunderstorm: 21 },
+ summer: { clear: 75, rain: 0, thunderstorm: 25 },
+ fall: { clear: 73, rain: 11, thunderstorm: 16 },
+ winter: { clear: 81, rain: 18, thunderstorm: 1 }
+ }
+ },
+ jungle: {
+ humidity: [70, 90],
+ windChances: { north: 5, west: 10, east: 65, south: 20 },
+ temperature: {
+ spring: [[10,15],[15,20],[20,30],[30,40]],
+ summer: [[15,20],[20,30],[30,40],[40,50]],
+ fall: [[5,10],[10,15],[15,20],[20,30]],
+ winter: [[0,5],[5,10],[10,15],[15,20]]
+ },
+ precipitation: {
+ spring: { clear: 21, rain: 51, thunderstorm: 28 },
+ summer: { clear: 74, rain: 15, thunderstorm: 11 },
+ fall: { clear: 12, rain: 44, thunderstorm: 44 },
+ winter: { clear: 15, rain: 48, thunderstorm: 37 }
+ }
+ },
+ cold: {
+ humidity: [35, 55],
+ windChances: { north: 65, west: 20, east: 10, south: 5 },
+ temperature: {
+ spring: [[-5,0],[0,5],[5,10],[10,15]],
+ summer: [[0,5],[5,10],[10,15],[15,20]],
+ fall: [[-10,-5],[-5,0],[0,5],[5,10]],
+ winter: [[-20,-10],[-10,-5],[-5,0],[0,5]]
+ },
+ precipitation: {
+ spring: { clear: 75, rain: 22, thunderstorm: 3 },
+ summer: { clear: 44, rain: 49, thunderstorm: 7 },
+ fall: { clear: 35, rain: 63, thunderstorm: 2 },
+ winter: { clear: 87, rain: 12, thunderstorm: 1 }
+ }
+ }
+ }
+ };
+
+ const CalendarConfig = {
+ days: ["Rilmor", "Eretor", "Nauri", "Neldir", "Veltor", "Eltor", "Mernach"],
+ months: [
+ { name: "Juras", length: 31 }, { name: "Fevnir", length: 28 }, { name: "Morsir", length: 31 },
+ { name: "Avalis", length: 30 }, { name: "Maï", length: 31 }, { name: "Jurn", length: 30 },
+ { name: "Jullirq", length: 31 }, { name: "Aors", length: 31 }, { name: "Septibir", length: 30 },
+ { name: "Octors", length: 31 }, { name: "Noval", length: 30 }, { name: "Devenir", length: 31 }
+ ],
+ seasons: [
+ { name: "spring", months: [2, 3, 4] },
+ { name: "summer", months: [5, 6, 7] },
+ { name: "fall", months: [8, 9, 10] },
+ { name: "winter", months: [11, 0, 1] }
+ ]
+ };
+
+ const MoonConfig = {
+ moons: [
+ { name: "Lunara", cycle: 28, phases: ["New", "Crescent", "First Quarter", "Gibbous", "Full", "Gibbous Waning", "Last Quarter", "Crescent Waning"] },
+ { name: "Virell", cycle: 35, phases: ["New", "First Quarter", "Full", "Last Quarter"] }
+ ]
+ };
+
+ // Translations
+ const i18n = {
+ en: {
+ climateNames: { temperate: "Temperate", desert: "Desert", jungle: "Jungle", cold: "Cold" },
+ moonPhases: {
+ "New": "New", "Crescent": "Crescent", "First Quarter": "First Quarter", "Gibbous": "Gibbous",
+ "Full": "Full", "Gibbous Waning": "Gibbous Waning", "Last Quarter": "Last Quarter", "Crescent Waning": "Crescent Waning"
+ },
+ windDirections: { north: "North", south: "South", east: "East", west: "West" },
+ windForces: {
+ "Slight Breeze": "Slight Breeze", "Nice Breeze": "Nice Breeze", "Strong Wind": "Strong Wind",
+ "Storm": "Storm", "Violent Storm": "Violent Storm", "Hurricane": "Hurricane", "Manual": "Manual Wind"
+ },
+ seasonNames: { spring: "Spring", summer: "Summer", fall: "Autumn", winter: "Winter" },
+ date: "Date", season: "Season", moon: "Moon Phases", weather: "Weather Report", climate: "Climate",
+ temperature: "Temperature", humidity: "Humidity", wind: "Wind", precipitation: "Precipitation",
+ clear: "Clear", rain: "Rain", snow: "Snow", thunderstorm: "Thunderstorm", windFrom: "from",
+ manual: "Manual Weather Mode", generate: "Generate Weather", setDay: "Set Day", setYear: "Set Year",
+ saveProfile: "Save Current", loadProfile: "Load", exportProfile: "Export to handout", importProfile: "Import from handout", month: "Month",
+ language: "Language", profiles: "Profiles", manualMode: "Manual Mode", type: "Type", temp: "Temp",
+ windSpeed: "Wind", back: "Back", yes: "Yes", no: "No", humidityShort: "Humidity"
+ },
+ fr: {
+ climateNames: { temperate: "Tempéré", desert: "Désertique", jungle: "Jungle", cold: "Froid" },
+ moonPhases: {
+ "New": "Nouvelle Lune", "Crescent": "Premier Croissant", "First Quarter": "Premier Quartier",
+ "Gibbous": "Gibbeuse Croissante", "Full": "Pleine Lune", "Gibbous Waning": "Gibbeuse Décroissante",
+ "Last Quarter": "Dernier Quartier", "Crescent Waning": "Dernier Croissant"
+ },
+ windDirections: { north: "Nord", south: "Sud", east: "Est", west: "Ouest" },
+ windForces: {
+ "Slight Breeze": "Brise Légère", "Nice Breeze": "Belle Brise", "Strong Wind": "Vent Fort",
+ "Storm": "Tempête", "Violent Storm": "Tempête Violente", "Hurricane": "Ouragan", "Manual": "Vent Manuel"
+ },
+ seasonNames: { spring: "Printemps", summer: "Été", fall: "Automne", winter: "Hiver" },
+ date: "Date", season: "Saison", moon: "Phases Lunaires", weather: "Météo", climate: "Climat",
+ temperature: "Température", humidity: "Humidité", wind: "Vent", precipitation: "Précipitations",
+ clear: "Clair", rain: "Pluie", snow: "Neige", thunderstorm: "Orage", windFrom: "depuis le",
+ manual: "Mode Météo Manuel", generate: "Générer la Météo", setDay: "Définir le Jour", setYear: "Définir l'Année",
+ saveProfile: "Sauvegarder", loadProfile: "Charger", exportProfile: "Exporter vers un handout", importProfile: "Importer depuis un handout", month: "Mois",
+ language: "Langue", profiles: "Profils", manualMode: "Mode Manuel", type: "Type", temp: "Temp",
+ windSpeed: "Vent", back: "Retour", yes: "Oui", no: "Non", humidityShort: "Humidité"
+ }
+ };
+
+ // Translation functions
+ const lang = () => state.WeatherMod?.language || 'en';
+ const t = (key) => i18n[lang()]?.[key] || key;
+ const tClimate = (key) => i18n[lang()].climateNames?.[key] || key;
+ const tPhase = (key) => i18n[lang()].moonPhases?.[key] || key;
+ const tWindDir = (key) => i18n[lang()].windDirections?.[key] || key;
+ const tWindForce = (key) => i18n[lang()].windForces?.[key] || key;
+ const tSeason = (key) => i18n[lang()].seasonNames?.[key] || key;
+
+ // Icons
+ const climateIcons = { temperate: "🌳", desert: "🏜️", jungle: "🌴", cold: "❄️" };
+ const seasonIcons = { spring: "🌼", summer: "☀️", fall: "🍂", winter: "❄️" };
+ const moonIcons = {
+ "New": "🌑", "Crescent": "🌒", "First Quarter": "🌓", "Gibbous": "🌔", "Full": "🌕",
+ "Gibbous Waning": "🌖", "Last Quarter": "🌗", "Crescent Waning": "🌘"
+ };
+ const skyIcons = { clear: "☀️", rain: "🌧️", snow: "❄️", thunderstorm: "⛈️" };
+
+ const tempIcon = (t) => t < -10 ? "🧊" : t < 0 ? "🥶" : t < 10 ? "❄️" : t < 20 ? "🌤️" : t < 30 ? "☀️" : t < 40 ? "🔥" : "🌋";
+ const humidityIcon = (h) => h < 20 ? "🌵" : h < 40 ? "💨" : h < 60 ? "🌤️" : h < 80 ? "💧" : "🌫️";
+ const windSpeedIcon = (s) => s <= 5 ? "🌬️" : s <= 20 ? "🍃" : s <= 40 ? "💨" : s <= 70 ? "🌪️" : s <= 100 ? "🌬️🌩️" : "🌀";
+
+ // Utility functions
+ const getSeason = () => {
+ const m = state.WeatherMod.calendar.month;
+ return CalendarConfig.seasons.find(s => s.months.includes(m))?.name || "spring";
+ };
+
+ const getMoonPhases = (day) => MoonConfig.moons.map(m => {
+ const idx = Math.floor((day % m.cycle) / m.cycle * m.phases.length);
+ return `${m.name}: ${m.phases[idx]}`;
+ });
+
+ const randomWeighted = (table) => {
+ const total = Object.values(table).reduce((a, b) => a + b, 0);
+ let roll = randomInteger(total);
+ for (const key in table) {
+ roll -= table[key];
+ if (roll <= 0) return key;
+ }
+ };
+
+ const randomRangeFromList = (ranges) => {
+ const [min, max] = ranges[Math.floor(Math.random() * ranges.length)];
+ return randomInteger(max - min + 1) + min - 1;
+ };
+
+ const clearOldWeatherMessages = () => {
+ sendChat("WeatherMod", ``);
+ };
+
+ // Build the full weather report HTML
+ const buildFullWeatherReportHTML = () => {
+ const c = state.WeatherMod.calendar;
+ const dayName = CalendarConfig.days[(c.day - 1) % CalendarConfig.days.length];
+ const monthName = CalendarConfig.months[c.month].name;
+ const season = getSeason();
+ const s = state.WeatherMod.settings;
+ const climate = WeatherConfig.climates[state.WeatherMod.selectedClimate];
+
+ let temp, humidity, windSpeed, windOrigin, windForce, precipType, precipStrength;
+
+ if (s.useManualWeather) {
+ temp = s.manualWeather.temperature;
+ windSpeed = s.manualWeather.windSpeed;
+ windOrigin = s.manualWeather.windDirection;
+ precipType = s.manualWeather.type;
+ humidity = s.manualWeather.humidity !== undefined ? s.manualWeather.humidity : "-";
+ windForce = { name: tWindForce("Manual") };
+ precipStrength = `${skyIcons[precipType] || ""} ${t(precipType)}`;
+ } else {
+ humidity = randomInteger(climate.humidity[1] - climate.humidity[0]) + climate.humidity[0];
+ windOrigin = randomWeighted(climate.windChances);
+ const forceKey = randomWeighted(Object.fromEntries(Object.entries(WeatherConfig.windForce).map(([k, v]) => [k, v.chance])));
+ windForce = WeatherConfig.windForce[forceKey];
+ windSpeed = randomInteger(windForce.speed[1] - windForce.speed[0]) + windForce.speed[0];
+ temp = randomRangeFromList(climate.temperature[season]);
+ precipType = randomWeighted(climate.precipitation[season]);
+
+ if (precipType === 'rain') {
+ precipStrength = (temp <= 0)
+ ? `❄️ ${t('snow')} (${randomWeighted(WeatherConfig.precipitationStrength.snow)})`
+ : `🌧️ ${t('rain')} (${randomWeighted(WeatherConfig.precipitationStrength.rain)})`;
+ } else if (precipType === 'thunderstorm') {
+ precipStrength = `⛈️ ${t('thunderstorm')} (${randomWeighted(WeatherConfig.precipitationStrength.thunderstorm)})`;
+ } else {
+ precipStrength = `☀️ ${t('clear')}`;
+ }
+ }
+
+ const moon = getMoonPhases(c.totalDays).map(m => {
+ const [name, phase] = m.split(": ");
+ return `${moonIcons[phase] || "🌑"} ${name}: ${tPhase(phase)}`;
+ }).join("
");
+
+ let html = ``;
+ html += `${t('weather')}`;
+ html += `${t('date')}: ${lang() === 'fr' ? `${dayName} ${c.day} ${monthName} ${c.year}` : `${monthName} ${c.day}, ${c.year} (${dayName})`}`;
+ html += `${t('season')}: ${seasonIcons[season]} ${tSeason(season)}`;
+ html += `${hr}${t('moon')}:${moon}${hr}`;
+ html += `${t('climate')}: ${climateIcons[state.WeatherMod.selectedClimate]} ${tClimate(state.WeatherMod.selectedClimate)}`;
+ html += `${t('temperature')}: ${temp}°C ${tempIcon(temp)}`;
+ html += `${t('humidity')}: ${humidity}${humidity !== "-" ? "%" : ""} ${humidity !== "-" ? humidityIcon(humidity) : ""}`;
+ html += `${t('wind')}: ${tWindForce(windForce.name)} (${windSpeed} km/h) ${t('windFrom')} ${tWindDir(windOrigin)} ${windSpeedIcon(windSpeed)}`;
+ html += `${t('precipitation')}: ${precipStrength}`;
+ html += ``;
+ return html;
+ };
+
+ const displayFullReport = () => {
+ clearOldWeatherMessages();
+ sendChat("WeatherMod", `/w gm ${buildFullWeatherReportHTML()}`);
+ };
+
+ const showWeatherToPlayers = () => {
+ sendChat("WeatherMod", buildFullWeatherReportHTML());
+ };
+
+ // Styled menus
+ const showGMMainMenu = () => {
+ const s = state.WeatherMod;
+ const climates = Object.keys(WeatherConfig.climates).map(climate =>
+ `${climateIcons[climate]} ${tClimate(climate)}`
+ ).join(" ");
+
+ const html = `
+ ${t('weather')}${hr}
+ ${t('climate')}: ${climateIcons[s.selectedClimate]} ${tClimate(s.selectedClimate)}
+ ${btnGroup(climates)}${hr}
+ ${t('language')}: ${s.language.toUpperCase()}
+ ${btnGroup(`EN FR`)}${hr}
+ ${btnGroup(`📅 ${t('date')}`)}
+ ${btnGroup(`🛠 ${t('manual')}`)}
+ ${btnGroup(`💾 ${t('profiles')}`)}
+ ${btnGroup(`🌦 ${t('generate')}`)}
+ ${btnGroup(`📣 ${t('weather')} → Players`)}
+ `;
+ sendChat("WeatherMod", `/w gm ${html}`);
+ };
+
+ const showDateMenu = () => {
+ const c = state.WeatherMod.calendar;
+ const months = CalendarConfig.months.map((m, i) =>
+ `${m.name}`
+ ).join(" ");
+ const html = `
+ ${t('date')}${hr}
+ ${btnGroup(`${t('setDay')} ${t('setYear')}`)}
+ ${hr}${btnGroup(months)}${hr}
+ ${btnGroup(`⬅️ ${t('back')}`)}
+ `;
+ sendChat("WeatherMod", `/w gm ${html}`);
+ };
+
+ const showManualWeatherMenu = () => {
+ const manual = state.WeatherMod.settings.manualWeather;
+ const weatherTypes = ['clear', 'rain', 'snow', 'thunderstorm'].map(type =>
+ `${skyIcons[type]} ${t(type)}`
+ ).join(" ");
+ const windDirs = ['north', 'east', 'south', 'west'].map(dir =>
+ `${tWindDir(dir)}`
+ ).join(" ");
+
+ const html = `
+ ${t('manual')}${hr}
+ ${t('manualMode')}: ${state.WeatherMod.settings.useManualWeather ? "🟢" : "🔴"} ${state.WeatherMod.settings.useManualWeather ? t('yes') : t('no')}
+ ${t('precipitation')}:${btnGroup(weatherTypes)}
+ ${t('temperature')}: ${manual.temperature}°C
+ ${t('windSpeed')}: ${manual.windSpeed} km/h
+ ${t('windFrom')}: ${btnGroup(windDirs)}
+ ${t('humidityShort')}: ${manual.humidity !== undefined ? manual.humidity : 50}%
+ ${hr}${btnGroup(`⬅️ ${t('back')}`)}
+ `;
+ sendChat("WeatherMod", `/w gm ${html}`);
+ };
+
+ const showProfilesMenu = () => {
+ const html = `
+ ${t('profiles')}${hr}
+ ${btnGroup(`
+ ${t('saveProfile')}
+ ${t('loadProfile')}
+ ${t('exportProfile')}
+ ${t('importProfile')}
`)}
+ ${hr}${btnGroup(`⬅️ ${t('back')}`)}
+ `;
+ sendChat("WeatherMod", `/w gm ${html}`);
+ };
+
+ // Weather profiles
+ const saveWeatherProfile = (name) => {
+ if (!name) return;
+ state.WeatherMod.profiles[name] = {
+ language: state.WeatherMod.language,
+ selectedClimate: state.WeatherMod.selectedClimate,
+ calendar: { ...state.WeatherMod.calendar },
+ settings: JSON.parse(JSON.stringify(state.WeatherMod.settings))
+ };
+ };
+
+ const loadWeatherProfile = (name) => {
+ if (!name || !state.WeatherMod.profiles[name]) return;
+ const data = state.WeatherMod.profiles[name];
+ state.WeatherMod.language = data.language;
+ state.WeatherMod.selectedClimate = data.selectedClimate;
+ state.WeatherMod.calendar = { ...data.calendar };
+ state.WeatherMod.settings = JSON.parse(JSON.stringify(data.settings));
+ };
+
+ // Import a weather profile from a handout
+ const importProfileFromHandout = (name) => {
+ const handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0];
+ if (!handout) {
+ sendChat('WeatherMod', `/w gm [${name}] Handout not found / Handout introuvable.`);
+ return;
+ }
+ handout.get('notes', (notes) => {
+ // Try to extract JSON from the handout notes (between ...
or after a marker)
+ let jsonMatch = notes.match(/([\s\S]+?)<\/pre>/) || notes.match(/([\s\S]+)$/);
+ let json;
+ if (jsonMatch) {
+ try {
+ json = JSON.parse(jsonMatch[1]);
+ } catch (e) {
+ sendChat('WeatherMod', `/w gm Error: Invalid JSON in handout / JSON invalide dans le handout.`);
+ return;
+ }
+ } else {
+ sendChat('WeatherMod', `/w gm No JSON found in handout / Aucun JSON trouvé dans le handout.`);
+ return;
+ }
+ // Save imported profile
+ state.WeatherMod.profiles[name] = json;
+ sendChat('WeatherMod', `/w gm Profile "${name}" imported from handout / Profil "${name}" importé depuis le handout.`);
+ showGMMainMenu();
+ });
+ };
+
+ // Export profile to handout
+ const exportProfileToHandout = (name) => {
+ const profile = state.WeatherMod.profiles[name];
+ if (!profile) return;
+
+ // Save JSON in for easy import
+ const html = `
+ ${t('profiles')}: ${name}
+ ${t('climate')}: ${tClimate(profile.selectedClimate)}
+ ${t('date')}: ${profile.calendar.day} ${CalendarConfig.months[profile.calendar.month].name} ${profile.calendar.year}
+ ${t('language')}: ${profile.language}
+ ${t('manualMode')}: ${profile.settings.useManualWeather ? t('yes') : t('no')}
+ ${profile.settings.useManualWeather ? `
+ ${t('type')}: ${t(profile.settings.manualWeather.type)}
+ ${t('temp')}: ${profile.settings.manualWeather.temperature}°C
+ ${t('windSpeed')}: ${profile.settings.manualWeather.windSpeed} km/h ${t('windFrom')} ${tWindDir(profile.settings.manualWeather.windDirection)}
+ ${t('humidityShort')}: ${profile.settings.manualWeather.humidity !== undefined ? profile.settings.manualWeather.humidity : 50}%
+ ` : ''}
+
+ JSON:
+ ${JSON.stringify(profile, null, 2)}
+ ${JSON.stringify(profile)}
+ `;
+
+ let handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0];
+ if (!handout) {
+ handout = createObj("handout", { name: `WeatherProfile_${name}` });
+ }
+ handout.set({ notes: html });
+ };
+
+ // Advance one day
+ const advanceDay = () => {
+ const c = state.WeatherMod.calendar;
+ c.day++;
+ c.totalDays++;
+ const max = CalendarConfig.months[c.month].length;
+ if (c.day > max) {
+ c.day = 1;
+ c.month++;
+ if (c.month >= CalendarConfig.months.length) {
+ c.month = 0;
+ c.year++;
+ }
+ }
+ };
+
+ // State initialization
+ if (!state.WeatherMod) {
+ state.WeatherMod = {
+ language: 'fr',
+ selectedClimate: 'temperate',
+ calendar: { day: 1, month: 0, year: 1000, totalDays: 0 },
+ settings: {
+ useManualWeather: false,
+ manualWeather: { type: "clear", windDirection: "north", temperature: 20, windSpeed: 10, humidity: 50 }
+ },
+ profiles: {}
+ };
+ }
+
+ // Chat commands
+ on('chat:message', (msg) => {
+ if (msg.type !== 'api' || !playerIsGM(msg.playerid)) return;
+
+ const args = msg.content.trim().split(" ");
+ const command = args[0];
+ const subcommand = args[1];
+ const value = args.slice(2).join(" ");
+
+ if (command !== '!weather') return;
+
+ switch (subcommand) {
+ case 'report': displayFullReport(); break;
+ case 'showplayers': showWeatherToPlayers(); break;
+ case 'menu': showGMMainMenu(); break;
+ case 'menu-date': showDateMenu(); break;
+ case 'menu-manual': showManualWeatherMenu(); break;
+ case 'menu-profiles': showProfilesMenu(); break;
+
+ case 'next':
+ case 'next-day':
+ advanceDay();
+ displayFullReport();
+ break;
+
+ case 'lang':
+ if (['en', 'fr'].includes(args[2])) {
+ state.WeatherMod.language = args[2];
+ sendChat("WeatherMod", `/w gm ${t('language')} : ${args[2].toUpperCase()}`);
+ } else {
+ sendChat("WeatherMod", `/w gm ${t('language')}: en, fr`);
+ }
+ break;
+
+ case 'setgm': {
+ const param = args[2];
+ const val = args.slice(3).join(" ");
+ const s = state.WeatherMod;
+ const manual = s.settings.manualWeather;
+
+ switch (param) {
+ case 'climate':
+ if (val in WeatherConfig.climates) s.selectedClimate = val;
+ break;
+ case 'manual':
+ s.settings.useManualWeather = (val === 'on');
+ break;
+ case 'weathertype':
+ manual.type = val;
+ break;
+ case 'winddir':
+ manual.windDirection = val;
+ break;
+ case 'temp':
+ const tval = parseInt(val, 10);
+ if (!isNaN(tval)) manual.temperature = tval;
+ break;
+ case 'windspeed':
+ const wval = parseInt(val, 10);
+ if (!isNaN(wval)) manual.windSpeed = wval;
+ break;
+ case 'humidity':
+ const hval = parseInt(val, 10);
+ if (!isNaN(hval)) manual.humidity = hval;
+ break;
+ case 'day':
+ const d = parseInt(val, 10);
+ if (!isNaN(d)) s.calendar.day = d;
+ break;
+ case 'month':
+ const m = parseInt(val, 10);
+ if (!isNaN(m)) s.calendar.month = m;
+ break;
+ case 'year':
+ const y = parseInt(val, 10);
+ if (!isNaN(y)) s.calendar.year = y;
+ break;
+ }
+ showGMMainMenu();
+ break;
+ }
+
+ case 'save':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('saveProfile')} : !weather save MonProfil`);
+ } else {
+ saveWeatherProfile(value.trim());
+ sendChat('WeatherMod', `/w gm ${t('saveProfile')}: ${value.trim()}`);
+ }
+ break;
+
+ case 'load':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('loadProfile')} : !weather load MonProfil`);
+ } else {
+ loadWeatherProfile(value.trim());
+ sendChat('WeatherMod', `/w gm ${t('loadProfile')}: ${value.trim()}`);
+ showGMMainMenu();
+ }
+ break;
+
+ case 'export':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('exportProfile')} : !weather export MonProfil`);
+ } else {
+ exportProfileToHandout(value.trim());
+ sendChat('WeatherMod', `/w gm ${t('exportProfile')}: WeatherProfile_${value.trim()}`);
+ }
+ break;
+
+ case 'import':
+ if (!value.trim()) {
+ sendChat('WeatherMod', `/w gm ${t('importProfile')} : !weather import MonProfil`);
+ } else {
+ importProfileFromHandout(value.trim());
+ showGMMainMenu();
+ }
+ break;
+ }
+ });
+});
From c4b80b263f4b23098adbf3b05becfcd15e61e001 Mon Sep 17 00:00:00 2001
From: mailare49 <57602257+mailare49@users.noreply.github.com>
Date: Thu, 12 Jun 2025 20:39:40 +0200
Subject: [PATCH 12/12] Update script.json
---
Calendar and Weather/script.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Calendar and Weather/script.json b/Calendar and Weather/script.json
index ddea56859..3b4f6c37b 100644
--- a/Calendar and Weather/script.json
+++ b/Calendar and Weather/script.json
@@ -5,7 +5,7 @@
"description": "An entirely customizable weather and calendar mod in english and french / Un mod pour le calendrier et la météo entièrement personnalisable en anglais et en français.",
"authors": "Maïlare",
"roll20userid": "3234089",
- "dependencies": "",
- "modifies": "",
- "conflicts": ""
+ "dependencies": [],
+ "modifies":[],
+ "conflicts": []
}