diff --git a/src/apps/epic/migrations/0030_sigreservation_seat_fk.py b/src/apps/epic/migrations/0030_sigreservation_seat_fk.py new file mode 100644 index 0000000..2426ef2 --- /dev/null +++ b/src/apps/epic/migrations/0030_sigreservation_seat_fk.py @@ -0,0 +1,23 @@ +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('epic', '0029_fix_schizo_cautions'), + ] + + operations = [ + migrations.AddField( + model_name='sigreservation', + name='seat', + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='sig_reservation', + to='epic.tableseat', + ), + ), + ] diff --git a/src/apps/epic/models.py b/src/apps/epic/models.py index 41c0f34..ed5f999 100644 --- a/src/apps/epic/models.py +++ b/src/apps/epic/models.py @@ -359,6 +359,10 @@ class SigReservation(models.Model): gamer = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='sig_reservations' ) + seat = models.ForeignKey( + 'TableSeat', null=True, blank=True, + on_delete=models.SET_NULL, related_name='sig_reservation', + ) card = models.ForeignKey( 'TarotCard', on_delete=models.CASCADE, related_name='sig_reservations' ) diff --git a/src/apps/epic/views.py b/src/apps/epic/views.py index f9f1bc5..dfe4772 100644 --- a/src/apps/epic/views.py +++ b/src/apps/epic/views.py @@ -10,8 +10,11 @@ from django.shortcuts import redirect, render from django.utils import timezone from apps.drama.models import GameEvent, record +from django.db.models import Case, IntegerField, Value, When + from apps.epic.models import ( - GateSlot, Room, RoomInvite, SigReservation, TableSeat, TarotCard, TarotDeck, + GateSlot, Room, RoomInvite, SIG_SEAT_ORDER, SigReservation, TableSeat, + TarotCard, TarotDeck, active_sig_seat, debit_token, levity_sig_cards, gravity_sig_cards, select_token, sig_deck_cards, ) @@ -92,6 +95,22 @@ def _notify_sig_reserved(room_id, card_id, role, reserved): SLOT_ROLE_LABELS = {1: "PC", 2: "NC", 3: "EC", 4: "SC", 5: "AC", 6: "BC"} +_SIG_SEAT_ORDERING = Case( + *[When(role=r, then=Value(i)) for i, r in enumerate(SIG_SEAT_ORDER)], + default=Value(99), + output_field=IntegerField(), +) + + +def _canonical_user_seat(room, user): + """Return the user's seat whose role comes first in PC→NC→EC→SC→AC→BC order. + + In normal play (one user = one seat) this is equivalent to .first(). + For Carte Blanche (one user = all seats) it returns the PC seat, ensuring + sig-select cursor placement is seat-based, not position/slot-based. + """ + return room.table_seats.filter(gamer=user).order_by(_SIG_SEAT_ORDERING).first() + _ROLE_SCRAWL_NAMES = { "PC": "Player", "NC": "Narrator", "EC": "Economist", "SC": "Shepherd", "AC": "Alchemist", "BC": "Builder", @@ -242,7 +261,7 @@ def _role_select_context(room, user): "slots": room.gate_slots.order_by("slot_number"), } if room.table_status == Room.SIG_SELECT: - user_seat = room.table_seats.filter(gamer=user).first() if user.is_authenticated else None + user_seat = _canonical_user_seat(room, user) if user.is_authenticated else None user_role = user_seat.role if user_seat else None user_polarity = None if user_role in _LEVITY_ROLES: @@ -583,7 +602,7 @@ def sig_reserve(request, room_id): if room.table_status != Room.SIG_SELECT: return HttpResponse(status=400) - user_seat = room.table_seats.filter(gamer=request.user).first() + user_seat = _canonical_user_seat(room, request.user) if not user_seat or not user_seat.role: return HttpResponse(status=403) @@ -622,7 +641,7 @@ def sig_reserve(request, room_id): SigReservation.objects.create( room=room, gamer=request.user, card=card, - role=user_seat.role, polarity=polarity, + seat=user_seat, role=user_seat.role, polarity=polarity, ) _notify_sig_reserved(room_id, card.pk, user_seat.role, reserved=True) return HttpResponse(status=200) diff --git a/src/core/settings.py b/src/core/settings.py index 34f1865..200b039 100644 --- a/src/core/settings.py +++ b/src/core/settings.py @@ -57,13 +57,13 @@ INSTALLED_APPS = [ # Board apps 'apps.dashboard', 'apps.gameboard', + 'apps.billboard', # Gamer apps 'apps.lyric', 'apps.epic', 'apps.drama', - 'apps.billboard', - 'apps.ap', # Custom apps + 'apps.ap', 'apps.api', 'apps.applets', 'functional_tests', diff --git a/src/static_src/scss/_card-deck.scss b/src/static_src/scss/_card-deck.scss index 6a88d43..8f772f8 100644 --- a/src/static_src/scss/_card-deck.scss +++ b/src/static_src/scss/_card-deck.scss @@ -507,7 +507,7 @@ html:has(.sig-backdrop) { position: fixed; inset: 0; pointer-events: none; - z-index: 9999; + z-index: 200; // above sig-overlay (120), below tray (310) overflow: visible; }