108 lines
3.9 KiB
Python
108 lines
3.9 KiB
Python
from django.conf import settings
|
|
from django.db import models
|
|
|
|
|
|
class GameEvent(models.Model):
|
|
# Gate phase
|
|
ROOM_CREATED = "room_created"
|
|
SLOT_RESERVED = "slot_reserved"
|
|
SLOT_FILLED = "slot_filled"
|
|
SLOT_RETURNED = "slot_returned"
|
|
SLOT_RELEASED = "slot_released"
|
|
INVITE_SENT = "invite_sent"
|
|
# Role Select phase
|
|
ROLE_SELECT_STARTED = "role_select_started"
|
|
ROLE_SELECTED = "role_selected"
|
|
ROLES_REVEALED = "roles_revealed"
|
|
|
|
VERB_CHOICES = [
|
|
(ROOM_CREATED, "Room created"),
|
|
(SLOT_RESERVED, "Gate slot reserved"),
|
|
(SLOT_FILLED, "Gate slot filled"),
|
|
(SLOT_RETURNED, "Gate slot returned"),
|
|
(SLOT_RELEASED, "Gate slot released"),
|
|
(INVITE_SENT, "Invite sent"),
|
|
(ROLE_SELECT_STARTED, "Role select started"),
|
|
(ROLE_SELECTED, "Role selected"),
|
|
(ROLES_REVEALED, "Roles revealed"),
|
|
]
|
|
|
|
room = models.ForeignKey(
|
|
"epic.Room", on_delete=models.CASCADE, related_name="events",
|
|
)
|
|
actor = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, null=True, blank=True,
|
|
on_delete=models.SET_NULL, related_name="game_events",
|
|
)
|
|
verb = models.CharField(max_length=30, choices=VERB_CHOICES)
|
|
data = models.JSONField(default=dict)
|
|
timestamp = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ["timestamp"]
|
|
|
|
def to_prose(self):
|
|
"""Return a human-readable action description (actor rendered separately in template)."""
|
|
d = self.data
|
|
if self.verb == self.SLOT_FILLED:
|
|
_token_names = {
|
|
"coin": "Coin-on-a-String", "Free": "Free Token",
|
|
"tithe": "Tithe Token", "pass": "Backstage Pass", "carte": "Carte Blanche",
|
|
}
|
|
code = d.get("token_type", "token")
|
|
token = d.get("token_display") or _token_names.get(code, code)
|
|
days = d.get("renewal_days", 7)
|
|
slot = d.get("slot_number", "?")
|
|
return f"deposits a {token} for slot {slot} ({days} days)"
|
|
if self.verb == self.SLOT_RESERVED:
|
|
return "reserves a seat"
|
|
if self.verb == self.SLOT_RETURNED:
|
|
return "withdraws from the gate"
|
|
if self.verb == self.SLOT_RELEASED:
|
|
return f"releases slot {d.get('slot_number', '?')}"
|
|
if self.verb == self.ROOM_CREATED:
|
|
return "opens this room"
|
|
if self.verb == self.INVITE_SENT:
|
|
return "sends an invitation"
|
|
if self.verb == self.ROLE_SELECT_STARTED:
|
|
return "Role selection begins"
|
|
if self.verb == self.ROLE_SELECTED:
|
|
_role_names = {
|
|
"PC": "Player", "BC": "Builder", "SC": "Shepherd",
|
|
"AC": "Alchemist", "NC": "Narrator", "EC": "Economist",
|
|
}
|
|
code = d.get("role", "?")
|
|
role = d.get("role_display") or _role_names.get(code, code)
|
|
return f"elects to start as {role}"
|
|
if self.verb == self.ROLES_REVEALED:
|
|
return "All roles assigned"
|
|
return self.verb
|
|
|
|
def __str__(self):
|
|
actor = self.actor.email if self.actor else "system"
|
|
return f"[{self.timestamp:%Y-%m-%d %H:%M}] {actor} → {self.verb}"
|
|
|
|
|
|
class ScrollPosition(models.Model):
|
|
user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
|
|
related_name="scroll_positions",
|
|
)
|
|
room = models.ForeignKey(
|
|
"epic.Room", on_delete=models.CASCADE,
|
|
related_name="scroll_positions",
|
|
)
|
|
position = models.PositiveIntegerField(default=0)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
unique_together = [("user", "room")]
|
|
|
|
def __str__(self):
|
|
return f"{self.user.email} @ {self.room.name}: {self.position}px"
|
|
|
|
|
|
def record(room, verb, actor=None, **data):
|
|
"""Record a game event in the drama log."""
|
|
return GameEvent.objects.create(room=room, actor=actor, verb=verb, data=data)
|