From 920dcd90fd743f17656361f7a134c497258995c9 Mon Sep 17 00:00:00 2001 From: Logan Davidson Date: Sun, 8 Oct 2023 17:23:48 +0100 Subject: [PATCH] Make dead senators have more info Make dead senators show more info, specifically by making the senator's portrait, list item and details show former faction and titles. After death, senators are now considered to remain "in" the faction that they were in when they died - which is a bit weird but easier than making a new model to represent the former allegiance of dead senators. --- backend/rorapp/admin/senator.py | 2 +- backend/rorapp/functions/face_mortality.py | 29 ++++++----- .../functions/rank_senators_and_factions.py | 8 ++-- ...senator_death_step_remove_senator_alive.py | 48 +++++++++++++++++++ .../migrations/0037_dead_senators_faction.py | 39 +++++++++++++++ backend/rorapp/models/senator.py | 3 +- backend/rorapp/serializers/senator.py | 2 +- backend/rorapp/views/title.py | 11 +++-- frontend/classes/Senator.ts | 4 +- frontend/components/FactionListItem.tsx | 6 ++- frontend/components/GamePage.tsx | 2 +- frontend/components/SenatorList.tsx | 5 +- frontend/components/SenatorListItem.tsx | 4 +- .../components/SenatorPortrait.module.css | 4 -- frontend/components/SenatorPortrait.tsx | 10 ++-- frontend/components/TitleIcon.tsx | 3 +- .../detailSections/DetailSection_Senator.tsx | 32 +++++++++---- 17 files changed, 161 insertions(+), 51 deletions(-) create mode 100644 backend/rorapp/migrations/0036_add_senator_death_step_remove_senator_alive.py create mode 100644 backend/rorapp/migrations/0037_dead_senators_faction.py diff --git a/backend/rorapp/admin/senator.py b/backend/rorapp/admin/senator.py index 6d83be78..9e2b4be1 100644 --- a/backend/rorapp/admin/senator.py +++ b/backend/rorapp/admin/senator.py @@ -5,4 +5,4 @@ # Admin configuration for senators @admin.register(Senator) class SenatorAdmin(admin.ModelAdmin): - list_display = ('id', 'name', 'game', 'faction', 'alive', 'code', 'generation', 'rank') + list_display = ('id', 'name', 'game', 'faction', 'death_step', 'code', 'generation', 'rank') diff --git a/backend/rorapp/functions/face_mortality.py b/backend/rorapp/functions/face_mortality.py index 96c8884b..c5acba79 100644 --- a/backend/rorapp/functions/face_mortality.py +++ b/backend/rorapp/functions/face_mortality.py @@ -44,17 +44,18 @@ def face_mortality(game, faction, potential_action, step): # Perform mortality drawn_codes = draw_mortality_chits(1) + killed_senator_count = 0 for code in drawn_codes: - senators = Senator.objects.filter(game=game, alive=True, code=code) + senators = Senator.objects.filter(game=game, death_step__isnull=True, code=code) if senators.exists(): senator = senators.first() senators_former_faction = senator.faction # Kill the senator - senator.alive = False + senator.death_step = step senator.faction = None senator.save() - + killed_senator_count += 1 messages_to_send.append(ws_message_update("senator", SenatorSerializer(senator).data)) @@ -70,8 +71,6 @@ def face_mortality(game, faction, potential_action, step): if title.major_office == True: ended_major_office = title.name - messages_to_send.append(ws_message_destroy("title", title.id)) - # If the title is faction leader, create an heir senator as faction leader if title.name == "Faction Leader": @@ -118,16 +117,16 @@ def face_mortality(game, faction, potential_action, step): heir_senator_action_log.save() messages_to_send.append(ws_message_create("senator_action_log", SenatorActionLogSerializer(heir_senator_action_log).data)) - # If nobody dies, issue a notification to say so - else: - new_action_log_index = ActionLog.objects.filter(step__phase__turn__game=game).order_by('-index')[0].index + 1 - action_log = ActionLog( - index=new_action_log_index, - step=step, - type="face_mortality" - ) - action_log.save() - messages_to_send.append(ws_message_create("action_log", ActionLogSerializer(action_log).data)) + # If nobody dies, issue a notification to say so + if killed_senator_count == 0: + new_action_log_index = ActionLog.objects.filter(step__phase__turn__game=game).order_by('-index')[0].index + 1 + action_log = ActionLog( + index=new_action_log_index, + step=step, + type="face_mortality" + ) + action_log.save() + messages_to_send.append(ws_message_create("action_log", ActionLogSerializer(action_log).data)) # Update senator ranks messages_to_send.extend(rank_senators_and_factions(game.id)) diff --git a/backend/rorapp/functions/rank_senators_and_factions.py b/backend/rorapp/functions/rank_senators_and_factions.py index 09840124..486809d0 100644 --- a/backend/rorapp/functions/rank_senators_and_factions.py +++ b/backend/rorapp/functions/rank_senators_and_factions.py @@ -13,7 +13,7 @@ def rank_senators_and_factions(game_id): ''' # Get aligned alive senators - senators = Senator.objects.filter(game=game_id, alive=True, faction__isnull=False) + senators = Senator.objects.filter(game=game_id, death_step__isnull=True, faction__isnull=False) # Sort by descending influence, descending oratory and ascending code (ID) senators = senators.order_by('-influence', '-oratory', 'code') @@ -63,8 +63,8 @@ def rank_senators_and_factions(game_id): rank_to_assign += 1 # Get unaligned and dead senators - unaligned_senators = Senator.objects.filter(game=game_id, alive=True, faction__isnull=True) - dead_senators = Senator.objects.filter(game=game_id, alive=False) + unaligned_senators = Senator.objects.filter(game=game_id, death_step__isnull=True, faction__isnull=True) + dead_senators = Senator.objects.filter(game=game_id, death_step__isnull=False) unaligned_and_dead_senators = unaligned_senators.union(dead_senators) # Set rank to None for unaligned and dead senators @@ -78,7 +78,7 @@ def rank_senators_and_factions(game_id): messages_to_send.append(ws_message_update("senator", SenatorSerializer(senator).data)) # Get the HRAO - hrao = Senator.objects.filter(game=game_id, alive=True, rank=0).first() + hrao = Senator.objects.filter(game=game_id, death_step__isnull=True, rank=0).first() # Get factions in order of position factions = Faction.objects.filter(game=game_id).order_by('position') diff --git a/backend/rorapp/migrations/0036_add_senator_death_step_remove_senator_alive.py b/backend/rorapp/migrations/0036_add_senator_death_step_remove_senator_alive.py new file mode 100644 index 00000000..f12865f8 --- /dev/null +++ b/backend/rorapp/migrations/0036_add_senator_death_step_remove_senator_alive.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.2 on 2023-10-08 13:55 + +from django.db import migrations, models +import django.db.models.deletion + + +def convert_alive_to_senator_death(apps, schema_editor): + Senator = apps.get_model('rorapp', 'Senator') + dead_senators = Senator.objects.filter(alive=False) + + ActionLog = apps.get_model('rorapp', 'ActionLog') + death_action_logs = ActionLog.objects.filter(type='face_mortality') + + for dead_action_log in death_action_logs: + if (dead_action_log.data is None): + continue + dead_senator_id = dead_action_log.data.get('senator') + if (dead_senator_id is None): + continue + dead_senator = dead_senators.get(id=dead_senator_id) + dead_senator.death_step = dead_action_log.step + dead_senator.save() + + +def convert_senator_death_to_alive(apps, schema_editor): + Senator = apps.get_model('rorapp', 'Senator') + dead_senators = Senator.objects.filter(death_step__isnull=False) + dead_senators.update(alive=False) + + +class Migration(migrations.Migration): + + dependencies = [ + ('rorapp', '0035_alter_faction_game_alter_player_game_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='senator', + name='death_step', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='rorapp.step'), + ), + migrations.RunPython(convert_alive_to_senator_death, convert_senator_death_to_alive), + migrations.RemoveField( + model_name='senator', + name='alive', + ), + ] diff --git a/backend/rorapp/migrations/0037_dead_senators_faction.py b/backend/rorapp/migrations/0037_dead_senators_faction.py new file mode 100644 index 00000000..0d1c52e3 --- /dev/null +++ b/backend/rorapp/migrations/0037_dead_senators_faction.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.2 on 2023-10-08 13:55 + +from django.db import migrations + + +def set_dead_senators_faction(apps, schema_editor): + Senator = apps.get_model('rorapp', 'Senator') + dead_senators = Senator.objects.filter(death_step__isnull=False) + + ActionLog = apps.get_model('rorapp', 'ActionLog') + death_action_logs = ActionLog.objects.filter(type='face_mortality') + + for dead_action_log in death_action_logs: + if (dead_action_log.data is None): + continue + dead_senator_id = dead_action_log.data.get('senator') + if (dead_senator_id is None): + continue + dead_senator = dead_senators.get(id=dead_senator_id) + dead_senator.faction = dead_action_log.faction + dead_senator.save() + + +def unset_dead_senators_faction(apps, schema_editor): + Senator = apps.get_model('rorapp', 'Senator') + dead_senators = Senator.objects.filter(death_step__isnull=False) + dead_senators.update(faction=None) + + + +class Migration(migrations.Migration): + + dependencies = [ + ('rorapp', '0036_add_senator_death_step_remove_senator_alive'), + ] + + operations = [ + migrations.RunPython(set_dead_senators_faction, unset_dead_senators_faction), + ] \ No newline at end of file diff --git a/backend/rorapp/models/senator.py b/backend/rorapp/models/senator.py index 05696542..2da6f461 100644 --- a/backend/rorapp/models/senator.py +++ b/backend/rorapp/models/senator.py @@ -1,6 +1,7 @@ from django.db import models from rorapp.models.game import Game from rorapp.models.faction import Faction +from rorapp.models.step import Step # Model for representing senators @@ -8,7 +9,7 @@ class Senator(models.Model): name = models.CharField(max_length=10) game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name='senators') faction = models.ForeignKey(Faction, blank=True, null=True, on_delete=models.SET_NULL) - alive = models.BooleanField(default=True) + death_step = models.ForeignKey(Step, on_delete=models.CASCADE, blank=True, null=True) # Null means the senator is alive code = models.IntegerField() generation = models.IntegerField(default=1) rank = models.IntegerField(blank=True, null=True) diff --git a/backend/rorapp/serializers/senator.py b/backend/rorapp/serializers/senator.py index eec6f5ca..5e1e97b6 100644 --- a/backend/rorapp/serializers/senator.py +++ b/backend/rorapp/serializers/senator.py @@ -12,7 +12,7 @@ class Meta: 'name', 'game', 'faction', - 'alive', + 'death_step', 'code', 'generation', 'rank', diff --git a/backend/rorapp/views/title.py b/backend/rorapp/views/title.py index 301ee73b..808a3764 100644 --- a/backend/rorapp/views/title.py +++ b/backend/rorapp/views/title.py @@ -1,3 +1,4 @@ +from django.db.models import Q, F from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated from rorapp.models import Title @@ -6,7 +7,7 @@ class TitleViewSet(viewsets.ReadOnlyModelViewSet): """ - Read titles. + Read titles. Optionally accepts a `relevant` URL parameter for filtering. """ permission_classes = [IsAuthenticated] @@ -20,10 +21,10 @@ def get_queryset(self): if game_id is not None: queryset = queryset.filter(senator__game__id=game_id) - # Filter against an `active` query parameter in the URL - # Active means that the title's end step is null - active = self.request.query_params.get('active', None) + # Filter against an `relevant` query parameter in the URL + # Active means that the title's end step is null or the senator is dead + active = self.request.query_params.get('relevant', None) if active is not None: - queryset = queryset.filter(end_step__isnull=True) + queryset = queryset.filter(Q(end_step__isnull=True) | Q(end_step=F('senator__death_step'))) return queryset diff --git a/frontend/classes/Senator.ts b/frontend/classes/Senator.ts index 012435a4..5d6f6a1f 100644 --- a/frontend/classes/Senator.ts +++ b/frontend/classes/Senator.ts @@ -5,7 +5,7 @@ interface SenatorData { name: string game: number faction: number - alive: boolean + death_step: number | null code: number generation: number rank: number | null @@ -43,7 +43,7 @@ class Senator { this.id = data.id this.name = data.name this.game = data.game - this.alive = data.alive + this.alive = data.death_step === null // Senator is alive if death_step is null (a simplification compared to the backend) this.code = data.code this.generation = data.generation this.rank = data.rank diff --git a/frontend/components/FactionListItem.tsx b/frontend/components/FactionListItem.tsx index c76c1f17..faae2105 100644 --- a/frontend/components/FactionListItem.tsx +++ b/frontend/components/FactionListItem.tsx @@ -32,8 +32,10 @@ const FactionListItem = (props: FactionListItemProps) => { const player = allPlayers.byId[props.faction.player] ?? null const senators = new Collection( allSenators.asArray - .filter((s) => s.faction === props.faction.id) - .sort((a, b) => a.name.localeCompare(b.name)) ?? [] + .filter(s => s.alive) // Filter by alive + .filter(s => s.faction === props.faction.id) // Filter by faction + .sort((a, b) => a.generation - b.generation) // Sort by generation + .sort((a, b) => a.name.localeCompare(b.name)) ?? [] // Sort by name ) const senatorsCount = senators.allIds.length const totalInfluence = senators.asArray.reduce( diff --git a/frontend/components/GamePage.tsx b/frontend/components/GamePage.tsx index 5eec4b4b..070fd983 100644 --- a/frontend/components/GamePage.tsx +++ b/frontend/components/GamePage.tsx @@ -275,7 +275,7 @@ const GamePage = (props: GamePageProps) => { const fetchTitles = useCallback(async () => { const response = await request( "GET", - `titles/?game=${props.gameId}&active`, + `titles/?game=${props.gameId}&relevant`, accessToken, refreshToken, setAccessToken, diff --git a/frontend/components/SenatorList.tsx b/frontend/components/SenatorList.tsx index 5d3887f4..0e418fa3 100644 --- a/frontend/components/SenatorList.tsx +++ b/frontend/components/SenatorList.tsx @@ -126,7 +126,10 @@ const SenatorList = (props: SenatorListProps) => { (s) => (filterAlive && s.alive) || (filterDead && !s.alive) ) - // Sort by name in alphabetical order as base/default order + // Sort by generation in ascending order as base/default order + senators = senators.sort((a, b) => a.generation - b.generation) + + // Sort by name in alphabetical order senators = senators.sort((a, b) => a.name.localeCompare(b.name)) // Sort by the selected attribute diff --git a/frontend/components/SenatorListItem.tsx b/frontend/components/SenatorListItem.tsx index 38df2535..8c623884 100644 --- a/frontend/components/SenatorListItem.tsx +++ b/frontend/components/SenatorListItem.tsx @@ -103,8 +103,8 @@ const SenatorListItem = ({ senator, ...props }: SenatorListItemProps) => { {faction ? props.selectableFactions && ( - - {factionLeader ? " Leader" : null} + {" "} + {factionLeader ? "Leader" : "Member"} {player ? ({player.user?.username}) : null} ) diff --git a/frontend/components/SenatorPortrait.module.css b/frontend/components/SenatorPortrait.module.css index f99043f1..f583fd06 100644 --- a/frontend/components/SenatorPortrait.module.css +++ b/frontend/components/SenatorPortrait.module.css @@ -44,10 +44,6 @@ user-select: none; } -.dead { - filter: grayscale(80%); -} - .imageContainer { position: absolute; z-index: 1; diff --git a/frontend/components/SenatorPortrait.tsx b/frontend/components/SenatorPortrait.tsx index 28614ace..e41b2f55 100644 --- a/frontend/components/SenatorPortrait.tsx +++ b/frontend/components/SenatorPortrait.tsx @@ -124,7 +124,7 @@ const SenatorPortrait = ({ senator, size, ...props }: SenatorPortraitProps) => { const getBgStyle = () => { // Get base background color let bgColor = "" - if (faction) { + if (faction && senator.alive) { if (hover) { bgColor = faction.getColor("bgHover") // Brighter on hover } else { @@ -228,7 +228,7 @@ const SenatorPortrait = ({ senator, size, ...props }: SenatorPortraitProps) => { )} { )} {majorOffice && ( - + )} {senator.alive === false && ( { if (props.title.name.includes("Rome Consul")) { return ( { ) } + // Get JSX for the faction description + const getFactionDescription = () => { + console.log(faction) + if (!faction) return null + return ( + + {" "} + {factionLeader ? "Leader" : "Member"} + {player ? ({player.user?.username}) : null} + + ) + } + // If there is no senator selected, render nothing if (!senator) return null @@ -368,21 +381,24 @@ const SenatorDetails = (props: SenatorDetailsProps) => { {senator.displayName}

- {faction ? ( - - - {factionLeader ? " Leader" : null} - {player ? ({player.user?.username}) : null} - + {faction && senator.alive ? ( + {getFactionDescription()} ) : senator.alive ? ( "Unaligned" ) : ( - "Dead" + + {faction ? ( + Died as {getFactionDescription()} + ) : ( + "Dead" + )} + )}

{majorOffice && (

- Serving as{" "} + {senator.alive ? "Serving" : "Died"} + {" as "}