refactor(engine): 서비스 로직 정리
- ArenaService, PotionService, ProgressService 개선 - ResurrectionService, SkillService 정리
This commit is contained in:
@@ -19,7 +19,7 @@ import 'package:asciineverdie/src/core/util/deterministic_random.dart';
|
|||||||
/// - 장비 교환
|
/// - 장비 교환
|
||||||
class ArenaService {
|
class ArenaService {
|
||||||
ArenaService({DeterministicRandom? rng})
|
ArenaService({DeterministicRandom? rng})
|
||||||
: _rng = rng ?? DeterministicRandom(DateTime.now().millisecondsSinceEpoch);
|
: _rng = rng ?? DeterministicRandom(DateTime.now().millisecondsSinceEpoch);
|
||||||
|
|
||||||
final DeterministicRandom _rng;
|
final DeterministicRandom _rng;
|
||||||
|
|
||||||
@@ -309,7 +309,9 @@ class ArenaService {
|
|||||||
elapsedMs += tickMs;
|
elapsedMs += tickMs;
|
||||||
|
|
||||||
// 스킬 시스템 시간 업데이트
|
// 스킬 시스템 시간 업데이트
|
||||||
challengerSkillSystem = challengerSkillSystem.copyWith(elapsedMs: elapsedMs);
|
challengerSkillSystem = challengerSkillSystem.copyWith(
|
||||||
|
elapsedMs: elapsedMs,
|
||||||
|
);
|
||||||
opponentSkillSystem = opponentSkillSystem.copyWith(elapsedMs: elapsedMs);
|
opponentSkillSystem = opponentSkillSystem.copyWith(elapsedMs: elapsedMs);
|
||||||
|
|
||||||
int? challengerDamage;
|
int? challengerDamage;
|
||||||
@@ -469,10 +471,13 @@ class ArenaService {
|
|||||||
challengerSkillSystem = skillResult.updatedSkillSystem;
|
challengerSkillSystem = skillResult.updatedSkillSystem;
|
||||||
final debuffEffect = skillResult.debuffEffect;
|
final debuffEffect = skillResult.debuffEffect;
|
||||||
if (debuffEffect != null) {
|
if (debuffEffect != null) {
|
||||||
opponentDebuffs = opponentDebuffs
|
opponentDebuffs =
|
||||||
.where((ActiveBuff d) => d.effect.id != debuffEffect.effect.id)
|
opponentDebuffs
|
||||||
.toList()
|
.where(
|
||||||
..add(debuffEffect);
|
(ActiveBuff d) => d.effect.id != debuffEffect.effect.id,
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
..add(debuffEffect);
|
||||||
}
|
}
|
||||||
challengerSkillUsed = selectedSkill.name;
|
challengerSkillUsed = selectedSkill.name;
|
||||||
} else {
|
} else {
|
||||||
@@ -585,10 +590,13 @@ class ArenaService {
|
|||||||
opponentSkillSystem = skillResult.updatedSkillSystem;
|
opponentSkillSystem = skillResult.updatedSkillSystem;
|
||||||
final debuffEffect = skillResult.debuffEffect;
|
final debuffEffect = skillResult.debuffEffect;
|
||||||
if (debuffEffect != null) {
|
if (debuffEffect != null) {
|
||||||
challengerDebuffs = challengerDebuffs
|
challengerDebuffs =
|
||||||
.where((ActiveBuff d) => d.effect.id != debuffEffect.effect.id)
|
challengerDebuffs
|
||||||
.toList()
|
.where(
|
||||||
..add(debuffEffect);
|
(ActiveBuff d) => d.effect.id != debuffEffect.effect.id,
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
..add(debuffEffect);
|
||||||
}
|
}
|
||||||
opponentSkillUsed = selectedSkill.name;
|
opponentSkillUsed = selectedSkill.name;
|
||||||
} else {
|
} else {
|
||||||
@@ -628,7 +636,8 @@ class ArenaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 액션이 발생했을 때만 턴 전송
|
// 액션이 발생했을 때만 턴 전송
|
||||||
final hasAction = challengerDamage != null ||
|
final hasAction =
|
||||||
|
challengerDamage != null ||
|
||||||
opponentDamage != null ||
|
opponentDamage != null ||
|
||||||
challengerHealAmount != null ||
|
challengerHealAmount != null ||
|
||||||
opponentHealAmount != null ||
|
opponentHealAmount != null ||
|
||||||
@@ -722,12 +731,14 @@ class ArenaService {
|
|||||||
required bool isVictory,
|
required bool isVictory,
|
||||||
}) {
|
}) {
|
||||||
// 도전자 장비 목록 복사
|
// 도전자 장비 목록 복사
|
||||||
final challengerEquipment =
|
final challengerEquipment = List<EquipmentItem>.from(
|
||||||
List<EquipmentItem>.from(match.challenger.finalEquipment ?? []);
|
match.challenger.finalEquipment ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
// 상대 장비 목록 복사
|
// 상대 장비 목록 복사
|
||||||
final opponentEquipment =
|
final opponentEquipment = List<EquipmentItem>.from(
|
||||||
List<EquipmentItem>.from(match.opponent.finalEquipment ?? []);
|
match.opponent.finalEquipment ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
if (isVictory) {
|
if (isVictory) {
|
||||||
// 도전자 승리: 도전자가 선택한 슬롯의 상대 장비 획득
|
// 도전자 승리: 도전자가 선택한 슬롯의 상대 장비 획득
|
||||||
@@ -766,7 +777,9 @@ class ArenaService {
|
|||||||
|
|
||||||
/// 슬롯으로 장비 찾기
|
/// 슬롯으로 장비 찾기
|
||||||
EquipmentItem _findItemBySlot(
|
EquipmentItem _findItemBySlot(
|
||||||
List<EquipmentItem> equipment, EquipmentSlot slot) {
|
List<EquipmentItem> equipment,
|
||||||
|
EquipmentSlot slot,
|
||||||
|
) {
|
||||||
for (final item in equipment) {
|
for (final item in equipment) {
|
||||||
if (item.slot == slot) return item;
|
if (item.slot == slot) return item;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,8 +375,10 @@ class PotionService {
|
|||||||
required int typeRoll,
|
required int typeRoll,
|
||||||
}) {
|
}) {
|
||||||
// 기본 드랍 확률 계산
|
// 기본 드랍 확률 계산
|
||||||
var dropChance = (baseDropChance + playerLevel * dropChancePerLevel)
|
var dropChance = (baseDropChance + playerLevel * dropChancePerLevel).clamp(
|
||||||
.clamp(baseDropChance, maxDropChance);
|
baseDropChance,
|
||||||
|
maxDropChance,
|
||||||
|
);
|
||||||
|
|
||||||
// 몬스터 등급 보너스 (Elite +5%, Boss +15%)
|
// 몬스터 등급 보너스 (Elite +5%, Boss +15%)
|
||||||
dropChance += monsterGrade.potionDropBonus;
|
dropChance += monsterGrade.potionDropBonus;
|
||||||
|
|||||||
@@ -104,8 +104,10 @@ class ProgressService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ExpBar 초기화 (원본 743-746줄)
|
// ExpBar 초기화 (원본 743-746줄)
|
||||||
final expBar =
|
final expBar = ProgressBarState(
|
||||||
ProgressBarState(position: 0, max: ExpConstants.requiredExp(1));
|
position: 0,
|
||||||
|
max: ExpConstants.requiredExp(1),
|
||||||
|
);
|
||||||
|
|
||||||
// PlotBar 초기화 - Prologue 5분 (300초)
|
// PlotBar 초기화 - Prologue 5분 (300초)
|
||||||
final plotBar = const ProgressBarState(position: 0, max: 300);
|
final plotBar = const ProgressBarState(position: 0, max: 300);
|
||||||
@@ -299,23 +301,40 @@ class ProgressService {
|
|||||||
progress = progress.copyWith(currentCombat: combatForReset);
|
progress = progress.copyWith(currentCombat: combatForReset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 전투 상태 초기화, 몬스터 처치 수 증가 및 물약 사용 기록 초기화
|
// Boss 승리 처리: 시네마틱 트리거
|
||||||
progress = progress.copyWith(
|
if (progress.pendingActCompletion) {
|
||||||
currentCombat: null,
|
// Act Boss를 처치했으므로 시네마틱 재생
|
||||||
monstersKilled: progress.monstersKilled + 1,
|
final cinematicEntries = pq_logic.interplotCinematic(
|
||||||
);
|
config,
|
||||||
|
nextState.rng,
|
||||||
|
nextState.traits.level,
|
||||||
|
progress.plotStageCount,
|
||||||
|
);
|
||||||
|
queue = QueueState(entries: [...queue.entries, ...cinematicEntries]);
|
||||||
|
progress = progress.copyWith(
|
||||||
|
currentCombat: null,
|
||||||
|
monstersKilled: progress.monstersKilled + 1,
|
||||||
|
pendingActCompletion: false, // Boss 처치 완료
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 일반 전투 종료
|
||||||
|
progress = progress.copyWith(
|
||||||
|
currentCombat: null,
|
||||||
|
monstersKilled: progress.monstersKilled + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final resetPotionInventory = nextState.potionInventory.resetBattleUsage();
|
final resetPotionInventory = nextState.potionInventory.resetBattleUsage();
|
||||||
nextState = nextState.copyWith(
|
nextState = nextState.copyWith(
|
||||||
progress: progress,
|
progress: progress,
|
||||||
|
queue: queue,
|
||||||
potionInventory: resetPotionInventory,
|
potionInventory: resetPotionInventory,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 최종 보스 처치 체크
|
// 최종 보스 처치 체크
|
||||||
if (progress.finalBossState == FinalBossState.fighting) {
|
if (progress.finalBossState == FinalBossState.fighting) {
|
||||||
// 글리치 갓 처치 완료 - 게임 클리어
|
// 글리치 갓 처치 완료 - 게임 클리어
|
||||||
progress = progress.copyWith(
|
progress = progress.copyWith(finalBossState: FinalBossState.defeated);
|
||||||
finalBossState: FinalBossState.defeated,
|
|
||||||
);
|
|
||||||
nextState = nextState.copyWith(progress: progress);
|
nextState = nextState.copyWith(progress: progress);
|
||||||
|
|
||||||
// completeAct를 호출하여 게임 완료 처리
|
// completeAct를 호출하여 게임 완료 처리
|
||||||
@@ -406,22 +425,22 @@ class ProgressService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 플롯(plot) 바가 완료되면 InterplotCinematic 트리거
|
// 플롯(plot) 바가 완료되면 Act Boss 소환
|
||||||
// (원본 Main.pas:1301-1304)
|
// (개선: Boss 처치 → 시네마틱 → Act 전환 순서)
|
||||||
if (gain &&
|
if (gain &&
|
||||||
progress.plot.max > 0 &&
|
progress.plot.max > 0 &&
|
||||||
progress.plot.position >= progress.plot.max) {
|
progress.plot.position >= progress.plot.max &&
|
||||||
// InterplotCinematic을 호출하여 시네마틱 이벤트 큐에 추가
|
!progress.pendingActCompletion) {
|
||||||
final cinematicEntries = pq_logic.interplotCinematic(
|
// Act Boss 소환 및 플래그 설정
|
||||||
config,
|
final actBoss = _createActBoss(nextState);
|
||||||
nextState.rng,
|
progress = progress.copyWith(
|
||||||
nextState.traits.level,
|
plot: progress.plot.copyWith(position: 0), // Plot bar 리셋
|
||||||
nextState.progress.plotStageCount,
|
currentCombat: actBoss,
|
||||||
|
pendingActCompletion: true, // Boss 처치 대기 플래그
|
||||||
);
|
);
|
||||||
queue = QueueState(entries: [...queue.entries, ...cinematicEntries]);
|
|
||||||
// 플롯 바를 0으로 리셋하지 않음 - completeAct에서 처리됨
|
|
||||||
} else if (progress.currentTask.type != TaskType.load &&
|
} else if (progress.currentTask.type != TaskType.load &&
|
||||||
progress.plot.max > 0) {
|
progress.plot.max > 0 &&
|
||||||
|
!progress.pendingActCompletion) {
|
||||||
final uncappedPlot = progress.plot.position + incrementSeconds;
|
final uncappedPlot = progress.plot.position + incrementSeconds;
|
||||||
final int newPlotPos = uncappedPlot > progress.plot.max
|
final int newPlotPos = uncappedPlot > progress.plot.max
|
||||||
? progress.plot.max
|
? progress.plot.max
|
||||||
@@ -531,13 +550,44 @@ class ProgressService {
|
|||||||
return (progress: progress, queue: queue);
|
return (progress: progress, queue: queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 최종 보스 전투 체크
|
// 3. Act Boss 리트라이 체크
|
||||||
|
// pendingActCompletion이 true면 Act Boss 재소환
|
||||||
|
if (state.progress.pendingActCompletion) {
|
||||||
|
final actBoss = _createActBoss(state);
|
||||||
|
final combatCalculator = CombatCalculator(rng: state.rng);
|
||||||
|
final durationMillis = combatCalculator.estimateCombatDurationMs(
|
||||||
|
player: actBoss.playerStats,
|
||||||
|
monster: actBoss.monsterStats,
|
||||||
|
);
|
||||||
|
|
||||||
|
final taskResult = pq_logic.startTask(
|
||||||
|
progress,
|
||||||
|
l10n.taskDebugging(actBoss.monsterStats.name),
|
||||||
|
durationMillis,
|
||||||
|
);
|
||||||
|
|
||||||
|
progress = taskResult.progress.copyWith(
|
||||||
|
currentTask: TaskInfo(
|
||||||
|
caption: taskResult.caption,
|
||||||
|
type: TaskType.kill,
|
||||||
|
monsterBaseName: actBoss.monsterStats.name,
|
||||||
|
monsterPart: '*', // Boss는 WinItem 드랍
|
||||||
|
monsterLevel: actBoss.monsterStats.level,
|
||||||
|
monsterGrade: MonsterGrade.boss,
|
||||||
|
),
|
||||||
|
currentCombat: actBoss,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (progress: progress, queue: queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 최종 보스 전투 체크
|
||||||
// finalBossState == fighting이면 Glitch God 스폰
|
// finalBossState == fighting이면 Glitch God 스폰
|
||||||
if (state.progress.finalBossState == FinalBossState.fighting) {
|
if (state.progress.finalBossState == FinalBossState.fighting) {
|
||||||
return _startFinalBossFight(state, progress, queue);
|
return _startFinalBossFight(state, progress, queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. MonsterTask 실행 (원본 678-684줄)
|
// 5. MonsterTask 실행 (원본 678-684줄)
|
||||||
final level = state.traits.level;
|
final level = state.traits.level;
|
||||||
|
|
||||||
// 원본 Main.pas:548-551: 25% 확률로 Quest Monster 사용
|
// 원본 Main.pas:548-551: 25% 확률로 Quest Monster 사용
|
||||||
@@ -878,7 +928,8 @@ class ProgressService {
|
|||||||
var nextState = state;
|
var nextState = state;
|
||||||
|
|
||||||
// 현재 레벨이 목표 레벨보다 낮으면 레벨업 (최대 100레벨)
|
// 현재 레벨이 목표 레벨보다 낮으면 레벨업 (최대 100레벨)
|
||||||
while (nextState.traits.level < targetLevel && nextState.traits.level < 100) {
|
while (nextState.traits.level < targetLevel &&
|
||||||
|
nextState.traits.level < 100) {
|
||||||
nextState = _levelUp(nextState);
|
nextState = _levelUp(nextState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1390,10 +1441,13 @@ class ProgressService {
|
|||||||
|
|
||||||
// 디버프 효과 추가 (기존 같은 디버프 제거 후)
|
// 디버프 효과 추가 (기존 같은 디버프 제거 후)
|
||||||
if (skillResult.debuffEffect != null) {
|
if (skillResult.debuffEffect != null) {
|
||||||
activeDebuffs = activeDebuffs
|
activeDebuffs =
|
||||||
.where((d) => d.effect.id != skillResult.debuffEffect!.effect.id)
|
activeDebuffs
|
||||||
.toList()
|
.where(
|
||||||
..add(skillResult.debuffEffect!);
|
(d) => d.effect.id != skillResult.debuffEffect!.effect.id,
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
..add(skillResult.debuffEffect!);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 디버프 이벤트 생성
|
// 디버프 이벤트 생성
|
||||||
@@ -1533,6 +1587,55 @@ class ProgressService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Act Boss 생성 (Act 완료 시)
|
||||||
|
///
|
||||||
|
/// 보스 레벨 = min(플레이어 레벨, Act 최소 레벨)로 설정하여
|
||||||
|
/// 플레이어가 이길 수 있는 수준 보장
|
||||||
|
CombatState _createActBoss(GameState state) {
|
||||||
|
final plotStage = state.progress.plotStageCount;
|
||||||
|
final actNumber = plotStage + 1;
|
||||||
|
|
||||||
|
// 보스 레벨 = min(플레이어 레벨, Act 최소 레벨)
|
||||||
|
// → 플레이어가 현재 레벨보다 높은 보스를 만나지 않도록 보장
|
||||||
|
final actMinLevel = ActMonsterLevel.forPlotStage(actNumber);
|
||||||
|
final bossLevel = math.min(state.traits.level, actMinLevel);
|
||||||
|
|
||||||
|
// Named monster 생성 (pq_logic.namedMonster 활용)
|
||||||
|
final bossName = pq_logic.namedMonster(config, state.rng, bossLevel);
|
||||||
|
|
||||||
|
final bossStats = MonsterBaseStats.forLevel(bossLevel);
|
||||||
|
|
||||||
|
// 플레이어 전투 스탯 생성
|
||||||
|
final playerCombatStats = CombatStats.fromStats(
|
||||||
|
stats: state.stats,
|
||||||
|
equipment: state.equipment,
|
||||||
|
level: state.traits.level,
|
||||||
|
monsterLevel: bossLevel,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Boss 몬스터 스탯 생성 (일반 몬스터 대비 강화)
|
||||||
|
final monsterCombatStats = MonsterCombatStats(
|
||||||
|
name: bossName,
|
||||||
|
level: bossLevel,
|
||||||
|
atk: (bossStats.atk * 1.5).round(), // Boss 보정 (1.5배)
|
||||||
|
def: (bossStats.def * 1.5).round(),
|
||||||
|
hpMax: (bossStats.hp * 2.0).round(), // HP는 2.0배 (보스다운 전투 시간)
|
||||||
|
hpCurrent: (bossStats.hp * 2.0).round(),
|
||||||
|
criRate: 0.05,
|
||||||
|
criDamage: 1.5,
|
||||||
|
evasion: 0.0,
|
||||||
|
accuracy: 0.8,
|
||||||
|
attackDelayMs: 1000,
|
||||||
|
expReward: (bossStats.exp * 2.5).round(), // 경험치 보상 증가
|
||||||
|
);
|
||||||
|
|
||||||
|
// 전투 상태 초기화
|
||||||
|
return CombatState.start(
|
||||||
|
playerStats: playerCombatStats,
|
||||||
|
monsterStats: monsterCombatStats,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 플레이어 사망 처리 (Phase 4)
|
/// 플레이어 사망 처리 (Phase 4)
|
||||||
///
|
///
|
||||||
/// 모든 장비 상실 및 사망 정보 기록
|
/// 모든 장비 상실 및 사망 정보 기록
|
||||||
@@ -1578,9 +1681,11 @@ class ProgressService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 전투 상태 초기화 및 사망 횟수 증가
|
// 전투 상태 초기화 및 사망 횟수 증가
|
||||||
|
// pendingActCompletion 플래그는 유지 (Boss 리트라이를 위해)
|
||||||
final progress = state.progress.copyWith(
|
final progress = state.progress.copyWith(
|
||||||
currentCombat: null,
|
currentCombat: null,
|
||||||
deathCount: state.progress.deathCount + 1,
|
deathCount: state.progress.deathCount + 1,
|
||||||
|
// pendingActCompletion은 copyWith에서 명시하지 않으면 기존 값 유지
|
||||||
);
|
);
|
||||||
|
|
||||||
return state.copyWith(
|
return state.copyWith(
|
||||||
|
|||||||
@@ -167,10 +167,7 @@ class ResurrectionService {
|
|||||||
caption: firstTask.caption,
|
caption: firstTask.caption,
|
||||||
type: firstTask.taskType,
|
type: firstTask.taskType,
|
||||||
),
|
),
|
||||||
task: ProgressBarState(
|
task: ProgressBarState(position: 0, max: firstTask.durationMillis),
|
||||||
position: 0,
|
|
||||||
max: firstTask.durationMillis,
|
|
||||||
),
|
|
||||||
currentCombat: null, // 전투 상태 명시적 초기화
|
currentCombat: null, // 전투 상태 명시적 초기화
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -446,10 +446,12 @@ class SkillService {
|
|||||||
|
|
||||||
// ATK 증가량 기준 정렬
|
// ATK 증가량 기준 정렬
|
||||||
buffSkills.sort((a, b) {
|
buffSkills.sort((a, b) {
|
||||||
final aValue = (a.buff?.atkModifier ?? 0) +
|
final aValue =
|
||||||
|
(a.buff?.atkModifier ?? 0) +
|
||||||
(a.buff?.defModifier ?? 0) * 0.5 +
|
(a.buff?.defModifier ?? 0) * 0.5 +
|
||||||
(a.buff?.criRateModifier ?? 0) * 0.3;
|
(a.buff?.criRateModifier ?? 0) * 0.3;
|
||||||
final bValue = (b.buff?.atkModifier ?? 0) +
|
final bValue =
|
||||||
|
(b.buff?.atkModifier ?? 0) +
|
||||||
(b.buff?.defModifier ?? 0) * 0.5 +
|
(b.buff?.defModifier ?? 0) * 0.5 +
|
||||||
(b.buff?.criRateModifier ?? 0) * 0.3;
|
(b.buff?.criRateModifier ?? 0) * 0.3;
|
||||||
return bValue.compareTo(aValue);
|
return bValue.compareTo(aValue);
|
||||||
@@ -470,9 +472,11 @@ class SkillService {
|
|||||||
|
|
||||||
// 디버프 효과 크기 기준 정렬 (음수 값이므로 절대값으로 비교)
|
// 디버프 효과 크기 기준 정렬 (음수 값이므로 절대값으로 비교)
|
||||||
debuffSkills.sort((a, b) {
|
debuffSkills.sort((a, b) {
|
||||||
final aValue = (a.buff?.atkModifier ?? 0).abs() +
|
final aValue =
|
||||||
|
(a.buff?.atkModifier ?? 0).abs() +
|
||||||
(a.buff?.defModifier ?? 0).abs() * 0.5;
|
(a.buff?.defModifier ?? 0).abs() * 0.5;
|
||||||
final bValue = (b.buff?.atkModifier ?? 0).abs() +
|
final bValue =
|
||||||
|
(b.buff?.atkModifier ?? 0).abs() +
|
||||||
(b.buff?.defModifier ?? 0).abs() * 0.5;
|
(b.buff?.defModifier ?? 0).abs() * 0.5;
|
||||||
return bValue.compareTo(aValue);
|
return bValue.compareTo(aValue);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user