Time.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. const daysOfMonth = [31,28,31,30,31,30,31,31,30,31,30,31];
  2. //https://www.timeanddate.com/sun/russia/saint-peterburg
  3. const dayLengthMax = 1341;// The longest day has daylight of 18:51 and civil daylight of 3:30 which adds up to 22:21, which are 1341 minutes
  4. const dayLengthMin = 468; // The shortest day has daylight of 6:53 and civil daylight of 1:55 which adds up to 7:48, which are 468 minutes
  5. const seasons = [
  6. 'winter',
  7. 'winter',
  8. 'winter',
  9. 'spring',
  10. 'summer',
  11. 'summer',
  12. 'summer',
  13. 'summer',
  14. 'summer',
  15. 'autumn',
  16. 'winter',
  17. 'winter'
  18. ];
  19. const holidays = {
  20. winter : {
  21. start: [1,1],
  22. end: [1,15],
  23. school: true,
  24. work: false
  25. },
  26. spring : {
  27. start: [3,20],
  28. end: [3,26],
  29. school: true,
  30. work: false
  31. },
  32. summer : {
  33. start: [6,1],
  34. end: [8,31],
  35. school: true,
  36. work: false
  37. },
  38. autumn : {
  39. start: [11,4],
  40. end: [11,11],
  41. school: true,
  42. work: false
  43. },
  44. }
  45. /**
  46. * Only ever roll time forwards by increasing minutes. This is the only way skills, stats, etc. get updated.
  47. * Increasing day and hour only excute some function. Avoid if possible. These fields are for internatl use only.
  48. * Increasing month or year won't execute events.
  49. */
  50. class GameTime{
  51. _now = undefined;
  52. get minutes(){
  53. return this._now.getUTCMinutes();
  54. }
  55. set minutes(v:number){
  56. if(typeof v !== "number"){
  57. console.error("Time Increase by invalid value",v);
  58. return;
  59. }
  60. v = Math.ceil(v);
  61. let minutes = this._now.getUTCMinutes();
  62. let timeToAdd = v - minutes;
  63. if(timeToAdd == 0)
  64. return;
  65. this.execute_every_timeUpdate(timeToAdd);
  66. let currentMinutesIn15MinutesInterval = minutes % 15;
  67. let minutesToCompleteCurrent15MinutesInterval = 15 - currentMinutesIn15MinutesInterval;
  68. if(minutesToCompleteCurrent15MinutesInterval > timeToAdd){
  69. this._now.setUTCMinutes(minutes + timeToAdd);
  70. this.execute_every_timeUpdate_post();
  71. return;
  72. }
  73. let currentIterationTimeToAdd = minutesToCompleteCurrent15MinutesInterval;
  74. while(timeToAdd > 0){
  75. let timeToSet = this._now.getTime() + currentIterationTimeToAdd * 60000;
  76. this._now = new Date(timeToSet);
  77. if(timeToSet % 900000 == 0)
  78. this.execute_every_15_minutes();
  79. if(this.minutes == 0){
  80. this.execute_every_1_hour();
  81. if(this.hour == 0){
  82. this.execute_every_1_day();
  83. if(this.day == 1){
  84. this.execute_every_1_month();
  85. if(this.month == 1){
  86. this.execute_every_1_year();
  87. }
  88. }
  89. }
  90. }
  91. timeToAdd -= currentIterationTimeToAdd;
  92. currentIterationTimeToAdd = Math.min(15,timeToAdd);
  93. }
  94. //this._now.setUTCMinutes(minutes % 60);
  95. this.execute_every_timeUpdate_post();
  96. }
  97. get hour(){return this._now.getUTCHours();}
  98. set hour(v){
  99. const oneDay = 86400000;
  100. const now = this.now;
  101. let targetTime = new Date(now);
  102. targetTime.setUTCHours(v,0);
  103. let millisecondDifference = targetTime.getTime() - now.getTime();
  104. if(millisecondDifference < 0)
  105. millisecondDifference += 86400000;
  106. const minuteDifference = Math.floor(millisecondDifference / 60000);
  107. this.minutes += minuteDifference;
  108. }
  109. get seconds(){
  110. return 0;
  111. }
  112. _startDate = null;
  113. get startDate():Date{return this._startDate;}
  114. get year(){return this._now.getUTCFullYear();}
  115. set year(v){this._now.setUTCFullYear(v);}
  116. get month(){return this._now.getUTCMonth()+1;}
  117. /*set month(v){
  118. this._now.setUTCMonth(v-1);
  119. }*/
  120. get day(){return this._now.getUTCDate();}
  121. /*set day(v){
  122. let daysToAdd = v - this.day;
  123. let month = this.month;
  124. for(let i = 0; i < daysToAdd; i++){
  125. this._now.setUTCDate(this._now.getUTCDate()+1);
  126. if(this.month != month){
  127. this.execute_every_1_month();
  128. if(this.month == 1)
  129. this.execute_every_1_year();
  130. month = this.month;
  131. }
  132. }
  133. /*while(v > daysOfMonth[this.month + 1]){
  134. this.execute_every_1_month();
  135. v-= daysOfMonth[this.month + 1];
  136. this.month += 1;
  137. }
  138. this._now.setUTCDate(v);*/
  139. //}
  140. //
  141. /**
  142. * From 1 (Monday) to 7 (Sunday)
  143. * @readonly
  144. * @type {number}
  145. */
  146. get weekday(){
  147. return this._now.getUTCDay() || 7;
  148. }
  149. //Days since the game started. 1 is the first day.
  150. get daystart(){
  151. const oneDay = 86400000;
  152. return Math.floor(Math.abs((this.today.getTime() - this.startDate.getTime()) / oneDay)) + 1;
  153. }
  154. daystartNextAtNthOfMonth(n){
  155. const now = this.now;
  156. let nextDate = new Date(now);
  157. if(n <= now.getUTCDate())
  158. nextDate.setUTCMonth(now.getUTCMonth() + 1);
  159. nextDate.setUTCDate(n);
  160. const timeDifference = nextDate.getTime() - now.getTime();
  161. const daysDifference = Math.floor(timeDifference / 86400000);
  162. return daysDifference + this.daystart;
  163. }
  164. daystartNextAtWeekday(weekday){
  165. const currentWeekday = this.weekday;
  166. if(weekday > currentWeekday)
  167. return weekday - currentWeekday + this.daystart;
  168. return 7 + weekday - currentWeekday + this.daystart;
  169. }
  170. /**
  171. * @param {number} daystart
  172. * @returns {*}
  173. */
  174. daystartToDate(daystart){
  175. return new Date(this._startDate.getTime() + (daystart-1)*86400000);
  176. }
  177. // Years that have passed since the game started. Starts with 1 in the first year.
  178. get yearstart(){
  179. const today = this.today;
  180. const startDate = this.startDate;
  181. let startDateInCurrentYear = clone(startDate);
  182. startDateInCurrentYear.setUTCFullYear(today.getUTCFullYear());
  183. if(startDateInCurrentYear > today)
  184. //It's early in the year
  185. return today.getUTCFullYear() - startDate.getUTCFullYear();
  186. return today.getUTCFullYear() - startDate.getUTCFullYear() + 1;
  187. }
  188. initTime(yearOrDate,month=undefined,day=undefined,hour=undefined,minute=undefined){
  189. if(yearOrDate instanceof Date){
  190. this._startDate = clone(yearOrDate);
  191. this._now = clone(yearOrDate);
  192. this._startDate.setUTCHours(0,0,0,0);
  193. }
  194. else if(typeof yearOrDate == "number" &&
  195. typeof month == "number" &&
  196. typeof day == "number" &&
  197. typeof hour == "number" &&
  198. typeof minute == "number"){
  199. this._startDate = new Date(Date.UTC(yearOrDate,month-1,day));
  200. this._now = new Date(Date.UTC(yearOrDate,month-1,day,hour,minute));
  201. }else{
  202. console.error('Arguments for time.initDate not supported:',yearOrDate,month,day,hour,minute);
  203. }
  204. console.log("$time.initTime (startDate,now)",this._startDate,this._now);
  205. }
  206. get isNight(){
  207. const dayStage = this.dayStage;
  208. return !(dayStage >= 1 && dayStage <= 3);
  209. }
  210. get dayDistanceToMiddleOfYear(){
  211. let dayOfYear = this.dayOfYear;
  212. let distanceToMiddleOfYear = 0;
  213. if(dayOfYear >= 354)
  214. distanceToMiddleOfYear = 536 - dayOfYear; // 183 (half a year) + 353 (winter equinox) - current day
  215. else if(dayOfYear >= 171)
  216. distanceToMiddleOfYear = dayOfYear - 171;
  217. else
  218. distanceToMiddleOfYear = 171-dayOfYear;
  219. return distanceToMiddleOfYear;
  220. }
  221. get dayLength(){
  222. return (dayLengthMax-(dayLengthMax-dayLengthMin)*this.dayDistanceToMiddleOfYear/182);
  223. }
  224. get dayOfYear(){
  225. let days = 0;
  226. for(let i=0; i < this.month-1; i++){
  227. days += daysOfMonth[i];
  228. }
  229. days += this.day;
  230. return days;
  231. }
  232. get dawnDuskLength(){
  233. return 120;
  234. }
  235. get dayStage(){
  236. /*1 - dawn
  237. 2 - midday
  238. 3 - sunset
  239. 4 - the beginning of the night
  240. 5 - night
  241. 6 - the end of the night*/
  242. let minutesSinceMidnight = this.minutesSinceMidnight;
  243. let dayStages = this.dayStagesStartEnd;
  244. for (const [key, startEnd] of Object.entries(dayStages)) {
  245. if(startEnd[0] < startEnd[1]){
  246. if(minutesSinceMidnight >= startEnd[0] && minutesSinceMidnight < startEnd[1]){
  247. return parseInt(key);
  248. }
  249. }else{
  250. if(minutesSinceMidnight >= startEnd[0] || minutesSinceMidnight < startEnd[1]){
  251. return parseInt(key);
  252. }
  253. }
  254. }
  255. console.warn('dayStage calculation failed, returning default value');
  256. return 2;
  257. }
  258. // Return the start and end times of each stage of the current day in minutes since midnight
  259. get dayStagesStartEnd(){
  260. let DayLength = this.dayLength;
  261. let ddl = this.dawnDuskLength;
  262. let half_ddl = ddl/2;
  263. let sunZenithMinutes = this.sunZenithMinutes;
  264. let dawnStart = sunZenithMinutes - DayLength / 2;
  265. let dawnEnd = dawnStart + half_ddl;
  266. let earlyDawnStart = dawnStart - half_ddl;
  267. let earlyDawnEnd = dawnStart;
  268. let duskEnd = sunZenithMinutes + DayLength / 2;
  269. let duskStart = duskEnd - half_ddl;
  270. let lateDuskStart = duskEnd;
  271. let lateDuskEnd = lateDuskStart + half_ddl;
  272. let middayStart = dawnEnd;
  273. let middayEnd = duskStart;
  274. let nightStart = lateDuskEnd;
  275. let nightEnd = earlyDawnStart;
  276. return {
  277. "1": [dawnStart,dawnEnd],
  278. "2": [middayStart,middayEnd],
  279. "3": [duskStart,duskEnd],
  280. "4": [lateDuskStart,lateDuskEnd],
  281. "5": [nightStart,nightEnd],
  282. "6": [earlyDawnStart,earlyDawnEnd],
  283. };
  284. }
  285. get endTime(){
  286. return new Date(9999,11,31);
  287. }
  288. get isWinter(){
  289. return (seasons[this.month - 1] == 'winter');
  290. }
  291. get minutesSinceMidnight() {
  292. return this.hour * 60 + this.minutes;
  293. }
  294. /**
  295. * No need to clone this value.
  296. * @readonly
  297. * @type {Date}
  298. */
  299. get now():Date{
  300. return clone(this._now);
  301. }
  302. /**
  303. * Description placeholder
  304. * @date 10/3/2023 - 1:06:21 PM
  305. *
  306. * @param {number} offset
  307. * @returns {Date}
  308. */
  309. nowWithMinutesOffset(offset){
  310. let now = this.now;
  311. now.setUTCMinutes(now.getUTCMinutes() + offset);
  312. return now;
  313. }
  314. get tomorrow(){
  315. var tomorrow = this.now;
  316. tomorrow.setUTCDate(tomorrow.getUTCDate()+1);
  317. tomorrow.setUTCHours(0,0,0,0);
  318. return tomorrow;
  319. }
  320. secondsSinceDate(date){
  321. if(!(date instanceof Date))
  322. return Infinity;
  323. return Math.floor((this.now.getTime() - date.getTime()) / 1000);
  324. }
  325. /**
  326. * Returns whether a given Date is in the future.
  327. * @date 10/3/2023 - 1:10:48 PM
  328. *
  329. * @param {Date} date
  330. * @returns {boolean}
  331. */
  332. isFuture(date){
  333. try{
  334. return (this.secondsSinceDate(date) < 0);
  335. }catch(e){
  336. console.error("ERROR in $time.isFuture",date);
  337. return false;
  338. }
  339. }
  340. get today():Date{
  341. let now = clone(this.now);
  342. now.setUTCHours(0,0,0,0);
  343. return now;
  344. }
  345. dayOfIndex(index){
  346. let today = this.today;
  347. let currentIndex = this.daystart;
  348. let diff = index - currentIndex;
  349. today.setUTCDate(today.getUTCDate() + diff);
  350. return today;
  351. }
  352. dayWithOffset(offset){
  353. return this.dayOfIndex(this.daystart + offset);
  354. }
  355. time(hour,minute,flags={}){
  356. let currentTime = this.now;
  357. currentTime.setUTCHours(hour,minute);
  358. return currentTime;
  359. }
  360. //Minutes since midnight at which the sun is at his zenith
  361. //It's set to 13:00
  362. get sunZenithMinutes(){
  363. return 780;
  364. }
  365. get sunTime(){
  366. let CurTimeSun = this.hour*60 + this.minutes;
  367. //CurTimeSun (minuts) <0 - before midday; >0 - after midday
  368. CurTimeSun = CurTimeSun-this.sunZenithMinutes
  369. return CurTimeSun;
  370. }
  371. advanceTo(hour,minute,advanceToNextDay=false){
  372. let minuteDifference = (hour * 60 + minute) - (this.hour * 60 + this.minutes);
  373. if(minuteDifference > 0){
  374. this.minutes += minuteDifference;
  375. }else if(advanceToNextDay){
  376. this.minutes += 1440 + minuteDifference;
  377. }
  378. console.log("Time: advanceTo",hour,minute,advanceToNextDay,minuteDifference,this.now);
  379. }
  380. ageOfDate(dayOrDate:number|Date,month=undefined,year=undefined){
  381. let day = dayOrDate;
  382. if(dayOrDate instanceof Date){
  383. day = dayOrDate.getUTCDate();
  384. month=dayOrDate.getUTCMonth() + 1;
  385. year = dayOrDate.getUTCFullYear();
  386. }
  387. let yearDifference = this.year - year;
  388. if(month > this.month || (month == this.month && day > this.day))
  389. return yearDifference - 1;
  390. return yearDifference;
  391. }
  392. //29th Feb is not possible
  393. dateOfAge(age){
  394. let year = this.year - age;
  395. let randomMonth = rand(1,12);
  396. let randomDay = rand(1,daysOfMonth[randomMonth-1]);
  397. if(randomMonth > this.month || (randomMonth == this.month && randomDay > this.day))
  398. year -= 1;
  399. let date = new Date(Date.UTC(year,randomMonth-1,randomDay));
  400. console.log('Generated Date:',date);
  401. return date;
  402. }
  403. //Year -1: Current year, -2: Year with the next occurance of the date (either this our next)
  404. daysDifference(day,month,year=-1){
  405. const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
  406. let currentDate = this.today;
  407. let return_positive = false;
  408. if(year == -1)
  409. year = this.year;
  410. else if(year == -2){
  411. year = this.year;
  412. return_positive = true;
  413. }
  414. let testDate = new Date(Date.UTC(year, month, day));
  415. let diffDays = Math.round((testDate.getTime() - currentDate.getTime()) / oneDay);
  416. if(diffDays < 0 && return_positive)
  417. return this.daysDifference(day,month,this.year+1);
  418. return diffDays;
  419. }
  420. dayStartToDateArray(d){
  421. let currentDayStart = this.daystart;
  422. let dayStartDiff = d - currentDayStart;
  423. let currentDate = this.today;
  424. currentDate.setDate(currentDate.getDate() + dayStartDiff);
  425. return [currentDate.getDate(),currentDate.getMonth(),currentDate.getFullYear()];
  426. }
  427. /**
  428. * How many minutes is the time in the past. Negativ if it is in the future.
  429. * @date 9/10/2023 - 7:43:26 PM
  430. *
  431. * @param {number} hour
  432. * @param {number} minute
  433. * @returns {number}
  434. */
  435. timeDifference(hour,minute){
  436. return (this.hour - hour) * 60 + (this.minutes - minute);
  437. }
  438. // execute every n time-units
  439. execute_every_15_minutes(){
  440. console.log("Time: 15-Minutes-Tick",this.hour,this.minutes);
  441. State.variables.pc.execute_every_15_minutes();
  442. State.variables.wardrobe.execute_every_15_minutes();
  443. $.wiki(`<<include "stat_15minutes_changes">>`);
  444. }
  445. execute_every_1_hour(){
  446. console.log("Time: 1-Hour-Tick",this.hour,this.minutes);
  447. State.variables.pc.execute_every_1_hour();
  448. State.variables.wardrobe.execute_every_1_hour();
  449. $.wiki(`<<include "stat_hourly_changes">>`);
  450. }
  451. execute_every_1_day(){
  452. console.log("Time: 1-Day-Tick",this.daystart,this.hour,this.minutes);
  453. State.variables.pc.execute_every_1_day();
  454. State.variables.wardrobe.execute_every_1_day();
  455. State.variables.finances.execute_every_1_day();
  456. }
  457. execute_every_1_month(){
  458. console.log("Time: 1-Month-Tick",this.month,this.day,this.hour,this.minutes);
  459. }
  460. execute_every_1_year(){
  461. console.log("Time: 1-Year-Tick",this.year,this.month,this.day,this.hour,this.minutes);
  462. }
  463. execute_every_timeUpdate(timeToAdd){
  464. State.variables.pc.execute_every_timeUpdate(timeToAdd);
  465. }
  466. //executed whenever the time changes after everything else has been calculated
  467. execute_every_timeUpdate_post(){
  468. /*let missedEvents = this.getPastTimedEvents(undefined,true);
  469. for(let missedEventIndex in missedEvents){
  470. let missedEvent = missedEvents[missedEventIndex];
  471. switch(missedEvent.key){
  472. case 'date':
  473. $.wiki('<<datingMissed `'+JSON.stringify(missedEvent)+'`>>');
  474. break;
  475. case 'work':
  476. const jobs = State.variables.jobs;
  477. jobs.workMissed(missedEvent.data.jobId,missedEvent.endTime);
  478. default:
  479. if(missedEvent.data?.type){
  480. switch(missedEvent.data.type){
  481. case 'questEvent':
  482. let questId = missedEvent.data.questId;
  483. let eventId = missedEvent.data.eventId;
  484. let repeat = missedEvent.data.repeat;
  485. State.variables.q.eventExecute(questId,eventId);
  486. switch(repeat){
  487. case 'daily':
  488. this.rescheduleTimedEventByKey(missedEvent.key,'day',1);
  489. break;
  490. }
  491. break;
  492. }
  493. }
  494. }
  495. missedEvents[missedEventIndex].handled = true;
  496. }*/
  497. }
  498. get minutesTimestamp(){
  499. // Zero minutesTimestamp will be at New Years Eve of 2015
  500. //return this.minutesSince(2015,1,1,0,0);
  501. let yearDifference = this.year - 2015;
  502. let dayDifference = this.day - 1;
  503. let hourDifference = this.hour;
  504. let minutesDifference=this.minutes;
  505. let days = yearDifference * 365;
  506. for(let i=0 ; i < this.month - 1;i++){
  507. days += daysOfMonth[i];
  508. }
  509. days += dayDifference;
  510. let minutes = (days * 24 + hourDifference) * 60 + minutesDifference;
  511. return minutes;
  512. }
  513. // Output
  514. get dateTimeString(){
  515. if(!this._now)
  516. return '';
  517. let year = (''+this.year).padStart(4,'0');
  518. let month = (''+this.month).padStart(2,'0');
  519. let day = (''+this.day).padStart(2,'0');
  520. let hour = (''+this.hour).padStart(2,'0');
  521. let minutes = (''+this.minutes).padStart(2,'0');
  522. let date = day+'.'+month+'.'+year;
  523. let time = hour+':'+minutes;
  524. return date+' '+time;
  525. }
  526. // ----- special_events -----
  527. // Special Events are one day long, such has your graduation
  528. /*_specialEvents = [
  529. ]
  530. addEvent(key,day,month,year){
  531. this._specialEvents.push({
  532. key: key,
  533. day: day,
  534. month: month,
  535. year: year
  536. });
  537. }
  538. currentSpecialEvents(){
  539. let result = [];
  540. for(let specialEvent of this._specialEvents){
  541. if(specialEvent.day == this.day && specialEvent.month == this.month && specialEvent.year == this.year)
  542. result.push(specialEvent.key);
  543. }
  544. return result;
  545. }*/
  546. // ----- timed events -----
  547. // Timed events have a start date and time, duration and meta-informationen, such as a scheduled date
  548. _timedEvents:any = {_system:{nextId:0},events:{}}
  549. addTimedEvent(key,day,month,year,hour,minute,duration,data={}){
  550. let startTime = new Date(Date.UTC(year, month - 1, day, hour, minute, 0));
  551. return this.addTimedEventByDate(key,startTime,duration,data);
  552. }
  553. addTimedEventByDate(key,startTime,duration,data={}){
  554. let endTime = new Date(startTime.getTime() + duration*60000);
  555. let timedEvent = {
  556. key: key,
  557. startTime: startTime,
  558. endTime: endTime,
  559. duration : duration,
  560. data: data
  561. };
  562. let id = ''+this._timedEvents._system.nextId++;
  563. //this._timedEvents.push(timedEvents);
  564. this._timedEvents.events[id] = timedEvent;
  565. console.log('Timed event added',id,timedEvent);
  566. return id;
  567. }
  568. getCurrentTimedEvents(key=undefined){
  569. let result = [];
  570. let now = this.now;
  571. for(let timedEvent of Object.values(this._timedEvents.events)){
  572. if(key === undefined || timedEvent['key'] == key){
  573. if(now.getTime() >= timedEvent['startTime'].getTime() && now.getTime() <= timedEvent['endTime'].getTime())
  574. result.push(timedEvent);
  575. }
  576. }
  577. return result;
  578. }
  579. getPastTimedEvents(key=undefined,ignoreHandled = false){
  580. let result = [];
  581. let now = this.now;
  582. for(let timedEvent of Object.values(this._timedEvents.events)){
  583. if(ignoreHandled && timedEvent['handled'])
  584. continue;
  585. if(key === undefined || timedEvent['key'] == key){
  586. if(now.getTime() > timedEvent['endTime'].getTime())
  587. result.push(timedEvent);
  588. }
  589. }
  590. return result;
  591. }
  592. rescheduleTimedEventById(id,dateTime){
  593. if(!this._timedEvents.events[id])
  594. return false;
  595. const duration = this._timedEvents.events[id].duration;
  596. this._timedEvents.events[id].startTime = dateTime;
  597. this._timedEvents.events[id].endTime= new Date(dateTime.getTime() + duration*60000);
  598. return true;
  599. }
  600. rescheduleTimedEventByKey(key,offsetMode='day',offsetCount=1){
  601. for(let timedEvent of Object.values(this._timedEvents.events)){
  602. if(timedEvent['key'] == key){
  603. switch(offsetMode){
  604. case 'day':
  605. timedEvent['startTime'].setUTCDate(timedEvent['startTime'].getUTCDate() + offsetCount);
  606. timedEvent['endTime'].setUTCDate(timedEvent['endTime'].getUTCDate() + offsetCount);
  607. break;
  608. case 'month':
  609. timedEvent['startTime'].setUTCMonth(timedEvent['startTime'].getUTCMonth() + offsetCount);
  610. timedEvent['endTime'].setUTCMonth(timedEvent['endTime'].getUTCMonth() + offsetCount);
  611. break;
  612. case 'year':
  613. timedEvent['startTime'].setUTCFullYear(timedEvent['startTime'].getUTCFullYear() + offsetCount);
  614. timedEvent['endTime'].setUTCFullYear(timedEvent['endTime'].getUTCFullYear() + offsetCount);
  615. break;
  616. }
  617. }
  618. console.log("Timed event rescheduled:",timedEvent);
  619. }
  620. }
  621. unscheduleTimedEventsByKeyAndData(key,data){
  622. this._timedEvents = Object.values(this._timedEvents.events).filter((e)=>{
  623. if(e['key'] != key)
  624. return true;
  625. for (const [data_key, data_value] of Object.entries(data)) {
  626. if(data_value != e['data'][data_key])
  627. return true;
  628. }
  629. return false;
  630. });
  631. }
  632. unscheduleTimedEventById(id){
  633. return (delete this._timedEvents.events[id]);
  634. }
  635. // ----- holidays ----
  636. //Returns an array of the indizes of the current holidays
  637. currentHolidays(includeSchoolHolidays=true,includeWorkHolidays=true){
  638. return this.holidaysOfDate(this.now,includeSchoolHolidays,includeWorkHolidays);
  639. }
  640. holidaysOfDate(date,includeSchoolHolidays=true,includeWorkHolidays=true){
  641. date = new Date(date.getTime());
  642. date.setUTCHours(0,0,0,0);
  643. let h = holidays;
  644. let result = [];
  645. for (const [holiday_key, holiday_value] of Object.entries(h)) {
  646. let startDate = new Date(date.getTime());
  647. startDate.setUTCMonth(holiday_value.start[0]-1,holiday_value.start[1]);
  648. let endDate = new Date(date.getTime());
  649. endDate.setUTCMonth(holiday_value.end[0]-1,holiday_value.end[1]);
  650. if(date.getTime() >= startDate.getTime() && date.getTime() <= endDate.getTime()){
  651. if(
  652. (includeSchoolHolidays && holiday_value.school) ||
  653. (includeWorkHolidays && holiday_value.work)
  654. )
  655. result.push(holiday_key);
  656. }
  657. }
  658. return result;
  659. }
  660. daysOfHolidayLeft(holidayId){
  661. let holiday = holidays[holidayId];
  662. return this.daysDifference(holiday.end[1],holiday.end[0],-2) + 1; //+1 since the end of the holidays is counted as well
  663. }
  664. get isSchoolDay(){
  665. if(this.weekday >= 6)
  666. return false;
  667. return !this.isSchoolHoliday;
  668. }
  669. get isSchoolHoliday(){
  670. return this.dateIsSchoolHoliday(this.now);
  671. }
  672. /**
  673. * @param {Date} date
  674. * @returns {boolean}
  675. */
  676. dateIsSchoolHoliday(date){
  677. let holidayIds = this.holidaysOfDate(date);
  678. for(let holidayId of holidayIds){
  679. let holidayData = holidays[holidayId];
  680. if(holidayData.school)
  681. return true;
  682. }
  683. return false;
  684. }
  685. /**
  686. * Description placeholder
  687. * @param {string} condition
  688. * @param {Date|undefined} [dateTime=undefined]
  689. * @returns {boolean}
  690. */
  691. _timetableConditionMet(condition,dateTime=undefined){
  692. if(!condition)
  693. return true;
  694. dateTime ??= this.now;
  695. const weekDay = dateTime.getUTCDay();
  696. const isWeekend = (weekDay == 0 || weekDay == 6);
  697. switch(condition){
  698. case 'always': return true;
  699. case 'else': return false;
  700. case 'noSchoolHoliday': return !this.dateIsSchoolHoliday(dateTime);
  701. case 'schoolHoliday': return this.dateIsSchoolHoliday(dateTime);
  702. case 'weekday': return !isWeekend;
  703. case 'weekend': return isWeekend;
  704. }
  705. const todaysAbbrev = ['mo','tu','we','th','fr','sa','su'][weekDay];
  706. if(condition.indexOf(todaysAbbrev) == -1)
  707. return false;
  708. return true;
  709. }
  710. _timetableGetSchedules(timetable,dateTime=undefined){
  711. let todaysSchedules = [];
  712. dateTime ??= this.now;
  713. for(const [scheduleDay,daySchedules] of Object.entries(timetable)){
  714. if(!this._timetableConditionMet(scheduleDay))
  715. continue;
  716. if(Array.isArray(daySchedules))
  717. todaysSchedules = todaysSchedules.concat(daySchedules);
  718. else
  719. todaysSchedules = todaysSchedules.concat(this._timetableGetSchedules(daySchedules));
  720. }
  721. if(todaysSchedules.length == 0 && ('else' in timetable)){
  722. if(Array.isArray(timetable.else))
  723. todaysSchedules = timetable.else;
  724. else
  725. todaysSchedules = this._timetableGetSchedules(timetable.else);
  726. }
  727. todaysSchedules = todaysSchedules.filter(schedule => this._timetableConditionMet(schedule.condition));
  728. todaysSchedules.sort((a,b)=>(a.start[0]*100+a.start[1]) - (b.start[0]*100+b.start[1]));
  729. return todaysSchedules;
  730. }
  731. timetableLookup(timetable,dateTime=undefined){
  732. dateTime ??= this.now;
  733. const hour = dateTime.getUTCHours();
  734. const minute = dateTime.getUTCMinutes();
  735. const todaysSchedules = this._timetableGetSchedules(timetable,dateTime);
  736. let currentSchedule=undefined;
  737. let nextScheduleStart=undefined;
  738. for(const schedule of todaysSchedules){
  739. if(schedule.start[0] < hour || (schedule.start[0] == hour && schedule.start[1] <= minute)){
  740. currentSchedule = schedule;
  741. }else{
  742. nextScheduleStart = schedule.start;
  743. break;
  744. }
  745. }
  746. if(!nextScheduleStart)
  747. nextScheduleStart = [24,0];
  748. if("data" in currentSchedule)
  749. return {current: currentSchedule.data, nextStartTime: nextScheduleStart};
  750. if("either" in currentSchedule)
  751. return {current: currentSchedule.either.random(), nextStartTime: nextScheduleStart};
  752. return undefined;
  753. }
  754. _init(gameTime){
  755. Object.keys(gameTime).forEach(function (pn) {
  756. this[pn] = clone(gameTime[pn]);
  757. }, this);
  758. return this;
  759. }
  760. clone = function () {
  761. return (new setup.GameTime())._init(this);
  762. };
  763. toJSON = function () {
  764. var ownData = {};
  765. Object.keys(this).forEach(function (pn) {
  766. if(typeof this[pn] !== "function")
  767. ownData[pn] = clone(this[pn]);
  768. }, this);
  769. return JSON.reviveWrapper('(new setup.GameTime())._init($ReviveData$)', ownData);
  770. };
  771. }
  772. setup.GameTime = GameTime;
  773. setup.durationToString = function(duration){
  774. return Math.floor(duration/60).toString()+':'+(duration%60).toString().padStart(2, '0');
  775. }
  776. setup.weekDayIntToString ??= function(day:number){
  777. switch(day){
  778. case 1: return 'Monday';
  779. case 2: return 'Tuesday';
  780. case 3: return 'Wednesday';
  781. case 4: return 'Thursday';
  782. case 5: return 'Friday';
  783. case 6: return 'Saturday';
  784. case 7: return 'Sunday';
  785. default: return '???';}
  786. }