◆少前百科是非盈利性、非官方的少女前线维基百科。
◆如果您发现某些内容错误/空缺,请勇于修正/添加!参与进来其实很容易!点这里 加入少前百科
◆有任何意见、建议、纠错,欢迎在 GFwiki:反馈与建议 提出和讨论。编辑事务讨论QQ群:597764980,微博@GFwiki少前百科
◆To foreigners,You can use twitter to contact us.
Icon Nyto Silver.png

Widget:AvgPlayer/player.js

来自少前百科GFwiki
罗瓦讨论 | 贡献2023年10月8日 (日) 22:09的版本
跳转至: 导航搜索
const avgdiv = document.getElementById('avgdiv');
const avgbox = avgdiv.querySelector('#avgbox');
const avgbg = avgbox.querySelector('#avgbackground'),
  avgword = avgbox.querySelector('#avgword'),
  avgspeaker = avgbox.querySelector('#avgspeaker'),
  avgline = avgbox.querySelector('#avgline'),
  optionList = avgbox.querySelector('#avgbranchdiv'),
  avgblack = avgbox.querySelector('#avgblack'),
  avglog = avgbox.querySelector('#avglog'),
  avgauto = avgbox.querySelector('#avgauto'),
  avghideui = avgbox.querySelector('#avghideui');
avgblack.addEventListener('transitionend', regulateClickAccess);
const logBox = avgword.cloneNode(true);
logBox.id = 'avglogbox';
logBox.children[0].id = '';
logBox.children[1].id = '';
const xAnchors = new Map([
  [1, ['50']],
  [2, ['35', '65']]
]);
const blackTypes = new Map([[1, 'black'], [-1, 'white'], [0, '']]);
let toEnterBlack = 0,
  enteringBlack = false,
  exitingBlack = false,
  masks = new Set(),
  newMasks = new Set(),
  removingMasks = false,
  logOpen = false,
  autoPlay = false;
let logFragment = document.createDocumentFragment();
const logTpl = document.getElementById('log-tpl').content.children[0];

function readChar(target, text, pos, idx) {
  return new Promise((resolve) => {
    if (!readingLine) {
      resolve(true);
      return;
    }
    setTimeout(() => {
      target.childNodes[idx].nodeValue = target.childNodes[idx].nodeValue + text[pos];
      if (target.childNodes[idx].nodeValue.length < text.length) {
        resolve(readChar(target, text, pos+1, idx));
      } else {
        resolve(true);
      }
    }, 50);
  });
}

function readLine(target, nodes, nid) {
  return new Promise(function (resolve) {
    if (!readingLine) {
      resolve(false);
      return;
    }
    function res() {
      if (target.childNodes.length < nodes.length) {
        resolve(readLine(target, nodes, nid+1));
      } else {
        resolve(true);
      }
    }
    setTimeout(() => {
      if (nid === 0) {
        target.replaceChildren();
      }
      if (nodes.length === 0) {
        resolve(true);
        return;
      }
      const node = nodes[nid];
      if (node.nodeType == 3) {
        const child = document.createTextNode('');
        target.appendChild(child);
        readChar(target, node.textContent, 0, nid).then(res);
      } else {
        const child = document.createElement(node.tagName);
        const style = node.getAttribute('style');
        if (style !== '' && style !== null) {
          child.setAttribute('style', style);
        }
        target.appendChild(child);
        if (node.childNodes.length > 0) {
          readLine(child, node.childNodes, 0).then(res);
        } else {
          res();
        }
      }
    }, 25);
  });
}

function speak(curShot) {
  avgspeaker.textContent = curShot.chars?.speaker || '';
  avgword.style.visibility = curShot.lines[lineNum]?.length ? null : 'hidden';
  readingLine = true;
  readLine(avgline, curShot.lines[lineNum], 0).then(function(completed) {
    if (completed) {
      readingLine = false;
    } else {
      avgline.replaceChildren(...(curShot.lines[lineNum]));
    }
    if (autoPlay) {
      setTimeout(() => {avgbox.click();}, 2000);
    }
  });
}

function removePic(e) {
  e.style.transform = 'translate(-53%,-50%)';
  e.style.opacity = 0;
  setTimeout(() => e.remove(), 300);
}

function addPic(e) {
  setTimeout(() => {
    e.style.transform = null;
    e.style.opacity = 1;
  }, 5);
}

