Fix events

This commit is contained in:
2026-01-05 23:41:22 +07:00
parent 65b2512d8c
commit 18fe95effc
5 changed files with 99 additions and 29 deletions

View File

@@ -1335,17 +1335,31 @@ async def complete_bonus_assignment(
bonus_assignment.proof_url = proof_url bonus_assignment.proof_url = proof_url
# Complete the bonus assignment # Complete the bonus assignment
# NOTE: We store BASE points here. Event multiplier is applied when main assignment is completed
# This ensures multiplier is applied to the SUM (base + all bonuses), not separately
bonus_assignment.points_earned = bonus_assignment.challenge.points
bonus_assignment.status = BonusAssignmentStatus.COMPLETED.value bonus_assignment.status = BonusAssignmentStatus.COMPLETED.value
bonus_assignment.proof_comment = comment bonus_assignment.proof_comment = comment
bonus_assignment.points_earned = bonus_assignment.challenge.points
bonus_assignment.completed_at = datetime.utcnow() bonus_assignment.completed_at = datetime.utcnow()
# If main assignment is already COMPLETED, add bonus points immediately # If main assignment is already COMPLETED, add bonus points immediately
# This handles the case where a bonus was disputed and user is re-completing it # This handles the case where a bonus was disputed and user is re-completing it
if assignment.status == AssignmentStatus.COMPLETED.value: if assignment.status == AssignmentStatus.COMPLETED.value:
from app.models import EventType
from app.services.points import PointsService
# Apply event multiplier if assignment had one
points_to_add = bonus_assignment.points_earned
if assignment.event_type:
ps = PointsService()
multiplier = ps.EVENT_MULTIPLIERS.get(assignment.event_type, 1.0)
points_to_add = int(bonus_assignment.points_earned * multiplier)
# Update bonus assignment to show multiplied points
bonus_assignment.points_earned = points_to_add
participant = assignment.participant participant = assignment.participant
participant.total_points += bonus_assignment.points_earned participant.total_points += points_to_add
assignment.points_earned += bonus_assignment.points_earned assignment.points_earned += points_to_add
# NOTE: If main is not completed yet, points will be added when main is completed # NOTE: If main is not completed yet, points will be added when main is completed
# This prevents exploiting by dropping the main assignment after getting bonus points # This prevents exploiting by dropping the main assignment after getting bonus points

View File

@@ -161,10 +161,13 @@ async def spin_wheel(marathon_id: int, current_user: CurrentUser, db: DbSession)
game = random.choice(available_games) game = random.choice(available_games)
if game.game_type == GameType.PLAYTHROUGH.value: if game.game_type == GameType.PLAYTHROUGH.value:
# Playthrough game - no challenge selection, ignore events # Playthrough game - no challenge selection
# Events that apply to playthrough: GOLDEN_HOUR, DOUBLE_RISK, COMMON_ENEMY
# Events that DON'T apply: JACKPOT (hard challenges only)
is_playthrough = True is_playthrough = True
challenge = None challenge = None
active_event = None # Ignore events for playthrough if active_event and active_event.type == EventType.JACKPOT.value:
active_event = None # Jackpot doesn't apply to playthrough
else: else:
# Challenges game - select random challenge # Challenges game - select random challenge
if not game.challenges: if not game.challenges:
@@ -201,7 +204,7 @@ async def spin_wheel(marathon_id: int, current_user: CurrentUser, db: DbSession)
game_id=game.id, game_id=game.id,
is_playthrough=True, is_playthrough=True,
status=AssignmentStatus.ACTIVE.value, status=AssignmentStatus.ACTIVE.value,
# No event_type for playthrough event_type=active_event.type if active_event else None,
) )
db.add(assignment) db.add(assignment)
await db.flush() # Get assignment.id for bonus assignments await db.flush() # Get assignment.id for bonus assignments
@@ -224,6 +227,8 @@ async def spin_wheel(marathon_id: int, current_user: CurrentUser, db: DbSession)
"points": game.playthrough_points, "points": game.playthrough_points,
"bonus_challenges_count": len(bonus_challenges), "bonus_challenges_count": len(bonus_challenges),
} }
if active_event:
activity_data["event_type"] = active_event.type
else: else:
# Regular challenge assignment # Regular challenge assignment
assignment = Assignment( assignment = Assignment(
@@ -323,6 +328,7 @@ async def spin_wheel(marathon_id: int, current_user: CurrentUser, db: DbSession)
], ],
can_drop=True, can_drop=True,
drop_penalty=drop_penalty, drop_penalty=drop_penalty,
event_type=active_event.type if active_event else None,
) )
else: else:
# Return challenge result # Return challenge result
@@ -346,6 +352,7 @@ async def spin_wheel(marathon_id: int, current_user: CurrentUser, db: DbSession)
is_playthrough=False, is_playthrough=False,
can_drop=True, can_drop=True,
drop_penalty=drop_penalty, drop_penalty=drop_penalty,
event_type=active_event.type if active_event else None,
) )
@@ -371,9 +378,17 @@ async def get_current_assignment(marathon_id: int, current_user: CurrentUser, db
# Handle playthrough assignments # Handle playthrough assignments
if assignment.is_playthrough: if assignment.is_playthrough:
game = assignment.game game = assignment.game
active_event = None # No events for playthrough # Use stored event_type for playthrough
# All events except JACKPOT apply (DOUBLE_RISK = free drop, others affect points)
playthrough_event = None
if assignment.event_type and assignment.event_type != EventType.JACKPOT.value:
class MockEvent:
def __init__(self, event_type):
self.type = event_type
playthrough_event = MockEvent(assignment.event_type)
drop_penalty = points_service.calculate_drop_penalty( drop_penalty = points_service.calculate_drop_penalty(
participant.drop_count, game.playthrough_points, None participant.drop_count, game.playthrough_points, playthrough_event
) )
# Build bonus challenges response # Build bonus challenges response
@@ -423,6 +438,7 @@ async def get_current_assignment(marathon_id: int, current_user: CurrentUser, db
completed_at=assignment.completed_at, completed_at=assignment.completed_at,
drop_penalty=drop_penalty, drop_penalty=drop_penalty,
bonus_challenges=bonus_responses, bonus_challenges=bonus_responses,
event_type=assignment.event_type,
) )
# Regular challenge assignment # Regular challenge assignment
@@ -457,6 +473,7 @@ async def get_current_assignment(marathon_id: int, current_user: CurrentUser, db
started_at=assignment.started_at, started_at=assignment.started_at,
completed_at=assignment.completed_at, completed_at=assignment.completed_at,
drop_penalty=drop_penalty, drop_penalty=drop_penalty,
event_type=assignment.event_type,
) )
@@ -570,19 +587,37 @@ async def complete_assignment(
if assignment.is_playthrough: if assignment.is_playthrough:
game = assignment.game game = assignment.game
marathon_id = game.marathon_id marathon_id = game.marathon_id
base_points = game.playthrough_points base_playthrough_points = game.playthrough_points
# No events for playthrough # Calculate BASE bonus points from completed bonus assignments (before multiplier)
total_points, streak_bonus, _ = points_service.calculate_completion_points( base_bonus_points = sum(
base_points, participant.current_streak, None ba.challenge.points for ba in assignment.bonus_assignments
)
# Calculate bonus points from completed bonus assignments
bonus_points = sum(
ba.points_earned for ba in assignment.bonus_assignments
if ba.status == BonusAssignmentStatus.COMPLETED.value if ba.status == BonusAssignmentStatus.COMPLETED.value
) )
total_points += bonus_points
# Total base = playthrough + all bonuses
total_base_points = base_playthrough_points + base_bonus_points
# Get event for playthrough (use stored event_type from assignment)
# All events except JACKPOT apply to playthrough
playthrough_event = None
if assignment.event_type and assignment.event_type != EventType.JACKPOT.value:
class MockEvent:
def __init__(self, event_type):
self.type = event_type
playthrough_event = MockEvent(assignment.event_type)
# Apply multiplier to the TOTAL (base + bonuses), then add streak bonus
total_points, streak_bonus, event_bonus = points_service.calculate_completion_points(
total_base_points, participant.current_streak, playthrough_event
)
# Update bonus assignments to reflect multiplied points for display
if playthrough_event:
multiplier = points_service.EVENT_MULTIPLIERS.get(playthrough_event.type, 1.0)
for ba in assignment.bonus_assignments:
if ba.status == BonusAssignmentStatus.COMPLETED.value:
ba.points_earned = int(ba.challenge.points * multiplier)
# Update assignment # Update assignment
assignment.status = AssignmentStatus.COMPLETED.value assignment.status = AssignmentStatus.COMPLETED.value
@@ -607,12 +642,15 @@ async def complete_assignment(
"game": game.title, "game": game.title,
"is_playthrough": True, "is_playthrough": True,
"points": total_points, "points": total_points,
"base_points": base_points, "base_points": base_playthrough_points,
"bonus_points": bonus_points, "bonus_points": base_bonus_points,
"streak": participant.current_streak, "streak": participant.current_streak,
} }
if is_redo: if is_redo:
activity_data["is_redo"] = True activity_data["is_redo"] = True
if playthrough_event:
activity_data["event_type"] = playthrough_event.type
activity_data["event_bonus"] = event_bonus
activity = Activity( activity = Activity(
marathon_id=marathon_id, marathon_id=marathon_id,
@@ -796,9 +834,17 @@ async def drop_assignment(assignment_id: int, current_user: CurrentUser, db: DbS
game = assignment.game game = assignment.game
marathon_id = game.marathon_id marathon_id = game.marathon_id
# No events for playthrough # Use stored event_type for drop penalty calculation
# DOUBLE_RISK = free drop (0 penalty)
playthrough_event = None
if assignment.event_type and assignment.event_type != EventType.JACKPOT.value:
class MockEvent:
def __init__(self, event_type):
self.type = event_type
playthrough_event = MockEvent(assignment.event_type)
penalty = points_service.calculate_drop_penalty( penalty = points_service.calculate_drop_penalty(
participant.drop_count, game.playthrough_points, None participant.drop_count, game.playthrough_points, playthrough_event
) )
# Update assignment # Update assignment
@@ -823,16 +869,21 @@ async def drop_assignment(assignment_id: int, current_user: CurrentUser, db: DbS
participant.drop_count += 1 participant.drop_count += 1
# Log activity # Log activity
activity_data = {
"game": game.title,
"is_playthrough": True,
"penalty": penalty,
"lost_bonuses": completed_bonuses_count,
}
if playthrough_event:
activity_data["event_type"] = playthrough_event.type
activity_data["free_drop"] = True
activity = Activity( activity = Activity(
marathon_id=marathon_id, marathon_id=marathon_id,
user_id=current_user.id, user_id=current_user.id,
type=ActivityType.DROP.value, type=ActivityType.DROP.value,
data={ data=activity_data,
"game": game.title,
"is_playthrough": True,
"penalty": penalty,
"lost_bonuses": completed_bonuses_count,
},
) )
db.add(activity) db.add(activity)

View File

@@ -56,6 +56,7 @@ class AssignmentResponse(BaseModel):
completed_at: datetime | None = None completed_at: datetime | None = None
drop_penalty: int = 0 # Calculated penalty if dropped drop_penalty: int = 0 # Calculated penalty if dropped
bonus_challenges: list[BonusAssignmentResponse] = [] # Для playthrough bonus_challenges: list[BonusAssignmentResponse] = [] # Для playthrough
event_type: str | None = None # Event type if assignment was created during event
class Config: class Config:
from_attributes = True from_attributes = True
@@ -70,6 +71,7 @@ class SpinResult(BaseModel):
bonus_challenges: list[ChallengeResponse] = [] # Для playthrough - список доступных бонусных челленджей bonus_challenges: list[ChallengeResponse] = [] # Для playthrough - список доступных бонусных челленджей
can_drop: bool can_drop: bool
drop_penalty: int drop_penalty: int
event_type: str | None = None # Event type if active during spin
class CompleteResult(BaseModel): class CompleteResult(BaseModel):

View File

@@ -268,6 +268,7 @@ export interface Assignment {
completed_at: string | null completed_at: string | null
drop_penalty: number drop_penalty: number
bonus_challenges?: BonusAssignment[] // For playthrough bonus_challenges?: BonusAssignment[] // For playthrough
event_type?: EventType // Event active when assignment was created
} }
export interface SpinResult { export interface SpinResult {
@@ -279,6 +280,7 @@ export interface SpinResult {
bonus_challenges?: Challenge[] // Available bonus challenges for playthrough bonus_challenges?: Challenge[] // Available bonus challenges for playthrough
can_drop: boolean can_drop: boolean
drop_penalty: number drop_penalty: number
event_type?: EventType // Event active during spin
} }
export interface CompleteResult { export interface CompleteResult {

View File

@@ -152,11 +152,12 @@ export function formatActivityMessage(activity: Activity): { title: string; deta
const points = data.points ? `+${data.points}` : '' const points = data.points ? `+${data.points}` : ''
const streak = data.streak && (data.streak as number) > 1 ? `серия ${data.streak}` : '' const streak = data.streak && (data.streak as number) > 1 ? `серия ${data.streak}` : ''
const bonus = data.common_enemy_bonus ? `+${data.common_enemy_bonus} бонус` : '' const bonus = data.common_enemy_bonus ? `+${data.common_enemy_bonus} бонус` : ''
const eventBonus = data.event_bonus ? `(+${data.event_bonus} ${EVENT_NAMES[data.event_type as EventType] || 'бонус'})` : ''
return { return {
title: `завершил ${points}`, title: `завершил ${points}`,
details: challenge || undefined, details: challenge || undefined,
extra: [game, streak, bonus].filter(Boolean).join(' • ') || undefined, extra: [game, streak, bonus, eventBonus].filter(Boolean).join(' • ') || undefined,
} }
} }