diff --git a/Calendar and Weather/1.0/Calendar and weather.js b/Calendar and Weather/1.0/Calendar and weather.js new file mode 100644 index 000000000..171e86dc7 --- /dev/null +++ b/Calendar and Weather/1.0/Calendar and weather.js @@ -0,0 +1,581 @@ +on('ready', () => { + // Styles + const chatStyle = 'background-color:#926239; border:1px solid #000; border-radius:8px; padding:8px; width:100%; height:fit-content; font-size:1.1em;'; + const hr = '
'; + const styleTitle = 'text-align:center; font-size:1.5em; font-weight:bold;'; + const styleSection = 'font-size:1.3em; font-weight:bold; margin-top:8px;'; + const styleCenter = 'text-align:center;'; + const btnStyle = 'background-color:#574530; border:1px solid #352716; border-radius:4px; padding:2px 8px; font-size:0.9em; color:#fff; text-decoration:none; margin:2px; display:inline-block;'; + const btnGroup = (html) => `
${html}
`; + + // Configurations + const WeatherConfig = { + windForce: { + "1": { speed: [0, 11], chance: 55, name: "Slight Breeze" }, + "2": { speed: [12, 38], chance: 25, name: "Nice Breeze" }, + "3": { speed: [39, 88], chance: 12, name: "Strong Wind" }, + "4": { speed: [89, 102], chance: 5, name: "Storm" }, + "5": { speed: [103, 117], chance: 2, name: "Violent Storm" }, + "6": { speed: [118, 200], chance: 1, name: "Hurricane" } + }, + precipitationStrength: { + rain: { light: 55, moderate: 25, heavy: 15, torrential: 5 }, + snow: { light: 65, moderate: 25, snowstorm: 10 }, + thunderstorm: { slight: 55, moderate: 25, strong: 15, severe: 5 } + }, + climates: { + temperate: { + humidity: [40, 60], + windChances: { north: 10, west: 20, east: 65, south: 5 }, + temperature: { + spring: [[5,10],[10,15],[15,20],[20,30]], + summer: [[10,15],[15,20],[20,30],[30,40]], + fall: [[0,5],[5,10],[10,15],[15,20]], + winter: [[-5,0],[0,5],[5,10],[10,15]] + }, + precipitation: { + spring: { clear: 40, rain: 53, thunderstorm: 7 }, + summer: { clear: 42, rain: 46, thunderstorm: 12 }, + fall: { clear: 43, rain: 53, thunderstorm: 4 }, + winter: { clear: 35, rain: 60, thunderstorm: 5 } + } + }, + desert: { + humidity: [5, 15], + windChances: { north: 5, west: 10, east: 20, south: 65 }, + temperature: { + spring: [[15,20],[20,25],[25,35],[35,45]], + summer: [[20,25],[25,35],[35,45],[45,55]], + fall: [[10,15],[15,20],[20,25],[25,35]], + winter: [[-5,0],[0,5],[5,10],[10,15]] + }, + precipitation: { + spring: { clear: 66, rain: 13, thunderstorm: 21 }, + summer: { clear: 75, rain: 0, thunderstorm: 25 }, + fall: { clear: 73, rain: 11, thunderstorm: 16 }, + winter: { clear: 81, rain: 18, thunderstorm: 1 } + } + }, + jungle: { + humidity: [70, 90], + windChances: { north: 5, west: 10, east: 65, south: 20 }, + temperature: { + spring: [[10,15],[15,20],[20,30],[30,40]], + summer: [[15,20],[20,30],[30,40],[40,50]], + fall: [[5,10],[10,15],[15,20],[20,30]], + winter: [[0,5],[5,10],[10,15],[15,20]] + }, + precipitation: { + spring: { clear: 21, rain: 51, thunderstorm: 28 }, + summer: { clear: 74, rain: 15, thunderstorm: 11 }, + fall: { clear: 12, rain: 44, thunderstorm: 44 }, + winter: { clear: 15, rain: 48, thunderstorm: 37 } + } + }, + cold: { + humidity: [35, 55], + windChances: { north: 65, west: 20, east: 10, south: 5 }, + temperature: { + spring: [[-5,0],[0,5],[5,10],[10,15]], + summer: [[0,5],[5,10],[10,15],[15,20]], + fall: [[-10,-5],[-5,0],[0,5],[5,10]], + winter: [[-20,-10],[-10,-5],[-5,0],[0,5]] + }, + precipitation: { + spring: { clear: 75, rain: 22, thunderstorm: 3 }, + summer: { clear: 44, rain: 49, thunderstorm: 7 }, + fall: { clear: 35, rain: 63, thunderstorm: 2 }, + winter: { clear: 87, rain: 12, thunderstorm: 1 } + } + } + } + }; + + const CalendarConfig = { + days: ["Rilmor", "Eretor", "Nauri", "Neldir", "Veltor", "Eltor", "Mernach"], + months: [ + { name: "Juras", length: 31 }, { name: "Fevnir", length: 28 }, { name: "Morsir", length: 31 }, + { name: "Avalis", length: 30 }, { name: "Maï", length: 31 }, { name: "Jurn", length: 30 }, + { name: "Jullirq", length: 31 }, { name: "Aors", length: 31 }, { name: "Septibir", length: 30 }, + { name: "Octors", length: 31 }, { name: "Noval", length: 30 }, { name: "Devenir", length: 31 } + ], + seasons: [ + { name: "spring", months: [2, 3, 4] }, + { name: "summer", months: [5, 6, 7] }, + { name: "fall", months: [8, 9, 10] }, + { name: "winter", months: [11, 0, 1] } + ] + }; + + const MoonConfig = { + moons: [ + { name: "Lunara", cycle: 28, phases: ["New", "Crescent", "First Quarter", "Gibbous", "Full", "Gibbous Waning", "Last Quarter", "Crescent Waning"] }, + { name: "Virell", cycle: 35, phases: ["New", "First Quarter", "Full", "Last Quarter"] } + ] + }; + + // Translations + const i18n = { + en: { + climateNames: { temperate: "Temperate", desert: "Desert", jungle: "Jungle", cold: "Cold" }, + moonPhases: { + "New": "New", "Crescent": "Crescent", "First Quarter": "First Quarter", "Gibbous": "Gibbous", + "Full": "Full", "Gibbous Waning": "Gibbous Waning", "Last Quarter": "Last Quarter", "Crescent Waning": "Crescent Waning" + }, + windDirections: { north: "North", south: "South", east: "East", west: "West" }, + windForces: { + "Slight Breeze": "Slight Breeze", "Nice Breeze": "Nice Breeze", "Strong Wind": "Strong Wind", + "Storm": "Storm", "Violent Storm": "Violent Storm", "Hurricane": "Hurricane", "Manual": "Manual Wind" + }, + seasonNames: { spring: "Spring", summer: "Summer", fall: "Autumn", winter: "Winter" }, + date: "Date", season: "Season", moon: "Moon Phases", weather: "Weather Report", climate: "Climate", + temperature: "Temperature", humidity: "Humidity", wind: "Wind", precipitation: "Precipitation", + clear: "Clear", rain: "Rain", snow: "Snow", thunderstorm: "Thunderstorm", windFrom: "from", + manual: "Manual Weather Mode", generate: "Generate Weather", setDay: "Set Day", setYear: "Set Year", + saveProfile: "Save Current", loadProfile: "Load", exportProfile: "Export to handout", importProfile: "Import from handout", month: "Month", + language: "Language", profiles: "Profiles", manualMode: "Manual Mode", type: "Type", temp: "Temp", + windSpeed: "Wind", back: "Back", yes: "Yes", no: "No", humidityShort: "Humidity" + }, + fr: { + climateNames: { temperate: "Tempéré", desert: "Désertique", jungle: "Jungle", cold: "Froid" }, + moonPhases: { + "New": "Nouvelle Lune", "Crescent": "Premier Croissant", "First Quarter": "Premier Quartier", + "Gibbous": "Gibbeuse Croissante", "Full": "Pleine Lune", "Gibbous Waning": "Gibbeuse Décroissante", + "Last Quarter": "Dernier Quartier", "Crescent Waning": "Dernier Croissant" + }, + windDirections: { north: "Nord", south: "Sud", east: "Est", west: "Ouest" }, + windForces: { + "Slight Breeze": "Brise Légère", "Nice Breeze": "Belle Brise", "Strong Wind": "Vent Fort", + "Storm": "Tempête", "Violent Storm": "Tempête Violente", "Hurricane": "Ouragan", "Manual": "Vent Manuel" + }, + seasonNames: { spring: "Printemps", summer: "Été", fall: "Automne", winter: "Hiver" }, + date: "Date", season: "Saison", moon: "Phases Lunaires", weather: "Météo", climate: "Climat", + temperature: "Température", humidity: "Humidité", wind: "Vent", precipitation: "Précipitations", + clear: "Clair", rain: "Pluie", snow: "Neige", thunderstorm: "Orage", windFrom: "depuis le", + manual: "Mode Météo Manuel", generate: "Générer la Météo", setDay: "Définir le Jour", setYear: "Définir l'Année", + saveProfile: "Sauvegarder", loadProfile: "Charger", exportProfile: "Exporter vers un handout", importProfile: "Importer depuis un handout", month: "Mois", + language: "Langue", profiles: "Profils", manualMode: "Mode Manuel", type: "Type", temp: "Temp", + windSpeed: "Vent", back: "Retour", yes: "Oui", no: "Non", humidityShort: "Humidité" + } + }; + + // Translation functions + const lang = () => state.WeatherMod?.language || 'en'; + const t = (key) => i18n[lang()]?.[key] || key; + const tClimate = (key) => i18n[lang()].climateNames?.[key] || key; + const tPhase = (key) => i18n[lang()].moonPhases?.[key] || key; + const tWindDir = (key) => i18n[lang()].windDirections?.[key] || key; + const tWindForce = (key) => i18n[lang()].windForces?.[key] || key; + const tSeason = (key) => i18n[lang()].seasonNames?.[key] || key; + + // Icons + const climateIcons = { temperate: "🌳", desert: "🏜️", jungle: "🌴", cold: "❄️" }; + const seasonIcons = { spring: "🌼", summer: "☀️", fall: "🍂", winter: "❄️" }; + const moonIcons = { + "New": "🌑", "Crescent": "🌒", "First Quarter": "🌓", "Gibbous": "🌔", "Full": "🌕", + "Gibbous Waning": "🌖", "Last Quarter": "🌗", "Crescent Waning": "🌘" + }; + const skyIcons = { clear: "☀️", rain: "🌧️", snow: "❄️", thunderstorm: "⛈️" }; + + const tempIcon = (t) => t < -10 ? "🧊" : t < 0 ? "🥶" : t < 10 ? "❄️" : t < 20 ? "🌤️" : t < 30 ? "☀️" : t < 40 ? "🔥" : "🌋"; + const humidityIcon = (h) => h < 20 ? "🌵" : h < 40 ? "💨" : h < 60 ? "🌤️" : h < 80 ? "💧" : "🌫️"; + const windSpeedIcon = (s) => s <= 5 ? "🌬️" : s <= 20 ? "🍃" : s <= 40 ? "💨" : s <= 70 ? "🌪️" : s <= 100 ? "🌬️🌩️" : "🌀"; + + // Utility functions + const getSeason = () => { + const m = state.WeatherMod.calendar.month; + return CalendarConfig.seasons.find(s => s.months.includes(m))?.name || "spring"; + }; + + const getMoonPhases = (day) => MoonConfig.moons.map(m => { + const idx = Math.floor((day % m.cycle) / m.cycle * m.phases.length); + return `${m.name}: ${m.phases[idx]}`; + }); + + const randomWeighted = (table) => { + const total = Object.values(table).reduce((a, b) => a + b, 0); + let roll = randomInteger(total); + for (const key in table) { + roll -= table[key]; + if (roll <= 0) return key; + } + }; + + const randomRangeFromList = (ranges) => { + const [min, max] = ranges[Math.floor(Math.random() * ranges.length)]; + return randomInteger(max - min + 1) + min - 1; + }; + + const clearOldWeatherMessages = () => { + sendChat("WeatherMod", `
`); + }; + + // Build the full weather report HTML + const buildFullWeatherReportHTML = () => { + const c = state.WeatherMod.calendar; + const dayName = CalendarConfig.days[(c.day - 1) % CalendarConfig.days.length]; + const monthName = CalendarConfig.months[c.month].name; + const season = getSeason(); + const s = state.WeatherMod.settings; + const climate = WeatherConfig.climates[state.WeatherMod.selectedClimate]; + + let temp, humidity, windSpeed, windOrigin, windForce, precipType, precipStrength; + + if (s.useManualWeather) { + temp = s.manualWeather.temperature; + windSpeed = s.manualWeather.windSpeed; + windOrigin = s.manualWeather.windDirection; + precipType = s.manualWeather.type; + humidity = s.manualWeather.humidity !== undefined ? s.manualWeather.humidity : "-"; + windForce = { name: tWindForce("Manual") }; + precipStrength = `${skyIcons[precipType] || ""} ${t(precipType)}`; + } else { + humidity = randomInteger(climate.humidity[1] - climate.humidity[0]) + climate.humidity[0]; + windOrigin = randomWeighted(climate.windChances); + const forceKey = randomWeighted(Object.fromEntries(Object.entries(WeatherConfig.windForce).map(([k, v]) => [k, v.chance]))); + windForce = WeatherConfig.windForce[forceKey]; + windSpeed = randomInteger(windForce.speed[1] - windForce.speed[0]) + windForce.speed[0]; + temp = randomRangeFromList(climate.temperature[season]); + precipType = randomWeighted(climate.precipitation[season]); + + if (precipType === 'rain') { + precipStrength = (temp <= 0) + ? `❄️ ${t('snow')} (${randomWeighted(WeatherConfig.precipitationStrength.snow)})` + : `🌧️ ${t('rain')} (${randomWeighted(WeatherConfig.precipitationStrength.rain)})`; + } else if (precipType === 'thunderstorm') { + precipStrength = `⛈️ ${t('thunderstorm')} (${randomWeighted(WeatherConfig.precipitationStrength.thunderstorm)})`; + } else { + precipStrength = `☀️ ${t('clear')}`; + } + } + + const moon = getMoonPhases(c.totalDays).map(m => { + const [name, phase] = m.split(": "); + return `${moonIcons[phase] || "🌑"} ${name}: ${tPhase(phase)}`; + }).join("
"); + + let html = `
`; + html += `
${t('weather')}
`; + html += `
${t('date')}: ${lang() === 'fr' ? `${dayName} ${c.day} ${monthName} ${c.year}` : `${monthName} ${c.day}, ${c.year} (${dayName})`}
`; + html += `
${t('season')}:
${seasonIcons[season]} ${tSeason(season)}`; + html += `${hr}
${t('moon')}:
${moon}
${hr}`; + html += `
${t('climate')}:
${climateIcons[state.WeatherMod.selectedClimate]} ${tClimate(state.WeatherMod.selectedClimate)}`; + html += `
${t('temperature')}: ${temp}°C ${tempIcon(temp)}
`; + html += `
${t('humidity')}: ${humidity}${humidity !== "-" ? "%" : ""} ${humidity !== "-" ? humidityIcon(humidity) : ""}
`; + html += `
${t('wind')}: ${tWindForce(windForce.name)} (${windSpeed} km/h) ${t('windFrom')} ${tWindDir(windOrigin)} ${windSpeedIcon(windSpeed)}
`; + html += `
${t('precipitation')}: ${precipStrength}
`; + html += `
`; + return html; + }; + + const displayFullReport = () => { + clearOldWeatherMessages(); + sendChat("WeatherMod", `/w gm ${buildFullWeatherReportHTML()}`); + }; + + const showWeatherToPlayers = () => { + sendChat("WeatherMod", buildFullWeatherReportHTML()); + }; + + // Styled menus + const showGMMainMenu = () => { + const s = state.WeatherMod; + const climates = Object.keys(WeatherConfig.climates).map(climate => + `${climateIcons[climate]} ${tClimate(climate)}` + ).join(" "); + + const html = `
+
${t('weather')}
${hr} +
${t('climate')}:
${climateIcons[s.selectedClimate]} ${tClimate(s.selectedClimate)}
+ ${btnGroup(climates)}${hr} +
${t('language')}:
${s.language.toUpperCase()}
+ ${btnGroup(`EN FR`)}${hr} + ${btnGroup(`📅 ${t('date')}`)} + ${btnGroup(`🛠 ${t('manual')}`)} + ${btnGroup(`💾 ${t('profiles')}`)} + ${btnGroup(`🌦 ${t('generate')}`)} + ${btnGroup(`📣 ${t('weather')} → Players`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + const showDateMenu = () => { + const c = state.WeatherMod.calendar; + const months = CalendarConfig.months.map((m, i) => + `${m.name}` + ).join(" "); + const html = `
+
${t('date')}
${hr} + ${btnGroup(`${t('setDay')} ${t('setYear')}`)} + ${hr}${btnGroup(months)}${hr} + ${btnGroup(`⬅️ ${t('back')}`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + const showManualWeatherMenu = () => { + const manual = state.WeatherMod.settings.manualWeather; + const weatherTypes = ['clear', 'rain', 'snow', 'thunderstorm'].map(type => + `${skyIcons[type]} ${t(type)}` + ).join(" "); + const windDirs = ['north', 'east', 'south', 'west'].map(dir => + `${tWindDir(dir)}` + ).join(" "); + + const html = `
+
${t('manual')}
${hr} +
${t('manualMode')}:
${state.WeatherMod.settings.useManualWeather ? "🟢" : "🔴"} ${state.WeatherMod.settings.useManualWeather ? t('yes') : t('no')}

+
${t('precipitation')}:
${btnGroup(weatherTypes)}
+
${t('temperature')}: ${manual.temperature}°C
+
${t('windSpeed')}: ${manual.windSpeed} km/h
+
${t('windFrom')}: ${btnGroup(windDirs)}
+
${t('humidityShort')}: ${manual.humidity !== undefined ? manual.humidity : 50}%
+ ${hr}${btnGroup(`⬅️ ${t('back')}`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + const showProfilesMenu = () => { + const html = `
+
${t('profiles')}
${hr} + ${btnGroup(` + ${t('saveProfile')}
+ ${t('loadProfile')}
+ ${t('exportProfile')}
+ ${t('importProfile')}
`)} + ${hr}${btnGroup(`⬅️ ${t('back')}`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + // Weather profiles + const saveWeatherProfile = (name) => { + if (!name) return; + state.WeatherMod.profiles[name] = { + language: state.WeatherMod.language, + selectedClimate: state.WeatherMod.selectedClimate, + calendar: { ...state.WeatherMod.calendar }, + settings: JSON.parse(JSON.stringify(state.WeatherMod.settings)) + }; + }; + + const loadWeatherProfile = (name) => { + if (!name || !state.WeatherMod.profiles[name]) return; + const data = state.WeatherMod.profiles[name]; + state.WeatherMod.language = data.language; + state.WeatherMod.selectedClimate = data.selectedClimate; + state.WeatherMod.calendar = { ...data.calendar }; + state.WeatherMod.settings = JSON.parse(JSON.stringify(data.settings)); + }; + + // Import a weather profile from a handout + const importProfileFromHandout = (name) => { + const handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0]; + if (!handout) { + sendChat('WeatherMod', `/w gm [${name}] Handout not found / Handout introuvable.`); + return; + } + handout.get('notes', (notes) => { + // Try to extract JSON from the handout notes (between
...
or after a marker) + let jsonMatch = notes.match(/
([\s\S]+?)<\/pre>/) || notes.match(/([\s\S]+)$/);
+      let json;
+      if (jsonMatch) {
+        try {
+          json = JSON.parse(jsonMatch[1]);
+        } catch (e) {
+          sendChat('WeatherMod', `/w gm Error: Invalid JSON in handout / JSON invalide dans le handout.`);
+          return;
+        }
+      } else {
+        sendChat('WeatherMod', `/w gm No JSON found in handout / Aucun JSON trouvé dans le handout.`);
+        return;
+      }
+      // Save imported profile
+      state.WeatherMod.profiles[name] = json;
+      sendChat('WeatherMod', `/w gm Profile "${name}" imported from handout / Profil "${name}" importé depuis le handout.`);
+      showGMMainMenu();
+    });
+  };
+
+  // Export profile to handout
+  const exportProfileToHandout = (name) => {
+    const profile = state.WeatherMod.profiles[name];
+    if (!profile) return;
+
+    // Save JSON in 
 for easy import
+    const html = `
+      ${t('profiles')}: ${name}
+ ${t('climate')}: ${tClimate(profile.selectedClimate)}
+ ${t('date')}: ${profile.calendar.day} ${CalendarConfig.months[profile.calendar.month].name} ${profile.calendar.year}
+ ${t('language')}: ${profile.language}
+ ${t('manualMode')}: ${profile.settings.useManualWeather ? t('yes') : t('no')}
+ ${profile.settings.useManualWeather ? ` + ${t('type')}: ${t(profile.settings.manualWeather.type)}
+ ${t('temp')}: ${profile.settings.manualWeather.temperature}°C
+ ${t('windSpeed')}: ${profile.settings.manualWeather.windSpeed} km/h ${t('windFrom')} ${tWindDir(profile.settings.manualWeather.windDirection)}
+ ${t('humidityShort')}: ${profile.settings.manualWeather.humidity !== undefined ? profile.settings.manualWeather.humidity : 50}%
+ ` : ''} +
+ JSON: +
${JSON.stringify(profile, null, 2)}
+ ${JSON.stringify(profile)} + `; + + let handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0]; + if (!handout) { + handout = createObj("handout", { name: `WeatherProfile_${name}` }); + } + handout.set({ notes: html }); + }; + + // Advance one day + const advanceDay = () => { + const c = state.WeatherMod.calendar; + c.day++; + c.totalDays++; + const max = CalendarConfig.months[c.month].length; + if (c.day > max) { + c.day = 1; + c.month++; + if (c.month >= CalendarConfig.months.length) { + c.month = 0; + c.year++; + } + } + }; + + // State initialization + if (!state.WeatherMod) { + state.WeatherMod = { + language: 'fr', + selectedClimate: 'temperate', + calendar: { day: 1, month: 0, year: 1000, totalDays: 0 }, + settings: { + useManualWeather: false, + manualWeather: { type: "clear", windDirection: "north", temperature: 20, windSpeed: 10, humidity: 50 } + }, + profiles: {} + }; + } + + // Chat commands + on('chat:message', (msg) => { + if (msg.type !== 'api' || !playerIsGM(msg.playerid)) return; + + const args = msg.content.trim().split(" "); + const command = args[0]; + const subcommand = args[1]; + const value = args.slice(2).join(" "); + + if (command !== '!weather') return; + + switch (subcommand) { + case 'report': displayFullReport(); break; + case 'showplayers': showWeatherToPlayers(); break; + case 'menu': showGMMainMenu(); break; + case 'menu-date': showDateMenu(); break; + case 'menu-manual': showManualWeatherMenu(); break; + case 'menu-profiles': showProfilesMenu(); break; + + case 'next': + case 'next-day': + advanceDay(); + displayFullReport(); + break; + + case 'lang': + if (['en', 'fr'].includes(args[2])) { + state.WeatherMod.language = args[2]; + sendChat("WeatherMod", `/w gm ${t('language')} : ${args[2].toUpperCase()}`); + } else { + sendChat("WeatherMod", `/w gm ${t('language')}: en, fr`); + } + break; + + case 'setgm': { + const param = args[2]; + const val = args.slice(3).join(" "); + const s = state.WeatherMod; + const manual = s.settings.manualWeather; + + switch (param) { + case 'climate': + if (val in WeatherConfig.climates) s.selectedClimate = val; + break; + case 'manual': + s.settings.useManualWeather = (val === 'on'); + break; + case 'weathertype': + manual.type = val; + break; + case 'winddir': + manual.windDirection = val; + break; + case 'temp': + const tval = parseInt(val, 10); + if (!isNaN(tval)) manual.temperature = tval; + break; + case 'windspeed': + const wval = parseInt(val, 10); + if (!isNaN(wval)) manual.windSpeed = wval; + break; + case 'humidity': + const hval = parseInt(val, 10); + if (!isNaN(hval)) manual.humidity = hval; + break; + case 'day': + const d = parseInt(val, 10); + if (!isNaN(d)) s.calendar.day = d; + break; + case 'month': + const m = parseInt(val, 10); + if (!isNaN(m)) s.calendar.month = m; + break; + case 'year': + const y = parseInt(val, 10); + if (!isNaN(y)) s.calendar.year = y; + break; + } + showGMMainMenu(); + break; + } + + case 'save': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('saveProfile')} : !weather save MonProfil`); + } else { + saveWeatherProfile(value.trim()); + sendChat('WeatherMod', `/w gm ${t('saveProfile')}: ${value.trim()}`); + } + break; + + case 'load': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('loadProfile')} : !weather load MonProfil`); + } else { + loadWeatherProfile(value.trim()); + sendChat('WeatherMod', `/w gm ${t('loadProfile')}: ${value.trim()}`); + showGMMainMenu(); + } + break; + + case 'export': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('exportProfile')} : !weather export MonProfil`); + } else { + exportProfileToHandout(value.trim()); + sendChat('WeatherMod', `/w gm ${t('exportProfile')}: WeatherProfile_${value.trim()}`); + } + break; + + case 'import': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('importProfile')} : !weather import MonProfil`); + } else { + importProfileFromHandout(value.trim()); + showGMMainMenu(); + } + break; + } + }); +}); diff --git a/Calendar and Weather/Calendar and Weather.js b/Calendar and Weather/Calendar and Weather.js new file mode 100644 index 000000000..885d292b0 --- /dev/null +++ b/Calendar and Weather/Calendar and Weather.js @@ -0,0 +1,581 @@ +on('ready', () => { + // Styles + const chatStyle = 'background-color:#926239; border:1px solid #000; border-radius:8px; padding:8px; width:100%; height:fit-content; font-size:1.1em;'; + const hr = '
'; + const styleTitle = 'text-align:center; font-size:1.5em; font-weight:bold;'; + const styleSection = 'font-size:1.3em; font-weight:bold; margin-top:8px;'; + const styleCenter = 'text-align:center;'; + const btnStyle = 'background-color:#574530; border:1px solid #352716; border-radius:4px; padding:2px 8px; font-size:0.9em; color:#fff; text-decoration:none; margin:2px; display:inline-block;'; + const btnGroup = (html) => `
${html}
`; + + // Configurations + const WeatherConfig = { + windForce: { + "1": { speed: [0, 11], chance: 55, name: "Slight Breeze" }, + "2": { speed: [12, 38], chance: 25, name: "Nice Breeze" }, + "3": { speed: [39, 88], chance: 12, name: "Strong Wind" }, + "4": { speed: [89, 102], chance: 5, name: "Storm" }, + "5": { speed: [103, 117], chance: 2, name: "Violent Storm" }, + "6": { speed: [118, 200], chance: 1, name: "Hurricane" } + }, + precipitationStrength: { + rain: { light: 55, moderate: 25, heavy: 15, torrential: 5 }, + snow: { light: 65, moderate: 25, snowstorm: 10 }, + thunderstorm: { slight: 55, moderate: 25, strong: 15, severe: 5 } + }, + climates: { + temperate: { + humidity: [40, 60], + windChances: { north: 10, west: 20, east: 65, south: 5 }, + temperature: { + spring: [[5,10],[10,15],[15,20],[20,30]], + summer: [[10,15],[15,20],[20,30],[30,40]], + fall: [[0,5],[5,10],[10,15],[15,20]], + winter: [[-5,0],[0,5],[5,10],[10,15]] + }, + precipitation: { + spring: { clear: 40, rain: 53, thunderstorm: 7 }, + summer: { clear: 42, rain: 46, thunderstorm: 12 }, + fall: { clear: 43, rain: 53, thunderstorm: 4 }, + winter: { clear: 35, rain: 60, thunderstorm: 5 } + } + }, + desert: { + humidity: [5, 15], + windChances: { north: 5, west: 10, east: 20, south: 65 }, + temperature: { + spring: [[15,20],[20,25],[25,35],[35,45]], + summer: [[20,25],[25,35],[35,45],[45,55]], + fall: [[10,15],[15,20],[20,25],[25,35]], + winter: [[-5,0],[0,5],[5,10],[10,15]] + }, + precipitation: { + spring: { clear: 66, rain: 13, thunderstorm: 21 }, + summer: { clear: 75, rain: 0, thunderstorm: 25 }, + fall: { clear: 73, rain: 11, thunderstorm: 16 }, + winter: { clear: 81, rain: 18, thunderstorm: 1 } + } + }, + jungle: { + humidity: [70, 90], + windChances: { north: 5, west: 10, east: 65, south: 20 }, + temperature: { + spring: [[10,15],[15,20],[20,30],[30,40]], + summer: [[15,20],[20,30],[30,40],[40,50]], + fall: [[5,10],[10,15],[15,20],[20,30]], + winter: [[0,5],[5,10],[10,15],[15,20]] + }, + precipitation: { + spring: { clear: 21, rain: 51, thunderstorm: 28 }, + summer: { clear: 74, rain: 15, thunderstorm: 11 }, + fall: { clear: 12, rain: 44, thunderstorm: 44 }, + winter: { clear: 15, rain: 48, thunderstorm: 37 } + } + }, + cold: { + humidity: [35, 55], + windChances: { north: 65, west: 20, east: 10, south: 5 }, + temperature: { + spring: [[-5,0],[0,5],[5,10],[10,15]], + summer: [[0,5],[5,10],[10,15],[15,20]], + fall: [[-10,-5],[-5,0],[0,5],[5,10]], + winter: [[-20,-10],[-10,-5],[-5,0],[0,5]] + }, + precipitation: { + spring: { clear: 75, rain: 22, thunderstorm: 3 }, + summer: { clear: 44, rain: 49, thunderstorm: 7 }, + fall: { clear: 35, rain: 63, thunderstorm: 2 }, + winter: { clear: 87, rain: 12, thunderstorm: 1 } + } + } + } + }; + + const CalendarConfig = { + days: ["Rilmor", "Eretor", "Nauri", "Neldir", "Veltor", "Eltor", "Mernach"], + months: [ + { name: "Juras", length: 31 }, { name: "Fevnir", length: 28 }, { name: "Morsir", length: 31 }, + { name: "Avalis", length: 30 }, { name: "Maï", length: 31 }, { name: "Jurn", length: 30 }, + { name: "Jullirq", length: 31 }, { name: "Aors", length: 31 }, { name: "Septibir", length: 30 }, + { name: "Octors", length: 31 }, { name: "Noval", length: 30 }, { name: "Devenir", length: 31 } + ], + seasons: [ + { name: "spring", months: [2, 3, 4] }, + { name: "summer", months: [5, 6, 7] }, + { name: "fall", months: [8, 9, 10] }, + { name: "winter", months: [11, 0, 1] } + ] + }; + + const MoonConfig = { + moons: [ + { name: "Lunara", cycle: 28, phases: ["New", "Crescent", "First Quarter", "Gibbous", "Full", "Gibbous Waning", "Last Quarter", "Crescent Waning"] }, + { name: "Virell", cycle: 35, phases: ["New", "First Quarter", "Full", "Last Quarter"] } + ] + }; + + // Translations + const i18n = { + en: { + climateNames: { temperate: "Temperate", desert: "Desert", jungle: "Jungle", cold: "Cold" }, + moonPhases: { + "New": "New", "Crescent": "Crescent", "First Quarter": "First Quarter", "Gibbous": "Gibbous", + "Full": "Full", "Gibbous Waning": "Gibbous Waning", "Last Quarter": "Last Quarter", "Crescent Waning": "Crescent Waning" + }, + windDirections: { north: "North", south: "South", east: "East", west: "West" }, + windForces: { + "Slight Breeze": "Slight Breeze", "Nice Breeze": "Nice Breeze", "Strong Wind": "Strong Wind", + "Storm": "Storm", "Violent Storm": "Violent Storm", "Hurricane": "Hurricane", "Manual": "Manual Wind" + }, + seasonNames: { spring: "Spring", summer: "Summer", fall: "Autumn", winter: "Winter" }, + date: "Date", season: "Season", moon: "Moon Phases", weather: "Weather Report", climate: "Climate", + temperature: "Temperature", humidity: "Humidity", wind: "Wind", precipitation: "Precipitation", + clear: "Clear", rain: "Rain", snow: "Snow", thunderstorm: "Thunderstorm", windFrom: "from", + manual: "Manual Weather Mode", generate: "Generate Weather", setDay: "Set Day", setYear: "Set Year", + saveProfile: "Save Current", loadProfile: "Load", exportProfile: "Export to handout", importProfile: "Import from handout", month: "Month", + language: "Language", profiles: "Profiles", manualMode: "Manual Mode", type: "Type", temp: "Temp", + windSpeed: "Wind", back: "Back", yes: "Yes", no: "No", humidityShort: "Humidity" + }, + fr: { + climateNames: { temperate: "Tempéré", desert: "Désertique", jungle: "Jungle", cold: "Froid" }, + moonPhases: { + "New": "Nouvelle Lune", "Crescent": "Premier Croissant", "First Quarter": "Premier Quartier", + "Gibbous": "Gibbeuse Croissante", "Full": "Pleine Lune", "Gibbous Waning": "Gibbeuse Décroissante", + "Last Quarter": "Dernier Quartier", "Crescent Waning": "Dernier Croissant" + }, + windDirections: { north: "Nord", south: "Sud", east: "Est", west: "Ouest" }, + windForces: { + "Slight Breeze": "Brise Légère", "Nice Breeze": "Belle Brise", "Strong Wind": "Vent Fort", + "Storm": "Tempête", "Violent Storm": "Tempête Violente", "Hurricane": "Ouragan", "Manual": "Vent Manuel" + }, + seasonNames: { spring: "Printemps", summer: "Été", fall: "Automne", winter: "Hiver" }, + date: "Date", season: "Saison", moon: "Phases Lunaires", weather: "Météo", climate: "Climat", + temperature: "Température", humidity: "Humidité", wind: "Vent", precipitation: "Précipitations", + clear: "Clair", rain: "Pluie", snow: "Neige", thunderstorm: "Orage", windFrom: "depuis le", + manual: "Mode Météo Manuel", generate: "Générer la Météo", setDay: "Définir le Jour", setYear: "Définir l'Année", + saveProfile: "Sauvegarder", loadProfile: "Charger", exportProfile: "Exporter vers un handout", importProfile: "Importer depuis un handout", month: "Mois", + language: "Langue", profiles: "Profils", manualMode: "Mode Manuel", type: "Type", temp: "Temp", + windSpeed: "Vent", back: "Retour", yes: "Oui", no: "Non", humidityShort: "Humidité" + } + }; + + // Translation functions + const lang = () => state.WeatherMod?.language || 'en'; + const t = (key) => i18n[lang()]?.[key] || key; + const tClimate = (key) => i18n[lang()].climateNames?.[key] || key; + const tPhase = (key) => i18n[lang()].moonPhases?.[key] || key; + const tWindDir = (key) => i18n[lang()].windDirections?.[key] || key; + const tWindForce = (key) => i18n[lang()].windForces?.[key] || key; + const tSeason = (key) => i18n[lang()].seasonNames?.[key] || key; + + // Icons + const climateIcons = { temperate: "🌳", desert: "🏜️", jungle: "🌴", cold: "❄️" }; + const seasonIcons = { spring: "🌼", summer: "☀️", fall: "🍂", winter: "❄️" }; + const moonIcons = { + "New": "🌑", "Crescent": "🌒", "First Quarter": "🌓", "Gibbous": "🌔", "Full": "🌕", + "Gibbous Waning": "🌖", "Last Quarter": "🌗", "Crescent Waning": "🌘" + }; + const skyIcons = { clear: "☀️", rain: "🌧️", snow: "❄️", thunderstorm: "⛈️" }; + + const tempIcon = (t) => t < -10 ? "🧊" : t < 0 ? "🥶" : t < 10 ? "❄️" : t < 20 ? "🌤️" : t < 30 ? "☀️" : t < 40 ? "🔥" : "🌋"; + const humidityIcon = (h) => h < 20 ? "🌵" : h < 40 ? "💨" : h < 60 ? "🌤️" : h < 80 ? "💧" : "🌫️"; + const windSpeedIcon = (s) => s <= 5 ? "🌬️" : s <= 20 ? "🍃" : s <= 40 ? "💨" : s <= 70 ? "🌪️" : s <= 100 ? "🌬️🌩️" : "🌀"; + + // Utility functions + const getSeason = () => { + const m = state.WeatherMod.calendar.month; + return CalendarConfig.seasons.find(s => s.months.includes(m))?.name || "spring"; + }; + + const getMoonPhases = (day) => MoonConfig.moons.map(m => { + const idx = Math.floor((day % m.cycle) / m.cycle * m.phases.length); + return `${m.name}: ${m.phases[idx]}`; + }); + + const randomWeighted = (table) => { + const total = Object.values(table).reduce((a, b) => a + b, 0); + let roll = randomInteger(total); + for (const key in table) { + roll -= table[key]; + if (roll <= 0) return key; + } + }; + + const randomRangeFromList = (ranges) => { + const [min, max] = ranges[Math.floor(Math.random() * ranges.length)]; + return randomInteger(max - min + 1) + min - 1; + }; + + const clearOldWeatherMessages = () => { + sendChat("WeatherMod", `
`); + }; + + // Build the full weather report HTML + const buildFullWeatherReportHTML = () => { + const c = state.WeatherMod.calendar; + const dayName = CalendarConfig.days[(c.day - 1) % CalendarConfig.days.length]; + const monthName = CalendarConfig.months[c.month].name; + const season = getSeason(); + const s = state.WeatherMod.settings; + const climate = WeatherConfig.climates[state.WeatherMod.selectedClimate]; + + let temp, humidity, windSpeed, windOrigin, windForce, precipType, precipStrength; + + if (s.useManualWeather) { + temp = s.manualWeather.temperature; + windSpeed = s.manualWeather.windSpeed; + windOrigin = s.manualWeather.windDirection; + precipType = s.manualWeather.type; + humidity = s.manualWeather.humidity !== undefined ? s.manualWeather.humidity : "-"; + windForce = { name: tWindForce("Manual") }; + precipStrength = `${skyIcons[precipType] || ""} ${t(precipType)}`; + } else { + humidity = randomInteger(climate.humidity[1] - climate.humidity[0]) + climate.humidity[0]; + windOrigin = randomWeighted(climate.windChances); + const forceKey = randomWeighted(Object.fromEntries(Object.entries(WeatherConfig.windForce).map(([k, v]) => [k, v.chance]))); + windForce = WeatherConfig.windForce[forceKey]; + windSpeed = randomInteger(windForce.speed[1] - windForce.speed[0]) + windForce.speed[0]; + temp = randomRangeFromList(climate.temperature[season]); + precipType = randomWeighted(climate.precipitation[season]); + + if (precipType === 'rain') { + precipStrength = (temp <= 0) + ? `❄️ ${t('snow')} (${randomWeighted(WeatherConfig.precipitationStrength.snow)})` + : `🌧️ ${t('rain')} (${randomWeighted(WeatherConfig.precipitationStrength.rain)})`; + } else if (precipType === 'thunderstorm') { + precipStrength = `⛈️ ${t('thunderstorm')} (${randomWeighted(WeatherConfig.precipitationStrength.thunderstorm)})`; + } else { + precipStrength = `☀️ ${t('clear')}`; + } + } + + const moon = getMoonPhases(c.totalDays).map(m => { + const [name, phase] = m.split(": "); + return `${moonIcons[phase] || "🌑"} ${name}: ${tPhase(phase)}`; + }).join("
"); + + let html = `
`; + html += `
${t('weather')}
`; + html += `
${t('date')}: ${lang() === 'fr' ? `${dayName} ${c.day} ${monthName} ${c.year}` : `${monthName} ${c.day}, ${c.year} (${dayName})`}
`; + html += `
${t('season')}:
${seasonIcons[season]} ${tSeason(season)}`; + html += `${hr}
${t('moon')}:
${moon}
${hr}`; + html += `
${t('climate')}:
${climateIcons[state.WeatherMod.selectedClimate]} ${tClimate(state.WeatherMod.selectedClimate)}`; + html += `
${t('temperature')}: ${temp}°C ${tempIcon(temp)}
`; + html += `
${t('humidity')}: ${humidity}${humidity !== "-" ? "%" : ""} ${humidity !== "-" ? humidityIcon(humidity) : ""}
`; + html += `
${t('wind')}: ${tWindForce(windForce.name)} (${windSpeed} km/h) ${t('windFrom')} ${tWindDir(windOrigin)} ${windSpeedIcon(windSpeed)}
`; + html += `
${t('precipitation')}: ${precipStrength}
`; + html += `
`; + return html; + }; + + const displayFullReport = () => { + clearOldWeatherMessages(); + sendChat("WeatherMod", `/w gm ${buildFullWeatherReportHTML()}`); + }; + + const showWeatherToPlayers = () => { + sendChat("WeatherMod", buildFullWeatherReportHTML()); + }; + + // Styled menus + const showGMMainMenu = () => { + const s = state.WeatherMod; + const climates = Object.keys(WeatherConfig.climates).map(climate => + `${climateIcons[climate]} ${tClimate(climate)}` + ).join(" "); + + const html = `
+
${t('weather')}
${hr} +
${t('climate')}:
${climateIcons[s.selectedClimate]} ${tClimate(s.selectedClimate)}
+ ${btnGroup(climates)}${hr} +
${t('language')}:
${s.language.toUpperCase()}
+ ${btnGroup(`EN FR`)}${hr} + ${btnGroup(`📅 ${t('date')}`)} + ${btnGroup(`🛠 ${t('manual')}`)} + ${btnGroup(`💾 ${t('profiles')}`)} + ${btnGroup(`🌦 ${t('generate')}`)} + ${btnGroup(`📣 ${t('weather')} → Players`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + const showDateMenu = () => { + const c = state.WeatherMod.calendar; + const months = CalendarConfig.months.map((m, i) => + `${m.name}` + ).join(" "); + const html = `
+
${t('date')}
${hr} + ${btnGroup(`${t('setDay')} ${t('setYear')}`)} + ${hr}${btnGroup(months)}${hr} + ${btnGroup(`⬅️ ${t('back')}`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + const showManualWeatherMenu = () => { + const manual = state.WeatherMod.settings.manualWeather; + const weatherTypes = ['clear', 'rain', 'snow', 'thunderstorm'].map(type => + `${skyIcons[type]} ${t(type)}` + ).join(" "); + const windDirs = ['north', 'east', 'south', 'west'].map(dir => + `${tWindDir(dir)}` + ).join(" "); + + const html = `
+
${t('manual')}
${hr} +
${t('manualMode')}:
${state.WeatherMod.settings.useManualWeather ? "🟢" : "🔴"} ${state.WeatherMod.settings.useManualWeather ? t('yes') : t('no')}

+
${t('precipitation')}:
${btnGroup(weatherTypes)}
+
${t('temperature')}: ${manual.temperature}°C
+
${t('windSpeed')}: ${manual.windSpeed} km/h
+
${t('windFrom')}: ${btnGroup(windDirs)}
+
${t('humidityShort')}: ${manual.humidity !== undefined ? manual.humidity : 50}%
+ ${hr}${btnGroup(`⬅️ ${t('back')}`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + const showProfilesMenu = () => { + const html = `
+
${t('profiles')}
${hr} + ${btnGroup(` + ${t('saveProfile')}
+ ${t('loadProfile')}
+ ${t('exportProfile')}
+ ${t('importProfile')}
`)} + ${hr}${btnGroup(`⬅️ ${t('back')}`)} +
`; + sendChat("WeatherMod", `/w gm ${html}`); + }; + + // Weather profiles + const saveWeatherProfile = (name) => { + if (!name) return; + state.WeatherMod.profiles[name] = { + language: state.WeatherMod.language, + selectedClimate: state.WeatherMod.selectedClimate, + calendar: { ...state.WeatherMod.calendar }, + settings: JSON.parse(JSON.stringify(state.WeatherMod.settings)) + }; + }; + + const loadWeatherProfile = (name) => { + if (!name || !state.WeatherMod.profiles[name]) return; + const data = state.WeatherMod.profiles[name]; + state.WeatherMod.language = data.language; + state.WeatherMod.selectedClimate = data.selectedClimate; + state.WeatherMod.calendar = { ...data.calendar }; + state.WeatherMod.settings = JSON.parse(JSON.stringify(data.settings)); + }; + + // Import a weather profile from a handout + const importProfileFromHandout = (name) => { + const handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0]; + if (!handout) { + sendChat('WeatherMod', `/w gm [${name}] Handout not found / Handout introuvable.`); + return; + } + handout.get('notes', (notes) => { + // Try to extract JSON from the handout notes (between
...
or after a marker) + let jsonMatch = notes.match(/
([\s\S]+?)<\/pre>/) || notes.match(/([\s\S]+)$/);
+      let json;
+      if (jsonMatch) {
+        try {
+          json = JSON.parse(jsonMatch[1]);
+        } catch (e) {
+          sendChat('WeatherMod', `/w gm Error: Invalid JSON in handout / JSON invalide dans le handout.`);
+          return;
+        }
+      } else {
+        sendChat('WeatherMod', `/w gm No JSON found in handout / Aucun JSON trouvé dans le handout.`);
+        return;
+      }
+      // Save imported profile
+      state.WeatherMod.profiles[name] = json;
+      sendChat('WeatherMod', `/w gm Profile "${name}" imported from handout / Profil "${name}" importé depuis le handout.`);
+      showGMMainMenu();
+    });
+  };
+
+  // Export profile to handout
+  const exportProfileToHandout = (name) => {
+    const profile = state.WeatherMod.profiles[name];
+    if (!profile) return;
+
+    // Save JSON in 
 for easy import
+    const html = `
+      ${t('profiles')}: ${name}
+ ${t('climate')}: ${tClimate(profile.selectedClimate)}
+ ${t('date')}: ${profile.calendar.day} ${CalendarConfig.months[profile.calendar.month].name} ${profile.calendar.year}
+ ${t('language')}: ${profile.language}
+ ${t('manualMode')}: ${profile.settings.useManualWeather ? t('yes') : t('no')}
+ ${profile.settings.useManualWeather ? ` + ${t('type')}: ${t(profile.settings.manualWeather.type)}
+ ${t('temp')}: ${profile.settings.manualWeather.temperature}°C
+ ${t('windSpeed')}: ${profile.settings.manualWeather.windSpeed} km/h ${t('windFrom')} ${tWindDir(profile.settings.manualWeather.windDirection)}
+ ${t('humidityShort')}: ${profile.settings.manualWeather.humidity !== undefined ? profile.settings.manualWeather.humidity : 50}%
+ ` : ''} +
+ JSON: +
${JSON.stringify(profile, null, 2)}
+ ${JSON.stringify(profile)} + `; + + let handout = findObjs({ type: "handout", name: `WeatherProfile_${name}` })[0]; + if (!handout) { + handout = createObj("handout", { name: `WeatherProfile_${name}` }); + } + handout.set({ notes: html }); + }; + + // Advance one day + const advanceDay = () => { + const c = state.WeatherMod.calendar; + c.day++; + c.totalDays++; + const max = CalendarConfig.months[c.month].length; + if (c.day > max) { + c.day = 1; + c.month++; + if (c.month >= CalendarConfig.months.length) { + c.month = 0; + c.year++; + } + } + }; + + // State initialization + if (!state.WeatherMod) { + state.WeatherMod = { + language: 'fr', + selectedClimate: 'temperate', + calendar: { day: 1, month: 0, year: 1000, totalDays: 0 }, + settings: { + useManualWeather: false, + manualWeather: { type: "clear", windDirection: "north", temperature: 20, windSpeed: 10, humidity: 50 } + }, + profiles: {} + }; + } + + // Chat commands + on('chat:message', (msg) => { + if (msg.type !== 'api' || !playerIsGM(msg.playerid)) return; + + const args = msg.content.trim().split(" "); + const command = args[0]; + const subcommand = args[1]; + const value = args.slice(2).join(" "); + + if (command !== '!weather') return; + + switch (subcommand) { + case 'report': displayFullReport(); break; + case 'showplayers': showWeatherToPlayers(); break; + case 'menu': showGMMainMenu(); break; + case 'menu-date': showDateMenu(); break; + case 'menu-manual': showManualWeatherMenu(); break; + case 'menu-profiles': showProfilesMenu(); break; + + case 'next': + case 'next-day': + advanceDay(); + displayFullReport(); + break; + + case 'lang': + if (['en', 'fr'].includes(args[2])) { + state.WeatherMod.language = args[2]; + sendChat("WeatherMod", `/w gm ${t('language')} : ${args[2].toUpperCase()}`); + } else { + sendChat("WeatherMod", `/w gm ${t('language')}: en, fr`); + } + break; + + case 'setgm': { + const param = args[2]; + const val = args.slice(3).join(" "); + const s = state.WeatherMod; + const manual = s.settings.manualWeather; + + switch (param) { + case 'climate': + if (val in WeatherConfig.climates) s.selectedClimate = val; + break; + case 'manual': + s.settings.useManualWeather = (val === 'on'); + break; + case 'weathertype': + manual.type = val; + break; + case 'winddir': + manual.windDirection = val; + break; + case 'temp': + const tval = parseInt(val, 10); + if (!isNaN(tval)) manual.temperature = tval; + break; + case 'windspeed': + const wval = parseInt(val, 10); + if (!isNaN(wval)) manual.windSpeed = wval; + break; + case 'humidity': + const hval = parseInt(val, 10); + if (!isNaN(hval)) manual.humidity = hval; + break; + case 'day': + const d = parseInt(val, 10); + if (!isNaN(d)) s.calendar.day = d; + break; + case 'month': + const m = parseInt(val, 10); + if (!isNaN(m)) s.calendar.month = m; + break; + case 'year': + const y = parseInt(val, 10); + if (!isNaN(y)) s.calendar.year = y; + break; + } + showGMMainMenu(); + break; + } + + case 'save': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('saveProfile')} : !weather save MonProfil`); + } else { + saveWeatherProfile(value.trim()); + sendChat('WeatherMod', `/w gm ${t('saveProfile')}: ${value.trim()}`); + } + break; + + case 'load': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('loadProfile')} : !weather load MonProfil`); + } else { + loadWeatherProfile(value.trim()); + sendChat('WeatherMod', `/w gm ${t('loadProfile')}: ${value.trim()}`); + showGMMainMenu(); + } + break; + + case 'export': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('exportProfile')} : !weather export MonProfil`); + } else { + exportProfileToHandout(value.trim()); + sendChat('WeatherMod', `/w gm ${t('exportProfile')}: WeatherProfile_${value.trim()}`); + } + break; + + case 'import': + if (!value.trim()) { + sendChat('WeatherMod', `/w gm ${t('importProfile')} : !weather import MonProfil`); + } else { + importProfileFromHandout(value.trim()); + showGMMainMenu(); + } + break; + } + }); +}); diff --git a/Calendar and Weather/README.md b/Calendar and Weather/README.md new file mode 100644 index 000000000..ecc038756 --- /dev/null +++ b/Calendar and Weather/README.md @@ -0,0 +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. diff --git a/Calendar and Weather/script.json b/Calendar and Weather/script.json new file mode 100644 index 000000000..3b4f6c37b --- /dev/null +++ b/Calendar and Weather/script.json @@ -0,0 +1,11 @@ +{ + "name": "Calendar and Weather", + "script": "Calendar and Weather.js", + "version": "1.0", + "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": [] +}