arcaea-offline-pyside-ui/ui/implements/tabs/tabTools/tabTools_StepCalculator.py

365 lines
14 KiB
Python

import logging
from arcaea_offline.calculate.world_step import (
AmaneBelowExPartnerBonus,
AwakenedEtoPartnerBonus,
AwakenedIlithPartnerBonus,
AwakenedLunaPartnerBonus,
LegacyMapStepBooster,
MayaPartnerBonus,
MemoriesStepBooster,
PartnerBonus,
PlayResult,
calculate_play_rating_from_step,
calculate_step,
calculate_step_original,
)
from PySide6.QtCore import (
QCoreApplication,
QEasingCurve,
QObject,
QSize,
QTimeLine,
Signal,
)
from PySide6.QtGui import QIcon, QPixmap
from PySide6.QtWidgets import (
QAbstractButton,
QButtonGroup,
QDialog,
QGraphicsColorizeEffect,
QPushButton,
QVBoxLayout,
QWidget,
)
from ui.designer.tabs.tabTools.tabTools_StepCalculator_ui import (
Ui_TabTools_StepCalculator,
)
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.implements.components.chartSelector import ChartSelector
from ui.implements.components.playRatingCalculator import PlayRatingCalculator
from ui.implements.components.songIdSelector import SongIdSelectorMode
logger = logging.getLogger(__name__)
class ButtonGrayscaleEffectApplier(QObject):
def __init__(self, parent: QAbstractButton):
super().__init__(parent)
self.timeline = QTimeLine(500, self)
self.timeline.setEasingCurve(QEasingCurve.Type.OutCubic)
self.timeline.frameChanged.connect(self.applyGrayscaleEffect)
parent.toggled.connect(self.triggerEffectAnimation)
self.triggerEffectAnimation(parent.isChecked())
def triggerEffectAnimation(self, buttonEnabled: bool):
if self.timeline.state() == QTimeLine.State.Running:
self.timeline.stop()
startFrame = self.timeline.currentFrame()
stopFrame = 0 if buttonEnabled else 100
self.timeline.setFrameRange(startFrame, stopFrame)
self.timeline.start()
def applyGrayscaleEffect(self, frame: int):
target: QAbstractButton = self.parent()
value = frame / 100
effect = QGraphicsColorizeEffect(target)
effect.setColor("#000000")
effect.setStrength(value)
target.setGraphicsEffect(effect)
class PlayRatingCalculatorDialog(QDialog):
accepted = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.verticalLayout = QVBoxLayout(self)
self.chartSelector = ChartSelector(self)
self.chartSelector.setSongIdSelectorMode(SongIdSelectorMode.Chart)
self.verticalLayout.addWidget(self.chartSelector)
self.playRatingCalculator = PlayRatingCalculator(self)
self.verticalLayout.addWidget(self.playRatingCalculator)
self.acceptButton = QPushButton(self)
self.acceptButton.setText(
# fmt: off
QCoreApplication.translate("StepCalculator", "playRatingCalculatorDialog.acceptButton")
# fmt: on
)
self.acceptButton.setEnabled(False)
self.verticalLayout.addWidget(self.acceptButton)
self.chartSelector.valueChanged.connect(self.updatePlayRatingCalculator)
self.playRatingCalculator.arcaeaScoreLineEdit.textChanged.connect(
self.updateAcceptButton
)
self.acceptButton.clicked.connect(self.accepted)
def updatePlayRatingCalculator(self):
chart = self.chartSelector.value()
if chart is None:
self.playRatingCalculator.setConstant(None)
else:
self.playRatingCalculator.setConstant(chart.constant)
self.updateAcceptButton()
def updateAcceptButton(self):
if self.playRatingCalculator.result is None:
self.acceptButton.setEnabled(False)
else:
self.acceptButton.setEnabled(True)
def reset(self):
self.chartSelector.resetButton.click()
self.playRatingCalculator.arcaeaScoreLineEdit.clear()
def value(self):
return self.playRatingCalculator.result
class TabTools_StepCalculator(Ui_TabTools_StepCalculator, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.calculate_toStep_calculatePlayResultFromScoreButton.clicked.connect(
self.openChartAndScoreInputDialog
)
# set icons
staminaIcon = QIcon(":/images/stepCalculator/stamina.png")
for radioButton in [
self.legacyPlayPlus_x2StaminaRadioButton,
self.legacyPlayPlus_x4StaminaRadioButton,
self.legacyPlayPlus_x6StaminaRadioButton,
]:
radioButton.setIcon(staminaIcon)
radioButton.setIconSize(QSize(25, 15))
memoryBoostIcon = QIcon(":/images/stepCalculator/memory-boost.png")
self.play_memoryBoostCheckBox.setIcon(memoryBoostIcon)
self.play_memoryBoostCheckBox.setIconSize(QSize(75, 100))
mapTypeIconSize = QSize(150, 50)
for button, pixmap in zip(
[
self.mapTypeLegacyPlayRadioButton,
self.mapTypeLegacyPlayPlusRadioButton,
self.mapTypePlayRadioButton,
],
[
QPixmap(":/images/stepCalculator/legacy-play.png"),
QPixmap(":/images/stepCalculator/legacy-play-plus.png"),
QPixmap(":/images/stepCalculator/play.png"),
],
):
button.setIconSize(mapTypeIconSize)
button.setIcon(pixmap)
# apply grayscale effects
self.buttonGrayscaleEffectAppliers = []
for button in [
self.mapTypeLegacyPlayRadioButton,
self.mapTypeLegacyPlayPlusRadioButton,
self.mapTypePlayRadioButton,
self.legacyPlayPlus_x2StaminaRadioButton,
self.legacyPlayPlus_x4StaminaRadioButton,
self.legacyPlayPlus_x6StaminaRadioButton,
self.play_memoryBoostCheckBox,
]:
applier = ButtonGrayscaleEffectApplier(button)
self.buttonGrayscaleEffectAppliers.append(applier)
# create button groups
self.mapTypeButtonGroup = QButtonGroup(self)
self.mapTypeButtonGroup.addButton(self.mapTypeLegacyPlayRadioButton, 0)
self.mapTypeButtonGroup.addButton(self.mapTypeLegacyPlayPlusRadioButton, 1)
self.mapTypeButtonGroup.addButton(self.mapTypePlayRadioButton, 2)
self.mapTypeButtonGroup.idToggled.connect(self.stackedWidget.setCurrentIndex)
self.mapTypePlayRadioButton.setChecked(True)
self.legacyPlayPlusStaminaButtonGroup = QButtonGroup(self)
self.legacyPlayPlusStaminaButtonGroup.addButton(
self.legacyPlayPlus_x2StaminaRadioButton, 2
)
self.legacyPlayPlusStaminaButtonGroup.addButton(
self.legacyPlayPlus_x4StaminaRadioButton, 4
)
self.legacyPlayPlusStaminaButtonGroup.addButton(
self.legacyPlayPlus_x6StaminaRadioButton, 6
)
self.legacyPlayPlus_x2StaminaRadioButton.setChecked(True)
self.legacyPlayPlusFragmentsButtonGroup = QButtonGroup(self)
self.legacyPlayPlusFragmentsButtonGroup.addButton(
self.legacyPlayPlus_x11fragRadioButton, 100
)
self.legacyPlayPlusFragmentsButtonGroup.addButton(
self.legacyPlayPlus_x125fragRadioButton, 250
)
self.legacyPlayPlusFragmentsButtonGroup.addButton(
self.legacyPlayPlus_x15fragRadioButton, 500
)
# bind calculate functions
self.mapTypeButtonGroup.buttonToggled.connect(self.tryCalculate)
self.legacyPlayPlusStaminaButtonGroup.buttonToggled.connect(self.tryCalculate)
self.legacyPlayPlusFragmentsButtonGroup.buttonToggled.connect(self.tryCalculate)
self.play_memoryBoostCheckBox.toggled.connect(self.tryCalculate)
self.partnerStepValueSpinBox.valueChanged.connect(self.tryCalculate)
self.partnerSkillGroupBox.toggled.connect(self.tryCalculate)
self.partnerSkillStepBonusLineEdit.textChanged.connect(self.tryCalculate)
self.partnerSkillFinalMultiplierLineEdit.textChanged.connect(self.tryCalculate)
self.calculate_toStep_playResultSpinBox.valueChanged.connect(self.tryCalculate)
self.calculate_fromStep_targetStepSpinBox.valueChanged.connect(
self.tryCalculate
)
# connect partner skill preset buttons
self.partnerSkillPresetButton_awakenedIlith.clicked.connect(
self.applyPartnerPreset
)
self.partnerSkillPresetButton_awakenedEto.clicked.connect(
self.applyPartnerPreset
)
self.partnerSkillPresetButton_awakenedLuna.clicked.connect(
self.applyPartnerPreset
)
self.partnerSkillPresetButton_amaneBelowEx.clicked.connect(
self.applyPartnerPreset
)
self.partnerSkillPresetButton_maya.clicked.connect(self.applyPartnerPreset)
self.playRatingCalculatorDialog = PlayRatingCalculatorDialog(self)
self.playRatingCalculatorDialog.accepted.connect(self.set_toStep_PlayRating)
def openChartAndScoreInputDialog(self):
self.playRatingCalculatorDialog.reset()
self.playRatingCalculatorDialog.show()
def set_toStep_PlayRating(self):
result = self.playRatingCalculatorDialog.value()
if result is not None:
self.calculate_toStep_playResultSpinBox.setValue(result)
self.playRatingCalculatorDialog.close()
def applyPartnerPreset(self):
if not self.sender():
return
objectName = self.sender().objectName()
if not objectName:
return
formatString = "partnerSkillPresetButton_{}"
if objectName == formatString.format("awakenedIlith"):
pb = AwakenedIlithPartnerBonus
elif objectName == formatString.format("awakenedEto"):
pb = AwakenedEtoPartnerBonus
elif objectName == formatString.format("awakenedLuna"):
pb = AwakenedLunaPartnerBonus
elif objectName == formatString.format("amaneBelowEx"):
pb = AmaneBelowExPartnerBonus
elif objectName == formatString.format("maya"):
pb = MayaPartnerBonus
else:
return
self.partnerSkillStepBonusLineEdit.setText(str(pb.step_bonus))
self.partnerSkillFinalMultiplierLineEdit.setText(str(pb.final_multiplier))
def toStepPlayResult(self):
return PlayResult(
play_rating=self.calculate_toStep_playResultSpinBox.value(),
partner_step=self.partnerStepValueSpinBox.value(),
)
def partnerBonus(self):
if self.partnerSkillGroupBox.isChecked():
try:
partnerBonus = PartnerBonus(
step_bonus=self.partnerSkillStepBonusLineEdit.text(),
final_multiplier=self.partnerSkillFinalMultiplierLineEdit.text(),
)
partnerBonus.step_bonus
partnerBonus.final_multiplier
return partnerBonus
except Exception:
if self.detailedLogOutputCheckBox.isChecked():
logger.exception("Cannot parse PartnerBonus input")
return PartnerBonus()
return PartnerBonus()
def stepBooster(self):
if self.mapTypeButtonGroup.checkedId() == 1:
# Legacy Play+
stamina = self.legacyPlayPlusStaminaButtonGroup.checkedId()
if self.legacyPlayPlus_useFragmentsGroupBox.isChecked():
fragment = self.legacyPlayPlusFragmentsButtonGroup.checkedId()
fragment = fragment if fragment > -1 else None
else:
fragment = None
return None if stamina < 0 else LegacyMapStepBooster(stamina, fragment)
elif self.mapTypeButtonGroup.checkedId() == 2:
# General Music Play
if self.play_memoryBoostCheckBox.isChecked():
return MemoriesStepBooster()
else:
return None
else:
return None
def tryCalculate(self):
if self.partnerStepValueSpinBox.value() <= 0.0:
self.calculate_toStep_resultLabel.setText("...")
self.calculate_fromStep_resultLabel.setText("...")
return
# toStep
try:
playResult = self.toStepPlayResult()
partnerBonus = self.partnerBonus()
stepBooster = self.stepBooster()
stepOriginal = calculate_step_original(
playResult, partner_bonus=partnerBonus, step_booster=stepBooster
)
step = calculate_step(
playResult, partner_bonus=partnerBonus, step_booster=stepBooster
)
self.calculate_toStep_resultLabel.setText(str(step))
self.calculate_toStep_detailedResultLabel.setText(str(stepOriginal))
except Exception:
if self.detailedLogOutputCheckBox.isChecked():
logger.exception("Cannot calculate toStep")
self.calculate_toStep_resultLabel.setText("...")
# fromStep
try:
fromStepResult = calculate_play_rating_from_step(
self.calculate_fromStep_targetStepSpinBox.value(),
self.partnerStepValueSpinBox.value(),
partner_bonus=partnerBonus,
step_booster=stepBooster,
)
self.calculate_fromStep_resultLabel.setText(str(round(fromStepResult, 2)))
self.calculate_fromStep_detailedResultLabel.setText(str(fromStepResult))
except Exception:
if self.detailedLogOutputCheckBox.isChecked():
logger.exception("Cannot calculate fromStep")
self.calculate_fromStep_resultLabel.setText("...")