function arrangeChars(chars) {
  const prevPics = avgbox.querySelectorAll('.avgdollpic');
  const currPics = chars?.pics?.map(e => e.code);
  if (!currPics?.length) {
  	prevPics.forEach(removePic);
  } else {
  	const done = currPics.map(e => false);
    prevPics.forEach(e => {
  	  const i = currPics.indexOf(e.getAttribute('name-data'));
  	  if (i == -1) {
  	  	removePic(e);
  	  } else if (chars.pics[i].position) {
  	  	const xAnchor = xAnchors.get(currPics.length)[i];
  	  	e.style.left = `calc(${xAnchor}% + ${chars.pics[i].position[0]*0.625}px)`;
  	  	e.style.top = `calc(50% = ${chars.pics[i].position[1]*0.625}px)`;
  	  	if (!chars.allDark && i == chars.speakerIndex) {
  	  	  e.classList.add('speakerPic');
  	  	} else {
  	  	  e.classList.remove('speakerPic');
  	  	}
  	  	done[i] = true;
  	  } else {
  	  	e.style.left = xAnchors.get(currPics.length)[i] + '%';
  	  	e.style.top = '50%';
  	  	if (!chars.allDark && i == chars.speakerIndex) {
  	  	  e.classList.add('speakerPic');
  	  	} else {
  	  	  e.classList.remove('speakerPic');
  	  	}
  	  	done[i] = true;
  	  }
    });
    chars.pics.forEach((pic, i) => {
      if (!pic.file) {
      	return;
      }
      if (!done[i]) {
      	const dollpic = picTpl.cloneNode(true);
      	dollpic.children[1].src = pic.file;
      	if (!chars.allDark && i == chars.speakerIndex) {
      	  dollpic.classList.add('speakerPic');
      	}
      	if (chars.teles?.includes(i)) {
      	  dollpic.classList.add('tele');
      	}
      	dollpic.setAttribute('name-data', pic.code);
      	dollpic.style.opacity = 0;
      	dollpic.style.transform = 'translate(-53%,-50%)';
      	const xAnchor = xAnchors.get(currPics.length)[i];
      	if (pic.position) {
      	  dollpic.style.left = `calc(${xAnchor}% + ${pic.position[0]*0.625}px)`;
      	  dollpic.style.top = `calc(50% - ${pic.position[1]*0.625}px)`;
      	} else {
      	  dollpic.style.left = xAnchor + '%';
      	  dollpic.style.top = '50%';
      	}
      	addPic(avgbox.appendChild(dollpic));
      }
    });
  }
}

function applyEffects(effects) {
  let narrator = false;
  newMasks.clear();
  for (const effect of effects) {
    switch (effect.tagName) {
      case 'bin':
        const bgcg = new Image();
        bgcg.src = bgDict[effect.content];
        avgbg.replaceChildren(bgcg);
        break;
      case 'bgm':
        let avgbgm = document.getElementById('avgbgm');
        if (effect.content == 'BGM_Empty') {
          document.getElementById('avgbgm')?.remove();
        } else {
          if (!avgbgm) {
            avgbgm = bgmTpl.cloneNode();
            avgbgm.id = 'avgbgm';
            avgdiv.appendChild(avgbgm);
          }
          avgbgm.addEventListener('error', function() {
            console.error(`Error ${this.error.code}; details: ${this.error.message}`, shotNum, effect.content);
          })
          avgbgm.volume = 0.5*volumeInput.value/10;
          avgbgm.src = bgmDict[effect.content] || '/images/' + window.gfUtils.createWikiPathPart(effect.content+'.mp3') + '/' + effect.content+'.mp3';
          avgbgm.play().catch(error => console.error(error));
        }
        break;
      case 'narrator':
        narrator = true;
        break;
      case 'se':
      case 'se1':
      case 'se2':
      case 'se3':
      	const avgse = seTpl.cloneNode();
      	avgse.addEventListener('error', function() {
      	  console.error(`Error ${this.error.code}; details: ${this.error.message}`, shotNum, effect.content);
      	});
      	avgse.volume = 0.5*volumeInput.value/10;
      	avgse.src = seDict[effect.content] || '/images/' + window.gfUtils.createWikiPathPart(effect.content+'.mp3') + '/' + effect.content+'.mp3';
      	avgse.addEventListener('ended', function() {this.remove();});
      	avgdiv.appendChild(avgse);
      	avgse.play();
      	break;
      case '黑屏1':
      case '黑点1':
      	toEnterBlack = 1;
      	break;
      case '黑屏2':
      case '黑点2':
      case '睁眼':
      	break;
      case '白屏1':
      	toEnterBlack = -1;
      	break;
      case '白屏2':
      	break;
      case '回忆':
      	masks.add('memory');
      	newMasks.add('memory');
      	break;
      case '关闭蒙版':
      	removingMasks = true;
      	break;
    }
  }
  if (narrator != avgword.classList.contains('narrator')) {
  	if (toEnterBlack) {
  		setTimeout(() => avgword.classList.toggle('narrator'), 1000);
  	} else {
      avgword.classList.toggle('narrator');
  	}
  }
}

