!(function (e) { var t = {}; function a(s) { if (t[s]) return t[s].exports; var i = (t[s] = { i: s, l: !1, exports: {} }); return (e[s].call(i.exports, i, i.exports, a), (i.l = !0), i.exports); } ((a.m = e), (a.c = t), (a.d = function (e, t, s) { a.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: s }); }), (a.r = function (e) { ("undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 })); }), (a.t = function (e, t) { if ((1 & t && (e = a(e)), 8 & t)) return e; if (4 & t && "object" == typeof e && e && e.__esModule) return e; var s = Object.create(null); if ( (a.r(s), Object.defineProperty(s, "default", { enumerable: !0, value: e }), 2 & t && "string" != typeof e) ) for (var i in e) a.d( s, i, function (t) { return e[t]; }.bind(null, i), ); return s; }), (a.n = function (e) { var t = e && e.__esModule ? function () { return e.default; } : function () { return e; }; return (a.d(t, "a", t), t); }), (a.o = function (e, t) { return Object.prototype.hasOwnProperty.call(e, t); }), (a.p = ""), a((a.s = 0))); })([ function (e, t, a) { "use strict"; (a.r(t), a.d(t, "navigationHelper", function () { return _; })); class s { static get availableLanguages() { return [{ id: "zh", text: "汉语", dict: i, moment: "zh-cn" }]; } constructor(e) { ((this.selectedLanguage = s.availableLanguages.find((t) => t.id == e)), this.selectedLanguage && (n.setSetting("language", e), moment.locale(this.selectedLanguage.moment))); } static createInstance(e) { (void 0 !== this.instance && (null == this.instance.selectedLanguage ? "en" === e : this.instance.selectedLanguage.id === e)) || (this.instance = new s(e)); } static getInstance() { return this.instance; } T(e, t, a = []) { let s = ""; return ( (s = null == this.selectedLanguage ? t : this.selectedLanguage.dict[e] || t), a.reduce((e, t) => e.replace(/%s/, t), s) ); } } const i = { credits: "学分", prerequisites: "先修课程", "tier-navigation": "类别", "navigation-home": "主页", "navigation-majors": "专业", "navigation-major": "专业", "navigation-classes": "课程", "navigation-class": "课程", "navigation-clubs": "社团", "navigation-club": "社团", "navigation-partners": "基/姬友", "navigation-partner": "基/姬友", "navigation-punishments": "处分", "navigation-punishment": "处分", "navigation-schedule": "日程", "navigation-progress": "进度", "navigation-roulette": "轮盘", "navigation-help": "帮助", "navigation-settings": "设置", "navigation-mapping": "地图", "navigation-graduated": "已毕业", "navigation-disclaimer": "免责条款", "scheduled-toast-error-header": "第三方cookies被禁止", "scheduled-toast-error-content": "请切换至完整的地图工具网页,或在浏览器设置里允许它。", "user-confirmation-yes": "确定", "user-confirmation-cancel": "取消", "user-confirmation-close": "关闭", "update-notification": "后台版本已更新,点击刷新。", "time-day": "天", "time-days": "天", "time-day-binding-no-minutes": " ", "time-day-binding": " ", "time-hour": "小时", "time-hours": "小时", "time-hours-binding": " ", "time-minute": "分钟", "time-minutes": "分钟", "time-minutes-binding": " ", "time-second": "秒", "time-seconds": "秒", "time-less-then-a-minute": "不足 1 分钟。", "home-feedback-title": "用户反馈", "home-feedback-description": '只需要轻轻点击一下,发送一个"心跳信号"给作者,Ta就能知道一位陌生的好朋友还默默支持、陪伴着Ta。', "home-feedback-discord": "如果你想加入PU的Discord讨论组 - 请点击这里。", "home-welcome-header": "Project University欢迎你的加入", "home-welcome-info-1": "在这里,你可以创建属于自己的大学或者加入别人已经建好的大学。由于本网站不会提供关于玩法的任何内容,因此你需要自己导入一张PU-map地图文件。该文件由玩家自行创建,用于提供具体的玩法。本网站完全运行在玩家自己的设备上,网站的后台服务器不会储存任何私人内容,因此请确保存档进度的安全,请确保导入内容的安全性,请确保人身安全和法律安全。", "home-welcome-info-2": "在这里,你的大学生活将大致包括如下内容:选择专业、选择课程和进行日常任务。如果你没有达到任务的要求,你将受到处分。你可以通过完成课程考试的方式来获得相应的学分和解锁更难的课程。学完所有的专业必修课并获得足够的学分之后,你就可以尝试毕业,参加最后的专业结业考试。课程之余,你还可以参加社团或者勾搭基/姬友来改变特定课程的难度或获得福利。", "home-welcome-info-3": "赶紧导入一张PU-map地图,并选择一个专业吧!你的选课学习之旅就此开启!", "home-load-map-button": "导入地图", "home-create-map-button": "创建地图", "home-changelog-header": "更新日志", "home-show-changelogs-button": "以前的更新日志", "home-external-sources-header": "外部资源支持", "home-disclaimer-link": "如果您想再次确认“免责条款和数据保护”的内容, 请单击此处。", "home-special-thanks-header": "特别鸣谢", "tour-navigation-close": "关闭", "tour-navigation-next": "下一条", "tour-majors-0-title": "菜单", "tour-majors-0-text": "这里是主菜单,可以在不同的选项卡之间切换。", "tour-majors-1-title": "折叠菜单", "tour-majors-1-text": "在移动设备上主菜单会折叠,点击这里折叠或展开主菜单。", "tour-majors-2-title": "专业", "tour-majors-2-text": "每张专业卡片对应一个可选专业。", "tour-majors-3-title": "详情", "tour-majors-3-text": "点击图片查看该专业的具体内容。", "tour-majors-4-title": "选项", "tour-majors-4-text": "这里会出现选专业、退选专业的选项。", "tour-majors-5-title": "内容一览", "tour-majors-5-text": "按下这个信息按钮会为你展示该专业的具体内容。", "tour-majors-6-title": "卡片信息", "tour-majors-6-text": "卡片底端将展示该专业的简要描述。", "tour-classes-0-title": "课程难度", "tour-classes-0-text": "点击查看不同难度的课程。", "tour-classes-1-title": "课程难度", "tour-classes-1-text": "点击此处或左右滑动来查看不同难度的课程,从左至右难度依次增加。", "tour-classes-2-title": "课程", "tour-classes-2-text": "每张卡片对应一个可选课程。", "tour-classes-3-title": "图片", "tour-classes-3-text": "点击图片查看该课程的具体内容。", "tour-classes-4-title": "选项", "tour-classes-4-text": "这里会出现选课、退课和重修的选项。", "tour-classes-5-title": "内容一览", "tour-classes-5-text": "按下这个信息按钮会为你展示该课程的具体内容。", "tour-classes-6-title": "卡片信息", "tour-classes-6-text": "卡片底端将展示该课程的简要描述。", "map-submap-failed-header": "设置子地图失败", "map-submap-failed-content": "PU无法储存子地图。这可能由于你的浏览器开启了匿名模式访问。", "map-save-failed-header": "进度保存失败", "map-save-failed-content": "PU无法保存你的进度。这可能由于你的浏览器开启了匿名模式访问。", "map-save-incompatible": "存档与当前主地图不兼容。", "map-save-empty": "存档为空。", "map-import-confirmation-header": "导入地图", "map-import-confirmation-conent": "确定要导入这张地图吗?请确保地图来源的可靠性,否则后果自负。", "map-import-script-header": "导入中止", "map-import-script-content": "该地图文件内包含不安全的信息。出于安全性考虑,我们中止了导入。", "map-import-submap-popup": "导入子地图", "map-import-map-popup": "地图导入成功", "task-time-without-mod-info": "原时长 %s。", "task-punishment-without-mod-info": "原数量 %s。", "task-random-punishment": "随机", "task-param-without-mod-info": "原数量 %s。", "task-perk-activated-header": "奖励处分已获得", "task-perk-activated-content": "你随机得到了 1 项处分。", "task-roulette-reset-header": "轮盘重置奖励", "task-roulette-reset-content": "轮盘已经重置了,再去碰碰运气吧。", "task-skip-perk-header": "请假条奖励", "task-skip-perk-single-content": "请假条已获得。", "task-skip-perk-multi-content": "%s 张请假条已获得", "task-punishment-drawn-header": "任务处分已获得", "task-punishment-drawn-content": "根据任务的要求,你随机得到了 1 项 %s处分。", "task-punishment-header": "受到处分", "task-punishment-custom-task": "自定义任务失败!", "task-punishment-task": "日常任务失败!", "task-punishment-exam": "课程考试失败!", "task-punishment-thesis": "毕业考核失败!", "task-punishment-drawn": "你获得了 1 项处分!", "task-punishments-drawn": "你获得了 %s 项处分!", "task-failed-header": "任务失败", "task-failed-content": "你的 %s 任务完成失败!", "task-custom-task-finished-header": "自定义任务完成", "task-custom-task-finished-content": "你完成了 1 项自定义任务!", "task-class-finished-header": "课程通过", "task-class-finished-content": "恭喜你通过了 %s 课程!", "task-finished-header": "任务完成", "task-finished-content": "你完成了 %s 的课程任务!", "task-major-finished-header": "专业毕业", "task-major-finished-content": "你从 %s 专业中毕业了!", "task-punishment-finished-header": "处分结束", "task-punishment-finished-content": "对你的处分内容 %s 已经结束!", "task-manual-counter-button": "剩余 %s 次", "task-manual-finish-button": "完成", "task-finish-by-text": "本任务需在%s完成。", "task-fail-button": "失败", "task-time-left-info": "剩余时长", "task-resume-button": "继续", "task-pause-button": "暂停", "task-punish-button": "惩罚", "task-status-paused": "任务已暂停", "task-status-active": "任务进行中", "task-active-task": "进行中的任务", "task-class-missed-header": "翘课", "task-class-missed-content": "你因为翘掉一节课而受到 1 项处分!", "task-fail-header": "任务失败", "task-fail-content": "确定要放弃该任务或任务失败了吗?", "graduated-certificate-header": "毕业证书:%s", "graduated-congratulations-header": "恭喜你从 %s 专业中毕业。", "graduated-congratulations-1": "经过不懈努力之后,恭喜你离自己梦中的样子更近了一步。", "graduated-congratulations-2": "为了你的梦想,你依然还有很多能去做的。再选择一个专业或者换一张地图继续努力吧~", "graduated-congratulations-3": "作为已经毕业的学长/学姐,你也可以为学弟学妹们准备一些属于你的原创内容,让ta们的校园生活更加丰富多彩。", "graduated-table-class": "课程", "graduated-table-knowledge-points": "出勤点", "graduated-table-grade": "绩点", "progress-active-major": "你的当前专业", "progress-completed-classes": "课程完成情况", "progress-active-classes": "正在学习", "progress-active-classes-no": "没有正在学习中的课程。", "progress-mandatory-classes": "必修课程", "progress-mandatory-classes-no": "没有必修课程。", "progress-finished-majors": "已毕业专业", "progress-finished-majors-no": "还没有从任何专业中毕业。", "progress-finished-classes": "已通过课程", "progress-finished-classes-no": "还没有通过任何课程。", "progress-credits-info": "只有通过了课程考试,你才能获得学分。", "progress-punishments-endured": "尚未结束的处分", "progress-thesis-button": "选择结业考核内容", "schedule-active-tasks-header": "进行中的任务", "schedule-active-punishments-header": "执行中的处分", "schedule-active-punishments-info": "在处分执行完毕前,禁止参加课程考试和毕业考核。", "schedule-todays-classes-header": "今日课程", "schedule-clubs-partner-header": "社团 & 基/姬友", "schedule-weekly-header": "本周课表", "schedule-easy-mode-info": '当前是"简单模式",你可以随意补习各门课程,翘掉补习不会受处分。', "schedule-weekend-info": "你在周末可以随意补习各门课程,翘掉补习不会受处分。", "schedule-no-class-left": "今天的课已经上完了,明天再来吧。", "schedule-no-class-left-add": "今天的课已经上完了,你想多选一门课吗?", "schedule-no-classes": "无课程", "schedule-feedback-header": "用户反馈请求", "schedule-feedback-1": '同学你好~近一个月原作者收到的"心跳信号"急剧减少,这让Ta十分扫兴。如果一直这样下去,Ta可能会放弃这个项目的维护。先谢过你一直以来对本项目的支持,并由衷地希望你能够享受你的大学生活。由于原作者十分注重各位同学的隐私保护,所以Ta不会从本网站上获取用户的任何信息,但Ta又想知道自己的大学里还有多少学生,所以需要各位提供主动的信息反馈。只需要简单地点击这条%sbitly链接%s,就可以让Ta知道你还在这里上课。', "schedule-feedback-2": "此外,Ta还为这个项目开设了一个discord讨论组(英文),用于反馈用户体验和分享用户自己制作的地图,欢迎你的%s加入%s", "schedule-feedback-3": '这条"心跳信号"请求每个月会显示一次。', "schedule-import-task-error-header": "任务导入失败", "schedule-import-task-error-content": "未能成功导入他人分享的任务。", "schedule-script-header": "导入中止", "schedule-script-content": "该自定义任务内包含不安全的信息。出于安全性考虑,我们中止了导入。", "schedule-custom-task-default-name": "自定义任务", "schedule-import-custom-task-confirmation-header": "导入自定义任务", "schedule-import-custom-task-confirmation-content": "确定要导入这条自定义任务吗?任务将在导入后自动开始。请确保地图来源的可靠性,否则后果自负。", "roulette-header": "高潮轮盘", "roulette-info-1": "小骚货,又想要了吗?", "roulette-info-2": "来求我试试看~", "roulette-info-3": "什么?你胆敢擅自高潮?", "roulette-info-4": "滚过来受罚!", "roulette-punishment-button": "我不小心弄出来了", "roulette-spin-button": "求求您给我一次", "punishment-tier-text": "程度", "punishment-tier-light": "轻", "punishment-tier-hard": "重", "punishment-tier-hardcore": "硬核", "punishment-light-header": "轻处分", "punishment-hard-header": "重处分", "punishment-hard-requirement": "你需要至少 %s 学分才能解锁重处分。", "punishment-hardcore-header": "硬核处分", "punishment-hardcore-requirement": "你需要至少 %s 学分并在设置中开启了“硬核处分”才能解锁硬核处分。", "punishment-status-active": "执行中", "punishment-status-locked": "未解锁", "punishment-roll-button": "随机得到 %s处分", "punishment-detail-start-button": "开始执行处分", "punishment-detail-reroll-button": "随机更换处分", "punishment-rerolled-knowledge-point-header": "处分已更换", "punishment-rerolled-knowledge-point-content": "你因更换处分而被扣除了 %s 课程的 1 个出勤点。", "punishment-rerolled-exam-header": "处分已更换", "punishment-rerolled-exam-content": "你因更换处分而被取消了 %s 的课程考试成绩。你需要重新参加考试了。", "punishment-rerolled-unknown-header": "处分已更换", "punishment-rerolled-unknown-content": "你将为更换处分的行为付出代价。", "punishment-unavailable-header": "不可获得的处分程度", "punishment-unavailable-content": "你当前还无法获得此种程度的处分。", "punishment-max-header": "达到处分数量上限", "punishment-max-content": "你最多能够积累 %s 个执行中的处分。", "punishment-null-header": "无其他可获取的处分", "punishment-null-content": "你当前没有可以被给予的处分了。", "punishment-single-text": "处分", "punishment-multi-text": "处分", "punishment-card-unblock-button": "Unblock", "punishment-card-block-button": "Block", "punishment-detail-unblock-button": "Unblock punishment", "punishment-detail-block-button": "Block punishment", "club-tier-normal": "普通", "club-tier-elite": "精英", "club-normal-header": "社团", "club-elite-header": "精英社团", "club-elite-requirement": "你至少需要 %s 学分才能参加精英社团。", "club-status-active": "已加入", "club-status-locked": "未解锁", "club-card-join-button": "报名", "club-card-drop-button": "退出", "club-detail-modifiers": "可选奖励", "club-detail-join-button": "参加本社团", "club-detail-drop-button": "退出本社团", "club-modifier-activity": "活动", "club-modifier-perk": "奖励", "club-perk-activate-button": "领取奖励", "club-perk-active-button": "已领取", "club-max-clubs-header": "达到社团数上限", "club-max-clubs-content": "你最多只能同时参加 %s 个社团。", "club-max-perks-header": "达到社团活动数上限", "club-max-perks-content": "你最多只能同时参加 %s 个社团活动。", "club-perk-unavailable-header": "社团奖励无法领取", "club-perk-unavailable-content": "当前无法参加该社团活动领取奖励。", "club-perk-unavailable-tag-header": "社团奖励无法领取", "club-perk-unavailable-tag-content": "当前无相关课程(标签)与该社团活动相关。", "partner-status-active": "已结交", "partner-card-join-button": "勾搭", "partner-card-drop-button": "绝交", "partner-modifier-activity": "活动", "partner-modifier-perk": "福利", "partner-detail-modifiers": "可选福利", "partner-detail-add-button": "勾搭Ta", "partner-detail-leave-button": "和Ta绝交", "partner-perk-activate-button": "领取福利", "partner-perk-active-button": "已领取", "partner-perk-not-active-header": "基/姬友福利无法领取", "partner-perk-not-active-content": "你还没有勾搭上Ta。", "partner-perk-tags-header": "基/姬友福利无法领取", "partner-perk-tags-content": "当前无相关课程(标签)与该基/姬友活动相关。", "partner-perk-max-perks-header": "基/姬友福利达到上限", "partner-perk-max-perks-content": "你最多只能同时参加 %s 个基/姬友活动。", "major-status-unavailable": "不可选", "major-status-active": "当前专业", "major-status-grade": "绩点", "major-card-join-button": "选修", "major-card-drop-button": "退选", "major-detail-prerequisites": "必修课程", "major-detail-thesis": "可选毕业考核内容", "major-detail-thesis-button": "开始毕业考核", "major-detail-join-button": "选修本专业", "major-detail-drop-button": "退选本专业", "major-detail-thesis-requirement-header": "参加毕业考核条件", "major-detail-thesis-requirement-join": "你必须先选修本专业,才能参加毕业考核。", "major-detail-thesis-requirement-credits": "你必须修满 %s 个学分。", "major-detail-thesis-requirement-punishments": "你必须先执行完所有处分。", "major-detail-thesis-requirement-prerequisites": "你必须通过所有必修课程。", "major-detail-thesis-requirement-thesis": "你只能选择一条毕业考核内容。", "major-drop-confirm-header": "专业退选", "major-drop-confirm-content": "确定要退选该专业?", "major-dropped-header": "退选成功", "major-dropped-content": "你因为退选专业而受到处分!", "class-tier-beginner": "入门", "class-tier-intermediate": "普通", "class-tier-advanced": "高级", "class-tier-master": "大师", "class-beginner-header": "入门课程", "class-intermediate-header": "普通课程", "class-intermediate-requirement": "你至少需要获得 %s 学分,并完成所有的先修课程才能解锁普通课程。", "class-advanced-header": "高级课程", "class-advanced-requirement": "你至少需要获得 %s 学分,并完成所有的先修课程才能解锁高级课程。", "class-master-header": "大师课程", "class-master-requirement": "你至少需要获得 %s 学分,并完成所有的先修课程才能解锁大师课程。", "class-card-tasks": "任务", "class-card-exams": "考试", "class-card-exams-locked": "当前未解锁", "class-card-join-button": "选课", "class-card-drop-button": "退课", "class-card-retake-button": "重修", "class-status-locked": "未解锁", "class-status-grade": "绩点", "class-status-active": "正在学习", "class-detail-days": "每周开课", "class-detail-tasks": "可选任务选项", "class-detail-exams": "考试选项", "class-detail-expected-grade": "如果现在参加考试,本课程绩点为 %s。", "class-detail-mandatory": "本课程为当前专业的必修课程 %s 。", "class-detail-join-button": "选修本课程", "class-detail-drop-button": "退选本课程", "class-detail-retake-button": "重修本课程", "class-detail-retake-warning": "本课程最终绩点为 %s,你还有进步的空间。若想提高绩点,你可以重修本课程,继续获得更多出勤点。当然,你最后需要重新参加课程考试。", "class-detail-start-task-button": "开始学习", "class-detail-start-exam-button": "开始考试", "class-detail-exam-requirement-header": "考试条件", "class-detail-exam-requirement-join": "你必须先选课,而后才能完成学习任务或参加考试。", "class-detail-exam-requirement-attended": "你今日已经学过本课程了,劳逸结合才能进步更快。", "class-detail-exam-requirement-attendance": "所需出勤点为 %s 个,当前已获得 %s 个。", "class-detail-exam-requirement-attendance-tasks": "你还有尚未进行过的可选任务。", "class-knowledge-points-info": "%s knowledge points collected.", "class-detail-exam-requirement-punishment": "你还有未结束的处分。", "class-detail-exam-requirement-days": "本课程今日不开课。", "class-max-classes-header": "达到课程数上限", "class-max-classes-content": "你最多只能同时选修 %s 门课。", "class-retake-confirmation-header": "重修课程", "class-retake-confirmation-content": "确定想要重修本课程吗?你已有的出勤点会保留,但你的课程考试将作废。", "class-drop-confirmation-header": "退选课程", "class-drop-confirmation-content": "确定想要退选本课程?", "disclaimer-header": "免责条款和数据保护", "disclaimer-block-1": "可以公开访问的网站需要满足一些法律要求 ,因此我们写下了这段简短的陈述来帮助您了解这个网站如何运作。 进入首页后,您可以在页面底部找到本页面的链接。 如果您同意以下内容,您可以点击页面底部的“我已理解并同意”, 并继续访问本网站。如果您不同意,我们将清理所有存储的数据,并自动退出。", "disclaimer-overview-header": "概述", "disclaimer-overview-1": "下文中的“用户”代指您,而“我们”代指网页提供者。", "disclaimer-overview-2": "只要我们有意开放,本网站对用户免费,但我们有权随时关闭甚至清空本网站。", "disclaimer-overview-3": "本网站除用于提供网站内容的简单网页服务外,不使用任何后端。", "disclaimer-overview-4": "本网站完全基于您的设备运行,如果发生文件损坏或丢失,所有之前的信息均无法找回。", "disclaimer-overview-5": "本网站只是一个框架,因此需要用户生成内容,这些内容是由用户手动创建和提供的,我们并不知道其他人如何使用这项服务。 您可以将其比作一个本地文本编辑器——您可以创建或打开文本文件,而一切都留在您的设备上。您亦可以使用这个工具来访问其他用户创建的文件。", "disclaimer-overview-6": "本网站运行所需的源代码完全由本域名提供。对于某些离开了本页面的链接,我们对其行为不负任何责任。", "disclaimer-overview-7": "用户生成的内容可能是恶意的,我们试图保护您免受明显的攻击,但最终您要对您加载的外部内容负责。", "disclaimer-inner-header": "免责条款", "disclaimer-inner": "使用本网站是免费的,没有广告或跟踪服务。任何人不得使用本框架获利。 由于本网站使用用户编辑生成的内容,但并不存储任何关于用户的信息,所以网站提供者无法审核将要加载的内容。 因此,您将对您添加的其他用户生成的内容承担全部责任。 提供者对链接页面的内容不负责任, 如果有法律要求从框架中删除任何引用,它将被遵守。", "disclaimer-data-protection-header": "数据保护声明", "disclaimer-data-protection": '没有数据被收集和存储在用户设备之外。进度、设置、内容和反馈标记都存储在本地设备上。 用户生成的内容会留在用户的设备上。如果您决定使用反馈选项提供 "心跳信号",您将离开本网站,之后便使用Bitly隐私条款。 反馈是可选的,Bitly-Link将被突出显示。', "disclaimer-bitly-link": "点击这里查看Bitly.com上的相关隐私条款。", "disclaimer-stored-data-header": "数据存储", "disclaimer-stored-data-description": "多种技术手段用于存储您的进度、设置和添加的用户生成内容。", "disclaimer-stored-data-local": "本地数据存储: 设置, 推迟的通知, 最后一次反馈信息请求, 接受的免责条款, 已经显示的教程", "disclaimer-stored-data-indexeddb": "IndexedDB存储: 用户生成内容, 当前进度", "disclaimer-stored-data-cache": "临时文件存储: 本网站框架数据支持离线模式", "disclaimer-accept-button": "我不同意", "disclaimer-reject-button": "了解并同意", "settings-header": "设置", "settings-easy-mode-checkbox": "简单模式", "settings-easy-mode-description": '在简单模式下,你可以在任何一天上课,取消了翘课的处分,并且可以任意随机更换处分而不被罚。该模式可以作为游戏的"暂停"。', "settings-hardcore-punishments-checkbox": "硬核处分", "settings-hardcore-punishments-description": "硬核处分往往相当困难,禁用它们通常不会有任何影响。默认禁用。", "settings-auto-hide-checkbox": "网页自动隐藏", "settings-auto-hide-description": "确保你的小秘密不会被旁人看到,本网页会在 %s 后自动隐藏。", "settings-back-top-checkbox": "回到顶端按钮", "settings-back-top-description": '在手机上不容易在页面上切换,你可以开启 "回到顶端按钮" 来方便你迅速回到页面顶端。', "settings-hide-active-classes-checkbox": "在课程页面不显示正在学习中的课程", "settings-hide-active-classes-description": "正在学习的课程会在%s日程%s中显示,勾选本项可以让你的%s课程%s列表更清爽。", "settings-hide-finished-classes-checkbox": "在课程页面不显示已经通过的课程", "settings-finished-active-classes-description": "已经通过的课程会在%s进度%s中显示,勾选本项可以让你的%s课程%s列表更清爽。", "settings-skip-tier-checkbox": "自动切换至更高级别页", "settings-skip-tier-description": "当你划至页面最下面时,将自动切换到下一层次页(课程难度、精英社团等)", "settings-show-confirmations-checkbox": "当做出重大更改时弹出确认框", "settings-show-confirmations-description": "防止误触和猫。", "settings-theme-description": "选择网页配色风格。", "settings-language-description": "选择想要使用的语言。[Select your prefered language.]", "settings-save-button": "保存更改", "settings-reset-button": "重置设定", "settings-maps-header": "地图管理", "settings-map-tool-button": "地图工具", "settings-map-tool-button-description": "打开地图工具来创建自定义内容。", "settings-map-import-button": "导入地图", "settings-map-import-button-description": "导入一张地图或更新一张已经存在的地图。", "settings-map-selection-header": "选择主地图", "settings-map-selection-info-1": "切换至另一张地图(不同地图的进度是独立保存的)。", "settings-map-selection-info-2": "如果你想要导出所有地图的进度,你需要对每一张主地图单独一一保存(子地图进度会保存在当前主地图进度中)。", "settings-switch-map-button": "切换至选择的地图", "settings-delete-map-button": "删除选择的地图", "settings-import-save-button": "导入存档", "settings-export-save-button": "导出存档", "settings-share-save-button": "导出存档但不生产存档文件", "settings-submap-selection-header": "选择子地图", "settings-submap-selection-description": '请勾选所有你想要作为子地图的选项。你需要先导入子地图(点击 "导入地图"),然后你才能在选项列表中找到它。如果你找不到导入的子地图,可能它被默认用作了主地图,这时你需要先更换一张主地图,然后才能在列表中看到你导入的子地图。', "settings-submap-selection-button": "加载勾选的子地图", "settings-global-actions-header": "全局操作", "settings-reset-tutorial-button": "重置教程", "settings-reset-tutorial-description": "这将让新手教学框重新显示。", "settings-reset-game-button": "重置游戏", "settings-reset-game-description": "这将完全重置整个游戏,包括地图、进度甚至是用户设置。", "settings-delete-map-confirmation-header": "删除地图", "settings-delete-map-confirmation-content": "确定要删除地图吗?你将会失去所有的存档。", "settings-reset-game-confirmation-header": "重置游戏", "settings-reset-game-confirmation-content": "确定要删除所有的地图和存档吗?你将丢失地图和当前进度。", "settings-script-header": "导入中止", "settings-script-content": "该存档文件内包含不安全的信息。出于安全性考虑,我们中止了导入。", "settings-save-import-failed-header": "存档导入失败", "settings-save-import-failed-content": "导入存档文件时出错,请检查后台记录以获取更多信息。", "settings-save-import-confirmation-header": "导入存档", "settings-save-import-confirmation-content": "确定要导入存档并覆盖你的现有进度吗?请确保存档来源的可靠性,否则后果自负。", "settings-save-export-failed-header": "存档导出失败", "settings-save-export-failed-content": "无法分享你的存档。", "settings-import-share-save-failed-header": "存档导入失败", "settings-import-share-save-failed-content": "无法导入分享的存档文件。", "settings-import-share-save-confirmation-header": "导入存档", "settings-import-share-save-confirmation-content": "确定要导入存档并覆盖你的现有进度吗?请确保存档来源的可靠性,否则后果自负。", "settings-import-share-map-failed-header": "地图导入失败", "settings-import-share-map-failed-content": "无法导入分享的地图。", "settings-import-share-map-confirmation-header": "导入地图", "settings-import-share-map-confirmation-content": "确定要导入这张地图吗?请确保地图来源的可靠性,否则后果自负。", "settings-save-selection-header": "保存游戏进度", "settings-save-sync-description": "You can now sync your saves over multiple devices or retain them without having to trouble yourself with the files. Just join our %sDiscord%s and use the channel %spu-saves%s to %s/register%s.", "settings-save-sync-button": "Sync this device now", "settings-reset-save-sync-button": "Remove save sync for this device", "settings-import-save-sync-header": "Save-Sync setup finished", "settings-import-save-sync-content": "Your saves will now be synced, if the required maps are available.", "settings-import-save-sync-failed-header": "Save-Sync setup failed", "settings-import-save-sync-failed-content": "The provided key was not accepted by the backend. You can try to create a new session or try again later.", "settings-download-save-sync-failed-header": "Save-Sync download failed", "settings-download-save-sync-failed-content": "The download of the latest SaveGame failed. You may need to create a new session.", "settings-check-save-sync-failed-header": "Save-Sync check failed", "settings-check-save-sync-failed-content": "Checking the status of the latest SaveGame failed. You may need to create a new session.", "settings-upload-save-sync-failed-header": "Save-Sync upload failed", "settings-upload-save-sync-failed-content": "Uploading the latest SaveGame failed. You may need to create a new session.", "settings-time-offset-selection": "自定义一个时间偏移量,让夜晚变成你的清晨。当前游戏时间: ", "settings-time-offset-hour": "时", "settings-compress-exports-checkbox": "压缩导出", "settings-compress-exports-description": "稍微压缩文件大小,能够更方便地分享它们。%s提示:%s在大多数苹果设备上会很有用!%s", "settings-image-exports-checkbox": "图像导出", "settings-image-exports-description": "分享一张图片而不是一段随机文本吧。必须压缩!%s警告:%s这是一个实验性功能,可能无法读取过大的文件!大部分即时通讯软件会删除附加的数据使其失效!%s", "settings-complete-exports-checkbox": "完全导出", "settings-complete-exports-description": "不必再逐个导入地图及存档,所有内容都将导出为单一文件。必须压缩!%s提示:%s这会让存档变得非常巨大!%s", "settings-detailed-table-view-checkbox": "使用详细表格视图。", "settings-detailed-table-view-description": "将*地图工具*的风格在原版“卡片”视图(模拟最终地图效果)和新增的“表格”视图之间切换。", "settings-import-save-clipboard-button": "从剪贴板导入存档", "settings-export-save-clipboard-button": "复制存档到剪贴板", "settings-join-discord-button": "加入Discord", "settings-join-discord-description": "欢迎加入PU的官方Discord。(仅限英语)", "settings-unsaved-changes-warning-checkbox": "在地图工具中显示尚未保存警告", "settings-unsaved-changes-warning-description": "虽然地图工具已支持自动保存,但你也可以开启这个警告来体型自己是否还有尚未导出的编辑。", "settings-filters-header": "过滤器", "settings-three-state-filters-checkbox": "三态过滤器", "settings-three-state-filters-description": "默认过滤器只检查是否包含特定的标签,勾选本项可以让它也能够排除特定的标签。", "settings-hide-active-classes-checkbox": "在课程页面中不显示正在学习的课程", "settings-hide-finished-classes-checkbox": "在课程页面中不显示已通过的课程", "settings-hide-locked-classes-checkbox": "在课程页面中不显示被锁定的课程", "settings-hide-locked-clubs-checkbox": "在社团页面中不显示被锁定的社团", "settings-hide-locked-punishments-checkbox": "在处分页面中不显示被锁定的处分", "settings-hide-blocked-punishments-checkbox": "在处分页面中不显示被禁用的处分", "help-table-grade": "绩点", "help-table-threshold": "比例", "help-table-min-knowledge": "最少出勤点数", "help-table-min-knowledge-intermediate": "最少出勤点数(普通课程)", "help-table-min-knowledge-advanced": "最少出勤点数(高级课程)", "help-table-min-knowledge-master": "最少出勤点数(大师课程)", "help-overview-header": "入学流程", "help-overview-1": "选择一个%s专业%s", "help-overview-2": "选择%s专业必修课程%s %s必修课程会带有星标%s 和一些%s附加课程%s", "help-overview-3": "参加%s社团%s或者勾搭%s基/姬友%s可以让你的学习过程更加丰富多彩", "help-overview-4": "上课并通过课程考试修够 %s 个学分", "help-overview-5": "翘课会让你受%s处分%s", "help-overview-6": "毕业", "help-notice-header": "校园安全教育", "help-notice-content": '人身安全必须始终放在首位。当你在课程期间感到不适,必须立刻结束当前课程,切忌拿自己的健康当儿戏!在进行任务途中如果感到不适,应立即停下来检查,避免发生意外。必要的时候请务必及时就医。此外,请珍惜自己的生命。一生中没有坎儿是过不去的,如果真的有事情想不开,去找人帮帮自己。', "help-progress-header": "课程进度说明", "help-progress-content": "所有的进度数据都保存在你自己的浏览器缓存里面,网上不会留存任何备份。浏览器在隐私模式下,通常不会储存数据,所以建议你使用普通模式打开本网站。如果你坚持使用隐私模式,提醒你注意保存好你的相关数据文件,通常包括地图文件(.pu)和存档(.puSave)。", "help-majors-header": "专业", "help-majors-content": "选择一个%s专业%s便意味着你将在该领域深造。每个专业都有自己的必修课程(毕业先修课),并且每个课程都可能有它自己的先修课要求。选完专业之后,你可以到%s进度%s页面去查看你的必修课程。你需要获得 %s 个学分才能尝试毕业。如果你中途决定换专业,你可以去专业详情界面退选,但你会因此受到 %s 项随机的处分。", "help-classes-header": "课程与课表", "help-classes-content": "请注意每门课程在每周的开课时间。在课程开课当天,你需要完成课程任务或参加考试才能得到该课程的 1 个出勤点。如果你在开课当天都没有来上课,你将会自动受到 1 项处分。想了解所有的课程要求,可以去课程详情页面查看。你在通过一门课程后,可以立马再选一门,但你最多不能同时上超过 %s 门课。最好不要同时学习太多课程,否则你可能会跟不上课程进度。所有的课程都在周末开设了补习班,翘掉补习课不会被处分(但是任务失败仍会被处分)。", "help-punishments-header": "处分", "help-punishments-1": "翘课(转点仍未开始课程任务)一节将被给予 1 项随机的处分。", "help-punishments-2": '任务失败时,你必须诚实地按下"失败",并接受处分。', "help-punishments-3": "暂停一个任务超过 %s 将被给予处分。当一个任务具有多个步骤时,你将获得额外的暂停时间,你需要在额外的暂停时间里完成其他任务。", "help-punishments-4": "处分可以在任何时候完成。", "help-punishments-5": "你最多可以积攒 10 个处分。", "help-punishments-6": "只要处分没有全部完成,你就不能参加考试和毕业考核。因此最好不要积攒太多的处分。", "help-punishments-7": "任务失败被给予的处分数:日常课程任务失败(%s 项),课程考试失败(%s 项),毕业考核失败(%s 项)。", "help-clubs-header": "社团", "help-clubs-1": "参加社团会给你不同的奖励,例如获得请假条或降低课程任务难度。", "help-clubs-2": "社团是对课程内容的补充,你可以自由选择社团。", "help-clubs-3": "奖励领取之后就不能退还了,所带来的影响将会持续一整天直到转点。", "help-clubs-4": "一些社团活动和课程内容重复了,这时你只需要完成课程任务即可获得社团奖励。", "help-clubs-5": "在领取奖励之前,先看看自己当日需要参加的课程内容。你一定不想自己因为社团任务被迫戴一整天口塞吧。", "help-clubs-6": "你最多同时参加 %s 个社团,同时获取 %s 个社团福利。", "help-clubs-7": "在学期的最后阶段,你会解锁精英社团。精英社团会给你更丰厚的奖励,与此同时也会给你更难的挑战。", "help-clubs-8": "注意,你最多参加 %s 个精英社团。", "help-clubs-9": "社团奖励领取之后,当日不可以取消!", "help-clubs-10": "所有领取过的奖励会在次日刷新。", "help-clubs-11": "注意,你最多只能从社团福利中获得 50% 的难度减免。即如果你在普通社团里得到50%的难度减免,同时你在精英社团里得到25%的减免,那么你最终只会得到50%的总减免,而非75%。", "help-partners-header": "基/姬友", "help-partners-content": "P基/姬友和社团很像,会给你带来相应的福利,例如给你额外的出勤点数。但不同之处在于,基/姬友会让你的课程难度增加。你最多可以同时拥有 %s 个基/姬友。Ta们带来的影响会持续一整天,并在次日重置。", "help-tasks-header": "任务与计时器", "help-tasks-content": '游戏中的专业和班级都有任务系统。在你的课程表页面上,你可以选择你的当日课程要做的任务。每个任务可以有多个子任务 一旦你开始一个任务,你会看到新的任务显示出来,在这里你可以启动它的计时器,或者按 "完成",如果任务不依赖于时间。定时器可以被暂停/恢复,并且它们可以在不同的日子里延续。如果任务从前一天开始但持续到第二天,你不会被处分。如果你的任务完成失败了,你必须按下 "失败 "键来接受处分。所有课程的计时器也可以在该课程的详情页面上看到。', "help-grades-header": "绩点", "help-grades-content": "每当你参加并完成课堂任务时,你将获得 1 个出勤点,如果你有修改器(来自基/姬友或社团),你可以获得额外的出勤点。根据这些出勤点,你将在参加考试后得到一个分数。这取决于你决定你想在考试中表现得多好。你获得的成绩将用于计算你的专业成绩。如果你已经完成了一门课,想提高自己的成绩,你可以随时重修。如果你重修一门课,你需要重考,并取消该课的相关学分,但你将保留已经获得的出勤点。你每更换一次处分,你就将会失去你当前正在上的一门随机课程的出勤点。
想要得到不同等级的绩点,你至少需要获得如下的出勤点数:", "help-credits-header": "学分", "help-credits-content": "毕业需要至少 %s 学分。一种参考的选课方法是 4 个入门课程,3 个普通课程,2 个专家课程和 2 个大师课程。你只有选修了课程之后才会解锁考试内容。完成考试后,你就会得到相应的学分。", "help-graduating-header": "毕业", "help-graduating-content": '你需要先完成所有的专业必修课,并得到 %s 学分,你才可以尝试毕业。某些专业毕业考核甚至可能会持续一个月。如果你在毕业考核的时候失败了,你需要按下 "失败" 按钮来受到 %s 个处分。某些专业毕业考核也会有额外的 "惩罚" 按钮,用来延长任务时长。当你从某个专业毕业,该专业就会被标记为 "完成",你上过的课程都会被重置。你可以继续选修另一门专业,为你的 "职业" 道路打好基础。', "help-roulette-header": "高潮轮盘系统", "help-roulette-content": "在校期间除课程要求之外,你能否高潮取决于%s高潮轮盘%s的结果。你每天仅有一次宝贵的机会去碰碰运气。", }; class n { static getLanguage() { return ( navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.userLanguage || navigator.language || navigator.browserLanguage || "en" ) .toLowerCase() .substr(0, 2); } static getTodaysDay() { return n.getNow().day(); } static getNow() { return moment().add(n.getSetting("timeOffsetSelection") || 0, "hours"); } static getNowUnix() { return n.getNow().unix(); } static getMoment(e) { return moment(e); } static hasMatchingDate(e) { if (n.getSetting("enableEasyMode")) return !0; const t = n.getTodaysDay(); return 0 === t || 6 === t || e.filter((e) => e === t).length > 0; } static daysToString(e) { return e.map((e) => n.weekdays[e]).join(", "); } static get weekdays() { return moment.weekdays(); } static getAcceptedDisclaimer() { return localStorage.getItem("acceptedDisclaimer") || !1; } static setAcceptedDisclaimer() { localStorage.setItem("acceptedDisclaimer", !0); } static getlastRequestedFeedback() { return localStorage.getItem("lastRequestedFeedback") || void 0; } static setlastRequestedFeedback(e) { localStorage.setItem("lastRequestedFeedback", e); } static getTutorial() { try { return localStorage.getItem("tutorial") || ""; } catch (e) { return "mapping;"; } } static setTutorial(e) { localStorage.setItem("tutorial", e); } static getLastPage() { return localStorage.getItem("lastPage"); } static setLastPage(e) { localStorage.setItem("lastPage", e); } static getSettings() { try { return ( JSON.parse(localStorage.getItem("settings")) || n.getDefaultSettings() ); } catch (e) { return { ...n.getDefaultSettings(), noSettings: !0 }; } } static setSettings(e) { e.noSettings || localStorage.setItem("settings", JSON.stringify(e)); } static getDefaultSettings() { return { enableHardcorePunishments: !1, enableEasyMode: !1, enableAutoHide: !0, enableBackToTop: !0, hideActiveClasses: !1, hideFinishedClasses: !0, autoExtendCarousel: !0, showConfirmations: !0, useTableView: !0, style: "darkly", useImageHoster: !1, sessionToken: void 0, timeOffsetSelection: 0, useImageExport: !1, useBlobExport: !0, useDynamicExport: !1, useBase64Export: !1, compressExports: !0, completeExports: !1, }; } static getSetting(e) { let t = n.getSettings()[e]; return null != t ? t : n.getDefaultSettings()[e]; } static setSetting(e, t) { let a = n.getSettings(); ((a[e] = t), n.setSettings(a)); } static getScheduledToasts() { try { return JSON.parse(localStorage.getItem("scheduledToasts")) || []; } catch (e) { const t = s.getInstance(); return ( n.showToast( t.T( "scheduled-toast-error-header", "Third-party cookies are disabled", ), t.T( "scheduled-toast-error-content", "Please switch to the full Mapping Tool or enable them in the browser settings.", ), "warning", ), [] ); } } static setScheduledToasts(e) { try { localStorage.setItem("scheduledToasts", JSON.stringify(e)); } catch (e) { return; } } static scheduleToast(e, t, a) { void 0 === a && (a = "info"); let s = n.getScheduledToasts(); (s.push({ title: e, message: t, style: a }), n.setScheduledToasts(s)); } static showScheduledToast() { let e = n.getScheduledToasts(); for (let t of e) n.showToast(t.title, t.message, t.style); n.setScheduledToasts([]); } static showToast(e, t, a) { void 0 === a && (a = "info"); const i = s.getInstance(); $("#toastContainer").append( $( ``, ) .toast("show") .on("hidden.bs.toast", function (e) { $(e.target).remove(); }), ); } static getBackdropContainer(e, t) { return `
\n ${e}\n
`; } static getBackdropProgressContainer(e, t) { let a = "success"; return ( t < 10 && (a = "warning"), `\n
\n
\n
\n
${e}
\n
\n
\n ` ); } static getButtonsContainer(e) { return (e = e.filter((e) => e && e.length > 0)).length <= 0 ? "" : `
\n ${e.join('
')}\n
`; } static toggleCard(e) { $("#" + e).toggleClass("active"); } static getRandomString(e) { return ( e || (e = 10), [...Array(e)] .map((e) => (~~(36 * Math.random())).toString(36)) .join("") ); } static scrollBackToTop() { $("html, body").animate({ scrollTop: 0 }, 800); } static isToday(e) { return moment.unix(e).isSame(n.getNow(), "day"); } static requestUserConfirmation(e, t, a) { if (!n.getSetting("showConfirmations")) return void a(); const i = s.getInstance(); let o = "requestUserConfirmationModal-" + n.getRandomString(), r = ``; ($("#confirmationContainer").append(r), $("#" + o) .modal({ keyboard: !1 }) .modal("show") .on("hidden.bs.modal", function (e) { $("#" + o) .modal("dispose") .remove(); })); } static hasScript(e) { const t = Object.getOwnPropertyNames(document) .concat( Object.getOwnPropertyNames( Object.getPrototypeOf(Object.getPrototypeOf(document)), ), ) .filter(function (e) { return ( !e.indexOf("on") && (null == document[e] || "function" == typeof document[e]) ); }) .join("|"), a = new RegExp(`\\s(?:${t})=`, "i"); return /)/.test(e) || a.test(e); } static optimizeImage(e, t) { const a = o.imageOptimization, s = new Image(); ((s.src = e), (s.onload = () => { const e = document.createElement("canvas"), i = e.getContext("2d"); let n = 0, o = 0; (s.height > s.width && s.height > a.height ? ((n = a.height), (o = s.width * (a.height / s.height))) : s.width > s.height && s.width > a.width ? ((o = a.width), (n = s.height * (a.width / s.width))) : ((o = s.width), (n = s.height)), (e.width = o), (e.height = n), i.drawImage(s, 0, 0, o, n), i.canvas.toBlob( function (e) { const s = i.canvas.toDataURL(a.filemime, a.quality); t && t(s, e); }, a.filemime, a.quality, )); })); } static getGrade(e, t) { if (!Number.isInteger(e) || !Number.isInteger(t)) return "F"; const a = e / t; for (const e of Object.entries(o.grades)) if (a >= e[1]) return e[0]; return "F"; } static getCurrentUrl() { return window.location.origin.endsWith("hwcdn.net") ? "https://projectuniversity.net/" : `${window.location.origin}${window.location.pathname}`; } static extendDistPath() { return window.IS_RAW ? "dist/" : ""; } static dataURLtoFile(e, t) { for ( var a = e.split(","), s = a[0].match(/:(.*?);/)[1], i = atob(a[a.length - 1]), n = i.length, o = new Uint8Array(n); n--; ) o[n] = i.charCodeAt(n); return new File([o], t, { type: s }); } static async encryptExportImage(e, t) { const a = n.stringToArraybuffer(n.compressJSObjectToString(e)); return { imageData: await n.appendDataToImage(t, a), ImageType: t.type, }; } static decryptExportImage(e) { const t = n.readDataFromImage(e); if (t instanceof Uint8Array && t.length > 0) { return n.decompressJsonFromString(n.arraybufferToString(t)); } } static async loadImageFile(e) { try { const t = await fetch(e); if (!t.ok) throw new Error("HTTP error! status: " + t.status); const a = await t.blob(); return new File([a], "save.jpg", { type: "image/jpeg" }); } catch (e) { throw (console.error("Error loading the image file:", e), e); } } static async appendDataToImage(e, t) { const a = await e.arrayBuffer(), s = new Uint8Array(a); let i = s.length; for (let e = s.length - 2; e >= 0; e--) if (255 === s[e] && 217 === s[e + 1]) { i = e + 2; break; } const n = new Uint8Array(i + t.length); return (n.set(s.subarray(0, i)), n.set(t, i), n); } static readDataFromImage(e) { const t = new Uint8Array(e); let a = t.length; for (let e = t.length - 2; e >= 0; e--) if (255 === t[e] && 217 === t[e + 1]) { a = e + 2; break; } return t.subarray(a); } static deepClone(e) { return JSON.parse(JSON.stringify(e)); } static canCompress() { return "undefined" != typeof z && z.hasOwnProperty("inflate"); } static shouldCompress() { return n.canCompress() && n.getSetting("compressExports"); } static shouldExportComplete() { return n.canCompress() && n.getSetting("completeExports"); } static isCompleteSavegame(e) { return !( !e.hasOwnProperty("primaryMapData") || !e.hasOwnProperty("subMapData") ); } static async sha256_hex(str) { const buf = await window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(str)); return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, "0")).join(""); } static compressJSObjectToString(e) { return btoa( n.arraybufferToString( z.deflate( n.stringToArraybuffer(encodeURIComponent(JSON.stringify(e))), ).buffer, ), ); } static decompressJsonFromString(e) { return decodeURIComponent( n.arraybufferToString(z.inflate(n.stringToArraybuffer(atob(e)))), ); } static arraybufferToString(e) { const t = new Uint8Array(e); let a = "", s = 0; for (; s < t.length; ) a += String.fromCharCode(t[s++]); return a; } static stringToArraybuffer(e) { const t = new Uint8Array(e.length); for (let a = 0; a < e.length; ++a) t[a] = e.charCodeAt(a); return t; } static async getServiceWorkerStatus() { if ((!1) in window.navigator || !window.navigator.serviceWorker) return o.serviceWorkerStatus.inactive; const e = await window.navigator.serviceWorker.getRegistration(); return e ? e.active ? o.serviceWorkerStatus.active : e.installing ? o.serviceWorkerStatus.installing : e.waiting ? o.serviceWorkerStatus.waiting : o.serviceWorkerStatus.unknown : o.serviceWorkerStatus.unregistered; } } class o { static get serviceWorkerStatus() { return { unregistered: { id: "unregistered", type: "danger", useable: !1 }, inactive: { id: "inactive", type: "danger", useable: !1 }, active: { id: "active", type: "success", useable: !0 }, waiting: { id: "waiting", type: "warning", useable: !1 }, installing: { id: "installing", type: "warning", useable: !1 }, unknown: { id: "unknown", type: "warning", useable: !1 }, }; } static get saveGameService() { return "https://save.projectuniversity.net"; } static get privateBinService() { return "https://privatebin.net/"; } static get imageService() { return "https://imgbb.com/"; } static get styles() { return { darkly: { name: "darkly", navbar: "dark", bg: "dark" }, pink: { name: "pink", navbar: "dark", bg: "dark" }, cosmo: { name: "cosmo", navbar: "dark", bg: "primary" }, minty: { name: "minty", navbar: "dark", bg: "primary" }, }; } static get imageOptimization() { return { height: 800, width: 600, filemime: "image/jpeg", quality: 0.75, }; } static isImage(e) { return "image/jpeg" === e; } static get exportTextType() { return "text/plain;charset=utf-8"; } static get exportCompressedType() { return "application/octet-stream"; } static get exportImageType() { return "image/jpg"; } static get exportOptions() { return { pu_complete: { linkFilenamePrefix: "pu_complete", exportFilenamePrefix: "pu_complete_", textExportExtension: "puC", imageExportExtension: "jpg", srcImage: "logo_complete", }, pu_save: { linkFilenamePrefix: "pu_save", exportFilenamePrefix: "pu_save_", textExportExtension: "puSave", imageExportExtension: "jpg", srcImage: "logo_save", }, pu_task: { linkFilenamePrefix: "pu_task", exportFilenamePrefix: "pu_task_", textExportExtension: "puTask", imageExportExtension: "jpg", srcImage: "logo_task", }, pu_map: { linkFilenamePrefix: "pu_map", exportFilenamePrefix: "pu_map_", textExportExtension: "puMap", imageExportExtension: "jpg", srcImage: "logo_map", }, pu_game_map: { linkFilenamePrefix: "pu_game_map", exportFilenamePrefix: "pu_gamemap_", textExportExtension: "pu", imageExportExtension: "jpg", srcImage: "logo_gamemap", }, }; } static get grades() { return { "A+": 0.96, A: 0.88, "A-": 0.8, "B+": 0.76, B: 0.68, "B-": 0.6, "C+": 0.52, C: 0.44, "C-": 0.4, "D+": 0.32, D: 0.24, "D-": 0.12, F: 0, }; } static get maxAttendances() { return { beginner: 25, intermediate: 25, advanced: 25, master: 25 }; } static get expectedAttendance() { return { beginner: 3, intermediate: 3, advanced: 3, master: 3 }; } static get maxConcurrentClasses() { return 99; } static get maxTaskPause() { return 240; } static get maxConcurrentPunishments() { return 10; } static get maxConcurrentClubs() { return 10; } static get maxConcurrentEliteClubs() { return 3; } static get maxConcurrentClubPerks() { return 15; } static get maxConcurrentPartnerPerks() { return 10; } static get maxConcurrentPartner() { return 8; } static get failDailyPunishment() { return 1; } static get failCustomTask() { return 1; } static get failExamPunishment() { return 2; } static get failThesisPunishment() { return 5; } static get classBeginnerCredits() { return 10; } static get classIntermediateCredits() { return 10; } static get classAdvancedCredits() { return 20; } static get classMasterCredits() { return 25; } static get baseTaskMultiplier() { return 1; } static get currentVersion() { return 1; } static get classesExamCredits() { return { beginner: o.classBeginnerCredits, intermediate: o.classIntermediateCredits, advanced: o.classAdvancedCredits, master: o.classMasterCredits, }; } static get rickRate() { return 1e3; } static get autoHideTimer() { return 1; } static get majorDropPunishments() { return 10; } static get defaultTaskListSize() { return 2; } } class r { constructor(e, t) { let a = indexedDB.open("PU", 1); ((a.onupgradeneeded = function (e) { ((this.db = e.target.result), (this.db.onabort = function (e) { console.error("db abort", e); }), (this.db.onclose = function (e) { console.error("db close", e); }), (this.db.onerror = function (e) { console.error("db error", e); }), this.db .createObjectStore("maps", { keyPath: "mapData.mapId" }) .createIndex("lastSaved", "lastSaved", { unique: !1 })); }.bind(this)), (a.onsuccess = function (t) { if ( ((this.db = t.target.result), void 0 !== e && e(this.db), void 0 !== this.successEvents) ) for (let e = this.successEvents.length - 1; e >= 0; e--) this.successEvents.pop(e)(); }.bind(this)), (a.onerror = function (e) { void 0 !== t && t(e.target.errorCode); })); } static getInstance(e, t) { return ( void 0 === this.instance && (this.instance = new r(void 0, t)), void 0 !== e && (void 0 !== this.instance.db ? e() : this.instance.registerLoadFinished(e)), this.instance ); } registerLoadFinished(e) { (void 0 === this.successEvents && (this.successEvents = []), this.successEvents.push(e)); } drop(e) { (this.db.close(), (indexedDB.deleteDatabase("PU").onsuccess = function () { ((this.instance = void 0), (this.db = void 0), e()); })); } static showDefaultError(e) { (console.error("db error", e), n.showToast( "Database error", "While attempting to save your progress an database error occured. Progress might have been lost.", "danger", )); } addMap(e, t, a) { if (!e.mapId) return void a("This map is missing an ID!"); e.moduleId && (e.mapId = `${e.mapId}(${e.moduleId})`); let s = { mapData: e, lastSaved: n.getNowUnix() - 60, saveGame: void 0, subMaps: [], }, i = function (i) { void 0 !== i && ((s.saveGame = i.saveGame), (s.subMaps = i.subMaps)); let n = this.db.transaction(["maps"], "readwrite"); n.objectStore("maps").put(s); ((n.onabort = function (a) { void 0 !== t && t(a, e); }), (n.oncomplete = function (a) { void 0 !== t && t(a, e); }), (n.onerror = function (e) { void 0 !== a ? a(e.target.errorCode) : r.showDefaultError(e); }), n.commit()); }.bind(this); this.getMap(e.mapId, (e) => { i(e); }); } updateMap(e, t, a) { if (!e.mapData.mapId) return void a("The map ID is missing!"); let s = this.db.transaction(["maps"], "readwrite"); s.objectStore("maps").put(e); ((s.onabort = function (e) { void 0 !== t && t(e); }), (s.oncomplete = function (e) { void 0 !== t && t(e); }), (s.onerror = function (e) { void 0 !== a ? a(e.target.errorCode) : r.showDefaultError(e); }), s.commit()); } getMap(e, t) { let a = this.db .transaction(["maps"], "readonly") .objectStore("maps") .get(e); ((a.onsuccess = function (e) { void 0 !== t && t(e.target.result); }), (a.onerror = function (e) { void 0 !== t ? t(void 0) : r.showDefaultError(e); })); } switchMap(e, t) { let a = this.db .transaction(["maps"], "readonly") .objectStore("maps") .get(e), s = this; ((a.onsuccess = function (e) { let a = e.target.result; ((a.lastSaved = n.getNowUnix()), s.updateMap(a, t, t)); }), (a.onerror = function (e) { void 0 !== cbFinished ? cbFinished(void 0) : r.showDefaultError(e); })); } removeMap(e, t) { let a = this.db.transaction(["maps"], "readwrite"); (a.objectStore("maps").delete(e), (a.onabort = function (e) { void 0 !== t && t(e); }), (a.oncomplete = function (e) { void 0 !== t && t(e); }), (a.onerror = function (e) { r.showDefaultError(e); }), a.commit()); } getMaps(e, t) { let a = this.db .transaction(["maps"], "readonly") .objectStore("maps") .index("lastSaved") .openCursor(null, "prev"), s = []; ((a.onsuccess = function (t) { let a = t.target.result; null != a ? (s.push(a.value), a.continue()) : void 0 !== e && e(s); }), (a.onerror = function (e) { void 0 !== t ? t(e.target.errorCode) : r.showDefaultError(e); })); } setSubMaps(e, t, a, s) { if (!Array.isArray(t)) return void (void 0 !== s && s("Invalid SubMaps")); let i = this; this.getMap(e, (e) => { ((e.subMaps = t), (e.lastSaved = n.getNowUnix()), i.updateMap(e, a, s)); }); } setSavegame(e, t, a, s) { let i = this; this.getMap(e, (e) => { ((e.saveGame = t), (e.lastSaved = n.getNowUnix()), i.updateMap(e, a, s)); }); } getSavegame(e, t) { this.getMap(e, (e) => { t(e.saveGame); }); } } class d { static applyParametersToObject(e) { if (void 0 === e || void 0 === e.tasks) return e; for (let t in e.tasks) { let a = e.tasks[t], s = x.getMultiplier(a.tags); ((void 0 === s || s <= 0.1) && (s = 1), (e.tasks[t] = d.applyParametersToTaskObject(a, s))); } return e; } static applyParametersToTaskObject(e, t) { if (void 0 === e || void 0 === e.task || void 0 === e.parameters) return e; if (null == e.task.match(/\$param\d+/)) return e; const a = s.getInstance(); for (let s in e.parameters) { let i = e.parameters[s], n = "", o = Math.round(i.value * t); if ("minutes" === i.unit) { let e = d.minutesToTime(o), s = d.minutesToTime(i.value); n = i.applyMultiplier && 1 !== t ? `${e} ${s}`])}">` : "" + s; } else if ("seconds" === i.unit) { let e = a.T("time-seconds", "seconds"); if ( (1 == i.value && (e = a.T("time-second", "second")), i.applyMultiplier && 1 !== t) ) { let t = a.T("time-seconds", "seconds"); (1 == o && (t = a.T("time-second", "second")), (n = `${o} ${t} ${i.value} ${e}`])}>`)); } else n = `${i.value} ${e}`; } else if ("punishments" === i.unit) { let e = i.punTier ? a.T("punishment-tier-" + i.punTier, i.punTier) : a.T("task-random-punishment", "random"), s = a.T("punishment-single-text", "punishment"); if ( (i.value > 1 && (s = a.T("punishment-multi-text", "punishments")), i.applyMultiplier && 1 !== t) ) { let t = a.T("punishment-single-text", "punishment"); (o > 1 && (t = a.T("punishment-multi-text", "punishments")), (n = `${o} ${e} ${t} ${i.value} ${e} ${s}`])}">`)); } else n = `${i.value} ${e} ${s}`; } else n = i.applyMultiplier && 1 !== t ? `${o} ${i.unit} ${i.value} ${i.unit}`])}>` : `${i.value} ${i.unit}`; e.task = e.task.replace( "$param" + s, `${n}`, ); } return e; } static getPercentageFromText(e) { let t = /([0-9]+)(?:%)/g.exec(e); return t ? Number(t[1]) / 100 : 0; } static getClassSkipsFromText(e) { let t = /(?:Skip) ([0-9])/g.exec(e); return t ? Number(t[1]) : 0; } static dateObjectToHumanTime(e) { const t = moment.duration(e, "seconds"); return `${t.days() > 0 ? t.humanize() + " " : ""}${("0" + t.hours()).slice(-2)}:${("0" + t.minutes()).slice(-2)}:${("0" + t.seconds()).slice(-2)}`; } static minutesToTime(e) { const t = moment.duration(e, "minutes"), a = s.getInstance(); let i = t.years(), n = t.months(), o = t.days(), r = t.hours(), l = t.minutes(); const d = a.T("time-day-binding", ";"); var c = []; return ( i && (1 === i ? c.push(`${i} ${a.T("time-year", "year")}`) : c.push(`${i} ${a.T("time-years", "years")}`)), n && (1 === n ? c.push(`${n} ${a.T("time-month", "month")}`) : c.push(`${n} ${a.T("time-months", "months")}`)), o && (1 === o ? c.push(`${o} ${a.T("time-day", "day")}`) : c.push(`${o} ${a.T("time-days", "days")}`)), r && ((o > 0 || n > 0 || i > 0) && l <= 0 ? c.push(a.T("time-day-binding-no-minutes", "and")) : (o > 0 || n > 0 || i > 0) && c.push(d), 1 === r ? c.push(`${r} ${a.T("time-hour", "hour")}`) : c.push(`${r} ${a.T("time-hours", "hours")}`)), (o > 0 || n > 0 || i > 0 || r > 0) && l > 0 && c.push(a.T("time-hours-binding", "and")), l && (1 === l ? c.push(`${l} ${a.T("time-minute", "minute")}`) : c.push(`${l} ${a.T("time-minutes", "minutes")}`)), c.length <= 0 && c.push(a.T("time-less-then-a-minute", "Less than a minute.")), c.join(" ").replace(` ${d} `, d + " ") ); } } class c { constructor() { ((this._publicImageHosterKey = void 0), (this._sessionToken = void 0)); } static getInstance() { return ( void 0 === this.instance && (this.instance = new c()), this.instance ); } get isPublicImageHosterKeySet() { return !!this._publicImageHosterKey; } static getQRCanvas(e, t) { return kjua({ render: "image", ecLevel: "H", text: e, mPosX: 50, mPosY: 50, mSize: 20, size: 400, rounded: 100, mode: "label", label: t, fontname: "Helvetica", fontcolor: "#ff0055", }); } static insertQR(e, t, a) { const s = `\n
\n
\n This export is only valid for 24h. You can scan the QR code with your target device or use the link.\n
\n
\n
\n \n
\n `; ($(a).html(s), $(".qr-container .qr-code", a).html(c.getQRCanvas(e, t))); } static sendRequest(e, t, a) { try { const i = new XMLHttpRequest(); if (t) { var s = i.open; i.open = function () { const e = "quiet-dawn-24177.herokuapp.com", t = "https://" + e + "/", a = [].slice, i = window.location.protocol + "//" + window.location.host; var n = a.call(arguments), o = /^https?:\/\/([^\/]+)/i.exec(n[1]); return ( o && o[0].toLowerCase() !== i && o[1] !== e && (n[1] = t + n[1]), s.apply(this, n) ); }; } let n = e.params; (!n || "object" != typeof n || n instanceof FormData || (n = e.hasOwnProperty("parseAsParameter") && !0 !== e.parseAsParameter ? JSON.stringify(n) : Object.keys(n) .map(function (e) { return `${encodeURIComponent(e)}=${encodeURIComponent(n[e])}`; }) .join("&")), "GET" === e.method && ((e.url = `${e.url}?${n}`), (n = null)), i.open(e.method, e.url), (i.onload = function () { this.status >= 200 && this.status < 300 ? a(i.response) : a( JSON.stringify({ error: !0, status: this.status, statusText: i.statusText, }), ); }), (i.onerror = function () { a( JSON.stringify({ error: !0, status: this.status, statusText: i.statusText, }), ); }), e.headers && Object.keys(e.headers).forEach(function (t) { i.setRequestHeader(t, e.headers[t]); }), i.send(n)); } catch (e) { a(JSON.stringify({ error: !0, statusText: "internal error" })); } } getPublicImageHosterKey(e) { if (this.isPublicImageHosterKeySet) return void e(this._publicImageHosterKey); const t = o.imageService; let a = function (t) { ((this._publicImageHosterKey = t.match( /PF\.obj\.config\.auth_token=\"(.*?)\"/, )[1]), e(this._publicImageHosterKey)); }.bind(this), s = { parseAsParameter: !1, method: "GET", url: t, params: "", headers: { Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", }, }; c.sendRequest(s, !0, a); } static uploadToImageHoster(e, t) { const a = o.imageService; let s = function (e) { const a = JSON.parse(e); "200" == a.status_code ? t({ error: !1, data: a.image.url }) : t({ error: !0, statusText: "failed to upload image" }); }.bind(this), i = function (t) { var i = new FormData(); (i.append("source", e), i.append("type", "file"), i.append("action", "upload"), i.append("timestamp", new Date().getTime()), i.append("auth_token", t)); let n = { parseAsParameter: !1, method: "POST", url: a + "/json", params: i, headers: { Accept: "application/json" }, }; c.sendRequest(n, !0, s); }.bind(this); c.getInstance().getPublicImageHosterKey(i); } static downloadFromPastebin(e, t) { let a = /\?(.*?)#(.*?)(?:&|$)/.exec(e); if (!a || 3 !== a.length) return void t({ error: !0, statusText: "invalid paste url" }); let s = function (e) { const a = JSON.parse(e); t({ error: !1, data: a.paste }); }.bind(this), i = function (e) { const i = JSON.parse(e); if (i && null != i.status && 0 == i.status) try { const e = c.getCryptTool(), t = [i.ct, i.adata], n = e.base58decode(a[2]).padStart(32, "\0"), o = ""; e.decipher(n, o, t).then(s); } catch (e) { t({ error: !0, statusText: "failed decrypt" }); } else t({ error: !0, statusText: "invalid response" }); }.bind(this), n = { parseAsParameter: !1, method: "GET", url: e, headers: { Accept: "application/json, text/javascript, */*; q=0.01", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With": "JSONHttpRequest", }, }; c.sendRequest(n, !0, i); } exchangeSaveGameToken(e, t) { const a = o.saveGameService; let s = function (e) { const a = JSON.parse(e); if (a.error) return ( t({ error: !0, statusText: "failed to check save game token" }), (this._sessionToken = void 0), void n.setSetting("sessionToken", this._sessionToken) ); ((this._sessionToken = a.sessionToken), n.setSetting("sessionToken", this._sessionToken), t({ error: !1, data: a })); }.bind(this), i = { exchangeToken: e }, r = { parseAsParameter: !1, method: "POST", url: a + "/token/activate", params: JSON.stringify(i), headers: { Accept: "application/json", "Content-Type": "application/json; charset=UTF-8", }, }; c.sendRequest(r, !1, s); } verifySaveGameToken(e, t) { const a = o.saveGameService; let s = function (a) { if ("" !== a) return ( (this._sessionToken = void 0), n.setSetting("sessionToken", this._sessionToken), void t(null) ); ((this._sessionToken = e), t(this._sessionToken)); }.bind(this), i = { sessionToken: e }, r = { parseAsParameter: !1, method: "POST", url: a + "/token/verify", params: JSON.stringify(i), headers: { Accept: "application/json", "Content-Type": "application/json; charset=UTF-8", }, }; c.sendRequest(r, !1, s); } getSessionToken(e) { !this._sessionToken && n.getSetting("sessionToken") ? this.verifySaveGameToken(n.getSetting("sessionToken"), e) : this._sessionToken ? e(this._sessionToken) : e(null); } static checkSaveGame(e, t, a) { if (!e || !t) return void a({ error: !0, statusText: "session token / mapId is not set", }); const s = o.saveGameService; let i = function (e) { const t = JSON.parse(e); t.error ? a({ error: !0, statusText: "failed to check save game status" }) : a({ error: !1, data: t }); }.bind(this), n = { mapId: t, sessionToken: e }, r = { parseAsParameter: !1, method: "POST", url: s + "/save/check", params: JSON.stringify(n), headers: { Accept: "application/json", "Content-Type": "application/json; charset=UTF-8", }, }; c.sendRequest(r, !1, i); } static uploadSaveGame(e, t, a, s) { if (!e || !t) return void s({ error: !0, statusText: "session token / mapId is not set", }); const i = o.saveGameService; let r = function (e) { const t = JSON.parse(e); t.error ? s({ error: !0, statusText: "failed to upload the save game" }) : s({ error: !1, data: t }); }.bind(this), l = { mapId: t, sessionToken: e, saveData: JSON.stringify(a), saveDate: n.getNow().format(), }, d = { parseAsParameter: !1, method: "POST", url: i + "/save/upload", params: JSON.stringify(l), headers: { Accept: "application/json", "Content-Type": "application/json; charset=UTF-8", }, }; c.sendRequest(d, !1, r); } static downloadSaveGame(e, t, a) { if (!e || !t) return void a({ error: !0, statusText: "session token / mapId is not set", }); const s = o.saveGameService; let i = function (e) { const t = JSON.parse(e); t.error ? a({ error: !0, statusText: "failed to download the save game" }) : a({ error: !1, data: t }); }.bind(this), n = { mapId: t, sessionToken: e }, r = { parseAsParameter: !1, method: "POST", url: s + "/save/download", params: JSON.stringify(n), headers: { Accept: "application/json", "Content-Type": "application/json; charset=UTF-8", }, }; c.sendRequest(r, !1, i); } static uploadToPastebin(e, t) { const a = o.privateBinService; try { const s = c.getCryptTool(), i = s.getSymmetricKey(), n = function (e) { if (0 === (e = JSON.parse(e)).status && !0 !== e.error) { const n = a.split("/"), o = s.base58encode(i); t({ error: !1, url: `${n[0]}//${n[2]}${e.url}#${o}` }); } else t({ error: !0, message: e.message }); }.bind(this), o = function (e) { let t = { parseAsParameter: !1, method: "POST", url: a, params: { v: 2, adata: e[1], ct: e[0], meta: { expire: "1day" }, }, headers: { Accept: "application/json, text/javascript, */*; q=0.01", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With": "JSONHttpRequest", }, }; c.sendRequest(t, !0, n); }.bind(this); let r = [null, "plaintext", 0, 0]; s.cipher(i, "", JSON.stringify({ paste: e }), r).then(o); } catch (e) { t({ error: !0, message: "A techical error occured while uploading.", }); } } static getCryptTool() { return (function () { const e = {}; let t = new baseX( "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", ); function a(e) { return decodeURIComponent( e .split("") .map(function (e) { return "%" + ("00" + e.charCodeAt(0).toString(16)).slice(-2); }) .join(""), ); } function s(e) { return encodeURIComponent(e).replace( /%([0-9A-F]{2})/g, function (e, t) { return String.fromCharCode("0x" + t); }, ); } function i(e) { const t = new Uint8Array(e); let a = "", s = 0; for (; s < t.length; ) a += String.fromCharCode(t[s++]); return a; } function n(e) { const t = new Uint8Array(e.length); for (let a = 0; a < e.length; ++a) t[a] = e.charCodeAt(a); return t; } async function o(e, t, a) { if (((e = n(s(e))), "zlib" === t)) { if (void 0 === a) throw "Error compressing paste, due to missing WebAssembly support."; return a.deflate(e).buffer; } return e; } function r(e) { let t = ""; const a = new Uint8Array(e); window.crypto.getRandomValues(a); for (let s = 0; s < e; ++s) t += String.fromCharCode(a[s]); return t; } function l(e, t) { console.error(e, t); } async function d(e, t, a) { let i = n(e); if (t.length > 0) { if ("rawdeflate" === a[7]) { let e = await window.crypto.subtle .digest({ name: "SHA-256" }, n(s(t))) .catch(l); t = Array.prototype.map .call(new Uint8Array(e), (e) => ("00" + e.toString(16)).slice(-2), ) .join(""); } let e = n(t), o = new Uint8Array(i.length + e.length); (o.set(i, 0), o.set(e, i.length), (i = o)); } const o = await window.crypto.subtle .importKey("raw", i, { name: "PBKDF2" }, !1, ["deriveKey"]) .catch(l); return window.crypto.subtle .deriveKey( { name: "PBKDF2", salt: n(a[1]), iterations: a[2], hash: { name: "SHA-256" }, }, o, { name: "AES-" + a[6].toUpperCase(), length: a[3] }, !1, ["encrypt", "decrypt"], ) .catch(l); } function c(e, t) { return { name: "AES-" + t[6].toUpperCase(), iv: n(t[0]), additionalData: n(e), tagLength: t[4], }; } return ( (e.cipher = async function (e, t, a, s) { let n = await z; const u = void 0 === n ? "none" : "zlib", p = [r(16), r(8), 1e5, 256, 128, "aes", "gcm", u], m = []; for (let e = 0; e < p.length; ++e) m[e] = e < 2 ? btoa(p[e]) : p[e]; return ( 0 === s.length ? (s = m) : null === s[0] && (s[0] = m), [ btoa( i( await window.crypto.subtle .encrypt( c(JSON.stringify(s), p), await d(e, t, p), await o(a, u, n), ) .catch(l), ), ), s, ] ); }), (e.decipher = async function (e, t, s) { let o, r, u, p, m = await z; if (s instanceof Array) ((o = JSON.stringify(s[1])), (r = (s[1][0] instanceof Array ? s[1][0] : s[1]).slice()), (u = s[0])); else { if ("string" != typeof s) throw "unsupported message format"; { let e = JSON.parse(s); ((o = atob(e.adata)), (r = [ e.iv, e.salt, e.iter, e.ks, e.ts, e.cipher, e.mode, "rawdeflate", ]), (u = e.ct)); } } if ( ((r[0] = atob(r[0])), (r[1] = atob(r[1])), "zlib" === r[7] && void 0 === m) ) throw "Error decompressing paste, due to missing WebAssembly support."; try { p = await window.crypto.subtle.decrypt( c(o, r), await d(e, t, r), n(atob(u)), ); } catch (e) { return (console.error(e), ""); } try { return await (async function (e, t, s) { if ("zlib" === t || "none" === t) { if ("zlib" === t) { if (void 0 === s) throw "Error decompressing paste, due to missing WebAssembly support."; e = s.inflate(new Uint8Array(e)).buffer; } return a(i(e)); } return "undefined" == typeof Base64 ? a(RawDeflate.inflate(a(atob(i(e))))) : Base64.btou(RawDeflate.inflate(Base64.fromBase64(i(e)))); })(p, r[7], m); } catch (e) { return (l(e), e); } }), (e.getSymmetricKey = function () { return r(32); }), (e.base58encode = function (e) { return t.encode(n(e)); }), (e.base58decode = function (e) { return i(t.decode(e)); }), e ); })(); } } class u { constructor() { ((this.isLoadedI = !1), (this.mapI = void 0), (this.mapsI = void 0), (this.staticsI = void 0), (this.saveBackupI = void 0), (this.savegameChangedI = !1), (this.multiplierChangedI = !1), (this.multipliedObjectsCacheI = void 0), (this.registeredTaskIntervalsI = []), (this.exportStorage = {}), (this.dbI = r.getInstance(() => { this.dbI.getMaps( this.loadMapSuccess.bind(this), this.loadMapError.bind(this), ); }))); } static getInstance(e) { return ( void 0 === this.instance && (this.instance = new u()), void 0 !== e && this.instance.registerLoadFinished(e), this.instance ); } static getDBInstance() { return this.getInstance().dbI; } registerLoadFinished(e) { (void 0 === this.successEvents && (this.successEvents = []), this.successEvents.push(e)); } loadMapSuccess(e) { if ( (!Array.isArray(e) || e.length <= 0 ? (this.mapI = void 0) : ((this.mapsI = e), (this.mapI = e[0]), void 0 === this.mapI.saveGame && (this.mapI.saveGame = {}), void 0 === this.mapI.subMaps && (this.mapI.subMaps = []), (this.mapI = this.importSubMapsContent(this.mapI, e)), (this.mapI.mapData = this.removeInvalidReferencesInMapData( this.mapI, )), (this.staticsI = this.loadStatics(this.mapI)), (this.mapI.saveGame = this.removeInvalidReferencesFromSaveGame( this.mapI, ))), (this.isLoadedI = void 0 !== this.mapI), void 0 !== this.successEvents) ) for (let e = this.successEvents.length - 1; e >= 0; e--) this.successEvents.pop(e)(); } removeInvalidReferencesInMapData(e) { const t = e.mapData, a = Object.keys(t.majors), s = Object.keys(t.classes); if (t.majors && t.classes) for (let e = 0; e < a.length; e++) { const s = t.majors[a[e]]; if (s.prerequisites && s.prerequisites.length > 0) for (let e = 0; e < s.prerequisites.length; e++) { const a = s.prerequisites[e]; t.classes.hasOwnProperty(a) || (s.prerequisites.splice(e, 1), e--); } } if (t.classes) for (let e = 0; e < s.length; e++) { const a = t.classes[s[e]]; if (a.prerequisites && a.prerequisites.length > 0) for (let e = 0; e < a.prerequisites.length; e++) { const s = a.prerequisites[e]; t.classes.hasOwnProperty(s) || (a.prerequisites.splice(e, 1), e--); } } return t; } removeInvalidReferencesFromSaveGame(e) { const t = e.mapData; let a = e.saveGame, s = { finishedPunishments: [], blockedPunishments: [], activePunishments: [], assignedClasses: [], assignedClubs: [], assignedPartners: [], }; if ( (a.activeMajor && t.majors && !t.majors[a.activeMajor] && (a.activeMajor = void 0), a.finishedPunishments && a.finishedPunishments.length > 0) ) for (let e = 0; e < a.finishedPunishments.length; e++) { const i = a.finishedPunishments[e]; (t.punishments && t.punishments[i]) || (s.finishedPunishments.push(i), a.finishedPunishments.splice(e, 1), e--); } if (a.blockedPunishments && a.blockedPunishments.length > 0) for (let e = 0; e < a.blockedPunishments.length; e++) { const i = a.blockedPunishments[e]; (t.punishments && t.punishments[i]) || (s.blockedPunishments.push(i), a.blockedPunishments.splice(e, 1), e--); } if (a.activePunishments && a.activePunishments.length > 0) for (let e = 0; e < a.activePunishments.length; e++) { const i = a.activePunishments[e]; (t.punishments && t.punishments[i]) || (s.activePunishments.push(i), a.activePunishments.splice(e, 1), e--); } if (a.assignedClasses && a.assignedClasses.length > 0) for (let e = 0; e < a.assignedClasses.length; e++) { const i = a.assignedClasses[e]; (t.classes && t.classes[i.id]) || (s.assignedClasses.push(i), a.assignedClasses.splice(e, 1), e--); } if (a.finishedClasses && a.finishedClasses.length > 0) for (let e = 0; e < a.finishedClasses.length; e++) { const i = a.finishedClasses[e]; (t.classes && t.classes[i]) || (s.finishedClasses.push(i), a.finishedClasses.splice(e, 1), e--); } if (a.assignedClubs && a.assignedClubs.length > 0) for (let e = 0; e < a.assignedClubs.length; e++) { const i = a.assignedClubs[e]; (t.clubs && t.clubs[i.id]) || (s.assignedClubs.push(i), a.assignedClubs.splice(e, 1), e--); } if (a.assignedPartners && a.assignedPartners.length > 0) for (let e = 0; e < a.assignedPartners.length; e++) { const i = a.assignedPartners[e]; (t.partners && t.partners[i.id]) || (s.assignedPartners.push(i), a.assignedPartners.splice(e, 1), e--); } if (a.finishedMajors && a.finishedMajors.length > 0) for (let e = 0; e < a.finishedMajors.length; e++) { const i = a.finishedMajors[e]; (t.majors && t.majors[i]) || (s.finishedMajors.push(i), a.finishedMajors.splice(e, 1), e--); } if (a.activeTasks && a.activeTasks.length > 0) for (let e = 0; e < a.activeTasks.length; e++) { const s = a.activeTasks[e]; if (s.object) { if ("custom" == s.object.type) continue; if ("class" == s.object.type) { if (t.classes && t.classes[s.object.id]) continue; } else if ("major" == s.object.type) { if (t.majors && t.majors[s.object.id]) continue; } else if ( "punishment" == s.object.type && t.punishments && t.punishments[s.object.id] ) continue; (a.activeTasks.splice(e, 1), e--); } } return ((this.saveBackupI = s), a); } importSubMapsContent(e, t) { if ( void 0 !== e && e.hasOwnProperty("subMaps") && e.subMaps.length > 0 ) { let a = { majors: {}, classes: {}, clubs: {}, partners: {}, punishments: {}, }, s = t.filter( (t) => e.mapData.mapId !== t.mapData.mapId && e.subMaps.includes(t.mapData.mapId), ); for (const t of s) { let s = t.mapData.mapId; const i = t.mapData.moduleId; if ( (void 0 !== i && i.length > 1 && (s = /^(.*?)\(/gm.exec(t.mapData.mapId)[1]), e.mapData.mapId.startsWith(s) && (s = ""), void 0 !== t.mapData.general.help && t.mapData.general.help.length > 0) ) { Array.isArray(e.mapData.general.help) || (e.mapData.general.help = []); for (const a of t.mapData.general.help) e.mapData.general.help.find( (e) => "" + e.id == `${s}${a.id}`, ) || e.mapData.general.help.push(a); } for (const e in t.mapData.majors) if (t.mapData.majors.hasOwnProperty(e)) { const i = t.mapData.majors[e], n = `${s}${e}`; ((a.majors[n] = i), (a.majors[n].id = n), (a.majors[n].prerequisites = i.prerequisites.map( (e) => `${s}${e}`, ))); } for (const e in t.mapData.classes) if (t.mapData.classes.hasOwnProperty(e)) { const i = t.mapData.classes[e], n = `${s}${e}`; ((a.classes[n] = i), (a.classes[n].id = n), (a.classes[n].prerequisites = i.prerequisites.map( (e) => `${s}${e}`, ))); } for (const e in t.mapData.clubs) if (t.mapData.clubs.hasOwnProperty(e)) { const i = t.mapData.clubs[e], n = `${s}${e}`; ((a.clubs[n] = i), (a.clubs[n].id = n)); } for (const e in t.mapData.partners) if (t.mapData.partners.hasOwnProperty(e)) { const i = t.mapData.partners[e], n = `${s}${e}`; ((a.partners[n] = i), (a.partners[n].id = n)); } for (const e in t.mapData.punishments) if (t.mapData.punishments.hasOwnProperty(e)) { const i = t.mapData.punishments[e], n = `${s}${e}`; ((a.punishments[n] = i), (a.punishments[n].id = n)); } } (Object.assign(e.mapData.majors, a.majors), Object.assign(e.mapData.classes, a.classes), Object.assign(e.mapData.clubs, a.clubs), Object.assign(e.mapData.partners, a.partners), Object.assign(e.mapData.punishments, a.punishments)); } return e; } loadMapError(e) { alert("failed to load maps from local storage " + e); } loadStatics(e) { let t = { requiredCreditsUnlockIntermediate: 30, requiredCreditsUnlockAdvanced: 60, requiredCreditsUnlockMaster: 100, requiredCreditsForGraduation: 160, requiredCreditsToUnlockEliteClubs: 100, requiredCreditsToUnlockHardPunishments: 50, }, a = 0, s = 0, i = 0, n = 0, r = 0; for (const t in e.mapData.classes) if (e.mapData.classes.hasOwnProperty(t)) { const a = e.mapData.classes[t]; "beginner" == a.tier ? s++ : "intermediate" == a.tier ? i++ : "advanced" == a.tier ? n++ : "master" == a.tier && r++; } return ( (s *= o.classBeginnerCredits), (i *= o.classIntermediateCredits), (n *= o.classAdvancedCredits), (r *= o.classMasterCredits), (a += s), s < t.requiredCreditsUnlockIntermediate && (t.requiredCreditsUnlockIntermediate = s), (a += i), a < t.requiredCreditsUnlockAdvanced && (t.requiredCreditsUnlockAdvanced = a), (a += n), a < t.requiredCreditsUnlockMaster && (t.requiredCreditsUnlockMaster = a), a < t.requiredCreditsToUnlockEliteClubs && (t.requiredCreditsToUnlockEliteClubs = a), a < t.requiredCreditsToUnlockHardPunishments && (t.requiredCreditsToUnlockHardPunishments = a), (a += r), a < t.requiredCreditsForGraduation && (t.requiredCreditsForGraduation = a), t ); } get static() { return this.staticsI; } get multiplierChanged() { return this.multiplierChangedI; } multiplierChanged() { this.multiplierChangedI = !0; } get multiplierCacheHandler() { return { get: function (e, t, a) { return e[t]; }, set: function (e, t, a) { return !1; }, }; } get multipliedObjectsChache() { if (this.multiplierChangedI || null == this.multipliedObjectsCacheI) { let e = $.extend(!0, {}, this.mapI.mapData); this.multiplierChangedI = !1; for (const t in e.classes) e.classes[t] = d.applyParametersToObject(e.classes[t]); for (const t in e.majors) e.majors[t] = d.applyParametersToObject(e.majors[t]); for (const t in e.punishments) e.punishments[t] = d.applyParametersToObject(e.punishments[t]); this.multipliedObjectsCacheI = e; } return new Proxy( this.multipliedObjectsCacheI, this.multiplierCacheHandler, ); } resetTaskIntervals() { for (let e of this.registeredTaskIntervalsI) clearInterval(e.ref); this.registeredTaskIntervalsI = []; } addTaskInterval(e, t) { for (let t of this.registeredTaskIntervalsI) t.id == e && clearInterval(t.ref); ((this.registeredTaskIntervalsI = this.registeredTaskIntervalsI.filter( (t) => t.id != e, )), this.registeredTaskIntervalsI.push({ id: e, ref: t })); } get isMapLoaded() { return this.isLoadedI; } get mapId() { return this.mapI.mapData.mapId; } get map() { return this.mapI.mapData; } get maps() { return (this.mapsI || []).map((e) => $.extend(!0, {}, e)); } get savegameHandler() { let e = this; return { get: function (e, t, a) { return e[t]; }, set: function (t, a, s) { return ((e.savegameChangedI = !0), (t[a] = s), !0); }, }; } get wasSavegameChanged() { return this.savegameChangedI; } get subMaps() { return this.mapI.subMaps; } set subMaps(e) { if (Array.isArray(e)) return (this.mapI.subMaps = e); } get anyMajorFinished() { return (this.mapI.saveGame.finishedMajors || []).length > 0; } setSubMaps(e, t) { const a = s.getInstance(); this.dbI.setSubMaps( this.mapI.mapData.mapId, this.mapI.subMaps, (t) => { (t && "abort" == t.type && n.scheduleToast( a.T("map-submap-failed-header", "Setting sub map aborted"), a.T( "map-submap-failed-content", "PU was unable to store the new sub map assignment. This can happen if you run the website in incognito mode.", ), "warning", ), e(t)); }, t, ); } getSavegame() { return this.isLoadedI ? new Proxy(this.mapI.saveGame, this.savegameHandler) : {}; } setSavegame(e, t, a) { if (!this.savegameChangedI) return; let i = this; const o = s.getInstance(), r = this.mapI.mapData.mapId; let l = JSON.parse(JSON.stringify(this.mapI.saveGame)); this.saveBackupI && ((l.finishedPunishments = this.saveBackupI.finishedPunishments.concat( l.finishedPunishments || [], )), (l.blockedPunishments = this.saveBackupI.blockedPunishments.concat( l.blockedPunishments || [], )), (l.activePunishments = this.saveBackupI.activePunishments.concat( l.activePunishments || [], )), (l.assignedClasses = this.saveBackupI.assignedClasses.concat( l.assignedClasses || [], )), (l.assignedClubs = this.saveBackupI.assignedClubs.concat( l.assignedClubs || [], )), (l.assignedPartners = this.saveBackupI.assignedPartners.concat( l.assignedPartners || [], ))); let d = function (t) { !t || ("success" != t.type && "complete" != t.type) ? t && "abort" == t.type && n.scheduleToast( o.T("map-save-failed-header", "Save aborted"), o.T( "map-save-failed-content", "PU was unable to store your progress. This can happen if you run the website in incognito mode.", ), "warning", ) : ((i.savegameChangedI = !1), a || this.uploadOnlineSaveGame(r, l, () => { e && e(t); })); }.bind(this); this.dbI.setSavegame(r, l, d, t); } checkOnlineSaveGame(e) { if (this.savegameChangedI) return; if (!this.mapI || !this.mapI.mapData || !this.mapI.mapData.mapId) return; const t = this.mapI.mapData.mapId, a = s.getInstance(); let i = null, o = null, r = function (t) { if (t.error || !t.data) return void n.showToast( a.T( "settings-download-save-sync-failed-header", "Save-Sync download failed", ), a.T( "settings-download-save-sync-failed-content", "The download of the latest SaveGame failed. You may need to create a new session.", ), ); let s = JSON.parse(t.data.saveData); s && ((this.saveBackupI = void 0), (this.mapI.saveGame = s), (this.savegameChangedI = !0), (this.mapI.lastSaved = o), this.setSavegame(e, void 0, !0)); }.bind(this), l = function (e) { !e.error && e.data ? null != e.data.saveDate && ((o = n.getMoment(e.data.saveDate).unix()), (o > this.mapI.lastSaved || !( void 0 !== this.mapI.saveGame && Object.entries(this.mapI.saveGame).length > 0 )) && c.downloadSaveGame(i, t, r)) : n.showToast( a.T( "settings-check-save-sync-failed-header", "Save-Sync check failed", ), a.T( "settings-check-save-sync-failed-content", "Checking the status of the latest SaveGame failed. You may need to create a new session.", ), ); }.bind(this), d = function (e) { ((i = e), null !== i && c.checkSaveGame(i, t, l)); }.bind(this); c.getInstance().getSessionToken(d); } uploadOnlineSaveGame(e, t, a) { const i = s.getInstance(); let o = function (e) { (!e.error && e.data) || n.showToast( i.T( "settings-upload-save-sync-failed-header", "Save-Sync upload failed", ), i.T( "settings-upload-save-sync-failed-content", "Uploading the latest SaveGame failed. You may need to create a new session.", ), ); }.bind(this), r = function (s) { (null !== s && c.uploadSaveGame(s, e, t, o), a()); }.bind(this); c.getInstance().getSessionToken(r); } importSaveGame(e, t, a) { const i = s.getInstance(); return e.mapId !== this.mapI.mapData.mapId ? i.T( "map-save-incompatible", "SaveGame not compatible with the current map.", ) : e.saveGame ? ((e.saveGame = p.resetAttendanceDeadline(e.saveGame)), (this.saveBackupI = void 0), (this.mapI.saveGame = e.saveGame), (this.savegameChangedI = !0), void this.setSavegame(t, a)) : i.T("map-save-empty", "SaveGame empty."); } changeMap(e) { (this.setSavegame(), (this.isLoadedI = !1), this.dbI.switchMap( e, this.loadMapSuccess.bind(this), this.loadMapError.bind(this), )); } deleteMap(e, t) { (this.setSavegame(), this.dbI.removeMap(e, t)); } async importMapFile(e) { const t = s.getInstance(), a = { title: t.T("map-import-confirmation-header", "Import a map"), text: t.T( "map-import-confirmation-conent", "Are you sure that you want to import this map? Make sure to only install maps from trusted sources. Proceed at your own risk.", ), }, i = { title: t.T("map-import-failed-header", "Map import failed"), text: t.T( "map-import-failed-content", "During the import for the selected file an error occured. You can check the console for more information.", ), }, o = { title: t.T( "map-file-import-compression-error-header", "Compression unavailable", ), text: t.T( "map-file-import-compression-error-content", "The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.", ), }, r = { title: t.T("map-file-import-aborted-header", "Import aborted"), text: t.T( "map-file-import-aborted-content", "The file you tried to import included untrusted code. To protect you the import was aborted.", ), }, l = await _.runImport(e, i, a, o, r); n.isCompleteSavegame(l) ? await this.importCompleteSavegame(l) : this.importMap(l); } async importCompleteSavegame(e) { const t = (e) => new Promise((t, a) => { this.dbI.addMap(e, t, a); }); (n.setSettings(e.settings || n.getDefaultSettings()), await t(e.primaryMapData)); for (const a of e.subMapData) await t(a); (await ((e, t) => new Promise((a, s) => { this.dbI.setSubMaps(e, t, a, s); }))(e.mapId, e.subMaps), await ((e, t) => new Promise((a, s) => { this.dbI.setSavegame(e, t, a, s); }))(e.mapId, e.saveGame), this.addMapSuccess(void 0, e.primaryMapData)); } importMap(e) { s.getInstance(); this.mapI ? this.dbI.addMap( e, this.addMapAsSubMapSuccess.bind(this), this.addMapError.bind(this), ) : this.dbI.addMap( e, this.addMapSuccess.bind(this), this.addMapError.bind(this), ); } addMapAsSubMapSuccess(e, t) { (n.scheduleToast( `${s.getInstance().T("map-import-submap-popup", "Submap imported")}: ${t.general.title}`, "" + t.general.description, ), this.subMaps.includes(t.mapId) || this.mapId == t.mapId || this.subMaps.push(t.mapId), this.setSubMaps(() => { _.navigate("?page=settings"); })); } addMapSuccess(e, t) { s.getInstance(); (n.scheduleToast( `${s.getInstance().T("map-import-map-popup", "Map imported")}: ${t.general.title}`, "" + t.general.description, ), this.registerLoadFinished(() => { _.navigate("?page=help"); }), this.changeMap(t.mapId)); } addMapError(e) { alert("failed to add the map to local storage " + e); } setExportStorageItem(e, t, a) { this.exportStorage[e] = { data: t, type: a, filename: e }; } getExportStorageItem(e) { return this.exportStorage[e]; } } class p { static resetAttendanceDeadline(e) { if (e.assignedClasses && e.assignedClasses.length > 0) { const t = n.getNow().add(-1, "day").endOf("day").unix(); e.assignedClasses = e.assignedClasses.map( (e) => ( "active" == e.status && e.lastAttended && e.lastAttended < t && (e.lastAttended = t), e ), ); } return e; } static getActiveMajor() { return u.getInstance().getSavegame().activeMajor; } static setActiveMajor(e) { u.getInstance().getSavegame().activeMajor = e; } static getAssignedClasses() { return u.getInstance().getSavegame().assignedClasses || []; } static setAssignedClasses(e) { u.getInstance().getSavegame().assignedClasses = e; } static getAssignedClubs() { return u.getInstance().getSavegame().assignedClubs || []; } static setAssignedClubs(e) { u.getInstance().getSavegame().assignedClubs = e; } static getActivePartners() { return u.getInstance().getSavegame().assignedPartners || []; } static setActivePartners(e) { u.getInstance().getSavegame().assignedPartners = e; } static getRoulette() { return u.getInstance().getSavegame().roulette || []; } static setRoulette(e) { u.getInstance().getSavegame().roulette = e; } static getActiveTasks() { return u.getInstance().getSavegame().activeTasks || []; } static setActiveTasks(e, t) { u.getInstance().getSavegame().activeTasks = e; } static getFinishedMajors() { return u.getInstance().getSavegame().finishedMajors || []; } static setFinishedMajors(e) { u.getInstance().getSavegame().finishedMajors = e; } static getFinishedPunishments() { return u.getInstance().getSavegame().finishedPunishments || []; } static setFinishedPunishments(e) { u.getInstance().getSavegame().finishedPunishments = e; } static getBlockedPunishments() { return u.getInstance().getSavegame().blockedPunishments || []; } static setBlockedPunishments(e) { u.getInstance().getSavegame().blockedPunishments = e; } static getActivePunishments() { return u.getInstance().getSavegame().activePunishments || []; } static setActivePunishments(e) { u.getInstance().getSavegame().activePunishments = e; } static getEnduredPunishments() { return u.getInstance().getSavegame().enduredPunishments || 0; } static setEnduredPunishments(e) { u.getInstance().getSavegame().enduredPunishments = e; } static getClassSkipTokens() { return u.getInstance().getSavegame().classSkipTokens || 0; } static setClassSkipTokens(e) { u.getInstance().getSavegame().classSkipTokens = e; } static get classesData() { return u.getInstance().multipliedObjectsChache.classes; } static get majorsData() { return u.getInstance().multipliedObjectsChache.majors; } static get partnersData() { return u.getInstance().map.partners; } static get clubsData() { return u.getInstance().map.clubs; } static get punishmentsData() { return u.getInstance().multipliedObjectsChache.punishments; } static get rouletteData() { return u.getInstance().map.rouletteOptions; } static get requiredCreditsToUnlockEliteClubs() { return u.getInstance().static.requiredCreditsToUnlockEliteClubs; } static get requiredCreditsUnlockIntermediate() { return u.getInstance().static.requiredCreditsUnlockIntermediate; } static get requiredCreditsUnlockAdvanced() { return u.getInstance().static.requiredCreditsUnlockAdvanced; } static get requiredCreditsUnlockMaster() { return u.getInstance().static.requiredCreditsUnlockMaster; } static get requiredCreditsForGraduation() { return u.getInstance().static.requiredCreditsForGraduation; } static get requiredCreditsToUnlockHardPunishments() { return u.getInstance().static.requiredCreditsToUnlockHardPunishments; } } class m { static get expectedParameters() { return { getView: [] }; } static getView() { const e = p.clubsData, t = s.getInstance(); let a = "", i = ""; for (const t in e) { let s = e[t]; const n = m.getCardHtml(s); "normal" === s.tier ? (a += n) : "elite" === s.tier && (i += n); } return `
\n
\n
\n \n
\n
\n \n
\n
\n
`; } static getCardHtml(e) { const t = s.getInstance(); let a = h.isClubLocked(e.id), i = h.isClubActive(e.id), o = n.getRandomString(); return `
\n
\n\n
\n \n \n \n ${i ? n.getBackdropContainer(t.T("club-status-active", "Active") + ' ', "success") : ""}\n ${a ? n.getBackdropContainer(t.T("club-status-locked", "Locked") + ' ', "danger") : ""} \n ${n.getButtonsContainer( [ ``, m.getJoinButton(e.id), m.getDropButton(e.id), ], )}\n
\n
\n
    \n ${m.getPerksHtml(e.id)}\n
\n
\n
\n
\n
\n
${e.name}
\n

${e.description}

\n
\n
\n
`; } static getJoinButton(e) { return h.isJoinButtonAvailable(e) ? `` : ""; } static getDropButton(e) { return h.isDropButtonAvailable(e) ? `` : ""; } static getPerksHtml(e) { const t = s.getInstance(); let a = h.getClubPerks(e), i = ""; for (let e in a) i += `
  • \n
    \n
    \n ${t.T("club-modifier-activity", "Activity")}:\n
    \n
    ${a[e].job}
    \n
    \n
    \n
    \n ${t.T("club-modifier-perk", "Perk")}:\n
    \n
    ${a[e].perk}
    \n
    \n
  • `; return i; } } class g { static get expectedParameters() { return { getView: ["clubId"] }; } static getView(e) { const t = e.clubId, a = h.isClubLocked(t), i = h.isClubActive(t), n = s.getInstance(); let o = p.clubsData[t]; return `
    \n

    ${o.name}

    \n
    \n
    \n
    \n
    \n \n \n \n ${a ? `
    ${n.T("club-status-locked", "Locked")}
    ` : ""}\n ${i ? `
    ${n.T("club-status-active", "Active")}
    ` : ""}\n
    \n
    \n
    \n

    ${o.description}${g.getCommentHtml(o.comment)}

    \n
      \n
    • ${n.T("club-detail-modifiers", "Available perks")}:
    • \n ${g.getPerksHtml(o.id)}\n
    \n
    \n ${g.getJoinButton(o.id)}\n ${g.getDropButton(o.id)}\n
    \n
    \n
    \n
    \n
    \n `; } static getCommentHtml(e) { return !e || null == e || e.length <= 0 ? "" : `
    ${e}`; } static getPerksHtml(e) { const t = s.getInstance(); let a = h.getClubPerks(e), i = ""; for (let s in a) i += `
  • \n
    \n
    \n ${t.T("club-modifier-activity", "Activity")}:\n\n
    \n
    ${a[s].job}
    \n
    \n
    \n
    \n ${t.T("club-modifier-perk", "Perk")}:\n
    \n
    ${a[s].perk}\n ${g.activatePerk(e, s)}\n
    \n
    \n
  • `; return i; } static getJoinButton(e) { return h.isJoinButtonAvailable(e) ? `
    ` : ""; } static getDropButton(e) { return h.isDropButtonAvailable(e) ? `
    ` : ""; } static activatePerk(e, t) { const a = h.isClubActive(e), i = h.isPerkActive(e, t), o = h.canActivatePerk(e, t), r = h.isMaxPerksActive(), l = n.getSetting("enableEasyMode"); return !r && o && a && !i ? `` : a && i && l ? `` : a && i ? `` : ""; } } class h { static isJoinButtonAvailable(e) { const t = h.getJoinedClub(e), a = h.isClubLocked(e), s = h.isMaxClubJoined(); return !t && !a && !s; } static isDropButtonAvailable(e) { return h.getJoinedClub(e); } static getClubPerks(e) { return p.clubsData[e].perks; } static getJoinedClub(e) { const t = p.getAssignedClubs().filter((t) => t.id == e); return 0 !== t.length && t[0]; } static joinClub(e) { if (h.getJoinedClub(e)) return; const t = s.getInstance(); if (h.isMaxClubJoined()) return void n.showToast( t.T("club-max-clubs-header", "Maximum clubs assigned"), t.T( "club-max-clubs-content", "You are only allowed to join %s clubs at a time.", [o.maxConcurrentClubs], ), ); let a = p.getAssignedClubs(), i = { id: e, activePerks: {} }; (a.push(i), p.setAssignedClubs(a), _.getInstance().inAppNavigation("?page=club&clubId=" + e)); } static dropClub(e) { let t = p.getAssignedClubs(); ((t = t.filter((t) => t.id !== e)), p.setAssignedClubs(t), _.getInstance().inAppNavigation("?page=clubs")); } static isClubActive(e) { return !!h.getJoinedClub(e); } static isPerkActive(e, t) { const a = h.getJoinedClub(e); return ( !!a && void 0 !== a.activePerks[t] && !!a.activePerks[t].active && !!n.isToday(a.activePerks[t].activatedOn) ); } static canActivatePerk(e, t) { return "8" !== e || h.getActiveClubPerks(e).length <= 0; } static isMaxPerksActive() { return h.getAllActivePerks() >= o.maxConcurrentClubPerks; } static isMaxClubJoined() { return p.getAssignedClubs().length >= o.maxConcurrentClubs; } static getActiveClubPerks(e) { if (!h.isClubActive(e)) return []; return h.getAllActivePerks().filter((t) => t.clubId === e); } static getAllActivePerks() { let e = p.getAssignedClubs(), t = []; for (let a in e) { let s = e[a], i = s.id, n = s.activePerks; for (let e in n) h.isPerkActive(i, e) && t.push({ clubId: i, perkId: e }); } return t; } static activatePerk(e, t) { const a = s.getInstance(); if (h.isMaxPerksActive()) return void n.showToast( a.T("club-max-perks-header", "Maximum club perks active"), a.T( "club-max-perks-content", "You are only allowed to activate %s club perks at a time.", [o.maxConcurrentClubPerks], ), ); if (!h.canActivatePerk(e, t)) return void n.showToast( a.T("club-perk-unavailable-header", "Perk can not be activated"), a.T( "club-perk-unavailable-content", "It's currently not possible to activate this perk.", ), ); let i = S.getActiveTags(), r = p.clubsData[e].perks[t], l = r.tags; if (i.length <= 0 || (l.length > 0 && !l.some((e) => i.includes(e)))) return void n.showToast( a.T( "club-perk-unavailable-tag-header", "Perk can not be activated", ), a.T( "club-perk-unavailable-tag-content", "You don't have any class with matching tags active.", ), ); const d = p .getAssignedClubs() .map( (a) => ( a.id === e && (a.activePerks[t] = { active: !0, activatedOn: n.getNowUnix(), }), a ), ); (p.setAssignedClubs(d), x.perkActivated(r), u.getInstance().multiplierChanged(), _.inAppReload()); } static deactivatePerk(e, t) { const a = s.getInstance(); if (!h.isPerkActive(e, t)) return void n.showToast( a.T("club-perk-not-active-header", "Perk can not be deactivated"), a.T( "club-perk-not-active-content", "It's currently not possible to deactivate this perk.", ), ); const i = p .getAssignedClubs() .map((a) => (a.id === e && delete a.activePerks[t], a)), o = p.clubsData[e].perks[t]; (p.setAssignedClubs(i), x.perkDeactivated(o), u.getInstance().multiplierChanged(), _.inAppReload()); } static isClubLocked(e) { const t = p.clubsData[e]; let a = 0, s = S.collectedCredits; return ( "elite" === t.tier && (a = p.requiredCreditsToUnlockEliteClubs), s < a ); } } class v { static get expectedParameters() { return { getView: [] }; } static getView() { const e = p.partnersData; let t = ""; for (const a in e) { let s = e[a]; t += v.getCardHtml(s); } return `
    \n
    ${t}
    \n
    `; } static getCardHtml(e) { const t = f.isPartnerActive(e.id), a = n.getRandomString(); return `
    \n
    \n
    \n \n \n \n ${t ? n.getBackdropContainer(s.getInstance().T("partner-status-active", "Active") + ' ', "success") : ""}\n ${n.getButtonsContainer( [ ``, v.getJoinButton(e.id), v.getDropButton(e.id), ], )}\n
    \n
    \n
      \n ${v.getPerksHtml(e.id)}\n
    \n
    \n
    \n
    \n \n
    \n
    ${e.name}
    \n

    ${e.description}

    \n
    \n
    \n
    `; } static getJoinButton(e) { return f.isJoinButtonAvailable(e) ? `` : ""; } static getDropButton(e) { return f.isDropButtonAvailable(e) ? `` : ""; } static getPerksHtml(e) { const t = s.getInstance(); let a = f.getPartnerPerks(e), i = ""; for (let e in a) i += `
  • \n
    \n
    \n ${t.T("partner-modifier-activity", "Activity")}:\n
    \n
    ${a[e].job}
    \n
    \n
    \n
    \n ${t.T("partner-modifier-perk", "Perk")}:\n
    \n
    ${a[e].perk}
    \n
    \n
  • `; return i; } } class b { static get expectedParameters() { return { getView: ["partnerId"] }; } static getView(e) { const t = e.partnerId, a = f.isPartnerActive(t), i = s.getInstance(); let n = p.partnersData[t]; return `
    \n

    ${n.name}

    \n
    \n
    \n
    \n
    \n \n \n \n ${a ? `
    ${i.T("partner-status-active", "Active")}
    ` : ""}\n
    \n
    \n
    \n

    ${n.description}

    \n
      \n
    • ${i.T("partner-detail-modifiers", "Available perks")}:
    • \n ${b.getPerksHtml(n.id)}\n
    \n
    \n ${b.getJoinButton(n.id)}\n ${b.getDropButton(n.id)}\n
    \n
    \n
    \n
    \n
    \n `; } static getPerksHtml(e) { const t = s.getInstance(); let a = f.getPartnerPerks(e), i = ""; for (let s in a) i += `
  • \n
    \n
    \n ${t.T("partner-modifier-activity", "Activity")}:\n
    \n
    ${a[s].job}
    \n
    \n
    \n
    \n ${t.T("partner-modifier-perk", "Perk")}:\n
    \n
    ${a[s].perk}\n ${b.activatePerk(e, s)}\n
    \n
    \n
  • `; return i; } static getJoinButton(e) { return f.isJoinButtonAvailable(e) ? `
    ` : ""; } static getDropButton(e) { return f.isDropButtonAvailable(e) ? `
    ` : ""; } static activatePerk(e, t) { const a = f.isPartnerActive(e), i = f.isPerkActive(e, t), o = f.isMaxPerksActive(), r = n.getSetting("enableEasyMode"), l = s.getInstance(); return o || !a || i ? a && i && r ? `` : a && i ? `` : "" : ``; } } class f { static isJoinButtonAvailable(e) { const t = f.getActivePartner(e), a = f.isMaxPartnerJoined(); return !t && !a; } static isDropButtonAvailable(e) { return f.getActivePartner(e); } static getPartnerPerks(e) { return p.partnersData[e].perks; } static isMaxPartnerJoined() { return p.getActivePartners().length >= o.maxConcurrentPartner; } static isMaxPerksActive() { return f.getAllActivePerks() >= o.maxConcurrentPartnerPerks; } static getAllActivePerks() { let e = p.getActivePartners(), t = []; for (let a in e) { let s = e[a], i = s.id, n = s.activePerks; for (let e in n) f.isPerkActive(i, e) && t.push({ partnerId: i, perkId: e }); } return t; } static getActivePartner(e) { const t = p.getActivePartners().filter((t) => t.id == e); return 0 !== t.length && t[0]; } static addPartner(e) { if (f.getActivePartner(e)) return; if (f.isMaxPartnerJoined()) return; let t = p.getActivePartners(), a = { id: e, activePerks: {} }; (t.push(a), p.setActivePartners(t), _.getInstance().inAppNavigation("?page=partner&partnerId=" + e)); } static leavePartner(e) { let t = p.getActivePartners(); ((t = t.filter((t) => t.id !== e)), p.setActivePartners(t), _.getInstance().inAppNavigation("?page=partners")); } static isPartnerActive(e) { return !!f.getActivePartner(e); } static isPerkActive(e, t) { const a = f.getActivePartner(e); return ( !!a && void 0 !== a.activePerks[t] && !!a.activePerks[t].active && !!n.isToday(a.activePerks[t].activatedOn) ); } static activatePerk(e, t) { const a = s.getInstance(); if (!f.isPartnerActive(e)) return void n.showToast( a.T("partner-perk-not-active-header", "Perk can not be activated"), a.T( "partner-perk-not-active-content", "This partner is not active.", ), ); if (f.isMaxPerksActive()) return void n.showToast( a.T( "partner-perk-max-perks-header", "Maximum partner perks active", ), a.T( "partner-perk-max-perks-content", "You are only allowed to activate %s partner perks at a time.", [o.maxConcurrentPartnerPerks], ), ); let i = S.getActiveTags(), r = p.partnersData[e].perks[t], l = r.tags; if (i.length <= 0 || (l.length > 0 && !l.some((e) => i.includes(e)))) return void n.showToast( a.T("partner-perk-tags-header", "Perk can not be activated"), a.T( "partner-perk-tags-content", "You don't have any class with matching tags active.", ), ); const d = p .getActivePartners() .map( (a) => ( a.id === e && (a.activePerks[t] = { active: !0, activatedOn: n.getNowUnix(), }), a ), ); (p.setActivePartners(d), x.perkActivated(r), u.getInstance().multiplierChanged(), _.inAppReload()); } static deactivatePerk(e, t) { const a = s.getInstance(); if (!f.isPerkActive(e, t)) return void n.showToast( a.T( "partner-perk-not-active-header", "Perk can not be deactivated", ), a.T( "partner-perk-not-active-content", "It's currently not possible to deactivate this perk.", ), ); const i = p .getActivePartners() .map((a) => (a.id === e && delete a.activePerks[t], a)), o = p.partnersData[e].perks[t]; (p.setActivePartners(i), x.perkDeactivated(o), u.getInstance().multiplierChanged(), _.inAppReload()); } } class k { static get expectedParameters() { return { getView: [] }; } static getView() { const e = p.getActiveMajor(), t = p.majorsData[e], a = I.mandatoryClasses(e), i = S.getActiveClasses(), r = S.getFinishedClasses(), l = p.getFinishedMajors(), d = s.getInstance(); let c = "", u = "", m = "", g = "", h = l.length, v = 0, b = 0, f = 0, y = 0, w = 0; for (const e of l) { let t = p.majorsData[e]; g += C.getCardHtml(t); } for (let e of a) { let t = S.getAssignedClass(e), a = p.classesData[e]; c += P.getCardHtml(a, t, !0, "col-lg-3 col-sm-3 pb-5"); } for (let e of i) { let t = p.classesData[e.id], s = a.some((t) => t == e.id); u += P.getCardHtml(t, e, s, "col-lg-3 col-sm-3 pb-5"); } for (let e of r) { let t = p.classesData[e.id], s = a.some((t) => t == e.id); m += P.getCardHtml(t, e, s, "col-lg-3 col-sm-3 pb-5"); } for (let e of r) { let t = p.classesData[e.id], s = a.some((t) => t == e.id); switch (t.tier) { case "beginner": v += 1; break; case "intermediate": b += 1; break; case "advanced": f += 1; break; case "master": y += 1; } s && (w += 1); } return `
    \n

    ${d.T("progress-active-major", "Current major")}

    \n
    \n
    \n
    \n
    \n \n ${n.getButtonsContainer( [ ``, ], )}\n
    \n
    \n
    \n

    ${t.name}

    \n

    ${t.description}${k.getCurrentCredits()}${k.getPunishmentsEndured()}

    \n ${k.getGraduationButtonHtml()}\n
    ${d.T("progress-completed-classes", "Class completion")}:
    \n
      \n
    • ${d.T("class-beginner-header", "Beginner classes")}: ${v}
    • \n
    • ${d.T("class-intermediate-header", "Intermediate classes")}: ${b}
    • \n
    • ${d.T("class-advanced-header", "Advanced classes")}: ${f}
    • \n
    • ${d.T("class-master-header", "Master classes")}: ${y}
    • \n
    \n
    \n
    \n
    \n

    ${d.T("progress-active-classes", "Active classes")} [${i.length}/${o.maxConcurrentClasses}]

    \n
    \n
    \n ${u.length <= 0 ? `
    ${d.T("progress-active-classes-no", "No active classes available.")}
    ` : u}\n
    \n
    \n

    ${d.T("progress-mandatory-classes", "Mandatory classes")} [${w}/${a.length}]

    \n
    \n
    \n ${c.length <= 0 ? `
    ${d.T("progress-mandatory-classes-no", "No mandatory classes available.")}
    ` : c}\n
    \n
    \n

    ${d.T("progress-finished-majors", "Finished majors")} [${h}]

    \n
    \n
    \n ${g.length <= 0 ? `
    ${d.T("progress-finished-majors-no", "No majors finished yet.")}
    ` : g}\n
    \n
    \n

    ${d.T("progress-finished-classes", "Finished classes")} [${r.length}]

    \n
    \n
    \n ${m.length <= 0 ? `
    ${d.T("progress-finished-classes-no", "No classes finished yet.")}
    ` : m}\n
    \n
    \n
    \n `; } static getGraduationButtonHtml() { const e = p.getActiveMajor(); return I.isThesisAvailable(e) ? `` : ""; } static getPunishmentsEndured() { let e = p.getEnduredPunishments(); return e > 0 ? `
    ${s.getInstance().T("progress-punishments-endured", "Punishments endured")}: ${e}` : ""; } static getCurrentCredits() { const e = s.getInstance(); return `
    ${e.T("credits", "Credits")}: ${S.collectedCredits}/${p.requiredCreditsForGraduation}${S.collectedCredits <= 0 ? `${e.T("progress-credits-info", "You only get credits for finishing a class by attending the exam.")}` : ""}`; } } class y { static get expectedParameters() { return { getView: [] }; } static getView() { const e = p.punishmentsData, t = s.getInstance(); let a = "", i = "", n = ""; for (const t in e) { let s = e[t]; "light" === s.tier ? (a += y.getCardHtml(s)) : "hard" === s.tier ? (i += y.getCardHtml(s)) : "hardcore" === s.tier && (n += y.getCardHtml(s)); } return `
    \n
    \n \n
    \n \n
    \n
    \n
    `; } static getCardHtml(e) { const t = T.isPunishmentLocked(e.id), a = T.isPunishmentActive(e.id), i = void 0 !== x.getActiveTaskForObject(e.id, "punishment"), o = T.wasFinishedBefore(e.id), r = T.isBlocked(e.id), l = s.getInstance(); let d = ''; return ( i && (d = ''), `
    \n
    \n
    \n \n ${n.getButtonsContainer( [ a || o ? `${d}` : "", y.getBlockButton(e), ], )}\n ${a ? n.getBackdropContainer(l.T("punishment-status-active", "Active") + ' ', "success") : ""}\n ${t ? n.getBackdropContainer(l.T("punishment-status-locked", "Locked") + ' ', "danger") : ""}\n ${r ? n.getBackdropContainer(l.T("punishment-status-blocked", "Blocked") + ' ', "warning") : ""}\n
    \n
    \n
    ${e.name}
    \n
    ${y.getPunishmentTasksHtml(e)}
    \n
    \n
    \n
    ` ); } static getPunishmentTasksHtml(e) { return ""; } static getRollPunishmentButton(e) { if (T.isPunishmentTierAvailable(e)) { const t = void 0 === e ? void 0 : "" + e, a = s.getInstance(); return ``; } return ""; } static getBlockButton(e) { if (!T.isPunishmentTierAvailable(e.tier)) return ""; if ( !T.wasFinishedBefore(e.id) && !T.isPunishmentActive(e.id) && !n.getSetting("enableEasyMode") ) return ""; const t = T.isBlocked(e.id); return t && !n.getSetting("enableEasyMode") ? "" : t ? `` : ``; } } class w { static get expectedParameters() { return { getView: ["punishmentId"] }; } static getView(e) { const t = e.punishmentId, a = T.isPunishmentLocked(t), i = T.isPunishmentActive(t), n = x.getActiveTaskForObject(t, "punishment"), o = s.getInstance(); let r = p.punishmentsData[t], l = w.getPunishmentTier( r.tier, o.T("punishment-tier-" + r.tier, r.tier), ); return `
    \n

    ${r.name}

    \n
    \n
    \n
    \n
    \n \n \n \n ${i ? `
    ${o.T("punishment-status-active", "Active")}
    ` : ""}\n ${a ? `
    ${o.T("punishment-status-locked", "Locked")}
    ` : ""}\n
    \n
    \n
    \n

    ${o.T("punishment-tier-text", "Tier")}: ${l}

    \n
    \n ${void 0 !== n ? x.getTaskProgressHtml(n) : w.getPunishmentTasksHtml(r)}\n
    \n
    \n ${w.getBlockButton(r)}\n
    \n
    \n
    \n
    \n
    \n `; } static getBlockButton(e) { if (!T.isPunishmentTierAvailable(e.tier)) return ""; if ( !T.wasFinishedBefore(e.id) && !T.isPunishmentActive(e.id) && !n.getSetting("enableEasyMode") ) return ""; const t = T.isBlocked(e.id); return t && !n.getSetting("enableEasyMode") ? "" : t ? `` : ``; } static getPunishmentTasksHtml(e) { let t = ""; for (let a in e.tasks) e.tasks[a].isExam || (t += `
  • \n ${e.tasks[a].task}${w.getPunishmentButton(e.id, a)}${w.getRerollButton(e.id, e.tier)}\n
  • `); return ``; } static getPunishmentTier(e, t) { let a = ""; return ( "light" === e ? (a = "text-success") : "hard" === e ? (a = "text-warning") : "hardcore" === e && (a = "text-danger"), `${t}` ); } static getPunishmentButton(e, t) { return T.isPunishmentActive(e) ? `
    ` : ""; } static getRerollButton(e, t) { return T.isPunishmentActive(e) ? `
    ` : ""; } } class T { static unblockPunishment(e) { let t = p.getBlockedPunishments().filter((t) => t !== e); (p.setBlockedPunishments(t), _.getInstance().inAppNavigation("?page=punishments")); } static blockPunishment(e, t) { let a = p.getBlockedPunishments(); (a.includes(e) || (a.push(e), p.setBlockedPunishments(a)), T.isPunishmentActive(e) ? (T.removeActivePunishment(e), T.getRandomPunishmentAction(t)) : _.getInstance().inAppNavigation("?page=punishments")); } static isPunishmentActive(e) { return 0 !== p.getActivePunishments().filter((t) => t === e).length; } static startPunishment(e, t) { if (!T.isPunishmentActive(e)) return !1; x.startTask(p.punishmentsData[e], "tasks", t); } static rerollPunishment(e, t) { if (!T.isPunishmentActive(e)) return !1; T.finishPunishment(e); const a = s.getInstance(); let i = !1; if (!n.getSetting("enableEasyMode")) { let e = p .getAssignedClasses() .filter((e) => "active" == e.status && e.attendances > 0); if ( (e.length <= 0 && (e = p .getAssignedClasses() .filter((e) => "finished" == e.status && e.attendances > 0)), e.length > 0) ) { const t = e[Math.floor(Math.random() * e.length)], s = p.classesData[t.id]; "active" == t.status ? ((i = !0), S.addClassAttendance(t.id, -1), n.scheduleToast( a.T( "punishment-rerolled-knowledge-point-header", "Punishment rerolled", ), a.T( "punishment-rerolled-knowledge-point-content", "You rerolled your punishment, you lost a knowledge point for %s.", [s.name], ), "warning", )) : "finished" == t.status && ((i = !0), S.updateAssignedClassStatus(t.id, "active"), n.scheduleToast( a.T("punishment-rerolled-exam-header", "Punishment rerolled"), a.T( "punishment-rerolled-exam-content", "You rerolled your punishment, you lost your exam attendance for %s.", [s.name], ), "warning", )); } } (i || n.scheduleToast( a.T("punishment-rerolled-unknown-header", "Punishment rerolled"), a.T( "punishment-rerolled-unknown-content", "You rerolled your punishment, this will have consequences.", ), "warning", ), T.getRandomPunishmentAction(t)); } static wasFinishedBefore(e) { return p.getFinishedPunishments().includes(e); } static isBlocked(e) { return p.getBlockedPunishments().includes(e); } static finishPunishment(e) { let t = p.getFinishedPunishments(); (t.includes(e) || (t.push(e), p.setFinishedPunishments(t)), T.removeActivePunishment(e)); } static removeActivePunishment(e) { let t = p.getActivePunishments().filter((t) => t !== e); p.setActivePunishments(t); } static isPunishmentLocked(e) { const t = p.punishmentsData[e]; return !T.isPunishmentTierAvailable(t.tier); } static isPunishmentTierAvailable(e) { return ( !("hardcore" === e && !n.getSetting("enableHardcorePunishments")) && (("hard" !== e && "hardcore" !== e) || !(S.collectedCredits < p.requiredCreditsToUnlockHardPunishments)) ); } static getRandomPunishmentAction(e) { let t = T.getRandomPunishment(e); t && _.getInstance().inAppNavigation("?page=punishment&punishmentId=" + t); } static getRandomPunishment(e) { const t = s.getInstance(); if (void 0 !== e && !T.isPunishmentTierAvailable(e)) return void n.showToast( t.T( "punishment-unavailable-header", "Punishment tier not available", ), t.T( "punishment-unavailable-content", "You are not allowed to use these punishments yet.", ), ); let a = p.getActivePunishments(); if (a.length >= o.maxConcurrentPunishments) return void n.showToast( t.T("punishment-max-header", "No additional punishment allowed"), t.T( "punishment-max-content", "You are allowed to have %s active punishments.", [o.maxConcurrentPunishments], ), ); const i = p.getBlockedPunishments(); let r = p.punishmentsData, l = []; for (const t in r) { const a = r[t]; T.isPunishmentActive(t) || (void 0 !== e && a.tier !== e) || (T.isPunishmentTierAvailable(a.tier) && (i.includes(t) || l.push(t))); } if (l.length <= 0) return void n.showToast( t.T("punishment-null-header", "No punishments available"), t.T( "punishment-null-content", "Unable to determine any punishment that you can do.", ), ); const d = l[Math.floor(Math.random() * l.length)]; return (a.push(d), p.setActivePunishments(a), d); } } class x { static getActiveModifiers(e) { let t = o.baseTaskMultiplier, a = p.getAssignedClubs(), s = p.getActivePartners(), i = 0, n = 0, r = 0, l = 0, c = 0; for (let t in a) { let s = a[t], o = s.id, u = s.activePerks, m = p.clubsData[o].perks; for (let t in u) { let a = m[t]; h.isPerkActive(o, t) && (void 0 === e || (a.tags.length > 0 && !a.tags.some((t) => e.includes(t))) || ("difficulty" === a.modType ? (i += Number.isNaN(Number.isInteger(a.modVal)) ? d.getPercentageFromText(a.job) : a.modVal / 100) : "punishment" === a.modType && (l += Number.isNaN(Number.isInteger(a.modVal)) ? 1 : a.modVal), "difficulty" === a.perkType ? (i -= Number.isNaN(Number.isInteger(a.perkVal)) ? d.getPercentageFromText(a.perk) : a.perkVal / -100) : "skip" === a.perkType ? (n += Number.isNaN(Number.isInteger(a.perkVal)) ? d.getClassSkipsFromText(a.perk) : a.perkVal) : "attendance" === a.perkType ? (r += Number.isNaN(Number.isInteger(a.perkVal)) ? 1 : a.perkVal) : "resetRoulette" === a.perkType && (c += Number.isNaN(Number.isInteger(a.perkVal)) ? 1 : a.perkVal))); } } for (let t in s) { let a = s[t], o = a.id, u = a.activePerks, m = p.partnersData[o].perks; for (let t in u) { let a = m[t]; f.isPerkActive(o, t) && (void 0 === e || (a.tags.length > 0 && !a.tags.some((t) => e.includes(t))) || ("difficulty" === a.modType ? (i += Number.isNaN(Number.isInteger(a.modVal)) ? d.getPercentageFromText(a.job) : a.modVal / 100) : "punishment" === a.modType && (l += Number.isNaN(Number.isInteger(a.modVal)) ? 1 : a.modVal), "difficulty" === a.perkType ? (i -= Number.isNaN(Number.isInteger(a.perkVal)) ? d.getPercentageFromText(a.perk) : a.perkVal / -100) : "skip" === a.perkType ? (n += Number.isNaN(Number.isInteger(a.perkVal)) ? d.getClassSkipsFromText(a.perk) : a.perkVal) : "attendance" === a.perkType ? (r += Number.isNaN(Number.isInteger(a.perkVal)) ? 1 : a.perkVal) : "resetRoulette" === a.perkType && (c += Number.isNaN(Number.isInteger(a.perkVal)) ? 1 : a.perkVal))); } } let u = t + i; return ( u < 0.5 && (u = 0.5), { multiplier: u, classSkips: n, bonusAttendance: r, punishments: l, resetRoulette: c, } ); } static perkActivated(e) { if (!e) return; const t = s.getInstance(); if ("punishment" == e.modType) { let a = Number.parseInt(e.modVal); Number.isNaN(a) && (a = 1); for (let e = 0; e < a; e++) (T.getRandomPunishment(void 0), n.scheduleToast( t.T("task-perk-activated-header", "Perk punishment drawn"), t.T( "task-perk-activated-content", "A punishment was automatically drawn.", ), "warning", )); } if ( ("resetRoulette" == e.perkType && (p.setRoulette({}), n.scheduleToast( t.T("task-roulette-reset-header", "Roulette reset perk"), t.T( "task-roulette-reset-content", "The roulette was reset, you may now try again.", ), "info", )), "skip" == e.perkType) ) { let a = Number.isInteger(e.perkVal) ? e.perkVal : 1; ((a = Number.isNaN(a) ? 1 : a), S.addClassSkipToken(a), n.scheduleToast( t.T("task-skip-perk-header", "Class skip perk"), 1 == a ? t.T("task-skip-perk-single-content", "Class skip added.") : t.T("task-skip-perk-multi-content", "%s class skips added.", [ a, ]), "info", )); } } static perkDeactivated(e) { if (!e) return; const t = s.getInstance(); if ( ("resetRoulette" == e.perkType && (p.setRoulette({ id: void 0, rolledOn: n.getNowUnix() }), n.scheduleToast( t.T( "task-roulette-undone-reset-header", "Undone: Roulette reset perk", ), t.T( "task-roulette-undone-reset-content", "The roulette reset was undone, you may now try tomorrow again.", ), "info", )), "skip" == e.perkType) ) { let a = Number.isInteger(e.perkVal) ? e.perkVal : 1; ((a = Number.isNaN(a) ? 1 : a), (a *= -1), S.addClassSkipToken(a), n.scheduleToast( t.T("task-skip-perk-removed-header", "Class skip perk removed"), -1 == a ? t.T( "task-skip-perk-removed-single-content", "Class skip removed.", ) : t.T( "task-skip-perk-removed-multi-content", "%s class skips removed.", [a], ), "info", )); } } static getMultiplier(e) { return x.getActiveModifiers(e).multiplier; } static startTask(e, t, a) { const i = s.getInstance(); let o = $.extend(!0, {}, e), r = o[t][a], l = r.task, d = o[t][a].tags, c = 0, u = 0, m = 0, g = 0; if (!l) return !1; let h = x.getActiveModifiers(d), v = { id: n.getRandomString(), object: o, objectTaskListReference: t, objectTaskReference: a, multiplier: h.multiplier, bonusAttendance: h.bonusAttendance, classSkips: h.classSkips, punishments: h.punishments, resetRoulette: h.resetRoulette, isExam: r.isExam, pauseBonus: 0, timers: [], }; (delete v.object.image, o && o.disableMultiplier && 1 == o.disableMultiplier && (v.multiplier = 1), o && o.disableBonusAttendance && 1 == o.disableBonusAttendance && (v.bonusAttendance = 0), o && o.disableClassSkips && 1 == o.disableClassSkips && (v.classSkips = 0), o && o.disablePunishments && 1 == o.disablePunishments && (v.punishments = 0), o && o.disableResetRoulette && 1 == o.disableResetRoulette && (v.resetRoulette = 0)); let b = n.getNow().hour(23).minute(0).second(0), f = n.getNow(); (f > b && (f = f.add(1, "day")), f.unix() < g && (f = moment.unix(g))); const k = (e) => ("minutes" == e.unit || "seconds" == e.unit) && (void 0 === e.spawnTimer || !1 !== e.spawnTimer), y = (e) => "punishments" == e.unit && null != e.punTier && !!Number.isInteger(e.value), w = (e) => void 0 === e.provideCounter || !1 !== e.provideCounter; for (let e of Object.keys(r.parameters)) { let t = r.parameters[e]; if (k(t)) { let e = 0; if ( ("minutes" == t.unit ? (e = 60 * t.value) : "seconds" == t.unit && (e = t.value), t.applyMultiplier && (e = Math.round(e * v.multiplier)), e > 0) ) { let a = n.getNowUnix(), s = a + e; ((m += e), g < s && (g = s)); let i = { id: n.getRandomString(), requiredTime: e, startAt: a, expectedFinishAt: s, timerInfo: t.timerInfo || "", pausedAt: void 0, totalPauseTime: 0, }; (t.hiddenTask && (i.hiddenTask = !0), t.punishTime && t.punishTimeMinutes && Number.isInteger(t.punishTimeMinutes) && ((i.punishTime = !0), (i.punishSeconds = 60 * Number.parseInt(t.punishTimeMinutes))), (t.hiddenTask || (void 0 !== t.startTimerAutomatically && !0 !== t.startTimerAutomatically)) && ((i.pausedAt = a), (v.pauseBonus += c)), (c = e), (u += 1), v.timers.push(i)); } } else if (y(t)) { "" == t.punTier && (t.punTier = void 0); for (let e = 0; e < Number.parseInt(t.value); e++) (T.getRandomPunishment(t.punTier), n.scheduleToast( i.T("task-punishment-drawn-header", "Task punishment drawn"), i.T( "task-punishment-drawn-content", "As defined by this task a %s punishment was automatically drawn.", [ t.punTier ? t.punTier : i.T("task-random-punishment", "random"), ], ), "info", )); } else if (w(t)) { let e = n.getNowUnix(), a = { id: n.getRandomString(), requiredTime: 0, startAt: e, expectedFinishAt: e, mustBeFinishedBy: f.endOf("day").unix(), pausedAt: void 0, totalPauseTime: 0, timerInfo: t.timerInfo || "", counter: t.value, unit: t.unit, }; (t.hiddenTask && (a.hiddenTask = !0), t.applyMultiplier && (a.counter = Math.round(a.counter * v.multiplier)), v.timers.push(a)); } } if ((u > 1 && c > 0 && (v.pauseBonus += c), v.timers.length <= 0)) { let e = n.getNowUnix(), t = { id: n.getRandomString(), requiredTime: 0, startAt: e, expectedFinishAt: e, mustBeFinishedBy: n.getNow().add(1, "day").startOf("day").unix(), pausedAt: void 0, totalPauseTime: 0, timerInfo: "", }; v.timers.push(t); } let C = p.getActiveTasks(); (C.push(v), p.setActiveTasks(C), _.inAppReload()); } static pauseTask(e, t) { let a = p.getActiveTasks(); ((a = a.map((a) => { if (a.id === e) { let e = n.getNowUnix(); for (let s in a.timers) { let i = a.timers[s]; i.id === t && (i.expectedFinishAt <= e || (void 0 === i.mustBeFinishedBy && (i.pausedAt = e))); } } return a; })), p.setActiveTasks(a), _.inAppReload()); } static resumeTask(e, t) { let a = p.getActiveTasks(); ((a = a.map((a) => { if (a.id === e) { let e = n.getNowUnix(); for (let s in a.timers) { let i = a.timers[s]; if (i.id !== t) continue; if (void 0 === i.pausedAt) continue; let n = i.pausedAt, o = i.startAt; i.totalPauseTime += e - n; let r = n - o; ((i.startAt = e), (i.requiredTime = i.requiredTime - r), (i.expectedFinishAt = i.startAt + i.requiredTime), (i.pausedAt = void 0)); } } return a; })), p.setActiveTasks(a), _.inAppReload()); } static finishManualTask(e) { let t = p.getActiveTasks(); ((t = t.map((t) => { if (t.id === e) for (let e in t.timers) { let a = t.timers[e]; void 0 !== a.mustBeFinishedBy && (a.mustBeFinishedBy = void 0); } return t; })), p.setActiveTasks(t), x.checkForFinishedTasks(), _.inAppReload()); } static punishTask(e) { let t = p.getActiveTasks(); ((t = t.map((t) => { if (t.id === e) for (let e in t.timers) { let a = t.timers[e], s = n.getNowUnix(); a.expectedFinishAt <= s || (a.punishTime && (a.expectedFinishAt = a.expectedFinishAt + a.punishSeconds)); } return t; })), p.setActiveTasks(t), _.inAppReload()); } static failTaskAction(e) { const t = s.getInstance(); n.requestUserConfirmation( t.T("task-fail-header", "Fail task"), t.T( "task-fail-content", "Are you sure that you want to fail this task?", ), () => { (x.failTask(e), _.inAppReload()); }, ); } static failTask(e) { let t = p.getActiveTasks(), a = t.find((t) => t.id === e); if (!a) return !1; if (a.failed) return !1; a.failed = !0; let s = n.getNowUnix(); for (let e in a.timers) { let t = a.timers[e]; t.expectedFinishAt <= s || ((t.expectedFinishAt = n.getNowUnix()), (t.pausedAt = void 0), (t.mustBeFinishedBy = void 0)); } return (p.setActiveTasks(t), a); } static failTaskPunishment(e) { const t = s.getInstance(); let a = 0; ("custom" == e.object.type && ((a = o.failCustomTask), n.scheduleToast( t.T("task-punishment-header", "Punishment"), `${t.T("task-punishment-custom-task", "You failed a custom Task!")} ${o.failCustomTask > 1 ? t.T("task-punishments-drawn", "%s punishments were drawn!", [o.failCustomTask]) : t.T("task-punishment-drawn", "A punishment was drawn!")}`, "danger", )), "class" == e.object.type && ("tasks" == e.objectTaskListReference && ((a = o.failDailyPunishment), n.scheduleToast( t.T("task-punishment-header", "Punishment"), `${t.T("task-punishment-task", "You failed a task!")} ${o.failDailyPunishment > 1 ? t.T("task-punishments-drawn", "%s punishments were drawn!", [o.failDailyPunishment]) : t.T("task-punishment-drawn", "A punishment was drawn!")}`, "danger", )), e.isExam && ((a = o.failExamPunishment), n.scheduleToast( t.T("task-punishment-header", "Punishment"), `${t.T("task-punishment-exam", "You failed an exam!")} ${o.failExamPunishment > 1 ? t.T("task-punishments-drawn", "%s punishments were drawn!", [o.failExamPunishment]) : t.T("task-punishment-drawn", "A punishment was drawn!")}`, "danger", ))), "major" == e.object.type && e.isExam && ((a = o.failThesisPunishment), n.scheduleToast( t.T("task-punishment-header", "Punishment"), `${t.T("task-punishment-thesis", "You failed a thesis!")} ${o.failThesisPunishment > 1 ? t.T("task-punishments-drawn", "%s punishments were drawn!", [o.failThesisPunishment]) : t.T("task-punishment-drawn", "A punishment was drawn!")}`, "danger", )), "punishment" == e.object.type && T.rerollPunishment(e.object.id, e.object.tier)); for (let e = 0; e < a; e++) T.getRandomPunishment(void 0); } static isAnyTaskActive() { let e = p.getActiveTasks(); return e && e.length > 0; } static checkForFinishedTasks() { if (!x.isAnyTaskActive()) return !1; const e = s.getInstance(); let t = p.getActiveTasks(), a = t.filter((e) => { if (e.failed) return !1; let t = n.getNowUnix(); for (let a in e.timers) { let s = e.timers[a]; if (!(s.counter && s.counter <= 0) && !(s.expectedFinishAt > t)) { if (void 0 !== s.mustBeFinishedBy && s.mustBeFinishedBy <= t) return !0; if ( void 0 !== s.pausedAt && t - e.pauseBonus - s.pausedAt + s.totalPauseTime > 60 * o.maxTaskPause ) return !0; } } return !1; }); for (let s of a) ("class" != s.object.type && "major" != s.object.type && "custom" != s.object.type) || ((s = x.failTask(s.id)), (t = t.filter((e) => e.id != s.id)), t.push(s), n.scheduleToast( e.T("task-failed-header", "Task failed"), e.T("task-failed-content", "You failed the task %s!", [ s.object.name, ]), "danger", )); a.length > 0 && p.setActiveTasks(t); let i = t.filter((e) => { if (e.failed) return !0; let t = n.getNowUnix(); for (let a in e.timers) { let s = e.timers[a]; if (s.counter && s.counter > 0) return !1; if (s.expectedFinishAt > t) return !1; if (void 0 !== s.mustBeFinishedBy) return !1; if (void 0 !== s.pausedAt) return !1; } return !0; }), r = !1, l = !1; for (let a of i) { if ( ((t = t.filter((e) => !(e.id === a.id))), "custom" == a.object.type) ) { if (((r = !0), a.failed)) { x.failTaskPunishment(a); continue; } n.scheduleToast( e.T("task-custom-task-finished-header", "Custom task finished"), e.T( "task-custom-task-finished-content", "You finished a custom task!", ), "success", ); } if ("class" == a.object.type) { let t = n.getNow().add(-1, "day").startOf("day").unix(); for (let e in a.timers) { let s = a.timers[e]; s.startAt > t && (t = s.startAt); } if ((S.setLastAttended(a.object.id, t), (r = !0), a.failed)) { x.failTaskPunishment(a); continue; } (S.addClassAttendance(a.object.id, 1 + a.bonusAttendance), a.isExam ? (S.updateAssignedClassStatus(a.object.id, "finished"), n.scheduleToast( e.T("task-class-finished-header", "Class finished"), e.T( "task-class-finished-content", "Congratulations you finished class %s!", [a.object.name], ), "success", )) : n.scheduleToast( e.T("task-finished-header", "Task finished"), e.T( "task-finished-content", "You finished a task for %s!", [a.object.name], ), "success", )); } if ("major" == a.object.type) { if (a.failed) { ((r = !0), x.failTaskPunishment(a)); continue; } ((r = !1), n.scheduleToast( e.T("task-major-finished-header", "Major finished"), e.T("task-major-finished-content", "You graduated %s!", [ a.object.name, ]), "success", ), (l = a.object.id)); let t = p.getFinishedMajors(); (t.push(l), p.setFinishedMajors(t), p.setActiveMajor()); } if ("punishment" == a.object.type) { if (((r = !0), a.failed)) { x.failTaskPunishment(a); continue; } (T.finishPunishment(a.object.id), n.scheduleToast( e.T("task-punishment-finished-header", "Punishment finished"), e.T( "task-punishment-finished-content", "You finished the punishment %s!", [a.object.name], ), "success", )); } } (i.length > 0 && p.setActiveTasks(t), l && _.navigate("?page=graduated&majorId=" + l), r && _.inAppReload()); } static getActiveTaskForObject(e, t) { if (x.isAnyTaskActive()) return p .getActiveTasks() .find((a) => a.object.id == e && a.object.type == t); } static isTaskActiveForObject(e, t) { return void 0 !== x.getActiveTaskForObject(e, t); } static manualTaskCountdown(e, t) { let a = 1; t.counter > 20 && (a = 10); let s = p.getActiveTasks(); ((s = s.map((s) => { if (s.id === e) for (let e in s.timers) { let i = s.timers[e]; i.id === t.id && ((i.counter -= a), i.counter > 0 || (i.mustBeFinishedBy = void 0)); } return s; })), p.setActiveTasks(s), $("#manualTaskCounter-" + t.id).text("" + t.counter), t.counter <= 0 && (x.checkForFinishedTasks(), _.inAppReload())); } static getTaskProgressHtml(e) { if (!e || void 0 === e) return ""; let t = e.object.tasks[e.objectTaskReference], a = "", i = n.getNowUnix(), o = !1; const r = s.getInstance(); for (let t in e.timers) { let s = e.timers[t]; if ( !( s.expectedFinishAt <= i && void 0 === s.pausedAt && void 0 === s.mustBeFinishedBy ) && (!o || !s.hiddenTask) ) if (void 0 !== s.mustBeFinishedBy) { o = !0; let t = ""; ((t = s.counter > 0 ? `` : ``), (a += `

    ${s.timerInfo.length > 0 ? `${s.timerInfo}: ` : ""}${r.T("task-finish-by-text", "This task must have been finished %s.", [n.getNow().to(moment.unix(s.mustBeFinishedBy))])}

    \n
    \n ${t}\n \n
    `)); } else { o = !0; let t = setInterval(() => { x.updateProgressTask( s.id, s.startAt, s.expectedFinishAt, s.pausedAt, t, s.totalPauseTime, e.pauseBonus, ); }, 1e3); (u.getInstance().addTaskInterval(s.id, t), (a += `

    ${s.timerInfo.length > 0 ? `${s.timerInfo}:` : `${r.T("task-time-left-info", "Time left")}:`} 00:00:00

    \n
    \n
    \n
    \n
    \n ${ void 0 !== s.pausedAt ? `` : `` }\n\n ${ s.punishTime ? `` : "" }\n \n
    `)); } } return `
    \n
    \n ${void 0 !== e.pausedAt ? ' ' + r.T("task-status-paused", "Task is paused") : ' ' + r.T("task-status-active", "Task in progress")}\n
    \n
    \n

    ${r.T("task-active-task", "Active task")}: ${t.task}

    \n ${a}\n
    \n
    `; } static updateProgressTask(e, t, a, s, i, r, l) { let c = $("#taskProgress-" + e), u = $("#taskTime-" + e); if (c.length <= 0 || u.length <= 0) return (clearInterval(i), !1); let p = n.getNowUnix(), m = !1, g = 0; ((t = t), (a = a), void 0 !== s && ((m = !0), (g = s - p - r + l + 60 * o.maxTaskPause), (p = s))); let h = Math.floor(((p - t) / (a - t)) * 100); (c.css("width", h + "%"), c.text(h + "%"), u.html( `${d.dateObjectToHumanTime(a - p)} ${m ? ' ' + d.minutesToTime(Math.floor(g / 60)) : ""}`, ), h >= 100 && clearInterval(i), (h >= 100 || (m && g <= 0)) && (x.checkForFinishedTasks(), _.inAppReload())); } static checkForSkippedClasses() { if (n.getSetting("enableEasyMode")) return; const e = s.getInstance(), t = n.getNow().add(-1, "day").startOf("day").unix(), a = p .getAssignedClasses() .filter( (e) => "active" == e.status && void 0 !== e.lastAttended && e.lastAttended < t, ); if (a.length <= 0) return; let i = p.getActivePunishments().length, r = !1; for (let s of a) { if (x.isTaskActiveForObject(s.id, "class")) continue; let a = s.lastAttended; const l = p.classesData[s.id]; for (; a < t && i < o.maxConcurrentPunishments; ) { a = moment.unix(a).add(1, "day").unix(); const t = moment.unix(a).day(); 0 != t && 6 != t && l.days.includes(t) && ((r = !0), p.getClassSkipTokens() > 0 ? p.setClassSkipTokens(p.getClassSkipTokens() - 1) : (i++, T.getRandomPunishment(), n.scheduleToast( e.T("task-class-missed-header", "Class missed"), e.T( "task-class-missed-content", "You missed a class and a punishment was rolled.", ), "danger", ))); } S.setLastAttended(s.id, t); } r && _.reload(); } } class C { static get expectedParameters() { return { getView: [] }; } static getView() { const e = p.majorsData; let t = ""; for (const a in e) { let s = e[a]; t += C.getCardHtml(s); } return `
    \n
    ${t}
    \n
    `; } static getCardHtml(e) { const t = p.getActiveMajor() === e.id, a = I.isMajorFinished(e.id), i = a && I.getGrade(e.id), o = I.isAnyMajorActive(), r = n.getRandomString(), l = x.getActiveTaskForObject(e.id, "major"), d = void 0 !== l, c = s.getInstance(); let u = ''; d && (u = ''); let m = "major"; return ( a && (m = "graduated"), `
    \n
    \n
    \n \n \n \n ${t ? n.getBackdropContainer(c.T("major-status-active", "Active") + ' ', "success") : ""}\n ${a ? n.getBackdropContainer(`${c.T("major-status-grade", "Grade")} ${i} `, "info") : ""} \n ${!o || t || a ? "" : n.getBackdropContainer(c.T("major-status-unavailable", "Not available") + ' ')}\n ${n.getButtonsContainer( [ `${u}`, C.getJoinButton(e.id), C.getDropButton(e.id), ], )}\n
    \n
    \n ${void 0 !== l ? x.getTaskProgressHtml(l) : `\n
      \n ${C.getThesisHtml(e)}\n
    `}\n
    \n
    \n
    \n
    \n
    ${e.name} ${e.name2}
    \n

    ${e.description}

    \n
    \n
    \n
    ` ); } static getThesisHtml(e) { let t = ""; for (let a in e.tasks) e.tasks[a].isExam && (t += `
  • \n ${e.tasks[a].task}\n
  • `); return t; } static getJoinButton(e) { return I.isJoinButtonAvailable(e) ? `` : ""; } static getDropButton(e) { return I.isDropButtonAvailable(e) ? `` : ""; } } class M { static get expectedParameters() { return { getView: ["majorId"] }; } static getView(e) { const t = e.majorId, a = I.isMajorActive(t), i = x.getActiveTaskForObject(t, "major"), n = s.getInstance(); let o = p.majorsData[t]; return `
    \n

    ${o.name}

    \n
    \n
    \n
    \n
    \n \n \n \n ${a ? `
    ${n.T("major-status-active", "Active")}
    ` : ""}\n
    \n
    \n
    \n

    \n ${n.T("major-detail-prerequisites", "Prerequisites")}: ${A.prerequisitesToString(o.prerequisites)}\n

    \n

    ${o.description}

    \n ${M.getThesisRequirements(o.id)}\n ${void 0 !== i ? x.getTaskProgressHtml(i) : `\n
      \n
    • ${n.T("major-detail-thesis", "Available thesis topics")}:
    • \n ${M.getThesisHtml(o)}\n
    `}\n
    \n ${M.getJoinButton(o.id)}\n ${M.getDropButton(o.id)}\n
    \n
    \n
    \n
    \n
    \n `; } static getThesisHtml(e) { let t = ""; for (let a in e.tasks) e.tasks[a].isExam && (t += `
  • \n
    \n
    \n \n
    \n
    ${e.tasks[a].task}${M.getThesisButton(e.id, a)}
    \n
    \n
  • `); return t; } static getThesisButton(e, t) { return I.isThesisAvailable(e) ? `
    ` : ""; } static getJoinButton(e) { return I.isJoinButtonAvailable(e) ? `
    ` : ""; } static getDropButton(e) { return I.isDropButtonAvailable(e) ? `
    ` : ""; } static getThesisRequirements(e) { const t = s.getInstance(); let a = `
  • ${t.T("major-detail-thesis-requirement-header", "Thesis requirements")}:
  • `; return ( p.getActiveMajor() != e && (a += `
  • \n
    \n
    \n \n
    \n
    ${t.T("major-detail-thesis-requirement-join", "You must join this major, before you can do the thesis.")}
    \n
    \n
  • `), S.collectedCredits < p.requiredCreditsForGraduation && (a += `
  • \n
    \n
    \n \n
    \n
    ${t.T("major-detail-thesis-requirement-credits", "You must collect at least %s credits.", [p.requiredCreditsForGraduation])}
    \n
    \n
  • `), p.getActivePunishments().length > 0 && (a += `
  • \n
    \n
    \n \n
    \n
    ${t.T("major-detail-thesis-requirement-punishments", "You must finish all punishments.")}
    \n
    \n
  • `), I.prerequisitesDone(e) || (a += `
  • \n
    \n
    \n \n
    \n
    ${t.T("major-detail-thesis-requirement-prerequisites", "You must finish all prerequisites.")}
    \n
    \n
  • `), x.isTaskActiveForObject(e, "major") && (a += `
  • \n
    \n
    \n \n
    \n
    ${t.T("major-detail-thesis-requirement-thesis", "You can only attend one thesis at a time.")}
    \n
    \n
  • `), `` ); } } class I { static getGrade(e) { let t = 0, a = 0; const s = I.mandatoryClasses(e); for (const e of s) ((t += S.getAssignedClass(e).attendances), (a += o.maxAttendances[p.classesData[e].tier])); return n.getGrade(t, a); } static isJoinButtonAvailable(e) { return !I.isAnyMajorActive() && !I.isMajorFinished(e); } static isDropButtonAvailable(e) { return I.isMajorActive(e); } static isMajorActive(e) { return p.getActiveMajor() === e; } static isMajorFinished(e) { return p.getFinishedMajors().includes(e); } static mandatoryClasses(e) { const t = p.majorsData[e].prerequisites; let a = []; a = a.concat(t); for (let e of t) a = a.concat(S.getClassPrerequisitesDeep(e)); return ((a = a.sort((e, t) => e - t)), [...new Set(a)]); } static isAnyMajorActive() { return void 0 !== p.getActiveMajor(); } static joinMajor(e) { (p.setActiveMajor(e), _.getInstance().inAppNavigation("?page=progress")); } static dropMajor() { const e = s.getInstance(); n.requestUserConfirmation( e.T("major-drop-confirm-header", "Drop major"), e.T( "major-drop-confirm-content", "Are you sure that you want to drop your current major?", ), () => { p.setActiveMajor(); for (let e = 0; e < o.majorDropPunishments; e++) { T.getRandomPunishment() || (e = o.majorDropPunishments); } (n.scheduleToast( e.T("major-dropped-header", "Major dropped"), e.T( "major-dropped-content", "For dropping the major punishments where drawn.", ), "danger", ), _.getInstance().inAppNavigation("?page=majors")); }, ); } static startThesis(e, t) { if (!I.isThesisAvailable(e)) return !1; x.startTask(p.majorsData[e], "tasks", t); } static isThesisAvailable(e) { return ( p.getActiveMajor() == e && !(S.collectedCredits < p.requiredCreditsForGraduation) && !(p.getActivePunishments().length > 0) && !!I.prerequisitesDone(e) && !x.isTaskActiveForObject(e, "major") ); } static prerequisitesDone(e) { const t = p.majorsData[e]; if (!t) return !1; if (0 === t.prerequisites.length) return !0; return !( t.prerequisites.filter((e) => { const t = S.getAssignedClass(e); return !t || "finished" !== t.status; }).length > 0 ); } } class P { static get expectedParameters() { return { getView: [] }; } static getView() { const e = p.classesData, t = n.getSetting("hideActiveClasses"), a = n.getSetting("hideFinishedClasses"), i = p.getActiveMajor(), o = I.mandatoryClasses(i), r = s.getInstance(); let l = "", d = "", c = "", u = "", m = !0, g = !0, h = !0, v = "beginner", b = 1, f = 2, k = 3, y = []; for (const s in e) { let i = S.getAssignedClass(s); if (i) { if (t && "active" === i.status) continue; if (a && "finished" === i.status) continue; } let n = e[s]; ((n.assigned = i), y.push(n)); } y = y.sort((e, t) => e.name.localeCompare(t.name)); for (const e of y) { const t = o.some((t) => t == e.id), a = P.getCardHtml(e, e.assigned, t); "beginner" === e.tier ? (l += a) : "intermediate" === e.tier ? (d += a) : "advanced" === e.tier ? (c += a) : "master" === e.tier && (u += a); } return ( (m = l.length > 0), (g = d.length > 0), (h = c.length > 0), m || ((v = "intermediate"), g || ((v = "advanced"), h || (v = "master"))), h || (k -= 1), g || ((k -= 1), (f -= 1)), m || ((k -= 1), (f -= 1), (b -= 1)), `
    \n
    \n \n
    \n \n
    \n
    \n
    ` ); } static getCardHtml(e, t, a, i) { let o = S.isClassLocked(e.id), r = x.getActiveTaskForObject(e.id, "class"), l = t ? t.status : "", d = void 0 !== r, c = "finished" === l, u = c && S.getGrade(e.id), p = "active" === l, m = p && S.getAttendanceRate(e.id), g = n.getRandomString(), h = s.getInstance(), v = ''; return ( d && (v = ''), `
    \n
    \n\n
    \n \n \n \n ${p ? n.getBackdropProgressContainer(h.T("class-status-active", "Active") + ' ', m) : ""}\n ${o ? n.getBackdropContainer(h.T("class-status-locked", "Locked") + ' ', "danger") : ""} \n ${c ? n.getBackdropContainer(`${h.T("class-status-grade", "Grade")} ${u} `, "info") : ""} \n ${n.getButtonsContainer( [ c ? "" : `${v}`, P.getJoinButton(e.id), P.getDropButton(e.id), P.getRetakeButton(e.id), ], )}\n
    \n
    \n ${void 0 !== r ? x.getTaskProgressHtml(r) : `\n
      \n
    • ${h.T("class-card-tasks", "Available tasks")}:
    • \n ${P.getTasksHtml(e, t.attendances || 0)}\n
    • ${h.T("class-card-exams", "Available exams")}:${p || c ? S.getKnowledgePointsInfo(e.id) : ""}
    • \n\n ${S.isExamAvailable(e.id) ? P.getExamsHtml(e) : `
    • \n
      \n
      \n \n
      \n
      ${h.T("class-card-exams-locked", "Currently locked")}
      \n
      \n
    • `}\n\n
    `}\n
    \n
    \n
    \n\n
    \n
    \n ${e.name} \n ${a ? ' ' : ""}\n ${e.name2}\n
    \n

    ${e.description}

    \n
    \n
    \n
    ` ); } static getJoinButton(e) { return S.isJoinButtonAvailable(e) ? `` : ""; } static getDropButton(e) { return S.isDropButtonAvailable(e) ? `` : ""; } static getRetakeButton(e) { return S.isRetakeButtonAvailable(e) ? `` : ""; } static getTasksHtml(e, t) { let a = "", s = S.getTodaysTasks(e.tasks, t, e.taskListSize); for (let e of s) a += `
  • \n
    \n
    \n \n
    \n
    ${e.task}
    \n
    \n
  • `; return a; } static getExamsHtml(e) { let t = ""; for (let a in e.tasks) e.tasks[a].isExam && (t += `
  • \n
    \n
    \n \n
    \n
    ${e.tasks[a].task}
    \n
    \n
  • `); return t; } } class A { static get expectedParameters() { return { getView: ["classId"] }; } static getView(e) { const t = e.classId, a = S.isClassLocked(t), i = S.getAssignedClass(t), o = i ? i.status : "", r = x.getActiveTaskForObject(t, "class"), l = void 0 !== r, d = "finished" === o, c = "active" === o, u = d && S.getGrade(t), m = s.getInstance(); let g = p.classesData[t]; return `
    \n

    ${g.name}

    \n
    \n
    \n \n
    \n
    \n
    \n \n \n \n ${a ? `
    ${m.T("class-status-locked", "Locked")}
    ` : ""}\n ${d ? `
    ${m.T("class-status-grade", "Grade")} ${u}
    ` : ""}\n ${c ? `
    ${m.T("class-status-active", "Active")}
    ` : ""}\n
    \n
    \n
    \n ${A.getPrerequisites(g.prerequisites)}\n

    ${m.T("class-detail-days", "Classes on")}: ${n.daysToString(g.days)}

    \n ${A.getMandatoryInfoHtml(g.id)}\n ${A.getBonusAttendanceDisabledHtml(g)}\n

    \n ${g.description}\n ${A.getCommentHtml(g.comment)}\n

    \n ${l ? x.getTaskProgressHtml(r) : d ? "" : `\n
      \n
    • ${m.T("class-detail-tasks", "Available task options")}:
    • \n ${A.getTasksHtml(g, i.attendances || 0)}\n
    \n ${S.isExamAvailable(g.id) ? `
      \n
    • ${m.T("class-detail-exams", "Available exam options")}:${c || d ? S.getKnowledgePointsInfo(g.id) : ""}
    • \n
    • ${m.T("class-detail-expected-grade", "If you take the exam now the expected grade is %s.", [S.getGrade(g.id)])}
    • \n ${A.getExamsHtml(g)}\n
    ` : A.getExamPlaceholder(i, g)}\n
    \n ${A.getJoinButton(g.id)}\n ${A.getDropButton(g.id)}\n
    \n `}\n ${A.getRetakeInfo(g.id)}\n
    \n
    \n \n
    \n
    \n
    `; } static getMandatoryInfoHtml(e) { return S.isClassMandatory(e) ? `

    ${s.getInstance().T("class-detail-mandatory", "This is a mandatory class %s for your currently active major.", [''])}

    ` : ""; } static getBonusAttendanceDisabledHtml(e) { return e.disableBonusAttendance || !1 ? `

    ${s.getInstance().T("class-detail-bonus-attendance-disabled", "This class disabled any bonus attendance modifiers!")}

    ` : ""; } static getTasksHtml(e, t) { let a = "", s = S.getTodaysTasks(e.tasks, t, e.taskListSize); for (let t of s) a += `
  • \n
    \n
    \n \n
    \n
    ${t.task} ${A.getDailyButton(e.id, t.id)}
    \n
    \n
  • `; return a; } static getExamsHtml(e) { let t = ""; for (let a in e.tasks) e.tasks[a].isExam && (t += `
  • \n
    \n
    \n \n
    \n
    ${e.tasks[a].task}${A.getExamButton(e.id, a)}
    \n
    \n
  • `); return t; } static getCommentHtml(e) { return !e || null == e || e.length <= 0 ? "" : `
    ${e}`; } static getPrerequisites(e) { const t = A.prerequisitesToString(e); return t.length > 0 ? `

    ${s.getInstance().T("prerequisites", "Prerequisites")}: ${t}

    ` : ""; } static prerequisitesToString(e) { return e .map( (e) => `${p.classesData[e].name}`, ) .join(", "); } static getJoinButton(e) { return S.isJoinButtonAvailable(e) ? `` : ""; } static getDropButton(e) { return S.isDropButtonAvailable(e) ? `` : ""; } static getRetakeInfo(e) { const t = s.getInstance(); return S.isRetakeButtonAvailable(e) ? `\n
    \n
    \n ${t.T("class-detail-retake-warning", "You already finished this class and got a %s, there is still room for improvement. You may retake this class and collect additional knowledge before attempting the exam again to get a better grade.", [`${S.getGrade(e)}`])}\n
    \n \n
    \n ` : ""; } static getDailyButton(e, t) { return S.isDailyAvailable(e) ? `
    ` : ""; } static getExamButton(e, t) { return S.isExamAvailable(e) ? `
    ` : ""; } static getExamPlaceholder(e, t) { const a = s.getInstance(); let i = `
  • ${a.T("class-detail-exam-requirement-header", "Exam unlock requirements")}:
  • `; return ( "active" !== e.status && (i += `
  • \n
    \n
    \n \n
    \n
    ${a.T("class-detail-exam-requirement-join", "You must join this class, before you can do any tasks or exams.")}
    \n
    \n
  • `), (S.wasAttendedToday(t.id) || x.isTaskActiveForObject(t.id, "class")) && (i += `
  • \n
    \n
    \n \n
    \n
    ${a.T("class-detail-exam-requirement-attended", "You already attended this class today, you can't do a task and the exam on the same day.")}
    \n
    \n
  • `), e.attendances < o.expectedAttendance[t.tier] && (i += `
  • \n
    \n
    \n \n
    \n
    ${a.T("class-detail-exam-requirement-attendance", "You must have collected at least %s knowledge points, you accumulated %s knowledge points.", [o.expectedAttendance[t.tier], e.attendances])}
    \n
    \n
  • `), e.attendances < Object.entries(t.tasks).filter((e) => !e[1].isExam).length && (i += `
  • \n
    \n
    \n \n
    \n
    ${a.T("class-detail-exam-requirement-attendance-tasks", "You must have done all available tasks.")}
    \n
    \n
  • `), p.getActivePunishments().length > 0 && (i += `
  • \n
    \n
    \n \n
    \n
    ${a.T("class-detail-exam-requirement-punishment", "You must finish all punishments.")}
    \n
    \n
  • `), n.hasMatchingDate(t.days) || (i += `
  • \n
    \n
    \n \n
    \n
    ${a.T("class-detail-exam-requirement-days", "You can only attend this exam on the defined days.")}
    \n
    \n
  • `), `` ); } } class S { static getKnowledgePointsInfo(e) { return `
    ${S.getAttendance(e)} / ${S.getMaxAttendances(e)}`])}">
    `; } static getAttendance(e) { return S.getAssignedClass(e).attendances; } static getMaxAttendances(e) { return o.maxAttendances[p.classesData[e].tier]; } static getAttendanceRate(e) { return (S.getAttendance(e) / S.getMaxAttendances(e)) * 100; } static getGrade(e) { return n.getGrade(S.getAttendance(e), S.getMaxAttendances(e)); } static startDaily(e, t) { if (!S.isDailyAvailable(e)) return !1; x.startTask(p.classesData[e], "tasks", t); } static startExam(e, t) { if (!S.isExamAvailable(e)) return !1; x.startTask(p.classesData[e], "tasks", t); } static isJoinButtonAvailable(e) { const t = S.getAssignedClass(e), a = S.isClassLocked(e); return ( !S.isMaxClassesReached() && !a && (!t || ("active" !== t.status && "finished" !== t.status)) ); } static isRetakeButtonAvailable(e) { const t = S.getAssignedClass(e); return t && "finished" === t.status && "A+" !== S.getGrade(e); } static isDropButtonAvailable(e) { const t = S.getAssignedClass(e); return t && "active" === t.status; } static getAssignedClass(e) { const t = p.getAssignedClasses().filter((t) => t.id == e); return 0 !== t.length && t[0]; } static getActiveClasses() { return p.getAssignedClasses().filter((e) => "active" == e.status); } static getFinishedClasses() { return p.getAssignedClasses().filter((e) => "finished" == e.status); } static updateAssignedClassStatus(e, t) { const a = p .getAssignedClasses() .map((a) => (a.id === e && (a.status = t), a)); p.setAssignedClasses(a); } static addAssignedClass(e) { if (S.getAssignedClass(e)) return void S.updateAssignedClassStatus(e, "active"); const t = { id: e, status: "active", attendances: 0 }; let a = p.getAssignedClasses(); (a.push(t), p.setAssignedClasses(a)); } static getActiveTags() { let e = S.getActiveClasses(), t = p.classesData, a = []; for (let s of e) { let e = t[s.id].tasks; for (let t of Object.keys(e)) a = a.concat(e[t].tags); } return [...new Set(a)]; } static addClassSkipToken(e) { if (!Number.isInteger(e)) return; let t = p.getClassSkipTokens() + e; (t > o.maxConcurrentClasses && (t = o.maxConcurrentClasses), t < 0 && (t = 0), p.setClassSkipTokens(t)); } static wasAttendedToday(e) { if (n.getSetting("enableEasyMode")) return !1; const t = S.getAssignedClass(e); if (!t) return !1; let a = t.lastAttended; return !(!a || void 0 === a) && n.isToday(a); } static setLastAttended(e, t) { let a = p.getAssignedClasses(); ((a = a.map((a) => (a.id == e && (a.lastAttended = t), a))), p.setAssignedClasses(a)); } static addClassAttendance(e, t) { let a = p.getAssignedClasses(); ((a = a.map((a) => (a.id == e && (a.attendances += t), a))), p.setAssignedClasses(a)); } static isClassMandatory(e) { return I.mandatoryClasses(p.getActiveMajor()).some((t) => t == e); } static getClassPrerequisitesDeep(e) { const t = p.classesData[e].prerequisites; let a = []; a = a.concat(t); for (let e of t) a = a.concat(S.getClassPrerequisitesDeep(e)); return a; } static isClassLocked(e) { const t = p.classesData[e]; let a = 0, s = S.collectedCredits; return ( "intermediate" === t.tier ? (a = p.requiredCreditsUnlockIntermediate) : "advanced" === t.tier ? (a = p.requiredCreditsUnlockAdvanced) : "master" === t.tier && (a = p.requiredCreditsUnlockMaster), s < a || !S.prerequisitesDone(e) ); } static get collectedCredits() { return S.getFinishedClasses().reduce( (e, t) => e + (o.classesExamCredits[p.classesData[t.id].tier] || 0), 0, ); } static isMaxClassesReached() { return S.getActiveClasses().length >= o.maxConcurrentClasses; } static retakeClass(e) { const t = s.getInstance(); S.isMaxClassesReached() ? n.showToast( t.T("class-max-classes-header", "Maximum classes active"), t.T( "class-max-classes-content", "You are only allowed to join %s classes at a time.", [o.maxConcurrentClasses], ), ) : n.requestUserConfirmation( t.T("class-retake-confirmation-header", "Retake class"), t.T( "class-retake-confirmation-content", "Are you sure that you want to retake this class? You will not loose any accumulated experience but must attend the exam again.", ), () => { (S.updateAssignedClassStatus(e, "active"), S.setLastAttended( e, n.getNow().add(-1, "day").startOf("day").unix(), ), _.getInstance().inAppNavigation("?page=class&classId=" + e)); }, ); } static joinClass(e) { const t = s.getInstance(); S.isMaxClassesReached() ? n.showToast( t.T("class-max-classes-header", "Maximum classes active"), t.T( "class-max-classes-content", "You are only allowed to join %s classes at a time.", [o.maxConcurrentClasses], ), ) : (S.addAssignedClass(e), _.getInstance().inAppNavigation("?page=class&classId=" + e)); } static dropAssignedClass(e) { const t = s.getInstance(); n.requestUserConfirmation( t.T("class-drop-confirmation-header", "Drop class"), t.T( "class-drop-confirmation-content", "Are you sure that you want to drop this class?", ), () => { (S.updateAssignedClassStatus(e, "dropped"), _.getInstance().inAppNavigation("?page=classes")); }, ); } static finishAssignedClass(e) { (S.updateAssignedClassStatus(e, "finished"), _.getInstance().inAppNavigation("?page=class&classId=" + e)); } static isDailyAvailable(e) { const t = S.getAssignedClass(e); if (!t || "active" !== t.status) return !1; if (S.wasAttendedToday(e)) return !1; const a = p.classesData[e]; return ( !!n.hasMatchingDate(a.days) && !x.isTaskActiveForObject(e, "class") ); } static prerequisitesDone(e) { const t = p.classesData[e]; if (!t) return !1; if (0 === t.prerequisites.length) return !0; return !( t.prerequisites.filter((e) => { const t = S.getAssignedClass(e); return !t || "finished" !== t.status; }).length > 0 ); } static isExamAvailable(e) { const t = S.getAssignedClass(e); if (!t) return !1; if ("active" !== t.status) return !1; if (S.wasAttendedToday(e)) return !1; const a = p.classesData[e]; return ( !(t.attendances < o.expectedAttendance[a.tier]) && !( t.attendances < Object.entries(a.tasks).filter((e) => !e[1].isExam).length ) && !!n.hasMatchingDate(a.days) && !x.isTaskActiveForObject(e, "class") && !(p.getActivePunishments().length > 0) ); } static getTodaysTasks(e, t, a) { (!a || a <= 0) && (a = o.defaultTaskListSize); const s = (e = Object.entries(e) .filter((e) => !e[1].isExam) .map((e) => e[1])).length; if (s <= a) return e; let i = [], n = t; for (; i.length < a; ) (i.push(e[n % s]), n++); return i; } } class j { static get expectedParameters() { return { getView: [] }; } static getView() { const e = s.getInstance(); let t = "", a = 0, i = u.getInstance(); if ( (a++, (t += j.getHelpEntryHtml( e.T("help-overview-header", "Game overview"), `\n \n `, "anchor" + a, void 0, "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-notice-header", "Game notice"), `\n

    \n ${e.T("help-notice-content", '\n Your health takes top priority. In case you start a task that turns out to be unhealthy,\n you should absolutely "finish" it without actually doing it! If you aren\'t feeling well,\n stop whatever you are currently doing and make sure you are fine. Contact a doctor if required.\n On the same notion, suicide is not an option, you are great the way you are and before doing \n anything that cannot be undone - take a helping hand.')}\n

    \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-progress-header", "Game progress notice"), `\n

    \n ${e.T("help-progress-content", "\n All game data is stored locally in your browser. Nothing is sent or stored online.\n Private browsing mode doesn't save data and sets limits, for the best experience it is therefore recommended to play in a normal \n browser window. In case you do play in a private window make sure to export your progress and keep the map file you used handy.\n You will need the map and save game file to continue playing where you left of.")}\n

    \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-majors-header", "Majors"), `\n

    \n ${e.T("help-majors-content", "\n Choose a %sMajor%s depending on what you'd like to specialise in. \n Each one has specific mandatory classes (prerequisites) and each of those classes has \n prerequisite classes of their own. Once you join a major go to the \n %sProgress page%s to see your mandatory classes. You need to \n get %s credits before attempting to graduate. If you decide to\n change your major you can go to it's page and drop it however this will result in a \n penalty of %s random punishments.", ['', "", '', "", p.requiredCreditsForGraduation, o.majorDropPunishments])}\n

    \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-classes-header", "Classes and schedule"), `\n

    \n ${e.T("help-classes-content", "\n Every class has a weekly schedule that you need to keep track of. \n You'll need to complete one of the two daily tasks or exams to receive a knowledge point.\n If you fail to attend a class by the end of the day a punishment will be automatically \n rolled for that class. To unlock its exam you need to have attended it at least a few times \n and to not have any pending punishments. You will see a full set of the requirements if you open the class.\n Once you pass a class you can immediately enroll in another one if you wish. You can NOT join a class again or do their tasks once you've passed it. \n You can not enroll in more than %s classes at once. \n Do not enroll in too many classes if you can't keep up with the schedule. \n Classes can also be done during the weekend. Weekend classes are optional and you won't be punished for skipping them.", [o.maxConcurrentClasses])}\n

    \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-punishments-header", "Punishments"), `\n \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-clubs-header", "Clubs"), `\n \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-partners-header", "Partners"), `\n

    \n ${e.T("help-partners-content", "\n Partners are very similar to clubs with the only difference being that they make \n things harder instead of easier but in return you'll get extra points. \n You can have up to %s partners. \n Their perks can be activated once per day and deactivate automatically on the next.", [o.maxConcurrentPartner])}\n

    \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-tasks-header", "Tasks and timers"), `\n

    \n ${e.T("help-tasks-content", '\n The game has a tasks system for majors and classes. On your schedule page you\'ll \n be able to select which task to do for your active classes. Each task can have multiple sub-tasks\n Once you start a task you\'ll see the new task show up where you can either start its timer or \n press "Complete" if the task is not time dependent. Timers can be paused/resumed\n and they carry over between days. You won\'t get punished if the task is running from\n yesterday. If you fail to do a task, you must press "Fail" in order to get punished.\n The active timers for all your classes are also visible on the schedule page for\n each class.')}\n

    \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-grades-header", "Grades"), `\n

    \n ${e.T("help-grades-content", "\n Whenever you attend and finish class task you will receive one knowledge point, if you have modifiers active (from partners / clubs)\n you can receive additional ones. Based on these knowledge points you will receive a grade once you have taken the exam. It's up\n to you to decide how well you want to perform during the exam. The grades you receive will be used to calculate your major grade.\n In case you already finished a class and want to improve your grade, you can retake them anytime. If you retake a class you need to repeat the\n exam and loose the credits related to the class but you will keep the already collect knowledge points.\n If you re-roll a punishment you will loose knowledge points to a random class you are currently taking.\n
    To make sure you don't fail, you need to collect at least:")}\n

    \n ${j.getGradeHtml()}\n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-credits-header", "Credits"), `\n

    \n ${e.T("help-credits-content", "\n Graduating requires that you have at least %s credits.\n You first need to attend classes to unlock\n the exam. Once you finish the exam for a class you will get matching credits.", [p.requiredCreditsForGraduation])}\n

    \n \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), a++, (t += j.getHelpEntryHtml( e.T("help-graduating-header", "Graduating"), `\n

    \n ${e.T("help-graduating-content", '\n Once you have completed all your mandatory classes and collected %s\n credits through attending extra classes you may attempt to graduate.\n Some Majors could take up to a month for the final task. If you fail to do one of the thesis requirements \n you must press "Fail" which will roll %s punishments. Some majors have an additional punishment option which \n increases the timer\'s duration. After you graduate your major will be marked as Completed and your progress will be preserved.\n You can sign up for another one in another field which will become very \n useful in the future when you start building your career.', [p.requiredCreditsForGraduation, o.failThesisPunishment])}\n

    \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), )), i.map.rouletteOptions) ) { const s = i.map.rouletteOptions; let n = ""; for (let e in s) { const t = s[e]; n += `
  • ${100 * t.probability}% ${t.description}
  • `; } (a++, (t += j.getHelpEntryHtml( e.T("help-roulette-header", "Orgasm roulette system"), `\n

    \n ${e.T("help-roulette-content", "\n The only way you are allowed to cum is either through classes or from the %sOrgasm Roulette%s. \n Every day you'll get the chance to spin is once and test your luck out.", ['', ""])}\n

    \n \n `, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), ))); } if (void 0 !== i.map.general.help && i.map.general.help.length > 0) for (const e of i.map.general.help) (a++, (t += j.getHelpEntryHtml( e.title, e.text, "anchor" + a, "anchor" + (a - 1), "anchor" + (a + 1), ))); return `
    \n ${t}\n
    `; } static getHelpEntryHtml(e, t, a, s, i) { return `\n
    \n
    \n \n

    \n ${void 0 !== i ? ` ` : ""}\n ${void 0 !== s ? ` ` : ""}\n ${e}\n

    \n ${t}\n
    \n
    \n `; } static getGradeHtml() { const e = s.getInstance(); let t = o.maxAttendances.beginner, a = o.maxAttendances.intermediate, i = o.maxAttendances.advanced, n = o.maxAttendances.master, r = "", l = t !== a, d = a !== i, c = i !== n; for (const e of Object.entries(o.grades)) r += `\n \n ${e[0]}\n ${100 * e[1]}%\n ${e[1] * t}\n ${l ? `${e[1] * a}` : ""}\n ${d ? `${e[1] * i}` : ""}\n ${c ? `${e[1] * n}` : ""}\n \n `; return `\n \n \n \n \n \n \n ${l ? `` : ""}\n ${d ? `` : ""}\n ${c ? `` : ""}\n \n \n \n ${r}\n \n
    ${e.T("help-table-grade", "Grade")}${e.T("help-table-threshold", "Threshold")}${e.T("help-table-min-knowledge", "Minimum Knowledge")}${e.T("help-table-min-knowledge-intermediate", "Minimum Knowledge Intermediate")}${e.T("help-table-min-knowledge-advanced", "Minimum Knowledge Advanced")}${e.T("help-table-min-knowledge-master", "Minimum Knowledge Master")}
    \n `; } } class D { static get expectedParameters() { return { getView: [], import_save: ["savegame"] }; } static import_save(e) { return ( _.replaceHistory("?page=settings"), E.importSaveGamePrivateBin(atob(e.savegame)), D.getView() ); } static import_save(e) { return ( _.replaceHistory("?page=settings"), E.importSaveGamePrivateBin(atob(e.savegame)), D.getView() ); } static getView() { document .querySelector("html") .addEventListener("navigationFinished", function (e) { ($("#settings-container select[multiple]").selectpicker(), E.updateServiceWorkerStatus(), E.populateServerSaves()); }); const e = s.getInstance(), t = n.canCompress(); return `
    \n

    ${e.T("settings-header", "Settings")}

    \n
    \n
    \n \x3c!-- filled by delayed function call --\x3e\n
    \n
    \n
    \n
    \n ${t ? "" : D.getStatusAlert(e.T("settings-compression-status-available", "The compression library is not available!"), "danger")}\n
    \n
    \n
    \n
    \n
    \n
    \n \n \n
    \n

    ${e.T("settings-easy-mode-description", 'Easy mode allows classes to be attended on any day, removes punishments for skipping them. It can be used as a "Pause" for the game.')}

    \n \n
    \n
    \n \n \n
    \n

    ${e.T("settings-compress-exports-description", "Make it just a little bit easier to share large files by compressing them slightly. %sINFO:%s This should help on most apple devices!%s", ['', "", ""])}

    \n
    \n\n
    \n
    \n \n \n
    \n

    ${e.T("settings-image-exports-description", "Let's share an image instead of a random text file. Requires compression! %sWARNING:%s This is only EXPERIMENTAL, big files can turn unreadable! Most messengers will remove the added data making them useless!%s", ['', "", ""])}

    \n
    \n\n
    \n
    \n \n \n
    \n

    ${e.T("settings-complete-exports-description", "No need to import maps and saves one by one Everything will be exported as a single file. Requires compression! %sINFO:%s This will result in giant saves!%s", ['', "", ""])}

    \n
    \n\n \n

    ${e.T("settings-time-offset-selection", "Choose a custom time offset and make the night your day. Current game time is: ")} ${n.getNow().format("YYYY-MM-DD HH:mm:ss")} (${n.getSetting("timeOffsetSelection") || 0}${e.T("settings-time-offset-hour", "h")})

    \n\n
    \n \n \n
    \n

    ${e.T("settings-hardcore-punishments-description", "Hardcore punishments are crueler than the rest and exist just for the novelty. They are disabled by default and don't provide any benefits when enabled besides enhanced gameplay.")}

    \n\n
    \n \n \n
    \n

    ${e.T("settings-auto-hide-description", "Make sure nobody sees your progress by accident. The page automatically hides it's content after %s.", [moment.duration(o.autoHideTimer, "minutes").humanize()])}

    \n\n
    \n \n \n
    \n

    ${e.T("settings-back-top-description", 'Especially on mobile it is easy to get lost on long pages. You can enable this handy "Back to top" button to easily return to the start.')}

    \n\n
    \n \n \n
    \n

    ${e.T( "settings-hide-active-classes-description", "Once you started classes they will be shown in the %sschedule%s. To make the %sclasses overview%s cleaner you can therefore choose to hide them.", [ ``, "", ``, "", ], )}

    \n\n
    \n \n \n
    \n

    ${e.T( "settings-finished-active-classes-description", "Once you finished classes they will be shown in the %sprogress overview%s. To make the %sclasses overview%s cleaner you can therefore choose to hide them.", [ ``, "", ``, "", ], )}

    \n\n
    \n \n \n
    \n

    ${e.T("settings-skip-tier-description", "Pages that use carousels to display multiple tiers of content will automatically switch to the next tier once you reach the bottom of the page.")}

    \n\n
    \n \n \n
    \n

    ${e.T("settings-show-confirmations-description", "Some actions have a bigger impact, to make sure you don't do them by accident you will get a confirmation request first.")}

    \n\n
    \n \n \n
    \n

    ${e.T("settings-detailed-table-view-description", "Switch between the original 'card'-view (which emulates the end result) and the new 'table'-view for the Mapping-Tool.")}

    \n\n \n

    ${e.T("settings-theme-description", "Select your prefered theme for this website.")}

    \n\n \n

    ${e.T("settings-language-description", "Select your prefered language for this website.")}

    \n\n
    \n \n \n
    \n
    \n \n ${u.getInstance().isMapLoaded ? D.saveManagementFormView() : ""}\n ${D.serverSavesFormView()}\n ${u.getInstance().isMapLoaded ? D.mapManagementFormView() : ""}\n\n
    \n
    \n

    ${e.T("settings-global-actions-header", "Global actions")}

    \n
      \n
    • \n ${e.T("settings-join-discord-button", "Join Discord")}\n ${e.T("settings-join-discord-description", "Feel free to join the official PU-Discord.")}\n
    • \n
    • \n \n ${e.T("settings-reset-tutorial-description", "Reset the introduction tutorials.")}\n
    • \n
    • \n \n ${e.T("settings-reset-game-description", "Reset game completely.")}\n
    • \n
    \n
    \n
    \n
    `; } static saveManagementFormView() { const e = s.getInstance(), t = n.canCompress(); return `
    \n
    \n

    ${e.T("settings-save-selection-header", "Save game actions")}

    \n
    \n ${ n.getSetting("sessionToken") ? `\n \n
    \n ` : "" }\n
    \n
    \n \n ${e.T("settings-import-save-button", "Import save")}\n \n \n \n \n \x3c!--\n \n
    \n --\x3e\n
    \n
    \n
    `; } static serverSavesFormView() { return `
    \n
    \n
    \n
    Server Saves
    \n
    \n \n
    \n \n
    \n
    \n \n Upload a complete export to the server under a custom name.\n
    \n \n
    \n \n \n
    \n
    \n \n Load a previously saved game from the server. This will overwrite your current progress.\n
    \n
    \n
    `; } static mapManagementFormView() { const e = s.getInstance(); return `
    \n
    \n

    ${e.T("settings-maps-header", "Map management")}

    \n
    \n
    \n ${e.T("settings-map-tool-button", "Mapping tool")}\n ${e.T("settings-map-tool-button-description", "Open the mapping tool to create your own courses.")}\n
    \n
    \n \n ${e.T("settings-map-import-button", "Import a map")}\n ${e.T("settings-map-import-button-description", "Import a new map or update an existing map.")}\n
    \n
    \n \n \n ${e.T("settings-map-selection-info-1", "Switch to a different map (the current progress is saved for each map separately).")}\n ${e.T("settings-map-selection-info-2", "The map is tied to your current save game. If you want to import/export a save game you will need to do so for each map separatly, selected Sub-Maps are included in the active map.")} \n\n \n \n
    \n
    \n \n \n ${e.T("settings-submap-selection-description", 'Please select all maps you want to use as a Sub-Map and import the related content. You must import maps using the "Import a new map" function above before you can select them here. Make sure to select the main map carefully.')}\n \n
    \n
    \n
    \n
    `; } static getMapSelection() { const e = u.getInstance().maps, t = u.getInstance().mapId; let a = ""; for (const s of e) a += ``; return a; } static getSubMapSelection() { const e = u.getInstance(), t = e.maps, a = e.mapId, s = e.subMaps; let i = ""; for (const e of t) e.mapData.mapId != a && (i += ``); return i; } static getStyleSelection() { let e = ""; const t = n.getSetting("style"); for (const a of Object.keys(o.styles)) e += ``; return e; } static getLanguageSelection() { const e = n.getSetting("language"); let t = ``; for (const a of s.availableLanguages) t += ``; return t; } static getStatusAlert(e, t = "info") { s.getInstance(); return `\n \n `; } } class E { static resetTutorial() { (n.setTutorial(""), _.inAppReload()); } static switchMap() { const e = $("#mapSelection").val(), t = u.getInstance(); e != t.mapId && (t.registerLoadFinished(() => { _.reload(); }), t.changeMap(e)); } static setSubMaps() { const e = $("#assigned-submaps").val(), t = u.getInstance(); ((t.subMaps = e), t.setSubMaps(() => { _.navigate("?page=home"); })); } static deleteMap() { const e = s.getInstance(); n.requestUserConfirmation( e.T("settings-delete-map-confirmation-header", "Delete map"), e.T( "settings-delete-map-confirmation-content", "Are you sure that you want delete the currently selected map? You will loose the save as well.", ), () => { const e = $("#mapSelection").val(); u.getInstance().deleteMap(e, () => { _.reload(); }); }, ); } static saveSettings() { let e = {}; for (let t of $('#settings-form-group input[type="checkbox"]')) e[t.id] = t.checked; for (let t of $("#settings-form-group select")) e[t.id] = t.value; for (let t of $('#settings-form-group input[type="range"]')) e[t.id] = parseFloat(t.value); (e.enableEasyMode !== n.getSetting("enableEasyMode") && p.resetAttendanceDeadline(u.getInstance().getSavegame()), n.setSettings(e), _.reload()); } static resetSettings() { (n.setSettings(n.getDefaultSettings()), _.reload()); } static async updateServiceWorkerStatus(e = "#serviceworker-status") { const t = s.getInstance(), a = t.T( "settings-serviceworker-status-loading", "Your ServiceWorker status is currently loading...", ), i = D.getStatusAlert(a); $(e).empty().append(i); const o = await n.getServiceWorkerStatus(); let r = ""; r = "inactive" === o.id ? t.T( "settings-serviceworker-status-inactive", "ServiceWorker feature is disabled!", ) : "active" === o.id ? t.T( "settings-serviceworker-status-active", "Your ServiceWorker is active!", ) : "waiting" === o.id ? t.T( "settings-serviceworker-status-waiting", "Your ServiceWorker is waiting for activation.", ) : "installing" === o.id ? t.T( "settings-serviceworker-status-installing", "Your ServiceWorker is currently installing.", ) : "unregistered" === o.id ? t.T( "settings-serviceworker-status-unregistered", "The ServiceWorker is not registered!", ) : t.T( "settings-serviceworker-status-unknown", "Your ServiceWorker is in an unknown state.", ); const l = D.getStatusAlert(r, o.type); $(e).empty().append(l); } static resetGame() { const e = s.getInstance(); n.requestUserConfirmation( e.T("settings-reset-game-confirmation-header", "Reset game"), e.T( "settings-reset-game-confirmation-content", "Are you sure that you want delete all local data and progress? You will loose your complete progress.", ), () => { (localStorage.clear(), r.getInstance().drop(() => { _.navigate("?page=home"); })); }, ); } static importSaveGame(e) { const t = s.getInstance(); let a = u.getInstance().importSaveGame(e, () => { _.navigate("?page=home"); }); a && n.showToast( t.T("settings-save-import-failed-header", "SaveGame import failed"), a, "danger", ); } static async importSaveGameFile(e) { const t = s.getInstance(), a = { title: t.T( "settings-save-import-confirmation-header", "Import a save", ), text: t.T( "settings-save-import-confirmation-content", "Are you sure that you want to overwrite your current progress? Make sure to only load saves from trusted sources. Proceed at your own risk.", ), }, i = { title: t.T( "settings-save-import-failed-header", "Save import failed", ), text: t.T( "settings-save-import-failed-content", "During the import for the selected file an error occured. You can check the console for more information.", ), }, n = { title: t.T( "settings-file-import-compression-error-header", "Compression unavailable", ), text: t.T( "settings-file-import-compression-error-content", "The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.", ), }, o = { title: t.T("settings-file-import-aborted-header", "Import aborted"), text: t.T( "settings-file-import-aborted-content", "The file you tried to import included untrusted code. To protect you the import was aborted.", ), }, r = await _.runImport(e, i, a, n, o); E.importSaveGame(r); } static async exportSaveGameFile() { const e = s.getInstance(), t = { title: e.T( "settings-save-export-maps-failed-header", "Save export interrupted", ), text: e.T( "settings-save-export-maps-failed-content", "During the export an error occured while loading the active maps for a full export.", ), }; await _.runExport("pu_save", await _.getSavegameData(t)); } static async exportNamedSave() { const save_name = $("#named-save-input").val().trim(); if (!save_name) return; const error_obj = { title: s.getInstance().T("settings-save-export-maps-failed-header", "Save export interrupted"), text: s.getInstance().T("settings-save-export-maps-failed-content", "During the export an error occured while loading the active maps for a full export."), }; const orig_complete = n.getSetting("completeExports"); const orig_compress = n.getSetting("compressExports"); n.setSetting("completeExports", true); n.setSetting("compressExports", true); const password = $("#named-save-password").val(); try { const data = await _.getSavegameData(error_obj); const compressed = n.compressJSObjectToString(data); const blob = new Blob([compressed], { type: "application/octet-stream" }); const headers = { "Content-Type": "application/octet-stream" }; if (password) headers["X-Password-Hash"] = await n.sha256_hex(password); await fetch(`https://pu-saves.burrson.de/saves/${encodeURIComponent(save_name)}`, { method: "POST", mode: "cors", headers, body: blob, }); const label = password ? " (protected)" : ""; n.showToast("Save stored", `"${save_name}"${label} was saved to the server.`, "success"); } catch (e) { n.showToast("Save failed", "Could not store the save on the server.", "danger"); } n.setSetting("completeExports", orig_complete); n.setSetting("compressExports", orig_compress); } static async populateServerSaves() { try { const res = await fetch("https://pu-saves.burrson.de/saves", { mode: "cors" }); const data = await res.json(); const select = $("#server-saves-select"); if (!select.length) return; select.empty(); if (data.saves.length === 0) { select.append(''); } else { data.saves.forEach(name => { const is_protected = data.protected.includes(name); select.append(``); }); } } catch (e) { $("#server-saves-select").empty().append(''); } } static async importNamedSave() { const save_name = $("#server-saves-select").val(); if (!save_name) return; const password = $("#named-load-password").val(); try { const headers = {}; if (password) headers["X-Password-Hash"] = await n.sha256_hex(password); const res = await fetch(`https://pu-saves.burrson.de/saves/${encodeURIComponent(save_name)}`, { mode: "cors", headers }); if (res.status === 401) { const msg = await res.text(); n.showToast("Access denied", msg.includes("required") ? "This save is password protected." : "Wrong password.", "danger"); return; } if (!res.ok) throw "Not found"; const text = await res.text(); if (n.hasScript(text)) throw "Script found!"; const parsed = JSON.parse(n.decompressJsonFromString(text)); E.importSaveGame(parsed); } catch (e) { n.showToast("Load failed", "Could not load the save from the server.", "danger"); } } static async deleteNamedSave() { const save_name = $("#server-saves-select").val(); if (!save_name) return; const password = $("#named-load-password").val(); try { const headers = {}; if (password) headers["X-Password-Hash"] = await n.sha256_hex(password); const res = await fetch(`https://pu-saves.burrson.de/saves/${encodeURIComponent(save_name)}`, { method: "DELETE", mode: "cors", headers }); if (res.status === 401) { const msg = await res.text(); n.showToast("Access denied", msg.includes("required") ? "This save is password protected." : "Wrong password.", "danger"); return; } if (!res.ok) throw "Not found"; n.showToast("Deleted", `Save "${save_name}" deleted from server.`, "success"); E.populateServerSaves(); } catch (e) { n.showToast("Delete failed", "Could not delete the save from the server.", "danger"); } } static async importSaveGameClipboard() { const e = s.getInstance(), t = { title: e.T( "settings-save-import-confirmation-header", "Import a save", ), text: e.T( "settings-save-import-confirmation-content", "Are you sure that you want to overwrite your current progress? Make sure to only load saves from trusted sources. Proceed at your own risk.", ), }, a = { title: e.T( "settings-save-import-failed-header", "Save import failed", ), text: e.T( "settings-save-import-failed-content", "During the import for the selected file an error occured. You can check the console for more information.", ), }, i = { title: e.T("settings-file-import-aborted-header", "Import aborted"), text: e.T( "settings-file-import-aborted-content", "The file you tried to import included untrusted code. To protect you the import was aborted.", ), }, n = await _.runClipboardImport("pu_save", a, t, i); E.importSaveGame(n); } static async exportSaveGameClipboard() { const e = s.getInstance(), t = { title: e.T( "mapping-export-clipboard-success-header", "Exported to clipboard", ), text: e.T( "mapping-export-clipboard-success-content", "The data was exported to the clipboard.", ), }, a = { title: e.T( "mapping-export-clipboard-error-header", "Clipboard export failed", ), text: e.T( "mapping-export-clipboard-error-content", "Failed to export the data to the clipboard.", ), }, i = { title: e.T( "settings-save-export-maps-failed-header", "Save export interrupted", ), text: e.T( "settings-save-export-maps-failed-content", "During the export an error occured while loading the active maps for a full export.", ), }; await _.runClipboardExport("pu_save", i, t, a); } static async exportSavegamePrivateBin() { const e = s.getInstance(), t = { title: e.T( "settings-save-export-maps-failed-header", "Save export interrupted", ), text: e.T( "settings-save-export-maps-failed-content", "During the export an error occured while loading the active maps for a full export.", ), }, a = JSON.stringify(await _.getSavegameData(t)); let i = function (t) { if (t.error || !t.url) return void n.showToast( e.T( "settings-save-export-failed-header", "Save game export failed", ), e.T( "settings-save-export-failed-content", "Sharing your save file was not successful.", ), ); const a = `${n.getCurrentUrl()}?page=settings&pageType=import_save&savegame=${btoa(t.url)}`; c.insertQR(a, "PU.save", "#savegameQR"); }.bind(this); c.uploadToPastebin(a, i); } static importSaveGamePrivateBin(e) { const t = s.getInstance(); let a = function (e) { !e.error && e.data ? E.importSaveGame(e.data) : n.showToast( t.T( "settings-import-share-save-failed-header", "Save game import failed", ), t.T( "settings-import-share-save-failed-content", "Importing the shared save file was not successful.", ), ); }.bind(this), i = function () { c.downloadFromPastebin(e, a); }.bind(this); n.requestUserConfirmation( t.T( "settings-import-share-save-confirmation-header", "Import a save", ), t.T( "settings-import-share-save-confirmation-content", "Are you sure that you want to overwrite your current progress? Make sure to only install saves from trusted sources. Proceed at your own risk.", ), i, ); } static importMapPrivateBin(e) { const t = s.getInstance(); let a = function (e) { !e.error && e.data ? u.getInstance().importMap(e.data) : n.showToast( t.T( "settings-import-share-map-failed-header", "Map import failed", ), t.T( "settings-import-share-map-failed-content", "Importing the shared map file was not successful.", ), ); }.bind(this), i = function () { c.downloadFromPastebin(e, a); }.bind(this); n.requestUserConfirmation( t.T("settings-import-share-map-confirmation-header", "Import a map"), t.T( "settings-import-share-map-confirmation-content", "Are you sure that you want to import this map? Make sure to only install maps from trusted sources. Proceed at your own risk.", ), i, ); } static resetSaveSync() { (n.setSetting("sessionToken", void 0), _.reload()); } static setupSaveSync(e) { const t = s.getInstance(); let a = function (e) { !e.error && e.data ? (n.scheduleToast( t.T( "settings-import-save-sync-header", "Save-Sync setup finished", ), t.T( "settings-import-save-sync-content", "Your saves will now be synced, if the required maps are available.", ), ), _.reload()) : n.showToast( t.T( "settings-import-save-sync-failed-header", "Save-Sync setup failed", ), t.T( "settings-import-save-sync-failed-content", "The provided key was not accepted by the backend. You can try to create a new session or try again later.", ), ); }.bind(this); c.getInstance().exchangeSaveGameToken(e, a); } } class N { static get expectedParameters() { return { getView: [], feedback: [], import_map: ["map"], save_sync: ["exchangeToken"], }; } static save_sync(e) { return ( _.replaceHistory("?page=home"), E.setupSaveSync(e.exchangeToken), N.getView() ); } static feedback() { const e = s.getInstance(); return ( _.replaceHistory("?page=home"), n.showToast( e.T("home-feedback-title", "Feedback"), `${e.T("home-feedback-description", 'Thank you very much for providing a quick "heartbeat"!')} ${e.T("home-feedback-discord", "In case you want to join the PU Discord - click here.")} `, "success", ), N.getView() ); } static import_map(e) { return ( _.replaceHistory("?page=home"), E.importMapPrivateBin(atob(e.map)), N.getView() ); } static async loadDummyMap() { try { const res = await fetch("Dummy.pu"); if (!res.ok) throw "fetch failed"; const text = await res.text(); if (n.hasScript(text)) throw "Script found!"; let data; try { data = JSON.parse(text); } catch { data = JSON.parse(n.decompressJsonFromString(text)); } u.getInstance().importMap(data); } catch (e) { n.showToast("Load failed", "Could not load the dummy map.", "danger"); } } static getView() { const e = s.getInstance(); return `
    \n
    \n
    \n

    ${e.T("home-welcome-header", "Welcome to Project University")}

    \n

    \n ${e.T("home-welcome-info-1", "This project aims to let you either create your very own university or \n join a university that was created by somebody else. As such this website doesn't provide any content \n itself and you will need to provide a PU-map. A PU-map is a user generated file that contains the data \n required to define a university. This website runs completly on the users device - no user generated content\n is stored on the server hosting this website. Therefore please make sure to make backups of your progress,\n think twice before importing unknown content, stay safe and legal.")}\n

    \n

    \n ${e.T("home-welcome-info-2", "\n The general idea of your university life consists of enrolling in a major, choosing classes and do the\n daily tasks required. To finish a class attend the exam and unlock new classes or the finals.\n Once all prerequisites are fulfilled you can attempt the final major exam. You can join clubs or get yourself \n a partner to change the difficulty for specific classes and gain small perks. You will be punished if you fail \n to meet any requests.")}\n

    \n

    \n ${e.T("home-welcome-info-3", "So import a map sign up for a major, join some classes and let the journey begin!")}\n

    \n \n ${e.T("home-load-map-button", "Load a university map!")}\n ${e.T("home-create-map-button", "Create your own map!")}\n Load dummy map\n
    \n
    \n
    \n
    \n

    ${e.T("home-changelog-header", "Changelog")}

    \n \n
    \n
    2025-07-31
    \n
    \n

    \n Just added some missing chinese translations. Thanks a lot to Nebulas Astra (github:@nebulas-star)!\n

    \n
    \n
    \n\n \n\n
    \n \n
    \n
    2024-09-24
    \n
    \n

    \n This update was mainly aimed to support exports on iOS devices again, but there were lots of attempts and none were quite what I wanted. The inital start of these changes was July, took longer than expected.\n The most interesting changes are the option to get a full export of a save and the auto-save function for the map editor.\n

    \n
      \n
    • New file type PUC, this is a complete save including all active maps, submaps and settings.
    • \n
    • Exports: Major rework, a lot happened here, hope nothing broke. This affects Maps, Saves, Tasks.
    • \n
    • Settings page: Visual updates, new export settings, Service Worker (offline mode) status and compression support status.
    • \n
    • Fixed class-skip-tokens not activating.
    • \n
    • Added counter for the currently available class-skip tokens.
    • \n
    • You can now undo a perk activation.
    • \n
    • Make it easier to share large files by enabling compressed exports - and might help with some iOS export issues.
    • \n
    • Share file-less using clipboard exports.
    • \n
    • Fixed the visual timer problem thanks a lot @Pink_3D for the resolution (active task).
    • \n
    • Fixed time output for anything that takes a month or longer (task selection).
    • \n
    • Added auto-save to the map editor, might help if you forgot to manually export it. However don't rely on it - think of it as a backup in case you mess up.
    • \n
    • The map editor is now a little bit better integrated into the general UI, using the same settings page.
    • \n
    • The map editor supports now: taskListSize, disableBonusAttendance
    • \n
    • Updated jQuery, moment.js, Bootstrap, Bootstrap Select, Bootstrap Table, Bootstrap Darkly.
    • \n
    • Experimental: Make your saves prettier by using Image exports. Sadly sharing these will be quite hard because most providers will delete the save data included.
    • \n
    \n
    \n
    \n\n
    \n
    2023-11-30.1
    \n
    \n

    \n Looks like the old hosting option for the Map-Editor is no longer available. Now it is included on-site and should work just as it did before.\n

    \n
    \n
    \n\n
    \n
    2023-10-20
    \n
    \n

    \n Some small bugfixes.\n

    \n
      \n
    • Fixed language navigation option for english.
    • \n
    • Fixed an incorrect help description.
    • \n
    \n
    \n
    \n\n
    \n
    2023-03-18
    \n
    \n

    \n Some small bugfixes.\n

    \n
      \n
    • Replaced PWA images, to allow better icons.
    • \n
    • Fixed a bug with save game imports for a missing active major.
    • \n
    \n
    \n
    \n\n
    \n
    2023-02-28
    \n
    \n

    \n Allow more advanced maps.\n

    \n
      \n
    • Removed alt texts from images.
    • \n
    \n
    \n
    \n\n
    \n
    2023-02-09.1
    \n
    \n

    \n More QOL updates.\n

    \n
      \n
    • You can now join more clubs, partners and classes. Enjoy.
    • \n
    • Added fix for removing finished class content.
    • \n
    • Added reference verification for maps (majors, classes).
    • \n
    \n
    \n
    \n\n
    \n
    2022-09-17.1
    \n
    \n

    \n Some small QOL updates.\n

    \n
      \n
    • Hidden tasks should now work as expected and follow the parameter order instead of the type and then the parameter order.
    • \n
    • Updated the error reporting, at least you should now see a reason which you can share for why the page is just empty.
    • \n
    • Classes are now filtered for itself to stop accidental loops which crash the site.
    • \n
    • Punishments should now be filtered if deleted tasks are assigned to them.
    • \n
    \n
    \n
    \n\n
    \n
    2022-01-15
    \n
    \n

    \n Custom timezones even if you don't travel ;)\n It is now possible to manipulate the time to make it easier for night owles to stay on schedule.\n

    \n
    \n
    \n\n
    \n
    2021-11-10
    \n
    \n

    \n Synced Saves Service added - check the Settings.\n

    \n
      \n
    • Join the PU Discord and use the new Save-Sync service.
    • \n
    \n
    \n
    \n\n
    \n
    2021-10-28
    \n
    \n

    \n Small fixes and improvments.\n

    \n
      \n
    • Fixed translation engine language selection
    • \n
    • Added info for collected knowledge points
    • \n
    • Added function to unlock and blacklist punishments
    • \n
    \n
    \n
    \n\n
    \n
    2021-08-29
    \n
    \n

    \n Please move your save to the backup instance since I plan to implement a big update for the main version which will make your current saves incompatible.\n

    \n
      \n
    • CORS check failed and required a fix.
    • \n
    \n
    \n
    \n
    \n
    2020-10-13.2
    \n
    \n

    \n Chinese translation is now available. In case you want to see PU in your own language feel free to contact me.\n

    \n
      \n
    • Fixed some stuff for pink theme.
    • \n
    • Added setting to change the language.
    • \n
    • Based on the language provided by your browser, PU will localize the website. Currently only a chinese translation is available, which was provided by Carlotta. Keep in mind this will only work for the framework and not for map content.
    • \n
    • Old changelogs are now collapsed.
    • \n
    \n
    \n
    \n
    \n
    2020-09-30.1
    \n
    \n

    \n SubMaps are great!\n

    \n
      \n
    • If you already have a map active and add a new one - the new one will be added as a SubMap to the active one.
    • \n
    • If you visit the page for the first time, you will be redirected after confirming the disclaimer.
    • \n
    • Updated feedback info for October.
    • \n
    \n
    \n
    \n
    \n
    2020-09-02
    \n
    \n

    \n Roulette is now up to the map creator.\n

    \n
      \n
    • Disclaimer and Data Protection notice added.
    • \n
    • Roulette is now up to the map creator
    • \n
    • If you disable Easy Mode you will not be bombarded with punishments
    • \n
    • A small performance improvement by skipping tour loading
    • \n
    \n
    \n
    \n
    \n
    2020-08-05.1
    \n
    \n

    \n Some nice new features all over the place.\n

    \n
      \n
    • Maps: Automatically lower required credits if a map doesn't provide enough content to collect 160 credits.
    • \n
    • Settings: Handle missing save data - you can now disable sub-maps if you don't want them anymore without breaking the save. (probably)
    • \n
    • Classes: New indicator for the progress in your classes
    • \n
    • Settings: Importing using quick share should now show an error instead of silently failing
    • \n
    • Mapping Tool: It's now possible to easily create new items (Save & New) and sometimes even to copy an existing item (Duplicate)
    • \n
    • Mapping Tool: Shortcuts: To save current input you can now use CTRL + S (make sure to select the modal) or discard the current input ESC
    • \n
    \n
    \n
    \n
    \n
    2020-08-02
    \n
    \n

    \n Mapping Tool fixes.\n

    \n
      \n
    • Mapping Tool: Share option fixed
    • \n
    • Mapping Tool: AutoHide is now disabled inside the Mapping Tool
    • \n
    • Mapping Tool: Added warning if functionality is limited by Third-Party Cookie block
    • \n
    \n
    \n
    \n
    \n
    2020-08-01.1
    \n
    \n

    \n Mapping Tool extracted.\n

    \n
      \n
    • Mapping Tool: Image Hosting improved
    • \n
    • Mapping Tool: Extracted for sparated access
    • \n
    • Settings: Moved Mapping Tool specific settings
    • \n
    \n
    \n
    \n
    \n
    2020-07-27
    \n
    \n

    \n Let's get the map size down and allow quick-start.\n

    \n
      \n
    • Mapping Tool: Optional automatic upload of images to a image hoster to reduce the map size [EXPERIMENTAL]
    • \n
    • Mapping Tool: Quick share maps (users can click / scan a QR code and will directly be able to run the map) [EXPERIMENTAL]
    • \n
    • Mapping Tool: Improved direct share for tasks (users can still click on a link / scan a QR code and will be able to start the task) [EXPERIMENTAL]
    • \n
    • Navigation: Fixed a potential bug for people that start a fresh game
    • \n
    • Settings: Theme pink (small fixes)
    • \n
    \n
    \n
    \n
    \n
    2020-07-26.1
    \n
    \n

    \n Themes and grades.\n

    \n
      \n
    • Progress: Added grades
    • \n
    • Settings: Themes (Pink theme updated)
    • \n
    • Settings: New SaveGame import/export without files [EXPERIMENTAL]
    • \n
    • Mapping Tool: added task export into the new table view
    • \n
    \n
    \n
    \n
    \n
    2020-07-23
    \n
    \n

    \n Graduation. Yay.\n

    \n
      \n
    • Progress: You can now graduate
    • \n
    • Mapping Tool: Added click-able task reference to punishment table
    • \n
    • Mapping Tool: Retain scroll position
    • \n
    • Mapping Tool: Auto resize images
    • \n
    \n
    \n
    \n
    \n
    2020-07-21.1
    \n
    \n

    \n Small fixes for the Mapping Tool und quicker map check.\n

    \n
      \n
    • Map: Map import is a lot faster and works again in FF
    • \n
    • Map: Show map description on import
    • \n
    • Mapping Tool: Fixed punishment task selection
    • \n
    • Mapping Tool: Fixed the modifier values to increase/decrease difficulty
    • \n
    • Mapping Tool: Retain hidden columns settings
    • \n
    • Mapping Tool: Show a warning before resetting the map
    • \n
    • Mapping Tool: Full width tables
    • \n
    • Mapping Tool: Updated edit buttons in table view
    • \n
    \n
    \n
    \n
    \n
    2020-07-20
    \n
    \n

    \n Lots of changes, especially in the background.\n

    \n
      \n
    • Maps: Added support for "Sub-Maps" (allows loading content of maps or map modules into a "main" map, therefore extending the existing content)
    • \n
    • Progress: Show current credits (even if you have 0)
    • \n
    • Tasks: Fixed counter / timer rounding error (not in sync with the task description)
    • \n
    • Mapping Tool: Support to set custom help entries (and removed SU specific help entries)
    • \n
    • Mapping Tool: Added support to export "partial" Maps (called modules)
    • \n
    • Mapping Tool: Added option to "hide"/"disable" the orgasm roulette (in case you want to handle it using classes or don't need it)
    • \n
    • Mapping Tool: Restore filter, sort, page while you edit items using the table view
    • \n
    • Mapping Tool: Fixed inputs with '"'
    • \n
    • Mapping Tool: Fixed sorting (was broken by direct access links)
    • \n
    • Mapping Tool: Format the "Available On" dates as a readable string instead of a representative number
    • \n
    • Mapping Tool: Fixed "Save" button hidden by back to top in full-screen modal
    • \n
    • Mapping Tool: Changed style for new multi-selections
    • \n
    • Schedule: In case you already have max. classes you will not be tempted to add another one if you already finished all classes for the day.
    • \n
    • Schedule: Easy mode warning added
    • \n
    \n
    \n
    \n
    \n
    2020-07-14
    \n
    \n

    \n Small fix for the Mapping Tool (Long items were pushed to the side).\n

    \n
    \n
    \n
    \n
    2020-07-13
    \n
    \n

    \n Some small changes.\n

    \n
      \n
    • Mapping Tool: uses now a better UI to select multiple items
    • \n
    • Help: your health is priority #1
    • \n
    • added "Update Now" info in case an update is pending
    • \n
    \n
    \n
    \n
    \n
    2020-07-11
    \n
    \n

    \n Various quality of life changes.\n

    \n
      \n
    • Mapping Tool: added table view to make it easier to create/edit maps
    • \n
    • Mapping Tool: punishment tasks now preview the degree as well
    • \n
    • Schedule: Weekend info added
    • \n
    • Tasks: Added confirmation for using the "fail" button on tasks
    • \n
    • Mobile Navigation: hide the "hamburger" icon if there is nothing to navigate to (no map loaded / no major chosen)
    • \n
    • Mobile Navigation: auto collapses the "hamburger" menu after a successful selection
    • \n
    • Other: SW will now skip waiting (immediate update)
    • \n
    \n
    \n
    \n
    \n
    2020-06-30.3
    \n
    \n

    \n Attempt to fix download on iOS.\n

    \n
    \n
    \n
    \n
    2020-06-21
    \n
    \n

    \n Updated dependencies: jQuery, Bootstrap, Popper.js, Font Awesome and Moment.js.\n

    \n
    \n
    \n
    \n
    2020-06-20
    \n
    \n

    \n Fixed some minor problems.\n

    \n
      \n
    • Counter tasks finished the complete task regardless if something else was still required.
    • \n
    • Offset for images in the detail view was off
    • \n
    • Manual tasks were required to be finished on the same day, can now also be done after the main task.
    • \n
    • Probably fixed the check for skipped classes.
    • \n
    \n
    \n
    \n
    \n
    2020-06-16
    \n
    \n

    \n This is the new release of PU. It ports most of the functions present in\n the original and adds a new flare at some parts.\n

    \n
      \n
    • Allow creating and running custom maps
    • \n
    • Option to create and share simple tasks
    • \n
    • Darkmode
    • \n
    • UI Tutorial
    • \n
    • Tasks can have sub-tasks
    • \n
    • Counter tasks (Keep track of your progress)
    • \n
    • Auto hide the page from curious eyes
    • \n
    • Run multiple different maps at the same time
    • \n
    • Navigation works now as it should
    • \n
    • Mobile support
    • \n
    • Offline mode (continues working without an internet connection)
    • \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n

    ${e.T("home-special-thanks-header", "Special thanks to")}

    \n
      \n
    • Maya for inspiring this framework.
    • \n
    • Nebulas Astra (github:@nebulas-star) and Carlotta (Twi:@Carl84534787) for providing the chinese translation.
    • \n
    • Sam, BG, ClaireTorres, Lieutenant Bites and aspiringanalslut for beta testing.
    • \n
    \n
    \n
    \n \n \n
    `; } } class q { static get expectedParameters() { return { getView: [] }; } static getView() { const e = s.getInstance(); return `
    \n

    ${e.T("roulette-header", "Orgasm Roulette")}

    \n
    \n
    \n
    \n
    \n \n
    \n
    \n
    \n

    \n ${e.T("roulette-info-1", "Do you want to cum?")} ${e.T("roulette-info-2", "Try your luck!")}
    \n ${e.T("roulette-info-3", "Did you cum without asking Random-chan?")} ${e.T("roulette-info-4", "Get your punishment!")}\n

    \n ${B.getLastRouletteHtml()}\n
    \n ${B.getRollRouletteButton()}\n
    \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n `; } } class B { static rollRoulette() { if (B.wasRouletteRolledToday()) return !1; let e = Math.random(), t = 0, a = p.rouletteData, s = void 0; for (let i in a) if (((s = a[i]), (t += s.probability), e < t)) break; s && (s.rollPunishment && T.getRandomPunishment(), p.setRoulette({ id: s.id, rolledOn: n.getNowUnix() }), _.inAppReload()); } static lastRouletteResult() { return p.getRoulette() || !1; } static wasRouletteRolledToday() { const e = B.lastRouletteResult(); return !(!e || !e.rolledOn) && !!n.isToday(e.rolledOn); } static getLastRouletteHtml() { if (!B.wasRouletteRolledToday()) return ""; const e = B.lastRouletteResult(); if (!e || !e.hasOwnProperty("id")) return ""; const t = p.rouletteData[e.id]; return `
    \n
    \n
    ${t.title}
    \n

    ${t.description}

    \n
    \n
    `; } static getRollRouletteButton() { return B.wasRouletteRolledToday() ? "" : ``; } } class R { static get expectedParameters() { return { getView: [], import_task: ["customTask"] }; } static import_task(e) { return ( _.replaceHistory("?page=schedule"), O.handleImport(atob(e.customTask)), R.getView() ); } static getView() { const e = p.getActiveMajor(), t = (p.majorsData[e], I.mandatoryClasses(e), S.getActiveClasses()), a = (S.getFinishedClasses(), n.getTodaysDay()), i = p.getActivePunishments(), o = p.getActiveTasks(), r = p.clubsData, l = p.getAssignedClubs(), d = p.partnersData, c = p.getActivePartners(), u = s.getInstance(); return ( O.requestFeedback(), `
    \n ${o.length > 0 ? `\n

    \n ${u.T("schedule-active-tasks-header", "Active tasks")}\n

    \n
    \n
    \n ${R.getActiveTasks(o)}\n
    \n
    ` : ""}\n\n ${i.length > 0 && i.length - o.filter((e) => "punishment" == e.object.type).length > 0 ? `\n

    \n ${u.T("schedule-active-punishments-header", "Active punishments")} \n

    \n
    \n
    \n ${R.getPunishmentsHtml(i)}\n
    \n
    ` : ""}\n\n \n

    \n ${u.T("schedule-todays-classes-header", "Todays classes")} \n

    \n
    \n
    \n ${R.getTodaysClasses(t, a)}\n
    \n
    \n\n ${l.length > 0 || c.length > 0 ? `\n

    \n ${u.T("schedule-clubs-partner-header", "Clubs & Partner")}\n

    \n
    \n
    \n ${R.getAssignedClubsAndPartners(r, l, d, c)}\n
    \n
    ` : ""}\n\n

    \n ${u.T("schedule-weekly-header", "Weekly overview")}\n

    \n
    \n
    \n ${R.getWeekOverviewHtml(t, a)}\n
    \n
    \n
    \n ` ); } static getAssignedClubsAndPartners(e, t, a, s) { let i = ""; for (let a of t) { const t = e[a.id]; i += `\n
    \n
    \n
    \n
    \n \n \n \n
    \n
    \n
    \n
    ${t.name}
    \n
    \n
      \n ${g.getPerksHtml(t.id)}\n
    \n
    \n
    \n
    \n
    \n
    \n
    `; } for (let e of s) { const t = a[e.id]; i += `\n
    \n
    \n
    \n
    \n \n \n \n
    \n
    \n
    \n
    ${t.name}
    \n
    \n
      \n ${b.getPerksHtml(t.id)}\n
    \n
    \n
    \n
    \n
    \n
    \n
    `; } return i; } static getTodaysClasses(e, t) { const a = p.classesData, i = s.getInstance(); let o = ""; const r = p.getClassSkipTokens(); n.getSetting("enableEasyMode") ? (o += `\n
    \n ${i.T("schedule-easy-mode-info", 'Currently the "Easy mode" is active. You may attend any class on any day and skip whenever you want.')}\n
    \n `) : 0 === t || 6 === t ? (o += `\n
    \n ${i.T("schedule-weekend-info", "During the weekend, you are free to do any classes you want. You will not be punished for skipping them today.")}\n
    \n `) : r > 0 && (o += `\n
    \n ${i.T("class-skip-token-info", "You have %s class-skip tokens. Each allows you to skip one class today.", [r])}\n
    \n `); for (const t of e) { let e = a[t.id]; S.wasAttendedToday(e.id) || (n.hasMatchingDate(e.days) && void 0 === x.getActiveTaskForObject(e.id, "class") && (o += `\n
    \n
    \n
    \n
    \n \n \n \n
    \n
    \n
    \n
    ${e.name}
    \n
    \n
      \n
    • ${i.T("class-detail-tasks", "Available task options")}:
    • \n ${A.getTasksHtml(e, t.attendances || 0)}\n\n ${S.isExamAvailable(e.id) ? `\n
    • ${i.T("class-detail-exams", "Available exam options")}:
    • \n ${A.getExamsHtml(e)} ` : ""}\n
    \n
    \n
    \n
    \n
    \n
    \n
    `)); } return ( o.length <= 0 && S.isMaxClassesReached() ? (o = `${i.T("schedule-no-class-left", "No classes left for today. Please visit tomorrow again.")}`) : o.length <= 0 && (o = `${i.T("schedule-no-class-left-add", "No classes left for today. Maybe you want to join a new class?")}`), o ); } static getActiveTasks(e) { let t = ""; for (let a of e) t += `
    ${x.getTaskProgressHtml(a)}
    `; return t; } static getPunishmentsHtml(e) { const t = p.punishmentsData; let a = ""; for (let s of e) { if (void 0 !== x.getActiveTaskForObject(s, "punishment")) continue; let e = t[s]; a += `\n
    \n
    \n
    \n
    \n \n \n \n
    \n
    \n
    \n
    ${e.name}
    \n
    ${w.getPunishmentTasksHtml(e)}
    \n
    \n
    \n
    \n
    \n
    `; } return a; } static getWeekOverviewHtml(e, t) { let a = { 1: [], 2: [], 3: [], 4: [], 5: [] }; const i = s.getInstance(), o = n.weekdays; for (const t of e) { const e = p.classesData[t.id], s = `${e.name}`; for (const t of e.days) t < 1 || t > 5 || a[t].push(s); } for (const e in a) a[e].length <= 0 && a[e].push(i.T("schedule-no-classes", "No classes")); return `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    ${o[1]}${o[2]}${o[3]}${o[4]}${o[5]}
    ${a[1].join("
    ")}
    ${a[2].join("
    ")}
    ${a[3].join("
    ")}
    ${a[4].join("
    ")}
    ${a[5].join("
    ")}
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    ${o[1]}${a[1].join("
    ")}
    ${o[2]}${a[2].join("
    ")}
    ${o[3]}${a[3].join("
    ")}
    ${o[4]}${a[4].join("
    ")}
    ${o[5]}${a[5].join("
    ")}
    `; } static requestUserFeedback() { let e = "requestUserFeedback-" + n.getRandomString(); const t = s.getInstance(); let a = ``; ($("#confirmationContainer").append(a), $("#" + e) .modal({ keyboard: !1 }) .modal("show") .on("hidden.bs.modal", function (t) { $("#" + e) .modal("dispose") .remove(); })); } } class O { static async importTask(e) { const t = s.getInstance(), a = { title: t.T( "schedule-task-import-failed-header", "Task import failed", ), text: t.T( "sschedule-task-import-failed-content", "During the import for the selected file an error occured. You can check the console for more information.", ), }, i = { title: t.T( "schedule-file-import-compression-error-header", "Compression unavailable", ), text: t.T( "schedule-file-import-compression-error-content", "The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.", ), }, n = { title: t.T("schedule-file-import-aborted-header", "Import aborted"), text: t.T( "schedule-file-import-aborted-content", "The file you tried to import included untrusted code. To protect you the import was aborted.", ), }, o = await _.runImport(e, a, void 0, i, n); O.importTaskData(o); } static importTaskData(e) { let t = { type: "custom", name: l.T("schedule-custom-task-default-name", "Custom Task"), tasks: {}, }; ((t.tasks["" + e.id] = e), (t = d.applyParametersToObject(t)), x.startTask(t, "tasks", "" + e.id)); } static handleImport(e) { const t = s.getInstance(); let a = function (e) { if (e.error || !e.data) return void n.showToast( t.T("schedule-import-task-error-header", "Task import failed"), t.T( "schedule-import-task-error-content", "Importing the shared task was not successful.", ), ); const a = e.data; O.importTaskData(a); }.bind(this), i = function () { c.downloadFromPastebin(e, a); }.bind(this); n.requestUserConfirmation( t.T( "schedule-import-custom-task-confirmation-header", "Import a custom task", ), t.T( "schedule-import-custom-task-confirmation-content", "Are you sure that you want to import this custom task? The task will be started automatically. Make sure to only load tasks from trusted sources. Proceed at your own risk.", ), i, ); } static requestFeedback() { let e = n.getlastRequestedFeedback(); (moment.unix(e).isValid() || ((e = n.getNow().add(3, "day").startOf("day").unix()), n.setlastRequestedFeedback(e)), e <= n.getNowUnix() && ((e = n.getNow().add(1, "month").startOf("day").unix()), R.requestUserFeedback(), n.setlastRequestedFeedback(e))); } } class H { static startTour(e) { let t = H.tour, a = []; if (!t.hasOwnProperty(e)) return !1; const i = s.getInstance(), o = new Shepherd.Tour({ tourName: "" + e, useModalOverlay: !0, defaultStepOptions: { classes: "shadow-md bg-purple-dark", scrollTo: !1, buttons: [ { text: i.T("tour-navigation-close", "Close"), action() { return this.complete(); }, }, { text: i.T("tour-navigation-next", "Next"), action() { return this.next(); }, }, ], }, }); (t[e] && (a = t[e].filter((e) => $(e.attachTo.element).is(":visible"))), a.length > 0 && (n.setTutorial(n.getTutorial().concat(e, ";")), o.addSteps(a), o.start())); } static get tour() { const e = s.getInstance(); return { majors: [ { id: "majors-0", title: e.T("tour-majors-0-title", "Navigation"), text: e.T( "tour-majors-0-text", "This is the global navigation. Here you can switch to different areas of the game.", ), attachTo: { element: "body nav.navbar", on: "bottom" }, }, { id: "majors-1", title: e.T("tour-majors-1-title", "Mobile navigation"), text: e.T( "tour-majors-1-text", "On mobile not all items are always visible, use this button to show the available entries.", ), attachTo: { element: "nav .navbar-toggler", on: "bottom" }, }, { id: "majors-2", title: e.T("tour-majors-2-title", "Major cards"), text: e.T( "tour-majors-2-text", "Each major available will be displayed as a card to allow interacting with it.", ), attachTo: { element: ".min-card-container", on: "bottom" }, }, { id: "majors-3", title: e.T("tour-majors-3-title", "Details"), text: e.T( "tour-majors-3-text", "You can access a detail view by clicking on the image of the major.", ), attachTo: { element: ".min-card-container .img-container-overview", on: "bottom", }, }, { id: "majors-4", title: e.T("tour-majors-4-title", "Card actions"), text: e.T( "tour-majors-4-text", "The major cards can have multiple actions related to it.", ), attachTo: { element: ".min-card-container .img-button-container-left-corner", on: "bottom", }, }, { id: "majors-5", title: e.T("tour-majors-5-title", "Display information"), text: e.T( "tour-majors-5-text", "This information button will allow you quick access to some detail information related to the major.", ), attachTo: { element: ".min-card-container .img-button-container-left-corner .text-white", on: "bottom", }, }, { id: "majors-6", title: e.T("tour-majors-6-title", "Card information"), text: e.T( "tour-majors-6-text", "The bottom of the card will give a small description for the major.", ), attachTo: { element: ".min-card-container .card-body", on: "bottom", }, }, ], classes: [ { id: "classes-0", title: e.T("tour-classes-0-title", "Tier navigation"), text: e.T( "tour-classes-0-text", "Sometimes there are multiple tiers, you can navigate to a different one using these items.", ), attachTo: { element: "#action-navigation", on: "right" }, }, { id: "classes-1", title: e.T("tour-classes-1-title", "Tier navigation"), text: e.T( "tour-classes-1-text", "Sometimes there are multiple tiers, you can navigate to a different one using these items or swipe to the side.", ), attachTo: { element: "#customCarousel .carousel-indicators", on: "bottom", }, }, { id: "classes-2", title: e.T("tour-classes-2-title", "Card"), text: e.T( "tour-classes-2-text", "Each class available will be displayed as a card to allow interacting with it.", ), attachTo: { element: ".min-card-container", on: "bottom" }, }, { id: "classes-3", title: e.T("tour-classes-3-title", "Image"), text: e.T( "tour-classes-3-text", "You can access a detail view by clicking on the image of the class.", ), attachTo: { element: ".min-card-container .img-container-overview", on: "bottom", }, }, { id: "classes-4", title: e.T("tour-classes-4-title", "Card actions"), text: e.T( "tour-classes-4-text", "The class cards can have multiple actions related to it.", ), attachTo: { element: ".min-card-container .img-button-container-left-corner", on: "bottom", }, }, { id: "classes-5", title: e.T("tour-classes-5-title", "Display information"), text: e.T( "tour-classes-5-text", "This information button will allow you quick access to some detail information related to the class.", ), attachTo: { element: ".min-card-container .img-button-container-left-corner .text-white", on: "bottom", }, }, { id: "classes-6", title: e.T("tour-classes-6-title", "Card information"), text: e.T( "tour-classes-6-text", "These information will give a small description for the class.", ), attachTo: { element: ".min-card-container .card-body", on: "bottom", }, }, ], mapping: [ { id: "mapping-0", title: "Global actions", text: "Use the actions to export and import the complete map.", attachTo: { element: "#action-navigation nav ul.list-group", on: "bottom", }, }, { id: "mapping-0-info", title: "Hide actions", text: 'If you need additional space (e.g. on mobile) you can click on the header "Actions" to collapse them.', attachTo: { element: "#action-navigation ul li.list-group-item", on: "bottom", }, }, { id: "mapping-0-desktop", title: "Navigation", text: "Use the navigation menu to switch to different sub menues.", attachTo: { element: "#action-navigation nav ul.list-group:nth-child(3)", on: "right", }, }, { id: "mapping-0-mobile", title: "Mobile navigation", text: "Use these buttons or swipe to switch between the different sub menues.", attachTo: { element: "#mappingCarousel .carousel-indicators", on: "bottom", }, }, { id: "mapping-1", title: "Save map", text: "Make sure to save your progress regularly!", attachTo: { element: "#action-navigation nav ul div.btn-group", on: "bottom", }, }, { id: "mapping-2", title: "Export finished map", text: "Once you are done export a playable version.", attachTo: { element: "#action-navigation nav ul div.btn-group", on: "bottom", }, }, { id: "mapping-3", title: "Save your changes", text: "Each change needs to be saved. If you don't want to keep the changes use the 'Close' button instead.", attachTo: { element: "#generalCardContainer .btn-primary", on: "bottom", }, }, { id: "mapping-4", title: "Add new items", text: "To add new items you will find such a 'plus' button next to the corresponding items, use it to open a new sub menu to create another one. If you create a new item related to something else it will automatically be linked.", attachTo: { element: ".header-add", on: "bottom" }, }, { id: "mapping-5", title: "Created items", text: "You will find created items below the corresponding header.", attachTo: { element: "#helpCardContainer", on: "bottom" }, }, ], }; } } class F { static get expectedParameters() { return { getView: ["majorId"] }; } static getView(e) { const t = e.majorId, a = I.getGrade(t), i = p.majorsData[t], n = s.getInstance(); return ( p.getFinishedMajors().includes(t) || _.navigateBack(), `
    \n

    \n ${n.T("graduated-certificate-header", "Certificate for %s", [i.name])}\n

    \n
    \n
    \n
    \n
    \n \n \n \n
    ${n.T("major-status-grade", "Grade")} ${a}
    \n
    \n
    \n
    \n
    ${n.T("graduated-congratulations-header", "Congratulations for graduating %s.", [i.name])}
    \n

    ${n.T("graduated-congratulations-1", "Many take on this journey but only few manage to finish it successfully. I am glad to see that you are one of those few!")}

    \n

    ${n.T("graduated-congratulations-2", "There is still a lot todo and I am sure you didn't have enough yet. Once you are ready for the next challenge start one of the other majors or import a new map.")}

    \n

    ${n.T("graduated-congratulations-3", "Since you are now very experienced you may also consider to create your own classes for fellow students to enrich their university life.")}

    \n
    \n ${F.getGradeTable(t)}\n
    \n
    \n
    \n
    \n
    \n ` ); } static getGradeTable(e) { let t = 0, a = 0, i = ""; const r = s.getInstance(); for (const s of I.mandatoryClasses(e)) { const e = p.classesData[s], r = S.getAssignedClass(s), l = o.maxAttendances[e.tier]; ((i += `\n \n ${e.name}\n ${r.attendances}/${l}\n ${n.getGrade(r.attendances, l)}\n \n `), (t += r.attendances), (a += l)); } return `\n \n \n \n \n \n \n \n \n \n ${i}\n \n \n \n \n \n \n \n \n
    ${r.T("graduated-table-class", "Class")}${r.T("graduated-table-knowledge-points", "Knowledge Points")}${r.T("graduated-table-grade", "Grade")}
    ${t}/${a}${n.getGrade(t, a)}
    \n `; } } class U { static get expectedParameters() { return { getView: [] }; } static getView() { const e = s.getInstance(); return `
    \n

    ${e.T("disclaimer-header", "Disclaimer and Data Protection")}

    \n
    \n
    \n
    \n

    \n ${e.T("disclaimer-block-1", "Websites that can be accessed publicly are required to fulfill some legal requirements. \n Which in turn makes us both suffer through this short introduction what this website does and so on.\n You can find a link to this page at the bottom of the home page.\n If you are okay with the following you may continue using this website and agree using the button at the bottom. \n If you disagree we will clean up all the stored data and send you on your way.")}\n

    \n\n

    ${e.T("disclaimer-overview-header", "Overview")}

    \n
      \n
    • ${e.T("disclaimer-overview-1", 'In the following the term "User" is used to reference you and "We" for us the provider.')}
    • \n
    • ${e.T("disclaimer-overview-2", "This website is a free tool you may use as-is as long as we want to provide it. You might come back here tomorrow and everything is gone.")}
    • \n
    • ${e.T("disclaimer-overview-3", "This website doesn't use any back-end besides a simple Web-Service used to provide the website content.")}
    • \n
    • ${e.T("disclaimer-overview-4", "This website runs completely on your device. If you lose it or clear the device/website storage everything will be gone.")}
    • \n
    • ${e.T("disclaimer-overview-5", "This website is only a framework, therefore requires user generated content which is manually created and provided by the users. We don't know what anybody uses this service for. \n You can compare this to a local text editor - you can create or open text files, while everything stays on your device. You may use this tool to access files created by other users.")}
    • \n
    • ${e.T("disclaimer-overview-6", "The source code required to run this website is served completely from this domain. There are some links which leave this page, we have no influence on whatever they are doing.")}
    • \n
    • ${e.T("disclaimer-overview-7", "User generated content may be malicious, we do attempt to protect you from obvious attacks - but in the end you are responsible for the external content you load.")}
    • \n
    \n\n

    ${e.T("disclaimer-inner-header", "Disclaimer")}

    \n

    \n ${e.T("disclaimer-inner", "Using this website is free of charge, there are no ad-services or tracking-services active. There is no aim to make any money using this framework.\n\n Since this website uses user generated content and doesn't store any information about the users, the provider is unable to audit any \n user generated content you are loading. You will therefore need to take full reponsibiltiy for any user generated content you are adding.\n Additionally the provider is not responsible for the content of linked pages, if there is a legal requirement to remove any reference from\n the framework it will be complied to.")}\n

    \n\n

    ${e.T("disclaimer-data-protection-header", "Data protection notice")}

    \n

    \n ${e.T("disclaimer-data-protection", 'No data is collected and stored outside of the user devices. Progress, settings, content and feedback marker are stored on your local device. \n User generated content stays on the device of the user. If you decide to provide a "heartbeat" using the feedback option you are leaving \n this website and the Bitly Privacy Policy applies. The feedback is optional and Bitly-Links will be highlighted.')}\n ${e.T("disclaimer-bitly-link", "Click here to view the Bitly Privacy Policy on bitly.com")}. \n

    \n\n

    ${e.T("disclaimer-stored-data-header", "Stored data")}

    \n

    \n ${e.T("disclaimer-stored-data-description", "Multiple technologies are used to store your progress, settings and added user generated content.")}\n

      \n
    • ${e.T("disclaimer-stored-data-local", "Local storage: Settings, Delayed notifications, Last feedback information request, Accepted disclaimer, Already displayed tutorials")}
    • \n
    • ${e.T("disclaimer-stored-data-indexeddb", "IndexedDB: User generated content, Current progress")}
    • \n
    • ${e.T("disclaimer-stored-data-cache", "Cache: The website framework data to allow offline mode")}
    • \n
    \n

    \n
    \n
    \n
    \n
    \n \n
    \n
    \n \n
    \n
    \n
    \n
    \n `; } } class L { static acceptedDisclaimer() { n.setAcceptedDisclaimer(); const e = new URLSearchParams( window.location.search.replace(/#.*?$/, ""), ); if (e.get("redirect")) { let t = atob(decodeURIComponent(e.get("redirect"))); if (t.startsWith("?")) return void _.navigate("" + t); } _.navigate("?page=home"); } static rejectedDisclaimer() { (localStorage.clear(), sessionStorage.clear(), "serviceWorker" in navigator && (caches.keys().then(function (e) { for (let t of e) caches.delete(t); }), navigator.serviceWorker.getRegistrations().then(function (e) { for (let t of e) t.unregister(); })), r.getInstance().drop( function () { _.navigate("about:blank"); }.bind(this), )); } } let V, G = 0; class J { static get expectedParameters() { return { getView: [] }; } static getView(e) { return ( Y.reset(), Y.registerWarning(), Y.autoSaveRestore(), document .querySelector("html") .addEventListener("mappingRefreshed", function (e) { ($("#majorsTable").bootstrapTable(), $("#classesTable").bootstrapTable(), $("#tasksTable").bootstrapTable(), $("#punishmentsTable").bootstrapTable(), $("#clubsTable").bootstrapTable(), $("#partnersTable").bootstrapTable(), $("#modifiersTable").bootstrapTable(), $("#tagsTable").bootstrapTable(), $("#helpTable").bootstrapTable(), $("#rouletteTable").bootstrapTable()); }), document .querySelector("html") .addEventListener("navigationFinished", function (e) { Y.refreshView(); }), `
    \n
    \n
    \n
    \n \n
    \n \n
    \n
    \n
    \n
    ` ); } static generateImageSelection(e) { return `\n
    \n
    \n
    \n Provided image\n
    \n
    \n
    \n
    \n Optimal size is 600 x 800 px.\n
    \n\n \n \n \n
    \n
    \n
    \n
    \n `; } static generateRouletteTableEntry(e) { return `\n \n ${e.id}\n ${e.title} \n ${e.description} \n ${e.probability} \n ${e.rollPunishment} \n \n
    \n \n \n
    \n \n \n `; } static generateHelpTableEntry(e) { return `\n \n ${e.id}\n ${e.title} \n ${e.text} \n \n
    \n \n \n
    \n \n \n `; } static generateTagTableEntry(e) { return `\n \n ${e} \n \n
    \n \n \n
    \n \n \n `; } static generateRouletteCard(e) { return `
    \n
    \n
    \n
    Roulette ${e.id}: ${e.title}
    \n

    ${e.description}

    \n
    \n
    \n Edit\n
    \n
    \n
    `; } static generateHelpCard(e) { return `
    \n
    \n
    \n
    Help ${e.id}: ${e.title}
    \n

    ${e.text}

    \n
    \n
    \n Edit\n
    \n
    \n
    `; } static generateTagCard(e) { return `
    \n
    \n
    \n
    ${e}
    \n Edit\n
    \n
    \n
    `; } static generateParamCard(e) { return `
    \n
    \n
    \n
    $param${e.id}
    \n

    ${e.value} ${"punishments" == e.valueType ? e.punTier : ""} ${e.valueType} ${e.description.length > 0 ? `(${e.description})` : ""}

    \n
    \n
      \n ${e.applyMultiplier ? '
    • Apply multiplier
    • ' : ""}\n ${e.spawnTimer ? '
    • Spawn timer
    • ' : ""}\n ${e.startTimerAutomatically ? '
    • Start timer automatically
    • ' : ""}\n ${e.provideCounter ? '
    • Provide counter
    • ' : ""}\n ${e.punishTime ? '
    • Provide punish option
    • ' : ""}\n ${e.hiddenTask ? '
    • Delayed sub-task
    • ' : ""}\n
    \n
    \n Edit\n Insert\n
    \n
    `; } static generateTaskTableEntry(e) { let t = Y.applyParamsToString(e.id, e.description), a = "", s = n.getRandomString(); if (void 0 !== e.tags && e.tags.length > 0) for (const t of e.tags) a += `${t}`; return `\n \n ${e.id}\n Task ${e.id} \n ${t}\n ${a}\n \n
    \n \n \n
    \n
    \n \n
    \n \n \n `; } static generateTaskCard(e) { let t = Y.applyParamsToString(e.id, e.description), a = ""; if (void 0 !== e.tags && e.tags.length > 0) for (const t of e.tags) a += `${t}`; return `
    \n
    \n
    \n
    Task ${e.id}
    \n

    ${t}

    \n ${a.length > 0 ? `

    ${a}

    ` : ""}\n
    \n \n
    `; } static generateClassTableEntry(e) { let t = "", a = "", s = "", i = n.getRandomString(); for (const s of V.tasks.sort((e, t) => e.id - t.id)) (e.tasks.includes(s.id) && (t += `
  • \n Task ${s.id}:
    \n ${Y.applyParamsToString(s.id, s.description)}\n
  • `), e.exams.includes(s.id) && (a += `
  • \n Task ${s.id}:
    \n ${Y.applyParamsToString(s.id, s.description)}\n
  • `)); for (const t of V.classes.sort((e, t) => e.id - t.id)) e.prerequisites.includes(t.id) && (s += `
  • \n Class ${t.id}:
    \n ${t.title}\n
  • `); return `\n \n ${e.id}\n ${e.tier}\n ${e.title}\n ${e.subtitle}\n ${e.comment}\n ${e.description}\n ${e.days.map((e) => n.weekdays[e]).join(", ")}\n
      ${s}
    \n
      ${t}
    \n
      ${a}
    \n \n
    \n \n \n
    \n \n \n `; } static generateClassCard(e) { let t = "", a = "", s = "", i = n.getRandomString(); for (const s of V.tasks.sort((e, t) => e.id - t.id)) (e.tasks.includes(s.id) && (t += `
  • \n
    \n
    \n \n
    \n
    Task ${s.id} ${Y.applyParamsToString(s.id, s.description)}
    \n
    \n
  • `), e.exams.includes(s.id) && (a += `
  • \n
    \n
    \n \n
    \n
    Task ${s.id} ${Y.applyParamsToString(s.id, s.description)}
    \n
    \n
  • `)); for (const t of V.classes.sort((e, t) => e.id - t.id)) e.prerequisites.includes(t.id) && (s += `
  • \n
    \n
    \n \n
    \n
    Class ${t.id} ${t.title}
    \n
    \n
  • `); return `
    \n
    \n
    \n \n \n ${n.getButtonsContainer( [ ``, ` Edit`, ], )}\n\n
    \n
    \n
      \n ${s.length > 0 ? '
    • Prerequisites:
    • \n ' + s : ""}\n ${a.length > 0 ? '
    • Available Exam Tasks:
    • \n ' + a : ""} \n ${t.length > 0 ? '
    • Available Tasks:
    • \n ' + t : ""}\n
    \n
    \n
    \n
    \n\n
    \n
    \n Class ${e.id}: ${e.title} \n ${e.subtitle}\n ${void 0 !== e.comment ? `${e.comment}` : ""}\n
    \n

    ${e.description}

    \n
    \n
    \n
    `; } static generateMajorTableEntry(e) { let t = "", a = "", s = n.getRandomString(); for (const a of V.tasks.sort((e, t) => e.id - t.id)) e.exams.includes(a.id) && (t += `
  • \n Task ${a.id}:
    \n ${Y.applyParamsToString(a.id, a.description)}\n
  • `); for (const t of V.classes.sort((e, t) => e.id - t.id)) e.prerequisites.includes(t.id) && (a += `
  • \n Class ${t.id}:
    \n ${t.title}\n
  • `); return `\n \n ${e.id}\n ${e.title}\n ${e.subtitle}\n ${e.description}\n
      ${a}
    \n
      ${t}
    \n \n
    \n \n \n
    \n \n \n `; } static generateMajorCard(e) { let t = "", a = "", s = n.getRandomString(); for (const a of V.tasks.sort((e, t) => e.id - t.id)) e.exams.includes(a.id) && (t += `
  • \n
    \n
    \n \n
    \n
    Task ${a.id} ${Y.applyParamsToString(a.id, a.description)}
    \n
    \n
  • `); for (const t of V.classes.sort((e, t) => e.id - t.id)) e.prerequisites.includes(t.id) && (a += `
  • \n
    \n
    \n \n
    \n
    Class ${t.id} ${t.title}
    \n
    \n
  • `); return `
    \n
    \n
    \n \n \n ${n.getButtonsContainer( [ ``, ` Edit`, ], )}\n\n
    \n
    \n
      \n ${a.length > 0 ? '
    • Prerequisites:
    • \n ' + a : ""}\n ${t.length > 0 ? '
    • Available Exam Tasks:
    • \n ' + t : ""}\n
    \n
    \n
    \n
    \n\n
    \n
    \n Major ${e.id}: ${e.title}\n ${e.subtitle}\n
    \n

    ${e.description}

    \n
    \n
    \n
    `; } static generatePunishmentTableEntry(e) { let t = V.tasks.filter((t) => t.id === e.punishment)[0], a = Y.applyParamsToString(t.id, t.description); return `\n \n ${e.id}\n ${e.tier}\n ${e.title}\n Task ${t.id}:
    ${a}\n \n
    \n \n \n
    \n \n \n `; } static generatePunishmentCard(e) { let t = V.tasks.filter((t) => t.id === e.punishment)[0], a = Y.applyParamsToString(t.id, t.description); return `
    \n
    \n
    \n \n ${n.getButtonsContainer( [ ` Edit`, ], )}\n
    \n\n
    \n
    \n Punishment ${e.id}: ${e.title}\n ${e.tier}\n
    \n

    ${a}

    \n
    \n
    \n
    `; } static generatePartnerTableEntry(e) { let t = "", a = n.getRandomString(); for (const a of V.modifiers.sort((e, t) => e.id - t.id)) e.modifiers.includes(a.id) && (t += `
  • \n Modifier ${a.id}:
    \n ${Y.getModifierText(a)}\n
  • `); return `\n \n ${e.id}\n ${e.name}\n ${e.description}\n
      ${t}
    \n \n
    \n \n \n
    \n \n \n `; } static generatePartnerCard(e) { let t = "", a = n.getRandomString(); for (const a of V.modifiers.sort((e, t) => e.id - t.id)) e.modifiers.includes(a.id) && (t += `
  • \n
    \n
    \n \n
    \n
    Modifier ${a.id} ${Y.getModifierText(a)}
    \n
    \n
  • `); return `
    \n
    \n
    \n \n \n ${n.getButtonsContainer( [ ``, ` Edit`, ], )}\n\n
    \n
    \n
      \n ${t.length > 0 ? '
    • Modifiers:
    • \n ' + t : ""}\n
    \n
    \n
    \n
    \n\n
    \n
    \n Partner ${e.id}: ${e.name}\n
    \n

    ${e.description}

    \n
    \n
    \n
    `; } static generateClubTableEntry(e) { let t = "", a = n.getRandomString(); for (const a of V.modifiers.sort((e, t) => e.id - t.id)) e.modifiers.includes(a.id) && (t += `
  • \n Modifier ${a.id}:
    \n ${Y.getModifierText(a)}\n
  • `); return `\n \n ${e.id}\n ${e.tier}\n ${e.name}\n ${e.comment}\n ${e.description}\n
      ${t}
    \n \n
    \n \n \n
    \n \n \n `; } static generateClubCard(e) { let t = "", a = n.getRandomString(); for (const a of V.modifiers.sort((e, t) => e.id - t.id)) e.modifiers.includes(a.id) && (t += `
  • \n
    \n
    \n \n
    \n
    Modifier ${a.id} ${Y.getModifierText(a)}
    \n
    \n
  • `); return `
    \n
    \n
    \n \n \n ${n.getButtonsContainer( [ ``, ` Edit`, ], )}\n\n
    \n
    \n
      \n ${t.length > 0 ? '
    • Modifiers:
    • \n ' + t : ""}\n
    \n
    \n
    \n
    \n\n
    \n
    \n Club ${e.id}: ${e.name} ${e.tier}\n ${void 0 !== e.comment ? `${e.comment}` : ""}\n
    \n

    ${e.description}

    \n
    \n
    \n
    `; } static generateModifierTableEntry(e) { let t = Y.getModifierTags(e.tags), a = "", s = n.getRandomString(); if (void 0 !== e.tags && e.tags.length > 0) for (const t of e.tags) a += `${t}`; return `\n \n ${e.id}\n Modifier ${e.id}\n ${Y.getModifierMod(e, t)}\n ${Y.getModifierPerk(e, t)}\n \n
    \n \n \n
    \n \n \n `; } static generateModifierCard(e) { let t = Y.getModifierText(e), a = ""; if (void 0 !== e.tags && e.tags.length > 0) for (const t of e.tags) a += `${t}`; return `
    \n
    \n
    \n
    Modifier ${e.id}
    \n

    ${t}

    \n ${a.length > 0 ? `

    ${a}

    ` : ""}\n
    \n \n
    `; } static getGeneralView() { return `
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n \n
    `; } static generateCustomExportModal(e) { let t = "", a = "", s = "", i = "", n = "", o = ""; if ( void 0 !== V.majors && Array.isArray(V.majors) && V.majors.length > 0 ) for (const e of V.majors.sort((e, t) => e.id - t.id)) t += ``; if ( void 0 !== V.classes && Array.isArray(V.classes) && V.classes.length > 0 ) for (const e of V.classes.sort((e, t) => e.id - t.id)) a += ``; if (void 0 !== V.clubs && Array.isArray(V.clubs) && V.clubs.length > 0) for (const e of V.clubs.sort((e, t) => e.id - t.id)) s += ``; if ( void 0 !== V.partners && Array.isArray(V.partners) && V.partners.length > 0 ) for (const e of V.partners.sort((e, t) => e.id - t.id)) i += ``; if ( void 0 !== V.punishments && Array.isArray(V.punishments) && V.punishments.length > 0 ) for (const e of V.punishments.sort((e, t) => e.id - t.id)) n += ``; if ( void 0 !== V.rouletteOptions && Array.isArray(V.rouletteOptions) && V.rouletteOptions.length > 0 ) for (const e of V.rouletteOptions.sort((e, t) => e.id - t.id)) o += ``; return `\n `; } static generateRouletteModal(e) { return `\n `; } static generateHelpModal(e) { return `\n `; } static generateTagModal(e) { return `\n `; } static generateParamModal(e) { let t = "seconds" === e.valueType || "minutes" === e.valueType || "hours" === e.valueType || "days" === e.valueType, a = "punishments" === e.valueType, s = !t && !a, i = e.value; return ( e.timeUnit && "hours" == e.timeUnit ? (i /= 60) : e.timeUnit && "days" == e.timeUnit && (i /= 1440), `\n ` ); } static generateTaskModal(e) { let t = ""; if (void 0 !== V.tags && Array.isArray(V.tags) && V.tags.length > 0) for (const a of V.tags) void 0 !== e.tags && e.tags.includes(a) ? (t += ``) : (t += ``); return `\n `; } static generateClassModal(e) { let t = "", a = "", s = ""; if ( void 0 !== V.classes && Array.isArray(V.classes) && V.classes.length > 0 ) for (const a of V.classes.sort((e, t) => e.id - t.id)) e.id !== a.id && (e.prerequisites.includes(a.id) ? (t += ``) : (t += ``)); if (void 0 !== V.tasks && Array.isArray(V.tasks) && V.tasks.length > 0) for (const t of V.tasks.sort((e, t) => e.id - t.id)) (e.tasks.includes(t.id) ? (a += ``) : (a += ``), e.exams.includes(t.id) ? (s += ``) : (s += ``)); return `\n `; } static generateMajorModal(e) { let t = "", a = ""; if ( void 0 !== V.classes && Array.isArray(V.classes) && V.classes.length > 0 ) for (const a of V.classes.sort((e, t) => e.id - t.id)) e.prerequisites.includes(a.id) ? (t += ``) : (t += ``); if (void 0 !== V.tasks && Array.isArray(V.tasks) && V.tasks.length > 0) for (const t of V.tasks.sort((e, t) => e.id - t.id)) e.exams.includes(t.id) ? (a += ``) : (a += ``); return `\n `; } static generatePunishmentModal(e) { let t = ""; if (void 0 !== V.tasks && Array.isArray(V.tasks) && V.tasks.length > 0) for (const a of V.tasks.sort((e, t) => e.id - t.id)) e.punishment == a.id ? (t += ``) : (t += ``); return `\n `; } static generatePartnerModal(e) { let t = ""; if ( void 0 !== V.modifiers && Array.isArray(V.modifiers) && V.modifiers.length > 0 ) for (const a of V.modifiers.sort((e, t) => e.id - t.id)) e.modifiers.includes(a.id) ? (t += ``) : (t += ``); return `\n `; } static generateClubModal(e) { let t = ""; if ( void 0 !== V.modifiers && Array.isArray(V.modifiers) && V.modifiers.length > 0 ) for (const a of V.modifiers.sort((e, t) => e.id - t.id)) e.modifiers.includes(a.id) ? (t += ``) : (t += ``); return `\n `; } static generateModifierModal(e) { let t = ""; if (void 0 !== V.tags && Array.isArray(V.tags) && V.tags.length > 0) for (const a of V.tags) void 0 !== e.tags && e.tags.includes(a) ? (t += ``) : (t += ``); return `\n `; } static generateQrModal(e) { return `\n `; } static generateImageModal(e, t) { return `\n `; } } class Y { static escapeQuote(e) { return "string" != typeof e ? e : e.replace(/"/g, """); } static minutesToTime(e) { let t = Math.floor(e / 1440), a = Math.floor((e - 1440 * t) / 60), s = Math.round(e % 60), i = ""; return ( 1 == t ? (i += t + " day") : t > 1 && (i += t + " days"), a > 0 && (t && !s ? (i += " and") : t && (i += ";"), (i += 1 == a ? ` ${a} hour` : ` ${a} hours`)), s > 0 && ((t || a) && (i += " and"), (i += 1 == s ? ` ${s} minute` : ` ${s} minutes`)), i.trim() ); } static applyParamsToString(e, t) { let a = "".concat(t); for (const t of V.params.filter((t) => t.taskId == e)) { let e = "", s = Math.round(1 * t.value); if ("minutes" === t.valueType) { Y.minutesToTime(s); let a = Y.minutesToTime(t.value); (t.applyMultiplier, (e = "" + a)); } else "punishments" === t.valueType ? (t.applyMultiplier, (e = `${t.value} ${t.punTier ? t.punTier : "random"} ${t.valueType}`)) : (t.applyMultiplier, (e = `${t.value} ${t.valueType}`)); a = a.replace("$param" + t.id, `${e}`); } return a; } static restoredTableSettings(e) { let t = $("#" + e).bootstrapTable("getOptions"); return ( ("object" == typeof t && t.hasOwnProperty("idField")) || (t = { searchText: "", sortName: "", sortOrder: "desc", pageNumber: 1, pageSize: 10, visibleColumns: !0, }), "object" == typeof t && Array.isArray(t.columns) && t.columns.length > 0 && (t.visibleColumns = t.columns[0] .filter((e) => e.visible) .map((e) => e.field)), t ); } static getModifierTags(e) { return void 0 !== e && e.length > 0 ? e.join(", ") : "all"; } static getModifierMod(e, t) { return "difficulty" === e.modType ? `Increase difficulty for ${t} tasks by ${e.modVal}%.` : "punishment" === e.modType ? "Roll a punishment." : "" + e.modVal; } static getModifierPerk(e, t) { return "difficulty" === e.perkType ? `Decrease difficulty for ${t} tasks by ${e.perkVal}%.` : "skip" === e.perkType ? "Allow skipping a class without punishment." : "attendance" === e.perkType ? `Add an additional attendance for ${t} classes.` : "resetRoulette" === e.perkType ? "Reset roulette." : "" + e.perkVal; } static getModifierText(e) { let t = "", a = ""; return ( (a = Y.getModifierTags(e.tags)), (t += `[${Y.getModifierMod(e, a)}]`), (t += `[${Y.getModifierPerk(e, a)}]`), t ); } static saveGeneral() { ((V.general.title = $("#mapTitle").val()), (V.general.description = $("#mapDescription").val()), Y.autoSaveUpdate("general")); } static resetGeneral() { ($("#mapTitle").val(V.general.title), $("#mapDescription").val(V.general.description)); } static addRoulette() { Y.openRoulette({ id: V.globalIndex++, title: "", description: "", probability: 0, rollPunishment: !1, }); } static editRoulette(e) { Y.openRoulette( V.rouletteOptions.find(function (t) { return t.id == e; }), ); } static openRoulette(e) { let t = "#rouletteModal" + e.id; ($("#modalContainer").append(J.generateRouletteModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { $(t + " .rouletteModal-title").trigger("focus"); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $("" + t) .modal("dispose") .remove()); })); } static removeRoulette(e) { ((V.rouletteOptions = V.rouletteOptions.filter(function (t) { return t.id != e; })), $("#rouletteModal" + e).is(":visible") ? $("#rouletteModal" + e).modal("hide") : Y.refreshView()); } static saveRouletteAndNew(e) { Y.saveRoulette(e) && Y.addRoulette(); } static saveRouletteAndDuplicate(e) { if (Y.saveRoulette(e)) { let t = JSON.parse( JSON.stringify( V.rouletteOptions.find(function (t) { return t.id == e; }), ), ); ((t.id = V.globalIndex++), Y.openRoulette(t)); } } static saveRoulette(e) { let t = { id: e, title: $(`#rouletteModal${e} .rouletteModal-title`).val(), description: $(`#rouletteModal${e} .rouletteModal-description`).val(), probability: parseFloat( document.querySelector("#rouletteModal-probability" + e).value, ), rollPunishment: document.querySelector( "#rouletteModal-rollPunishment" + e, ).checked, }; return void 0 === t.title || t.title.length <= 0 ? (n.showToast("Invalid title", "Please provide a title.", "warning"), !1) : void 0 === t.description || t.description.length <= 0 ? (n.showToast( "Invalid description", "Please provide a description.", "warning", ), !1) : (Y.removeRoulette(e), V.rouletteOptions.push(t), Y.autoSaveUpdate("roulette"), !0); } static addHelp() { Y.openHelp({ id: V.globalIndex++, title: "", text: "" }); } static editHelp(e) { Y.openHelp( V.help.find(function (t) { return t.id == e; }), ); } static handleModalKeyDown(e) { if (e.ctrlKey || e.metaKey) switch (String.fromCharCode(e.which).toLowerCase()) { case "s": const t = $("button.save-button", e.currentTarget); if (1 == t.length) return (t.trigger("click"), e.preventDefault(), !1); } } static openHelp(e) { let t = "#helpModal" + e.id; ($("#modalContainer").append(J.generateHelpModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { $(t + " .helpModal-title").trigger("focus"); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $("" + t) .modal("dispose") .remove()); })); } static removeHelp(e) { ((V.help = V.help.filter(function (t) { return t.id != e; })), $("#helpModal" + e).is(":visible") ? $("#helpModal" + e).modal("hide") : Y.refreshView()); } static saveHelpAndNew(e) { Y.saveHelp(e) && Y.addHelp(); } static saveHelpAndDuplicate(e) { if (Y.saveHelp(e)) { let t = JSON.parse( JSON.stringify( V.help.find(function (t) { return t.id == e; }), ), ); ((t.id = V.globalIndex++), Y.openHelp(t)); } } static saveHelp(e) { let t = { id: e, title: $(`#helpModal${e} .helpModal-title`).val(), text: $(`#helpModal${e} .helpModal-text`).val(), }; if (void 0 === t.title || t.title.length <= 0) return ( n.showToast("Invalid title", "Please provide a title.", "warning"), !1 ); if (void 0 === t.text || t.text.length <= 0) return ( n.showToast( "Invalid help text", "Please provide a help text.", "warning", ), !1 ); let a = document.createElement("div"); return ( (a.innerHTML = t.text), (t.text = a.innerHTML), Y.removeHelp(e), V.help.push(t), Y.autoSaveUpdate("help"), !0 ); } static addTag(e) { (Y.openTag(""), Y.enableTagReference(e)); } static enableTagReference(e) { if (!e) return !1; ($( "#tagModal .save-button, #tagModal .save-button-area a.customLink", ).on("click", function (t) { let a = $("#tagModal .tagModal-tag").val(); ($(e).append( ``, ), $(e).selectpicker("refresh").selectpicker("refresh")); }), $("#tagModal").attr("data-ref", e)); } static editTag(e) { Y.openTag(e); } static openTag(e) { ($("#modalContainer").append(J.generateTagModal(e)), $("#tagModal") .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { $("#tagModal .tagModal-tag").trigger("focus"); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $("#tagModal").modal("dispose").remove(), document .querySelector("#modalContainer") .dispatchEvent(new CustomEvent("removed-tag-modal", {}))); })); } static removeTag(e) { ((V.tags = V.tags.filter(function (t) { return t != e; })), $("#tagModal").is(":visible") ? $("#tagModal").modal("hide") : Y.refreshView()); } static saveTagAndNew(e) { if (Y.saveTag(e)) { const e = $("#tagModal").attr("data-ref"); document.querySelector("#modalContainer").addEventListener( "removed-tag-modal", function (t) { Y.addTag(e); }, { once: !0 }, ); } } static saveTag(e) { let t = $("#tagModal .tagModal-tag").val().toLowerCase(); if (void 0 === t || t.length <= 0) return ( n.showToast("Invalid tag", "Please insert a tag.", "warning"), !1 ); if (V.tags.includes(t)) return ( n.showToast( "Invalid tag", "This tag is already in use.", "warning", ), !1 ); let a = t.match(/\s/g); return null !== a && a.length > 0 ? (n.showToast( "Invalid tag", "Please insert a single tag (no spaces allowed).", "warning", ), !1) : (Y.removeTag(e), V.tags.push(t), Y.autoSaveUpdate("tag"), !0); } static addParam(e) { Y.openParam({ id: V.globalIndex++, taskId: e, description: "", value: "", valueType: "minutes", timeUnit: "minutes", applyMultiplier: !1, spawnTimer: !1, startTimerAutomatically: !1, provideCounter: !1, punishTime: !1, punishTimeMinutes: 0, hiddenTask: !1, }); } static insertParam(e, t) { let a = $(`#taskModal${t} .taskModal-description`); (a.val().indexOf(" ") === a.val().length - 1 ? (a[0].value += "$param" + e) : (a[0].value += " $param" + e), a.trigger("focus")); } static editParam(e) { Y.openParam( V.params.find(function (t) { return t.id == e; }), ); } static openParam(e) { let t = "#paramModal" + e.id; ($("#modalContainer").append(J.generateParamModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .paramModal-description").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (a) { ($(`#taskModal${e.taskId} .paramsCardContainer`).html( Y.getParams(e.taskId), ), $(t).modal("dispose").remove()); }), $(t + " .paramModal-timeUnit-time").on("change", function (e) { $(t + " .paramModal-valueType-time") .prop("checked", !0) .trigger("change"); }), $(t + " .paramModal-valueType-others-text").on( "change", function (e) { $(t + " .paramModal-valueType-others") .prop("checked", !0) .trigger("change"); }, ), $(t + " .paramModal-punishment-tier").on("change", function (e) { $(t + " .paramModal-valueType-punishment") .prop("checked", !0) .trigger("change"); }), $(t + " .paramModal-valueType-time").on("change", function (e) { e.target.checked && ($(t + " .paramModal-applyMultiplier").prop("disabled", !1), $(t + " .paramModal-spawnTimer").prop("disabled", !1), $(t + " .paramModal-startTimerAutomatically").prop( "disabled", !1, ), $(t + " .paramModal-provideCounter").prop("disabled", !0), $(t + " .paramModal-punishTime").prop("disabled", !1), $(t + " .paramModal-punishTime-minutes").prop("disabled", !1), $(t + " .paramModal-hiddenTask").prop("disabled", !1)); }), $(t + " .paramModal-valueType-others").on("change", function (e) { e.target.checked && ($(t + " .paramModal-applyMultiplier").prop("disabled", !1), $(t + " .paramModal-spawnTimer").prop("disabled", !0), $(t + " .paramModal-startTimerAutomatically").prop( "disabled", !0, ), $(t + " .paramModal-provideCounter").prop("disabled", !1), $(t + " .paramModal-punishTime").prop("disabled", !0), $(t + " .paramModal-punishTime-minutes").prop("disabled", !0), $(t + " .paramModal-hiddenTask").prop("disabled", !1)); }), $(t + " .paramModal-valueType-punishment").on("change", function (e) { e.target.checked && ($(t + " .paramModal-applyMultiplier").prop("disabled", !0), $(t + " .paramModal-spawnTimer").prop("disabled", !0), $(t + " .paramModal-startTimerAutomatically").prop( "disabled", !0, ), $(t + " .paramModal-provideCounter").prop("disabled", !0), $(t + " .paramModal-punishTime").prop("disabled", !0), $(t + " .paramModal-punishTime-minutes").prop("disabled", !0), $(t + " .paramModal-hiddenTask").prop("disabled", !0)); })); } static removeParam(e) { ((V.params = V.params.filter(function (t) { return t.id != e; })), $("#paramModal" + e).modal("hide")); } static saveParam(e, t) { let a = "#paramModal" + e, s = { id: e, taskId: t, description: $(a + " .paramModal-description").val(), value: Number.parseFloat($(a + " .paramModal-value").val()), valueType: $( `${a} input[name='paramModal-valueType${e}']:checked`, ).val(), timeUnit: $(a + " .paramModal-timeUnit-time").val(), punTier: $(a + " .paramModal-punishment-tier").val(), applyMultiplier: void 0 !== $(a + " .paramModal-applyMultiplier:checked").val(), spawnTimer: void 0 !== $(a + " .paramModal-spawnTimer:checked").val(), startTimerAutomatically: void 0 !== $(a + " .paramModal-startTimerAutomatically:checked").val(), provideCounter: void 0 !== $(a + " .paramModal-provideCounter:checked").val(), punishTime: void 0 !== $(a + " .paramModal-punishTime:checked").val(), punishTimeMinutes: Number.parseFloat( $(a + " .paramModal-punishTime-minutes").val(), ), hiddenTask: void 0 !== $(a + " .paramModal-hiddenTask:checked").val(), }; if (s.value <= 0) n.showToast( "Invalid Parameter", "Make sure to provide a value for this parameter.", "warning", ); else { if ("time" === s.valueType) { if (!Number.isInteger(s.value)) return void n.showToast( "Invalid Parameter", "Only integers are allowed while you use time.", "warning", ); if ( ("seconds" == s.timeUnit ? (s.valueType = "seconds") : "minutes" == s.timeUnit ? (s.valueType = "minutes") : "hours" == s.timeUnit ? ((s.valueType = "minutes"), (s.value *= 60)) : "days" == s.timeUnit && ((s.valueType = "minutes"), (s.value *= 1440)), (s.provideCounter = !1), (s.punTier = ""), s.punishTime && !Number.isInteger(s.punishTimeMinutes)) ) return void n.showToast( "Invalid Parameter", "Only integers are allowed if you want to use the 'punish' option.", "warning", ); } else if ("others" === s.valueType) { if ( ((s.valueType = $( a + " .paramModal-valueType-others-text", ).val()), ("" + s.valueType).length <= 0) ) return void n.showToast( "Invalid Parameter", "If you use a custom type make sure to provide a name for it!", "warning", ); if ( ((s.spawnTimer = !1), (s.startTimerAutomatically = !1), (s.punishTime = !1), (s.punishTimeMinutes = 0), (s.punTier = ""), s.provideCounter && !Number.isInteger(s.value)) ) return void n.showToast( "Invalid Parameter", "Only integers are allowed if you want to use the 'counter' option.", "warning", ); } else "punishments" === s.valueType && ((s.spawnTimer = !1), (s.startTimerAutomatically = !1), (s.punishTime = !1), (s.punishTimeMinutes = 0), (s.provideCounter = !1), (s.hiddenTask = !1)); (Y.removeParam(e), V.params.push(s), Y.autoSaveUpdate("param")); } } static openCustomExport(e) { ($("#modalContainer").append(J.generateCustomExportModal(e)), $("#customExportModal") .modal({ keyboard: !0 }) .modal("show") .on("shown.bs.modal", function () { ($("#customExportModal .customExportModal-moduleTitle").trigger( "focus", ), $("#customExportModal select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { $("#customExportModal").modal("dispose").remove(); })); } static addTask(e) { let t = V.globalIndex++; (Y.openTask({ id: t, description: "", tags: [] }), Y.enableTaskReference(t, e)); } static enableTaskReference(e, t) { if (!e || !t) return !1; ($( `#taskModal${e} .save-button, #taskModal${e} .save-button-area a.customLink`, ).on("click", function (a) { ($(t).append( ``, ), $(t).selectpicker("refresh").selectpicker("refresh")); }), $("#taskModal" + e).attr("data-ref", t)); } static editTask(e) { Y.openTask( V.tasks.find(function (t) { return t.id == e; }), ); } static openTask(e) { let t = "#taskModal" + e.id; ($("#modalContainer").append(J.generateTaskModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .taskModal-description").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $("" + t) .modal("dispose") .remove()); }), $(t + " .taskModal-description").on("input", function (a) { $(t + " .taskModal-description-preview").html( "Preview: " + Y.applyParamsToString(e.id, a.target.value), ); })); } static removeTask(e) { ((V.tasks = V.tasks.filter(function (t) { return t.id != e; })), $("#taskModal" + e).is(":visible") ? $("#taskModal" + e).modal("hide") : Y.refreshView()); } static saveTaskAndNew(e) { if (Y.saveTask(e)) { const t = $("#taskModal" + e).attr("data-ref"); Y.addTask(t); } } static saveTaskAndDuplicate(e) { if (Y.saveTask(e)) { let t = JSON.parse( JSON.stringify( V.tasks.find(function (t) { return t.id == e; }), ), ); t.id = V.globalIndex++; let a = JSON.parse( JSON.stringify( V.params.filter(function (t) { return t.taskId == e; }), ), ); for (const e of a) { const a = e.id; ((e.id = V.globalIndex++), (e.taskId = t.id), (t.description = t.description.replace( new RegExp("\\$param" + a, "g"), "$param" + e.id, )), V.params.push(e)); } (Y.openTask(t), Y.enableTaskReference(t.id, $("#taskModal" + e).attr("data-ref"))); } } static saveTask(e) { let t = { id: e, description: $(`#taskModal${e} .taskModal-description`).val(), tags: $(`#taskModal${e} select.taskModal-tags`).val(), }; return void 0 === t.description || t.description.length <= 0 ? (n.showToast( "Invalid description", "Please provide a description.", "warning", ), !1) : (Y.removeTask(e), V.tasks.push(t), Y.autoSaveUpdate("task"), !0); } static handleImageSelection(e) { ($(".image-selection-btn", e).on("click", function (t) { $(".image-selection", e).click(); }), $(".image-selection", e).on("change", function (t) { if (void 0 !== t.target.files && 1 === t.target.files.length) { const a = new FileReader(); ((a.onload = function (a) { ((t.target.value = ""), Y.optimizeImage(a.target.result, e)); }), a.readAsDataURL(t.target.files[0])); } })); } static optimizeImage(e, t) { let a = function (e, a) { ($(".image-preview", t).attr("src", e), $(".image-preview", t).attr("data-optimized-url", "")); }.bind(this); n.optimizeImage(e, a); } static addClass(e) { let t = V.globalIndex++; (Y.openClass({ id: t, title: "", subtitle: "", comment: "", prerequisites: [], days: [], description: "", tier: "beginner", tasks: [], exams: [], image: void 0, imageUrl: void 0, taskListSize: o.defaultTaskListSize, disableBonusAttendance: !1, }), e && ($( `#classModal${t} .save-button, #classModal${t} .save-button-area a.customLink`, ).on("click", function (a) { ($(e).append( ``, ), $(e).selectpicker("refresh").selectpicker("refresh")); }), $("#classModal" + t).attr("data-ref", e))); } static editClass(e) { Y.openClass( V.classes.find(function (t) { return t.id == e; }), ); } static openClass(e) { let t = "#classModal" + e.id; ($("#modalContainer").append(J.generateClassModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .classModal-title").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $(t).modal("dispose").remove()); }), Y.handleImageSelection(t)); } static removeClass(e) { ((V.classes = V.classes.filter(function (t) { return t.id != e; })), $("#classModal" + e).is(":visible") ? $("#classModal" + e).modal("hide") : Y.refreshView()); } static saveClassAndNew(e) { if (Y.saveClass(e)) { const t = $("#classModal" + e).attr("data-ref"); Y.addClass(t); } } static saveClass(e) { let t = "#classModal" + e, a = { id: e, title: $(t + " .classModal-title").val(), subtitle: $(t + " .classModal-subtitle").val(), comment: $(t + " .classModal-comment").val(), prerequisites: $(t + " select.classModal-prerequisites") .val() .map((e) => Number.parseInt(e)), days: $(t + " select.classModal-days") .val() .map((e) => Number.parseInt(e)), description: $(t + " .classModal-description").val(), tier: $(`${t} input[name='classModal-tier${e}']:checked`).val(), tasks: $(t + " select.classModal-tasks") .val() .map((e) => Number.parseInt(e)), exams: $(t + " select.classModal-exams") .val() .map((e) => Number.parseInt(e)), image: $(t + " .image-preview").attr("src"), imageUrl: $(t + " .image-preview").attr("data-optimized-url"), taskListSize: Number.parseInt( $(t + " .classModal-task-list-size").val(), ), disableBonusAttendance: $( `${t} #classModal-disable-bonus-attendance${e}`, )[0].checked, }; return void 0 === a.title || a.title.length <= 0 ? (n.showToast("Invalid Title", "Please provide a title.", "warning"), !1) : void 0 === a.days || a.days.length <= 0 ? (n.showToast( "Invalid Selection", "Please choose at least one day on which this class will be available.", "warning", ), !1) : (Y.removeClass(e), V.classes.push(a), Y.autoSaveUpdate("class"), !0); } static addMajor() { Y.openMajor({ id: V.globalIndex++, title: "", subtitle: "", description: "", prerequisites: [], exams: [], image: void 0, imageUrl: void 0, }); } static editMajor(e) { Y.openMajor( V.majors.find(function (t) { return t.id == e; }), ); } static openMajor(e) { let t = "#majorModal" + e.id; ($("#modalContainer").append(J.generateMajorModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .majorModal-title").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $(t).modal("dispose").remove()); }), Y.handleImageSelection(t)); } static removeMajor(e) { ((V.majors = V.majors.filter(function (t) { return t.id != e; })), $("#majorModal" + e).is(":visible") ? $("#majorModal" + e).modal("hide") : Y.refreshView()); } static saveMajorAndNew(e) { Y.saveMajor(e) && Y.addMajor(); } static saveMajor(e) { let t = "#majorModal" + e, a = { id: e, title: $(t + " .majorModal-title").val(), subtitle: $(t + " .majorModal-subtitle").val(), description: $(t + " .majorModal-description").val(), prerequisites: $(t + " select.majorModal-prerequisites") .val() .map((e) => Number.parseInt(e)), exams: $(t + " select.majorModal-exams") .val() .map((e) => Number.parseInt(e)), image: $(t + " .image-preview").attr("src"), imageUrl: $(t + " .image-preview").attr("data-optimized-url"), }; return void 0 === a.title || a.title.length <= 0 ? (n.showToast("Invalid title", "Please provide a title.", "warning"), !1) : (Y.removeMajor(e), V.majors.push(a), Y.autoSaveUpdate("major"), !0); } static addPunishment() { Y.openPunishment({ id: V.globalIndex++, title: "", tier: "", punishment: "", image: void 0, imageUrl: void 0, }); } static editPunishment(e) { Y.openPunishment( V.punishments.find(function (t) { return t.id == e; }), ); } static openPunishment(e) { let t = "#punishmentModal" + e.id; ($("#modalContainer").append(J.generatePunishmentModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .punishmentModal-title").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $(t).modal("dispose").remove()); }), Y.handleImageSelection(t)); } static removePunishment(e) { ((V.punishments = V.punishments.filter(function (t) { return t.id != e; })), $("#punishmentModal" + e).is(":visible") ? $("#punishmentModal" + e).modal("hide") : Y.refreshView()); } static savePunishmentAndNew(e) { Y.savePunishment(e) && Y.addPunishment(); } static savePunishment(e) { let t = "#punishmentModal" + e, a = { id: e, title: $(t + " .punishmentModal-title").val(), tier: $( `${t} input[name='punishmentModal-tier${e}']:checked`, ).val(), punishment: Number.parseInt( $(t + " select.punishmentModal-punishment").val(), ), image: $(t + " .image-preview").attr("src"), imageUrl: $(t + " .image-preview").attr("data-optimized-url"), }; return void 0 === a.title || a.title.length <= 0 ? (n.showToast("Invalid title", "Please provide a title.", "warning"), !1) : void 0 === a.punishment || Number.isNaN(a.punishment) ? (n.showToast( "Invalid punishment", "Please select a punishment!", "warning", ), !1) : (Y.removePunishment(e), V.punishments.push(a), Y.autoSaveUpdate("punishment"), !0); } static addPartner() { Y.openPartner({ id: V.globalIndex++, name: "", description: "", modifiers: [], image: void 0, imageUrl: void 0, }); } static editPartner(e) { Y.openPartner(V.partners.find((t) => t.id == e)); } static openPartner(e) { let t = "#partnerModal" + e.id; ($("#modalContainer").append(J.generatePartnerModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .partnerModal-name").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $(t).modal("dispose").remove()); }), Y.handleImageSelection(t)); } static removePartner(e) { ((V.partners = V.partners.filter((t) => t.id != e)), $("#partnerModal" + e).is(":visible") ? $("#partnerModal" + e).modal("hide") : Y.refreshView()); } static savePartnerAndNew(e) { Y.savePartner(e) && Y.addPartner(); } static savePartner(e) { let t = "#partnerModal" + e, a = { id: e, name: $(t + " .partnerModal-name").val(), description: $(t + " .partnerModal-description").val(), modifiers: $(t + " select.partnerModal-modifiers") .val() .map((e) => Number.parseInt(e)), image: $(t + " .image-preview").attr("src"), imageUrl: $(t + " .image-preview").attr("data-optimized-url"), }; return void 0 === a.name || a.name.length <= 0 ? (n.showToast( "Invalid name", "Please provide a name for your partner.", "warning", ), !1) : (Y.removePartner(e), V.partners.push(a), Y.autoSaveUpdate("partner"), !0); } static addClub() { Y.openClub({ id: V.globalIndex++, name: "", description: "", tier: "normal", modifiers: [], comment: "", image: void 0, imageUrl: void 0, }); } static editClub(e) { Y.openClub(V.clubs.find((t) => t.id == e)); } static openClub(e) { let t = "#clubModal" + e.id; ($("#modalContainer").append(J.generateClubModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .clubModal-name").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $(t).modal("dispose").remove()); }), Y.handleImageSelection(t)); } static removeClub(e) { ((V.clubs = V.clubs.filter((t) => t.id != e)), $("#clubModal" + e).is(":visible") ? $("#clubModal" + e).modal("hide") : Y.refreshView()); } static saveClubAndNew(e) { Y.saveClub(e) && Y.addClub(); } static saveClub(e) { let t = "#clubModal" + e, a = { id: e, name: $(t + " .clubModal-name").val(), comment: $(t + " .clubModal-comment").val(), description: $(t + " .clubModal-description").val(), tier: $(`${t} input[name='clubModal-tier${e}']:checked`).val(), modifiers: $(t + " select.clubModal-modifiers") .val() .map((e) => Number.parseInt(e)), image: $(t + " .image-preview").attr("src"), imageUrl: $(t + " .image-preview").attr("data-optimized-url"), }; return void 0 === a.name || a.name.length <= 0 ? (n.showToast( "Invalid name", "Please provide a name for your club.", "warning", ), !1) : (Y.removeClub(e), V.clubs.push(a), Y.autoSaveUpdate("club"), !0); } static addModifier(e) { let t = V.globalIndex++; (Y.openModifier({ id: t, modType: "", modVal: "", perkType: "", perkVal: "", tags: [], }), Y.enableModifierReference(t, e)); } static enableModifierReference(e, t) { if (!e || !t) return !1; ($( `#modifierModal${e} .save-button, #modifierModal${e} .save-button-area a.customLink`, ).on("click", function (a) { ($(t).append(``), $(t).selectpicker("refresh").selectpicker("refresh")); }), $("#modifierModal" + e).attr("data-ref", t)); } static editModifier(e) { Y.openModifier(V.modifiers.find((t) => t.id == e)); } static openModifier(e) { let t = "#modifierModal" + e.id; ($("#modalContainer").append(J.generateModifierModal(e)), $(t) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () { ($(t + " .modifierModal-modType").trigger("focus"), $(t + " select[multiple]").selectpicker()); }) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $(t).modal("dispose").remove()); }), $(t + " .modifierModal-modVal-text").on("change", function (e) { $(t + " .modifierModal-modifier-text") .prop("checked", !0) .trigger("change"); }), $(t + " .modifierModal-modVal-difficulty").on("change", function (e) { $(t + " .modifierModal-modifier-difficulty") .prop("checked", !0) .trigger("change"); }), $(t + " .modifierModal-perkVal-text").on("change", function (e) { $(t + " .modifierModal-perk-text") .prop("checked", !0) .trigger("change"); }), $(t + " .modifierModal-perkVal-difficulty").on( "change", function (e) { $(t + " .modifierModal-perk-difficulty") .prop("checked", !0) .trigger("change"); }, )); } static removeModifier(e) { ((V.modifiers = V.modifiers.filter((t) => t.id != e)), $("#modifierModal" + e).is(":visible") ? $("#modifierModal" + e).modal("hide") : Y.refreshView()); } static saveModifierAndNew(e) { if (Y.saveModifier(e)) { const t = $("#modifierModal" + e).attr("data-ref"); Y.addModifier(t); } } static saveModifierAndDuplicate(e) { if (Y.saveModifier(e)) { const t = $("#modifierModal" + e).attr("data-ref"); let a = JSON.parse( JSON.stringify( V.modifiers.find(function (t) { return t.id == e; }), ), ); ((a.id = V.globalIndex++), Y.openModifier(a), Y.enableModifierReference(a.id, t)); } } static saveModifier(e) { let t = "#modifierModal" + e, a = { id: e, tags: $(t + " select.modifierModal-tags").val(), modType: $( `${t} input[name='modifierModal-modifier${e}']:checked`, ).val(), modVal: "", perkType: $( `${t} input[name='modifierModal-perk${e}']:checked`, ).val(), perkVal: "", }; if ("difficulty" === a.modType) { if ( ((a.modVal = Number.parseInt( $(t + " .modifierModal-modVal-difficulty").val(), )), Number.isNaN(a.modVal)) ) return ( n.showToast( "Invalid difficulty", "Make sure to provide an integer value for the difficulty.", "warning", ), !1 ); if (a.modVal > 100 || a.modVal < 1) return ( n.showToast( "Invalid difficulty", "Make sure to provide a value between 1 and 100 for the difficulty.", "warning", ), !1 ); } else if ( "text" === a.modType && ((a.modVal = $(t + " .modifierModal-modVal-text").val()), void 0 === a.modVal || a.modVal.length <= 0) ) return ( n.showToast( "Invalid text", "Make sure to provide a value for the text.", "warning", ), !1 ); if ("difficulty" === a.perkType) { if ( ((a.perkVal = Number.parseInt( $(t + " .modifierModal-perkVal-difficulty").val(), )), Number.isNaN(a.perkVal)) ) return ( n.showToast( "Invalid difficulty", "Make sure to provide an integer value for the difficulty.", "warning", ), !1 ); if (a.perkVal < -100 || a.perkVal > -1) return ( n.showToast( "Invalid difficulty", "Make sure to provide a value between -1 and -100 for the difficulty.", "warning", ), !1 ); } else if ( "text" === a.perkType && ((a.perkVal = $(t + " .modifierModal-perkVal-text").val()), void 0 === a.perkVal || a.perkVal.length <= 0) ) return ( n.showToast( "Invalid text", "Make sure to provide a value for the text.", "warning", ), !1 ); return ( Y.removeModifier(e), V.modifiers.push(a), Y.autoSaveUpdate("modifier"), !0 ); } static getParams(e) { let t = ""; for (const a of V.params .filter((t) => t.taskId == e) .sort((e, t) => e.id - t.id)) t += J.generateParamCard(a); return ( t.length <= 0 && (t = '

    No params defined yet.

    '), `
    ${t}
    ` ); } static getTableColumns(e, t) { let a = ""; for (const s of e) ((a += `${s.text}`)); return a; } static getTasksTable() { let e = "", t = Y.restoredTableSettings("tasksTable"); for (const t of V.tasks.sort((e, t) => e.id - t.id)) e += J.generateTaskTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "title", text: "Title", additional: "" }, { id: "description", text: "Description", additional: "" }, { id: "tags", text: "Tags", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" data-width="25" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getTasks() { let e = ""; for (const t of V.tasks.sort((e, t) => e.id - t.id)) e += J.generateTaskCard(t); return ( e.length <= 0 && (e = "No tasks defined yet."), `
    ${e}
    ` ); } static getClassesTable() { let e = "", t = Y.restoredTableSettings("classesTable"); for (const t of V.classes.sort((e, t) => e.id - t.id)) e += J.generateClassTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "tier", text: "Tier", additional: 'data-sortable="true"' }, { id: "title", text: "Title", additional: 'data-sortable="true"' }, { id: "subtitle", text: "Subtitle", additional: 'data-sortable="true"', }, { id: "comment", text: "Comment", additional: "" }, { id: "description", text: "Description", additional: "" }, { id: "available", text: "Available On", additional: "" }, { id: "prerequisite", text: "Prerequisite classes", additional: "", }, { id: "tasks", text: "Tasks", additional: "" }, { id: "exams", text: "Exam tasks", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getClasses() { let e = ""; for (const t of V.classes.sort((e, t) => e.id - t.id)) e += J.generateClassCard(t); return ( e.length <= 0 && (e = "No classes defined yet."), `
    ${e}
    ` ); } static getRouletteTable() { let e = "", t = Y.restoredTableSettings("rouletteTable"); for (const t of V.rouletteOptions) e += J.generateRouletteTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "title", text: "Title", additional: 'data-sortable="true"' }, { id: "description", text: "Description", additional: "" }, { id: "probability", text: "Probability", additional: "" }, { id: "rollPunishment", text: "Roll Punishment", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getHelpTable() { let e = "", t = Y.restoredTableSettings("helpTable"); for (const t of V.help) e += J.generateHelpTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "title", text: "Title", additional: 'data-sortable="true"' }, { id: "text", text: "Content", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getTagsTable() { let e = "", t = Y.restoredTableSettings("tagsTable"); for (const t of V.tags) e += J.generateTagTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "tag", text: "Tag", additional: 'data-sortable="true" data-switchable="false"', }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getRoulette() { let e = ""; for (const t of V.rouletteOptions) e += J.generateRouletteCard(t); return ( e.length <= 0 && (e = "No roulette options defined yet."), `
    ${e}
    ` ); } static getHelp() { let e = ""; for (const t of V.help) e += J.generateHelpCard(t); return ( e.length <= 0 && (e = "No help defined yet."), `
    ${e}
    ` ); } static getTags() { let e = ""; for (const t of V.tags) e += J.generateTagCard(t); return ( e.length <= 0 && (e = "No tags defined yet."), `
    ${e}
    ` ); } static getMajorsTable() { let e = "", t = Y.restoredTableSettings("majorsTable"); for (const t of V.majors.sort((e, t) => e.id - t.id)) e += J.generateMajorTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "title", text: "Title", additional: 'data-sortable="true"' }, { id: "subtitle", text: "Subtitle", additional: 'data-sortable="true"', }, { id: "description", text: "Description", additional: "" }, { id: "prerequisite", text: "Prerequisite classes", additional: "", }, { id: "exams", text: "Exam tasks", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getMajors() { let e = ""; for (const t of V.majors.sort((e, t) => e.id - t.id)) e += J.generateMajorCard(t); return ( e.length <= 0 && (e = "No majors defined yet."), `
    ${e}
    ` ); } static getPunishmentsTable() { let e = "", t = Y.restoredTableSettings("punishmentsTable"); V.punishments = V.punishments.filter( (e) => void 0 !== V.tasks.find((t) => t.id === e.punishment), ); for (const t of V.punishments.sort((e, t) => e.id - t.id)) e += J.generatePunishmentTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "tier", text: "Tier", additional: 'data-sortable="true"' }, { id: "title", text: "Title", additional: 'data-sortable="true"' }, { id: "task", text: "Punishment task", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getPunishments() { let e = ""; for (const t of V.punishments.sort((e, t) => e.id - t.id)) e += J.generatePunishmentCard(t); return ( e.length <= 0 && (e = "No punishments defined yet."), `
    ${e}
    ` ); } static getPartnersTable() { let e = "", t = Y.restoredTableSettings("partnersTable"); for (const t of V.partners.sort((e, t) => e.id - t.id)) e += J.generatePartnerTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "name", text: "Name", additional: 'data-sortable="true"' }, { id: "description", text: "Description", additional: "" }, { id: "modifiers", text: "Available modifiers", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getPartners() { let e = ""; for (const t of V.partners.sort((e, t) => e.id - t.id)) e += J.generatePartnerCard(t); return ( e.length <= 0 && (e = "No partners defined yet."), `
    ${e}
    ` ); } static getClubsTable() { let e = "", t = Y.restoredTableSettings("clubsTable"); for (const t of V.clubs.sort((e, t) => e.id - t.id)) e += J.generateClubTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "tier", text: "Tier", additional: 'data-sortable="true"' }, { id: "name", text: "Name", additional: 'data-sortable="true"' }, { id: "comment", text: "Comment", additional: "" }, { id: "description", text: "Description", additional: "" }, { id: "modifiers", text: "Available modifiers", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getClubs() { let e = ""; for (const t of V.clubs.sort((e, t) => e.id - t.id)) e += J.generateClubCard(t); return ( e.length <= 0 && (e = "No clubs defined yet."), `
    ${e}
    ` ); } static getModifiersTable() { let e = "", t = Y.restoredTableSettings("modifiersTable"); for (const t of V.modifiers.sort((e, t) => e.id - t.id)) e += J.generateModifierTableEntry(t); return `\n
    \n
    \n \n
    \n \n \n \n ${Y.getTableColumns( [ { id: "id", text: "ID", additional: 'data-sortable="true"' }, { id: "title", text: "Title", additional: "" }, { id: "modifier", text: "Modifier", additional: "" }, { id: "perk", text: "Perk", additional: "" }, { id: "actions", text: "Actions", additional: 'data-switchable="false" class="actionsTableField"', }, ], t.visibleColumns, )}\n \n \n \n ${e}\n \n \n
    \n `; } static getModifiers() { let e = ""; for (const t of V.modifiers.sort((e, t) => e.id - t.id)) e += J.generateModifierCard(t); return ( e.length <= 0 && (e = "No modifiers defined yet."), `
    ${e}
    ` ); } static refreshView() { let e = $(window).scrollTop(); (n.getSetting("useTableView") ? ($("#tagsCardContainer").html(Y.getTagsTable()), $("#tasksCardContainer").html(Y.getTasksTable()), $("#classesCardContainer").html(Y.getClassesTable()), $("#majorsCardContainer").html(Y.getMajorsTable()), $("#punishmentsCardContainer").html(Y.getPunishmentsTable()), $("#clubsCardContainer").html(Y.getClubsTable()), $("#partnersCardContainer").html(Y.getPartnersTable()), $("#modifiersCardContainer").html(Y.getModifiersTable()), $("#helpCardContainer").html(Y.getHelpTable()), $("#rouletteCardContainer").html(Y.getRouletteTable())) : ($("#tagsCardContainer").html(Y.getTags()), $("#tasksCardContainer").html(Y.getTasks()), $("#classesCardContainer").html(Y.getClasses()), $("#majorsCardContainer").html(Y.getMajors()), $("#punishmentsCardContainer").html(Y.getPunishments()), $("#clubsCardContainer").html(Y.getClubs()), $("#partnersCardContainer").html(Y.getPartners()), $("#modifiersCardContainer").html(Y.getModifiers()), $("#helpCardContainer").html(Y.getHelp()), $("#rouletteCardContainer").html(Y.getRoulette())), Y.resetGeneral(), window.scrollTo(0, e), document .querySelector("html") .dispatchEvent(new CustomEvent("mappingRefreshed", {}))); } static async import(e) { const t = s.getInstance(), a = { title: t.T( "mapping-import-confirmation-header", "Import an editable map", ), text: t.T( "mapping-import-confirmation-conent", "Are you sure that you want to import this map? Make sure to only load maps from trusted sources. Proceed at your own risk.", ), }, i = { title: t.T("mapping-import-failed-header", "Map import failed"), text: t.T( "mapping-import-failed-content", "During the import for the selected file an error occured. You can check the console for more information.", ), }, n = { title: t.T( "mapping-file-import-compression-error-header", "Compression unavailable", ), text: t.T( "mapping-file-import-compression-error-content", "The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.", ), }, o = { title: t.T("mapping-file-import-aborted-header", "Import aborted"), text: t.T( "mapping-file-import-aborted-content", "The file you tried to import included untrusted code. To protect you the import was aborted.", ), }, r = await _.runImport(e, i, a, n, o); (Y.autoSaveReset(), Y.importMapStorage(r)); } static importMapStorage(e) { (Y.reset(), (V = e), null == V.help && (V.help = []), null == V.rouletteOptions && (V.rouletteOptions = []), Y.refreshView()); } static async export() { ((G = V.globalIndex), await _.runExport("pu_map", V)); } static prepareModifierForExport(e) { const t = Object.assign( {}, V.modifiers.find((t) => t.id == e), ); let a = "", s = { id: "" + e, job: "", perk: "", tags: t.tags, modType: t.modType, modVal: t.modVal, perkType: t.perkType, perkVal: t.perkVal, }; return ( (a = void 0 !== t.tags && t.tags.length > 0 ? t.tags.join(", ") : "all"), "difficulty" == t.modType ? (t.modVal < 0 && (t.modVal *= -1), (s.job = `Increase difficulty for ${a} tasks by ${t.modVal}%.`)) : "punishment" == t.modType ? ((s.modVal = 1), (s.job = "Roll a random punishment.")) : (s.job += "" + t.modVal), "difficulty" == t.perkType ? (t.perkVal < 0 && (t.perkVal *= -1), (s.perk = `Decrease difficulty for ${a} tasks by ${t.perkVal}%.`)) : "skip" == t.perkType ? ((s.perkVal = 1), (s.perk = "Skip 1 class every day.")) : "attendance" == t.perkType ? ((s.perkVal = 1), (s.perk = `Add an additional attendance for ${a} classes.`)) : "resetRoulette" == t.perkType ? ((s.perkVal = 1), (s.perk = "Reset roulette and roll again.")) : (s.perk = "" + t.perkVal), s ); } static prepareTaskForExport(e) { const t = Object.assign( {}, V.tasks.find((t) => t.id == e), ), a = { id: "" + e, task: t.description, tags: t.tags, parameters: {} }; for (const t of V.params.filter((t) => t.taskId == e)) a.parameters[t.id] = { value: t.value, unit: t.valueType, timerInfo: t.description, applyMultiplier: t.applyMultiplier, spawnTimer: t.spawnTimer, startTimerAutomatically: t.startTimerAutomatically, provideCounter: t.provideCounter, hiddenTask: t.hiddenTask, punTier: t.punTier, punishTime: t.punishTime, punishTimeMinutes: t.punishTimeMinutes, }; return a; } static async directExportTask(e) { let t = Y.prepareTaskForExport(e); ((t.id = t.id + "_CUST"), await _.runExport("pu_task", t)); } static exportPlayableModule(e) { let t = { mapId: V.mapId || Y.uuidv4(), moduleId: $("#customExportModal .customExportModal-moduleId").val(), general: { title: $("#customExportModal .customExportModal-moduleTitle").val(), description: $( "#customExportModal .customExportModal-moduleDescription", ).val(), }, majors: $("#customExportModal select.customExportModal-majors") .val() .map((e) => Number.parseInt(e)), classes: $("#customExportModal select.customExportModal-classes") .val() .map((e) => Number.parseInt(e)), clubs: $("#customExportModal select.customExportModal-clubs") .val() .map((e) => Number.parseInt(e)), partners: $("#customExportModal select.customExportModal-partners") .val() .map((e) => Number.parseInt(e)), punishments: $( "#customExportModal select.customExportModal-punishments", ) .val() .map((e) => Number.parseInt(e)), rouletteOptions: $( "#customExportModal select.customExportModal-rouletteOptions", ) .val() .map((e) => Number.parseInt(e)), }; if (void 0 === t.moduleId || t.moduleId.length <= 0) n.showToast( "Invalid module ID", "Please provide a module ID.", "warning", ); else if ( !t.general || void 0 === t.general.title || t.general.title.length <= 0 ) n.showToast("Invalid Title", "Please provide a title.", "warning"); else { $("#customExportModal").is(":visible") && $("#customExportModal").modal("hide"); for (const e of V.majors.filter((e) => t.majors.includes(e.id))) t.classes = t.classes.concat(e.prerequisites); for (const e of t.classes) t.classes = t.classes.concat(Y.recursiveGetPrerequisites(e)); ((t.classes = [...new Set(t.classes)]), e(Y.getPlayable(t))); } } static getPlayableImage(e) { return void 0 !== e.imageUrl && e.imageUrl.length > 0 ? e.imageUrl : void 0 !== e.image && e.image.length > 0 ? e.image : n.extendDistPath() + "img/unknown.jpg"; } static recursiveGetPrerequisites(e) { let t = []; const a = V.classes.find((t) => t.id == e); if (!a) return t; t = t.concat(a.prerequisites); for (const e of t) t = t.concat(Y.recursiveGetPrerequisites(e)); return t; } static getPlayable(e) { let t = { mapId: V.mapId || Y.uuidv4(), general: V.general, classes: {}, majors: {}, partners: {}, clubs: {}, punishments: {}, rouletteOptions: {}, }; (void 0 !== e && ((t.mapId = e.mapId), (t.moduleId = e.moduleId), (t.general = e.general)), (t.general.help = []), void 0 !== V.help && V.help.length > 0 && (t.general.help = t.general.help.concat(V.help))); for (const a of V.rouletteOptions.filter( (t) => void 0 === e || e.rouletteOptions.includes(t.id), )) t.rouletteOptions[a.id] = { id: "" + a.id, type: "roulette_option", title: a.title, description: a.description, probability: a.probability, rollPunishment: a.rollPunishment, }; for (const a of V.classes.filter( (t) => void 0 === e || e.classes.includes(t.id), )) { t.classes[a.id] = { id: "" + a.id, type: "class", name: a.title, name2: a.subtitle, comment: a.comment, prerequisites: a.prerequisites, days: a.days, description: a.description, tier: a.tier, image: Y.getPlayableImage(a), tasks: {}, taskListSize: a.taskListSize, disableBonusAttendance: a.disableBonusAttendance, }; for (const e of a.tasks) t.classes[a.id].tasks[e] = Y.prepareTaskForExport(e); for (const e of a.exams) ((t.classes[a.id].tasks[e] = Y.prepareTaskForExport(e)), (t.classes[a.id].tasks[e].isExam = !0)); } for (const a of V.majors.filter( (t) => void 0 === e || e.majors.includes(t.id), )) { t.majors[a.id] = { id: "" + a.id, type: "major", name: a.title, name2: a.subtitle, description: a.description, prerequisites: a.prerequisites, image: Y.getPlayableImage(a), tasks: {}, }; for (const e of a.exams) ((t.majors[a.id].tasks[e] = Y.prepareTaskForExport(e)), (t.majors[a.id].tasks[e].isExam = !0)); } for (const a of V.partners.filter( (t) => void 0 === e || e.partners.includes(t.id), )) { t.partners[a.id] = { id: "" + a.id, type: "partner", name: a.name, name2: "", tier: "1", description: a.description, image: Y.getPlayableImage(a), perks: {}, }; for (const e of a.modifiers) t.partners[a.id].perks[e] = Y.prepareModifierForExport(e); } for (const a of V.clubs.filter( (t) => void 0 === e || e.clubs.includes(t.id), )) { t.clubs[a.id] = { id: "" + a.id, type: "club", name: a.name, comment: a.comment, tier: a.tier, description: a.description, image: Y.getPlayableImage(a), perks: {}, }; for (const e of a.modifiers) t.clubs[a.id].perks[e] = Y.prepareModifierForExport(e); } for (const a of V.punishments.filter( (t) => void 0 === e || e.punishments.includes(t.id), )) ((t.punishments[a.id] = { id: "" + a.id, type: "punishment", name: a.title, tier: a.tier, image: Y.getPlayableImage(a), tasks: {}, }), (t.punishments[a.id].tasks[a.punishment] = Y.prepareTaskForExport( a.punishment, ))); return t; } static openImageSelection(e = { id: "", image: "", imageUrl: "" }, t) { let a = "#imageModal" + e.id; ($("#modalContainer").append(J.generateImageModal(e, t)), $(a) .modal({ keyboard: !0 }) .modal("show") .on("keydown", Y.handleModalKeyDown) .on("shown.bs.modal", function () {}) .on("hidden.bs.modal", function (e) { (Y.refreshView(), $(a).modal("dispose").remove()); }), Y.handleImageSelection(a)); } static exportPlayableWithCustomImage(e) { const t = { id: Y.uuidv4(), image: "", imageUrl: "" }; Y.openImageSelection(t, (t) => { let a = "#imageModal" + t; const s = n.dataURLtoFile( $(a + " .image-preview").attr("src"), "example.jpg", ); (_.runExport("pu_game_map", e, s), $("" + a).is(":visible") ? $("" + a).modal("hide") : Y.refreshView()); }); } static async exportPlayable(e) { await _.runExport("pu_game_map", e); } static uuidv4() { return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (e) => ( e ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (e / 4))) ).toString(16), ); } static resetButton() { n.requestUserConfirmation( "Reset current map", "Are you sure that you want to reset the current map? Make sure you exported your progress!", () => { (Y.autoSaveReset(), Y.reset()); }, ); } static gotoFullPage() { n.requestUserConfirmation( "Goto Mapping Tool", "Do you want to open the Mapping Tool in a separat tab? It's not recommended that you bookmark the Mapping Tool by itself, since you will not receive any updates!", () => { window.open(window.location.href, "_blank"); }, ); } static reset() { ((V = { version: 1, mapId: Y.uuidv4(), globalIndex: 0, general: {}, tags: [], majors: [], classes: [], tasks: [], params: [], punishments: [], clubs: [], partners: [], modifiers: [], help: [], rouletteOptions: [], }), Y.refreshView()); } static isChanged() { return V.globalIndex > 0 && G < V.globalIndex; } static autoSaveUpdate(e = "") { try { localStorage.setItem( "mappingAutoSave", n.compressJSObjectToString(V), ); } catch (e) { console.error(e); } } static autoSaveReset() { localStorage.removeItem("mappingAutoSave"); } static autoSaveRestore() { try { const e = localStorage.getItem("mappingAutoSave"); if (!e) return; Y.importMapStorage(JSON.parse(n.decompressJsonFromString(e))); } catch (e) { console.error(e); } } static registerWarning() { window.addEventListener("beforeunload", function (e) { Y.isChanged() && Y.autoSaveUpdate("unload"); }); } } class _ { constructor() { (this.inAppNavigation( `${window.location.search}${window.location.hash}`, ), (window.onpopstate = function (e) { e.state && e.state.url ? this.inAppNavigation(e.state.url) : e.path && Array.isArray(e.path) && e.path.length > 0 && this.inAppNavigation( `${e.path[0].location.search}${e.path[0].location.hash}`, ); }.bind(this))); } static getInstance() { return ( void 0 === this.instance && (this.instance = new _()), this.instance ); } static get navigationData() { const e = s.getInstance(); return { home: { id: "home", text: e.T("navigation-home", "Home"), enabled: !0, isInNav: !1, autoHideAllowed: !0, handledBy: N, requirements: [], }, majors: { id: "majors", text: e.T("navigation-majors", "Majors"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: C, requirements: ["map"], }, major: { id: "major", text: e.T("navigation-major", "Major"), enabled: !0, isInNav: !1, autoHideAllowed: !0, handledBy: M, requirements: ["map"], }, classes: { id: "classes", text: e.T("navigation-classes", "Classes"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: P, requirements: ["map", "major"], }, class: { id: "class", text: e.T("navigation-class", "Class"), enabled: !0, isInNav: !1, autoHideAllowed: !0, handledBy: A, requirements: ["map", "major"], }, clubs: { id: "clubs", text: e.T("navigation-clubs", "Clubs"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: m, requirements: ["map", "major"], }, club: { id: "club", text: e.T("navigation-club", "Club"), enabled: !0, isInNav: !1, autoHideAllowed: !0, handledBy: g, requirements: ["map", "major"], }, partners: { id: "partners", text: e.T("navigation-partners", "Partners"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: v, requirements: ["map", "major"], }, partner: { id: "partner", text: e.T("navigation-partner", "Partner"), enabled: !0, isInNav: !1, autoHideAllowed: !0, handledBy: b, requirements: ["map", "major"], }, punishments: { id: "punishments", text: e.T("navigation-punishments", "Punishments"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: y, requirements: ["map", "major"], }, punishment: { id: "punishment", text: e.T("navigation-punishment", "Punishment"), enabled: !0, isInNav: !1, autoHideAllowed: !0, handledBy: w, requirements: ["map", "major"], }, schedule: { id: "schedule", text: e.T("navigation-schedule", "Schedule"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: R, requirements: ["map", "major"], }, progress: { id: "progress", text: e.T("navigation-progress", "Progress"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: k, requirements: ["map", "major"], }, roulette: { id: "roulette", text: e.T("navigation-roulette", "Roulette"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: q, requirements: ["map", "major", "rouletteEnabled"], }, help: { id: "help", text: e.T("navigation-help", "Help"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: j, requirements: ["map"], }, settings: { id: "settings", text: e.T("navigation-settings", "Settings"), enabled: !0, isInNav: !0, autoHideAllowed: !0, handledBy: D, requirements: [], }, mapping: { id: "mapping", text: e.T("navigation-mapping", "Mapping"), enabled: !0, isInNav: !1, autoHideAllowed: !1, handledBy: J, requirements: [], }, graduated: { id: "graduated", text: e.T("navigation-graduated", "Graduated"), enabled: !0, isInNav: !1, autoHideAllowed: !0, handledBy: F, requirements: ["map", "majorFinished"], }, disclaimer: { id: "disclaimer", text: e.T("navigation-disclaimer", "Disclaimer"), enabled: !0, isInNav: !1, autoHideAllowed: !1, handledBy: U, requirements: [], }, }; } setParams(e) { const t = new URLSearchParams(e.replace(/#.*?$/, "")); this.allParams = {}; for (const [e, a] of t) this.allParams[e] = a; } get params() { return this.allParams; } hasParameter(e) { return this.allParams.hasOwnProperty(e); } getParameter(e) { return this.allParams[e]; } navigationCheck(e) { if (!this.params) return; const t = this.hasParameter("pageType") ? this.getParameter("pageType") : "getView", a = e.expectedParameters[t]; if (a) { if (!(a.length > 0)) return ($("main").html(e[t]()), void this.scrollToAnchor()); { let s = {}; if ( (a.forEach((e) => { this.params.hasOwnProperty(e) && (s[e] = this.params[e]); }), a.length === Object.keys(s).length) ) return ($("main").html(e[t](s)), void this.scrollToAnchor()); this.navigateBack(); } } else this.navigateBack(); } navigatePage(e) { if ("back" === e) return void this.navigateBack(); const t = _.navigationData[e]; t && t.enabled && this.requirementsMet(t.requirements) ? this.navigationCheck(t.handledBy) : _.navigate("?page=home"); } inAppNavigation(e) { _.checkForUnsavedChanges(() => { (wi.reset(), u.getInstance().resetTaskIntervals(), this.setParams(e)); const t = this.getParameter("page"), a = this.getParameter("pageType"); if ( !( n.getAcceptedDisclaimer() || (this.hasParameter("page") && "disclaimer" === t) ) ) { let e = ""; return ( window.location.search.length > 0 && (e = "&redirect=" + encodeURIComponent(btoa(window.location.search))), void _.navigate("?page=disclaimer" + e) ); } ($("#nav-auto").html(this.getNavigationBarHtml()), this.hasParameter("page") ? ($("#nav-" + t).addClass("active"), window.location.search.includes("page=back") || (a && "getView" != a) || (e !== `${window.location.search}${window.location.hash}` ? window.history.pushState(_.getHistoryObj(e), t, e) : e == `${window.location.search}${window.location.hash}` && window.history.replaceState( _.getHistoryObj(e, !0), t, e, )), this.navigatePage(t)) : _.navigate("?page=home"), document .querySelector("html") .dispatchEvent(new CustomEvent("navigationFinished", {})), n.getTutorial().includes(t.concat(";")) || H.startTour(t), $("#customCarousel").on("slid.bs.carousel", function (e) { let t = `${window.location.search}#slide${e.to}`; window.history.pushState(_.getHistoryObj(t), null, t); })); }); } navigateBack() { _.navigateBack(); } scrollToAnchor() { _.scrollToAnchor(); } getNavigationBarHtml() { return _.getNavigationBarHtml(); } requirementsMet(e) { return _.requirementsMet(e); } getCreditsHtml() { return _.getCreditsHtml(); } reload() { _.reload(); } inAppReload() { _.inAppReload(); } static getHistoryObj(e, t) { if ( t && window.history.state && window.history.state.url && window.history.state.prevUrl ) return { url: window.history.state.url, prevUrl: window.history.state.url.prevUrl, scrollHeight: $(window).scrollTop(), scrollHeightNew: $(window).scrollTop(), }; { let t = 0; return ( window.history.state && window.history.state.scrollHeightNew && window.history.state.scrollHeightNew > 0 && (t = window.history.state.scrollHeightNew), { url: e, prevUrl: `${window.location.search}${window.location.hash}`, scrollHeight: t, scrollHeightNew: $(window).scrollTop(), } ); } } static getCreditsHtml() { const e = S.collectedCredits; return !e || e <= 0 ? "" : ``; } static getNavigationBarHtml() { let e = "" + _.getCreditsHtml(), t = _.navigationData, a = 0; for (let s in t) { let i = t[s]; i.enabled && i.isInNav && _.requirementsMet(i.requirements) && ((e += ` `), a++); } return ( a <= 0 && $("nav.navbar .navbar-toggler").addClass("collapse"), e ); } static requirementsMet(e) { if (!e || null == e || e.length <= 0) return !0; for (let t of e) { if ("major" === t && !I.isAnyMajorActive()) return !1; if ("map" === t && !u.getInstance().isMapLoaded) return !1; if ("rouletteEnabled" === t && !p.rouletteData) return !1; if ("majorFinished" === t && !u.getInstance().anyMajorFinished) return !1; } return !0; } static scrollToAnchor() { if ( window.history.state && window.history.state.scrollHeight && window.history.state.scrollHeight > 0 ) window.scrollTo(0, window.history.state.scrollHeight); else { { const t = window.location.hash; if (t.length > 0) { let a = t.match(/^#slide(\d)+$/); if (null === a) { var e = $("" + t); return void $("html,body").animate( { scrollTop: e.offset().top }, "slow", ); } return void $("#customCarousel").carousel(Number.parseInt(a[1])); } } window.scrollTo(0, 0); } } static checkForUnsavedChanges(e) { const t = u.getInstance(); t.wasSavegameChanged ? t.setSavegame(e) : e(); } static reload() { _.checkForUnsavedChanges(() => { location.reload(); }); } static inAppReload() { _.getInstance().inAppNavigation(window.location.search); } static replaceHistory(e) { try { window.history.replaceState(_.getHistoryObj(e), null, e); } catch (e) { console.error("failed to update history"); } } static navigateBack() { _.checkForUnsavedChanges(() => { null !== window.history.state && void 0 !== window.history.state.prevUrl ? _.getInstance().inAppNavigation(window.history.state.prevUrl) : history.back(); }); } static navigate(e) { _.checkForUnsavedChanges(() => { window.location.href = e; }); } static removeModuleFromMapId(e) { return e.replace(/\(.*?\)/gm, ""); } static async getSavegameData(e) { if (n.shouldExportComplete()) { const t = u.getInstance().mapId, a = u.getInstance().subMaps; let s = { mapId: t, primaryMapData: {}, subMapData: [], subMaps: a, saveGame: u.getInstance().getSavegame(), settings: n.getSettings(), }; const i = () => new Promise((e, t) => { u.getDBInstance().getMaps(e, t); }); try { const e = await i(); for (const i of e) i.mapData.mapId === t ? (s.primaryMapData = i.mapData) : a.includes(i.mapData.mapId) && s.subMapData.push(i.mapData); s.primaryMapData.mapId = _.removeModuleFromMapId( s.primaryMapData.mapId, ); for (const e of s.subMapData) e.mapId = _.removeModuleFromMapId(e.mapId); return s; } catch (t) { throw ( n.showToast(e.title, e.text, "danger"), "loading maps failed" ); } } return { mapId: u.getInstance().mapId, saveGame: u.getInstance().getSavegame(), }; } static async runClipboardExport( e, t = { title: "", text: "" }, a = { title: "", text: "" }, s = { title: "", text: "" }, ) { const i = async () => { try { const a = await _.getSavegameData(t); return `${e}:${n.compressJSObjectToString(a)}`; } catch (e) { throw e; } }; try { const e = new ClipboardItem({ "text/plain": i().then( (e) => new Blob([e], { type: "text/plain" }), ), }); return ( await navigator.clipboard.write([e]), void n.showToast(a.title, a.text, "success") ); } catch (e) { try { (await navigator.clipboard.writeText(await i()), n.showToast(a.title, a.text, "success")); } catch (t) { n.showToast(s.title, `${s.text} [${e}][${t}]`, "danger"); } } } static async runClipboardImport( e, t = { title: "", text: "" }, a = { title: "", text: "" }, s = { title: "", text: "" }, ) { try { a && (await ((i = a.title), (o = a.text), new Promise((e) => { n.requestUserConfirmation(i, o, e); }))); let t = await navigator.clipboard.readText(); if (!t) throw "Empty file."; let r = t.split(":"); if (2 !== r.length) throw "Invalid clipboard"; if (r[0] !== e) throw "Invalid import action"; let l = n.decompressJsonFromString(r[1]); if (n.hasScript(l)) throw (n.showToast(s.title, s.text, "danger"), "Script found!"); return JSON.parse(l); } catch (e) { throw ( n.showToast(t.title, t.text), "Failed to import the clipboard." ); } var i, o; } static async runExport(e, t, a) { const s = n.isCompleteSavegame(t); "pu_save" == e && s && (e = "pu_complete"); const i = o.exportOptions[e]; let r, l, d, c, p; if (n.getSetting("useImageExport")) { ((r = i.imageExportExtension), a || (a = await n.loadImageFile( `/${n.extendDistPath()}img/${i.srcImage}.jpg`, ))); let e = await n.encryptExportImage(t, a); ((l = e.ImageType), (c = new Blob([e.imageData], { type: e.ImageType }))); } else ((t = n.shouldCompress() || s ? n.compressJSObjectToString(t) : JSON.stringify(t)), (r = i.textExportExtension), (l = n.shouldCompress() || s ? o.exportCompressedType : o.exportTextType), (d = t), (c = new Blob([t], { type: l }))); let m = n.getNow().format("YYYY-MM-DD_HH-mm"), g = `${i.exportFilenamePrefix}${m}.${r}`, h = ""; n.getSetting("useBase64Export") ? ((h = `download="${g}"`), (p = `data:${l};base64,${btoa(d)}`)) : n.getSetting("useDynamicExport") ? ((p = "/dynamic/" + g), u.getInstance().setExportStorageItem(g, c, l)) : ((h = `download="${g}"`), (p = window.URL.createObjectURL(c))); let v = $( ``, ); ($("body").append(v), v[0].click(), setTimeout( function () { (window.URL.revokeObjectURL(p), v.remove()); }.bind(this), 5e3, )); } static async runImport( e, t = { title: "", text: "" }, a = { title: "", text: "" }, s = { title: "", text: "" }, i = { title: "", text: "" }, ) { let r = e.target.files[0]; if (!r) return; e.target.value = ""; try { let e; if ( (a && (await ((c = a.title), (u = a.text), new Promise((e) => { n.requestUserConfirmation(c, u, e); }))), o.isImage(r.type)) ) { if (!n.canCompress()) throw ( n.showToast(s.title, s.text, "warning"), "Compression not available!" ); if ( ((e = await ((l = r), (d = !0), new Promise((e, t) => { const a = new FileReader(); ((a.onload = (t) => e( d ? n.decryptExportImage(t.target.result) : new Uint8Array(t.target.result), )), (a.onerror = (e) => t(e)), a.readAsArrayBuffer(l)); }))), n.hasScript(e)) ) throw (n.showToast(i.title, i.text, "danger"), "Script found!"); return JSON.parse(e); } if ( ((e = await ((e) => new Promise((t, a) => { const s = new FileReader(); ((s.onload = (e) => t(e.target.result)), (s.onerror = (e) => a(e)), s.readAsText(e)); }))(r)), !e) ) throw "Empty file."; if (n.hasScript(e)) throw (n.showToast(i.title, i.text, "danger"), "Script found!"); if ("string" == typeof e && e.startsWith("{")) return JSON.parse(e); if (!n.canCompress()) throw ( n.showToast(s.title, s.text, "warning"), "Compression not available!" ); if (((e = n.decompressJsonFromString(e)), n.hasScript(e))) throw (n.showToast(i.title, i.text, "danger"), "Script found!"); return JSON.parse(e); } catch (e) { throw ( n.showToast(t.title, t.text), "Failed to import the selected file." ); } var l, d, c, u; } } $(document).ready(() => { (!(function (e) { const t = document.querySelectorAll( `link[title="${e}"][rel="stylesheet"]`, ); (!e || e.length <= 0 || t.length <= 0) && (e = "darkly"); const a = document.querySelectorAll('link[title][rel="stylesheet"]'); for (const t of a) t.title == e ? (t.disabled = !1) : (t.disabled = !0); const s = o.styles[e], i = document.querySelector("body nav.navbar"); if (i) for (let e = 0; e < i.classList.length; e++) { const t = i.classList[e]; "navbar-expand-lg" !== t && t.startsWith("navbar-") ? i.classList.replace(t, "navbar-" + s.navbar) : t.startsWith("bg-") && i.classList.replace(t, "bg-" + s.bg); } })(n.getSetting("style")), moment.relativeTimeThreshold("s", 60), moment.relativeTimeThreshold("m", 60), moment.relativeTimeThreshold("h", 24), moment.relativeTimeThreshold("d", 31), moment.relativeTimeThreshold("M", 12), moment.relativeTimeThreshold("y", 365), moment.relativeTimeRounding(Math.floor)); const e = (function (e) { const t = new URLSearchParams(e.replace(/#.*?$/, "")); let a = []; for (const [e, s] of t) a[e] = s; return a; })(window.location.search); (e.hasOwnProperty("lang") ? (n.setSetting("language", "en"), s.createInstance(e.lang)) : n.getSetting("language") ? s.createInstance(n.getSetting("language")) : s.createInstance(n.getLanguage()), (document.getElementById("newUpdateAvailable").innerHTML = ' ' + s .getInstance() .T( "update-notification", "A new update is available, click to activate it now.", )), document .querySelector("html") .addEventListener("navigationFinished", () => { ($('[data-toggle="popover"]') .popover({ html: !0, placement: "auto", delay: { show: 0, hide: 100 }, }) .click(function (e) { setTimeout(function () { let t = void 0; (t = e.currentTarget.attributes["aria-describedby"]) && $("#" + t.value).popover("hide"); }, 5e3); }), n.showScheduledToast(), x.checkForFinishedTasks(), (function () { let e = function () { $("nav.navbar .navbar-toggler").is(":visible") && $("nav.navbar .navbar-collapse").collapse("hide"); }; document.querySelectorAll("nav.navbar li a").forEach((t) => { t.addEventListener("click", e); }); })()); })); let t = function () { (_.getInstance(), x.checkForSkippedClasses(), $("#backToTop").on("click", () => { n.scrollBackToTop(); }), (window.getFileData = async function (e) { const t = u.getInstance().getExportStorageItem(e); return t || { error: "Unknown filename" }; }), window.addEventListener("beforeunload", function (e) { _.checkForUnsavedChanges(() => {}); }), n.getSetting("enableAutoHide") && _.navigationData[_.getInstance().getParameter("page")] .autoHideAllowed && (function () { var e; function t() { document.body.className = "hidden"; } function a() { ((document.body.className = ""), clearTimeout(e), (e = setTimeout(t, 60 * o.autoHideTimer * 1e3))); } ((window.onload = a), (window.onmousemove = a), (window.onmousedown = a), (window.ontouchstart = a), (window.onclick = a), (window.onkeypress = a), window.addEventListener("scroll", a, !0)); })(), n.getSetting("enableBackToTop") && window.addEventListener("scroll", function (e) { document.body.scrollTop > 100 || document.documentElement.scrollTop > 100 ? $("#backToTop").fadeIn() : $("#backToTop").fadeOut(); }), n.getSetting("autoExtendCarousel") && window.addEventListener("scroll", function (e) { $("#customCarousel").length > 0 && document.documentElement.scrollTop == document.documentElement.scrollHeight - window.innerHeight && ($("#customCarousel").carousel("next"), window.scrollTo(0, 0)); })); }; try { u.getInstance(t); } catch (e) { console.error(e); } }); }, ]);