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 0000000000..8b13789179 --- /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 0000000000..fd853c6f9c --- /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
+ ${t('wind')}: ${s.manualWeather.windSpeed} km/h ${t('windFrom')} ${tWindDir(s.manualWeather.windDirection)}
+ ${t('precipitation')}: ${t(s.manualWeather.type)} + `; + } + + 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')}`; + } + + 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} + `; + }; + + 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)}`; + } 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')}`; + } + } + + 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(); + + 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

`; + + // 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}
`; + } + + // 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 += `
`; + + sendChat("WeatherMod", `/w gm ${output}`); + }; + + + // 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; + } + }); +}); \ No newline at end of file diff --git a/Calendar and Weather/script.json b/Calendar and Weather/script.json new file mode 100644 index 0000000000..2fe87a0b02 --- /dev/null +++ b/Calendar and Weather/script.json @@ -0,0 +1,12 @@ +{ + "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", + "authors": "Maïlare", + "roll20userid": "3234089", + "dependencies": "", + "modifies": "", + "conflicts": "" +} \ No newline at end of file From 4cf16f8db3c3eeb38d0d66d969b5c3559af58ee5 Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:13:51 +0200 Subject: [PATCH 03/12] Update README.md --- Calendar and Weather/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Calendar and Weather/README.md b/Calendar and Weather/README.md index 8b13789179..1c4841c9ea 100644 --- a/Calendar and Weather/README.md +++ b/Calendar and Weather/README.md @@ -1 +1 @@ - +Attention, ne changez pas le nom des saisons ici, il faut le changer dans les traductions plus bas dans le code +Attention, ne changez pas le nom des saisons ici, il faut le changer dans les traductions plus bas dans le code From 47f790d9e898f3122bb864f8b44685fb512ffc24 Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:14:36 +0200 Subject: [PATCH 05/12] Update README.md --- Calendar and Weather/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Calendar and Weather/README.md b/Calendar and Weather/README.md index 5c8e6c3ead..d5140d1b2f 100644 --- a/Calendar and Weather/README.md +++ b/Calendar and Weather/README.md @@ -1 +1 @@ -Attention, ne changez pas le nom des saisons ici, il faut le changer dans les traductions plus bas dans le code +Attention, ne changez pas le nom des saisons ici, il faut le changer dans les traductions plus bas dans le code From 0ded022e1e453bbe4948962504ade392ad64c598 Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:16:05 +0200 Subject: [PATCH 06/12] Update README.md --- Calendar and Weather/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Calendar and Weather/README.md b/Calendar and Weather/README.md index d5140d1b2f..c6864f91c8 100644 --- a/Calendar and Weather/README.md +++ b/Calendar and Weather/README.md @@ -1 +1 @@ -Attention, ne changez pas le nom des saisons ici, il faut le changer dans les traductions plus bas dans le code +Attention, ne changez pas le nom des saisons ici, il faut le changer dans les traductions plus bas dans le code From 399f648d6aacd59ba73467a5524072642c99be13 Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:16:13 +0200 Subject: [PATCH 07/12] Update README.md --- Calendar and Weather/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Calendar and Weather/README.md b/Calendar and Weather/README.md index c6864f91c8..8b13789179 100644 --- a/Calendar and Weather/README.md +++ b/Calendar and Weather/README.md @@ -1 +1 @@ -Attention, ne changez pas le nom des saisons ici, il faut le changer dans les traductions plus bas dans le code + From a96efd59ba8f87a3fefe57693a248ac08d965c31 Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:16:29 +0200 Subject: [PATCH 08/12] Update README.md --- Calendar and Weather/README.md | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/Calendar and Weather/README.md b/Calendar and Weather/README.md index 8b13789179..ecc0387560 100644 --- a/Calendar and Weather/README.md +++ b/Calendar and Weather/README.md @@ -1 +1,102 @@ +# Calendar and Weather Mod +## English + +This mod is fully customizable. You can generate a report in chat with the current date, season, weather, and moon phases. + +### Configuration + +#### Weather Parameters + +To change the weather settings, edit the `WeatherConfig` object in the script: + +- **Wind strength**: Change the values in `windForce`. +- **Precipitation strength**: Change the values in `precipitationStrength`. +- For each climate (`climates`), you can modify: + - **Humidity**: `humidity` + - **Wind direction probability**: `windChances` + - **Temperature ranges for each season**: `temperature` + - **Weather probabilities**: `precipitation` + +#### Calendar Parameters + +To customize the calendar, edit the `CalendarConfig` object: + +- **Day names**: Change or add/remove days in `days`. +- **Month names and lengths**: Change or add/remove months in `months`. +- **Season months**: Change which months belong to each season in `seasons` (months are zero-indexed: first month = 0). + +#### Moon Parameters + +Edit the `MoonConfig` object to change: + +- **Moon names** +- **Cycle length** +- **Phase names** (in order) +- You can add or remove moons, following the format. + +--- + +## Français + +Ce mod est entièrement personnalisable. Il permet de générer un rapport dans le chat avec la date, la saison, la météo et les phases de la lune. + +### Configuration + +#### Paramètres de la météo + +Pour modifier la météo, éditez l'objet `WeatherConfig` dans le script : + +- **Force du vent** : Modifiez les valeurs dans `windForce`. +- **Force des précipitations** : Modifiez les valeurs dans `precipitationStrength`. +- Pour chaque climat (`climates`), vous pouvez modifier : + - **Humidité** : `humidity` + - **Probabilité de direction du vent** : `windChances` + - **Plages de températures par saison** : `temperature` + - **Probabilités de météo** : `precipitation` + +#### Paramètres du calendrier + +Pour personnaliser le calendrier, éditez l'objet `CalendarConfig` : + +- **Noms des jours** : Modifiez, ajoutez ou retirez des jours dans `days`. +- **Noms et durées des mois** : Modifiez, ajoutez ou retirez des mois dans `months`. +- **Mois des saisons** : Modifiez les mois associés à chaque saison dans `seasons` (les mois commencent à 0). + +#### Paramètres de la lune + +Modifiez l'objet `MoonConfig` pour changer : + +- **Noms des lunes** +- **Durée du cycle** +- **Noms des phases** (dans l'ordre) +- Vous pouvez ajouter ou retirer des lunes en respectant le format. + +--- + +## Command List / Liste des commandes + +**EN:** All commands are accessible from the GM menu. + +**FR :** Toutes les commandes sont accessibles depuis le menu MJ. + +| Commande / Command | Description (EN) | Description (FR) | +|---------------------------|------------------------------------------|-----------------------------------------| +| `!weather menu` | Show the GM main menu | Affiche le menu principal MJ | +| `!weather report` | Show the full weather report to the GM | Affiche le rapport météo au MJ | +| `!weather showplayers` | Show the weather report to all players | Affiche la météo à tous les joueurs | +| `!weather menu-date` | Show the date settings menu | Affiche le menu de réglage de la date | +| `!weather menu-manual` | Show the manual weather menu | Affiche le menu météo manuel | +| `!weather menu-profiles` | Show the profiles menu | Affiche le menu des profils | +| `!weather next` | Advance the calendar by one day | Avance le calendrier d'un jour | +| `!weather lang en/fr` | Change the language | Change la langue | +| `!weather save ` | Save the current weather profile | Sauvegarde le profil météo actuel | +| `!weather load ` | Load a saved weather profile | Charge un profil météo | +| `!weather export ` | Export a profile to a handout | Exporte un profil dans un handout | +| `!weather import ` | Import a profile from a handout | Importe un profil depuis un handout | + +**EN:** To import a profile, use only the profile name you chose (for example: `weather1`). +**Do not** include the `WeatherProfile_` prefix from the handout name. + +**FR :** Pour importer un profil, indiquez uniquement le nom du profil que vous avez choisi (par exemple : `meteo1`). +**N’ajoutez pas** le préfixe `WeatherProfile_` du nom du handout. From d1586080d84179e378a607a110abf779a424af33 Mon Sep 17 00:00:00 2001 From: mailare49 <57602257+mailare49@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:19:02 +0200 Subject: [PATCH 09/12] Update Calendar and Weather.js --- Calendar and Weather/Calendar and Weather.js | 1146 +++++++----------- 1 file changed, 459 insertions(+), 687 deletions(-) diff --git a/Calendar and Weather/Calendar and Weather.js b/Calendar and Weather/Calendar and Weather.js index fd853c6f9c..885d292b0c 100644 --- a/Calendar and Weather/Calendar and Weather.js +++ b/Calendar and Weather/Calendar and Weather.js @@ -1,9 +1,15 @@ 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 = { + // 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" }, @@ -83,727 +89,493 @@ on('ready', () => { } } } - }; - - // 📅 Calendar Config - You can customized it - const CalendarConfig = { + }; + + 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 } + { 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 + 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] } ] - }; - - // 🌘 Moon Config - You can customized it (you can add moons respecting the defined format) - const MoonConfig = { + }; + + 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"] - } + { 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 = { + }; + + // Translations + const i18n = { en: { - climateNames: { - temperate: "Temperate", - desert: "Desert", - jungle: "Jungle", - cold: "Cold" - }, + 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" + "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" + "Slight Breeze": "Slight Breeze", "Nice Breeze": "Nice Breeze", "Strong Wind": "Strong Wind", + "Storm": "Storm", "Violent Storm": "Violent Storm", "Hurricane": "Hurricane", "Manual": "Manual Wind" }, - 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" + 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" - }, + 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" + "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" + "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: { // 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" + 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é" } - }; - - // 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}`; + }; + + // 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 { - return `${monthName} ${c.day}, ${c.year} (${dayName})`; + precipStrength = `☀️ ${t('clear')}`; } - }; - - // 🌍 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])}`; - }); + 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)}
+ + ${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)) }; - - // 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; + }; + + 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;
         }
-        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 2fe87a0b02..ddea568598 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 0000000000..171e86dc7c --- /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)}
+ + ${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 ddea568598..3b4f6c37b6 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": [] }