function handleAvgClickEvent(event) {
  let curShot = ongoingScene[shotNum];
  switch (event.target.id) {
    case 'avglog':
      event.stopPropagation();
      if (readingLine) {
        readingLine = false;
      }
      if (avgword.classList.contains('narrator')) {
        if (logOpen) {
          logBox.children[1].replaceChildren();
          logBox.className = '';
          setTimeout(() => {
            logBox.style.opacity = 0;
            avgcontrol.className = '';
          }, 167);
          setTimeout(() => {
            logBox.remove();
            logOpen = false;
      	    if (autoPlay) {
      	      setTimeout(() => {avgbox.click();}, 1000);
      	    }
          }, 375);
        } else {
          avgword.insertAdjacentElement('afterend', logBox);
          setTimeout(() => {
            logBox.style.opacity = 1;
            logBox.className = 'log';
          }, 5);
          avgcontrol.className = 'log';
          logOpen = true;
          setTimeout(() => {
            logBox.children[1].append(logFragment.cloneNode(true));
          }, 380);
        }
      } else {
      	avgspeaker.replaceChildren();
      	avgline.replaceChildren();
      	if (logOpen) {
      	  avgword.classList.remove('log');
      	  setTimeout(() => {
      	  	avgcontrol.className = '';
      	  	avgspeaker.textContent = curShot.chars?.speaker || '';
      	    avgline.append(...(curShot.lines[lineNum]));
      	    logOpen = false;
      	    if (autoPlay) {
      	      setTimeout(() => {avgbox.click();}, 1000);
      	    }
      	  }, 375);
      	} else {
          avgword.classList.add('log');
          avgcontrol.className = 'log';
          logOpen = true;
          setTimeout(() => {
            avgline.append(logFragment.cloneNode(true));
          }, 375);
      	}
      }
      break;
    case 'avgauto':
      event.stopPropagation();
      autoPlay = !autoPlay;
      avgauto.classList.toggle('on');
      if (!readingLine) {
        setTimeout(playShot, 1000);
      }
      break;
    case 'avghideui':
      event.stopPropagation();
      avgbox.classList.add('hide');
      break;
    default:
      if (event.target == optionList) {
        event.stopPropagation();
      } else if (event.target.classList.contains('avgbranch')) {
        event.stopPropagation();
        jumpTo(event);
      } else if (enteringBlack || exitingBlack) {
        event.stopPropagation();
        return;
      } else if (readingLine) {
      	event.stopPropagation();
        readingLine = false;
      } else if (avgbox.classList.contains('hide')) {
      	event.stopPropagation();
      	avgbox.classList.remove('hide');
      } else if (!logOpen) {
      	if (toEnterBlack) {
      	  enteringBlack = true;
      	  avgblack.classList.add(blackTypes.get(toEnterBlack));
      	}
        playShot();
      }
  }
}

function regulateClickAccess() {
  if (enteringBlack) {
    exitingBlack = true;
    avgblack.classList.remove(blackTypes.get(toEnterBlack));
    enteringBlack = false;
    toEnterBlack = 0;
  } else if (exitingBlack) {
    exitingBlack = false;
  }
}

function playJumpShot(index) {
  optionList.className = '';
  optionList.style.display = 'none';
  optionList.replaceChildren();
  shotNum = index;
  let curShot = ongoingScene[index];
  lineNum = 0;
  arrangeChars(curShot.chars);
  applyEffects(curShot.effects);
  if (!curShot.chars && !curShot.lines[0].length) {
  	return setTimeout(playShot, 1000);
  }
  avgspeaker.textContent = curShot.chars?.speaker || '';
  if (!curShot.lines[lineNum]?.length) {
    avgword.style.visibility = 'hidden';
  } else {
    avgword.style.visibility = 'visible';
  }
  readingLine = true;
  readLine(avgline, curShot.lines[lineNum], 0).then(() => {
    if (!readingLine) {
      avgline.replaceChildren(...(curShot.lines[lineNum]));
    } else {
      readingLine = false;
    }
  });
}

function jumpTo(event) {
  event.stopPropagation();
  playJumpShot(+event.currentTarget.getAttribute('index'));
}

