class Phasma { constructor() { this.ghosts = []; this.evidenceList = []; this.$ghostsContainer = $('div.ghosts'); this.buildGhostArray(); this.bindEvents(); let darkMode = localStorage.getItem('darkMode'); this.darkMode = darkMode != null ? darkMode == "true" : true; this.over50 = false; } static get evidence() { if (this._evidence) return this._evidence; this._evidence = new Map([ [0, 'None'], [1, 'DOTS Projector'], [2, 'EMF Level 5'], [3, 'Fingerprints'], [5, 'Ghost Orbs'], [6, 'Ghost Writing'], [7, 'SpiritBox'], [4, 'Freezing Temperatures'], ]); return this._evidence; } get darkMode() { return this._darkMode; } set darkMode(value) { this._darkMode = value this.$script && this.$script.remove(); let dm = ''; if (this._darkMode) { dm = ` :root { --background-color: 0,0,0; --background-image: url('bg-dark.jpg'); --background-ghost-image: url('bg-ghost-dark.jpg'); --font-color: 255,255,255; --border-ghost: rgb(0,0,0); } body { background-image: url('bg-ghost-dark.jpg'); } `; $('div.dm-toggle').addClass('hide-sun').removeClass('hide-moon'); } else { dm = ` :root { --background-color: 38, 38, 38; --background-image: url('bg-light.jpg'); --background-ghost-image: url('bg-ghost-light.jpg'); --font-color: 0,0,0; --border-ghost: rgb(132 97 65); } body { background-image: url('bg-ghost-light.jpg'); } `; $('div.dm-toggle').removeClass('hide-sun').addClass('hide-moon'); } this.$script = $('').text(dm).prependTo(document.head); } get over50() { return this._over50; } set over50(value) { this._over50 = value; $('#santiy')[value ? 'addClass' : 'removeClass']('toggle'); this.showGhosts(); } buildGhostArray() { this.ghosts = [ new Ghost('Banshee', [1, 3, 5], { strength: "Will target only one player at a time", weakness: "Crucifix effectiveness is increased to 5m against one" } ), new Ghost('Demon', [3, 4, 6], { strength: "Can initiate hunts more often", weakness: "Getting Ouija Board responses will lower sanity less", modifiers: [{ condition: 'Always', modifier: 20 }], other: 'A Demon has a chance of initiating a hunt at any time, regardless of sanity level; the real-world probability of this is low.' } ), new Ghost('Goryo', [1, 2, 3], { strength: "Can only be seen interacting with D.O.T.S. through a camera when nobody is nearby", weakness: "Never wanders far from its place of death" } ), new Ghost('Hantu', [3, 4, 5], { strength: "Lower temperatures allow the Hantu to move faster", weakness: "Warmer areas slow the Hantu's movement" } ), new Ghost('Jinn', [2, 3, 4], { strength: "Travels at faster speeds if its victim is far away", weakness: "Cannot use its ability if the site's fuse box is off" } ), new Ghost('Mare', [5, 6, 7], { strength: "Has an increased chance to attack in the dark", weakness: "Turning the lights on will reduce the chance of an attack", modifiers: [{ condition: 'Lights turned off at ghosts room', modifier: 10 }, { condition: 'Lights turned on at ghosts room', modifier: -10 }] } ), new Ghost('Myling', [2, 3, 6], { strength: "Has quieter footsteps during a hunt", weakness: "Produces paranormal sounds more frequently" } ), new Ghost('Obake', [2, 3, 5], { strength: "Rarely leaves a trace when interacting with the environment", weakness: "Sometimes leaves behind unique evidence" } ), new Ghost('Oni', [1, 2, 4], { strength: "Increased activity if there are multiple players nearby", weakness: "An Oni's increased activity makes them easier to find" } ), new Ghost('Onryo', [4, 5, 7], { strength: "Extinguishing a flame can cause an Onryo to attack", weakness: "When threatened, this ghost will be less likely to hunt", modifiers: [{ condition: "No fire source lit near the ghost", modifier: 10 }], other: "An Onryo has a chance of hunting when a flame is extinguished near it, regardless of sanity level." } ), new Ghost('Phantom', [1, 3, 7], { strength: "Looking at a Phantom will lower the player's sanity considerably", weakness: "Taking a photo of the Phantom will cause it to briefly disappear" } ), new Ghost('Poltergeist', [3, 6, 7], { strength: "Capable of throwing multiple objects at once", weakness: "Becomes powerless with no throwables nearby" } ), new Ghost('Raiju', [1, 2, 5], { strength: "Moves faster near electrical devices", weakness: "Constantly disrupt electronic equipment", modifiers: [{ condition: 'In the presence of active electronic equipment', modifier: 15 }] } ), new Ghost('Revenant', [4, 5, 6], { strength: "Can travel significantly faster if a player is spotted during a Hunt", weakness: "Without a target, the Revenant moves very slowly" } ), new Ghost('Shade', [2, 4, 6], { strength: "Being shy makes it more difficult to locate and obtain evidence", weakness: "Less likely to hunt if multiple people are nearby", modifiers: [{ condition: 'Always', modifier: -15 }], other: "A Shade is less likely to hunt in the presence of more than 1 person near it." } ), new Ghost('Spirit', [2, 6, 7], { strength: "None", weakness: "Smudge sticks are more effective, preventing a hunt for longer" } ), new Ghost('The Twins', [2, 4, 7], { strength: "Either Twin can be angered and initiate an attack on their prey", weakness: "The Twins will often interact with the environment at the same time" } ), new Ghost('Wraith', [1, 2, 7], { strength: "Does not leave UV footprints after stepping in salt", weakness: "Will become more active if it steps in salt" } ), new Ghost('Yokai', [1, 5, 7], { strength: "Talking near the Yokai will anger it, increasing the chance to attack", weakness: "Can only hear voices close to it during a hunt", modifiers: [{ condition: 'When players are talking within 5 metres[verify] of the ghost', modifier: 30 }] } ), new Ghost('Yurei', [1, 4, 5], { strength: "Has a stronger effect on sanity", weakness: "Smudging the Yurei's ghost room will reduce how often it wanders" } ), new Ghost('The Mimic', [3, 4, 5, 7], { strength: "Can mimic the abilities and traits of other ghosts", weakness: "Will present Ghost Orbs as a secondary evidence" } ), ] } bindEvents() { let $evidenceList = $('div.evidence-list'); Phasma.evidence.forEach((v, k) => { if (k != 0) { let $check = $(``); $evidenceList.append($check); } }); $evidenceList.find('input').on('change', (e) => { let $checkBox = $(e.currentTarget); if ($checkBox.hasClass('indeterminate')) { $checkBox .removeClass('indeterminate') .prop({ checked: false, indeterminate: false }); } else if (!$checkBox.is(":checked")) { $checkBox .addClass('indeterminate') .prop({ checked: false, indeterminate: true }); } this.getEvidence(); }) $('#santiy').on('click', () => { this.over50 = !this.over50; }); let open = false; $('#ghost-info-toggle').on('click', () => { open = !open; $('#ghost-info-toggle')[open ? 'addClass' : 'removeClass']('toggle'); this.ghosts.forEach((g) => { g.infoVisible = open; }) }) $('div.dm-toggle').on('click', () => { this.darkMode = !this.darkMode; localStorage.setItem('darkMode', this.darkMode); }) } getEvidence() { let $list = $('div.evidence-list'); let evidence = []; $list.find('input').toArray().forEach((v) => { v = $(v); if (v.hasClass('indeterminate') || v.is(':checked')) { evidence.push({ eid: parseInt(v.attr('name')), include: v.is(':checked') }); } }); //disable all checkboxes not in included evidence //$list.find('div.evidence-list input:not(:checked)').prop('disabled', evidence.filter(ev => ev.include).length >= 3); this.evidenceList = evidence; this.showGhosts(); } showGhosts() { this.$ghostsContainer.find('div.ghost').detach(); let ghosts = this.getGhosts(this.evidenceList); ghosts.forEach(g => { this.$ghostsContainer.append(g.$button); }); let remaining = ghosts.map((v) => v.remaining); remaining = [...new Set(remaining.flat(1))]; $('div.evidence-list input').toArray().forEach((v) => { v = $(v); if ( //if we have this evidence or it's already checked or it's in indeterminate then mark as available remaining.includes(parseInt(v.attr('name'))) || v.is(':checked') || v.hasClass('indeterminate') ) { v.removeClass('notavailable').prop('disabled', false); } else //disabled all other evidence no longer available v.addClass('notavailable').prop('disabled', true); }) } getGhosts(evidenceList) { //if (evidenceList.filter(x => x != 0).length === 0) return []; let ghosts = this.ghosts.filter(g => { return g.hasAllEvidence(evidenceList, this.over50); }); return ghosts; } } class Ghost { constructor(name, evidence, data) { this.name = name; this.evidence = evidence; this.data = data; this._infoVisible = false; this.createButton(); } get infoVisible() { return this._infoVisible; } set infoVisible(value) { if (!value) this.$button.find('div.info').hide(); else this.$button.find('div.info').show(); this._infoVisible = value; } hasAllEvidence(evidenceList, over50) { let hasAllEvidence = true; // Includes all evidence supplied evidenceList.filter(ev => ev.include).forEach(e => { if (!this.evidence.some((ev) => { return e.eid === ev })) hasAllEvidence = false; }); // Excludes all excluded evidence evidenceList.filter(ev => !ev.include).forEach(e => { if (this.evidence.some((ev) => { return e.eid === ev })) hasAllEvidence = false; }); // If over50 and no over50 evidence, not all evidence if (hasAllEvidence && over50 && this.data.modifiers.filter(x => x.modifier > 0).length === 0) hasAllEvidence = false; //if still has evidence, calculate evidence remaining if (hasAllEvidence) { this.remaining = this.evidence.filter(e => !evidenceList.find(ev => { return ev.eid === e })); let remainingEvidence = this.remaining.map(x => '' + Phasma.evidence.get(x) + ''); this.$button.find('div.remaining-evidence').empty().append(remainingEvidence.join(', ')); } return hasAllEvidence; } createButton() { this.$button = $(`