function clearStage() {
  toEnterBlack = 0;
  enteringBlack = false;
  exitingBlack = false;
  removingMasks = false;
  masks.clear();
  newMasks.clear();
  readingLine = false;
  autoPlay = false;
  logOpen = false;
  avgbg.replaceChildren();
  optionList.replaceChildren();
  logFragment.replaceChildren();
  logBox.remove();
  avgdiv.querySelectorAll('.avgdollpic, audio').forEach(e => e.remove());
  avgspeaker.textContent = '';
  avgline.replaceChildren();
  avgbox.className = '';
  avgword.className = '';
  avgblack.className = '';
  optionList.className = '';
  avgword.removeAttribute('style');
  avgbox.playEnd = false;
}

function playShotMain(curShot) {
  arrangeChars(curShot.chars);
  applyEffects(curShot.effects);
  newMasks.forEach(m => avgbox.classList.add(m));
  if (removingMasks) {
  	masks.forEach(m => avgbox.classList.remove(m));
  	removingMasks = false;
  	masks.clear();
  }
  if (!lineNum && curShot.lines[lineNum]?.length) {
  	setTimeout(() => {
  	  avgword.style.opacity = 1;
  	  avgword.style.transform = null;
  	  setTimeout(() => speak(curShot), 200);
  	}, 200);
  } else {
    speak(curShot);
  }
}

function playShot() {
  if (avgbox.playEnd) {
    avgbox.removeEventListener('click', throttledAvgClickEventHandler);
    setTimeout(() => {
      if (window.canPlayMainBgm && window.resumeMainBgm) {
        window.resumeMainBgm();
      }
    }, 2000);
    clearStage();
    return;
  }
  let curShot = ongoingScene[shotNum];
  if (curShot === undefined || lineNum == curShot.lines.length - 1) {
    shotNum = curShot?.exit ? curShot.exit : shotNum + 1;
    curShot = ongoingScene[shotNum];
    lineNum = 0;
  } else lineNum++;
  if (curShot === undefined) {
    avgbox.playEnd = true;
    return;
  }
  if (!lineNum) {
  	avgspeaker.replaceChildren();
  	avgline.replaceChildren();
  	avgword.style.opacity = 0;
  	if (!avgword.classList.contains('narrator')) {
  	  avgword.style.transform = 'scaleY(0)';
  	}
  	if (curShot.lines[0].length) {
  	  let logDiv = logTpl.cloneNode(true);
  	  logFragment.appendChild(logDiv);
  	  logDiv.children[0].textContent = curShot.chars?.speaker || '';
  	  let logLine = document.createElement('p');
  	  for (const node of curShot.lines[lineNum]) {
  	    logLine.append(node.cloneNode(true));
  	  }
  	  logLine.replaceChildren(logLine.textContent);
  	  logDiv.children[1].appendChild(logLine);
  	}
  	if (toEnterBlack) {
  	  setTimeout(() => playShotMain(curShot), 2000);
  	} else {
      playShotMain(curShot);
  	}
  } else {
  	let logLine = document.createElement('p');
  	for (const node of curShot.lines[lineNum]) {
  	  logLine.append(node.cloneNode(true));
  	}
  	logLine.replaceChildren(logLine.textContent);
  	logFragment.lastElementChild.children[1].appendChild(logLine);
  }
  if (!curShot.chars && !curShot.lines[0].length) {
  	setTimeout(() => {
  	  avgbox.click();
  	}, 100);
  } else if (lineNum) {
  	speak(curShot);
  }
  if (curShot.optionTag) {
  	const cg = curShot.optionTag == 'cg';
  	if (cg) {
  	  optionList.classList.add('cg-options');
  	}
    curShot.options.forEach((e, i) => {
      const optionItem = document.createElement('div');
      optionItem.className = 'avgbranch';
      if (cg) {
      	const scaleRatioHalf = avgbox.getBoundingClientRect().width / 1920;
      	const [xoff, yoff] = e.split(',');
      	optionItem.style.left = `calc(50% + ${xoff*scaleRatioHalf}px)`;
      	optionItem.style.top = `calc(50% - ${yoff*scaleRatioHalf}px)`;
      } else {
        optionItem.textContent = e;
      }
      optionItem.setAttribute('index', curShot.entries.get(+i+1));
      optionItem.addEventListener('click', jumpTo);
      optionList.appendChild(optionItem);
    });
    optionList.style.display = 'block';
  }
}

const throttledAvgClickEventHandler = _.throttle(handleAvgClickEvent, 100, { 'trailing': false });
export { throttledAvgClickEventHandler, clearStage };