From 53b1fc916cb8765699ed8ea6b6c2ab277a405b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20R=C3=A9aux?= Date: Tue, 3 Mar 2026 11:52:07 +0100 Subject: [PATCH 1/2] tie-break for narrative points --- src/warchron/constants.py | 3 + .../controller/campaign_controller.py | 12 ++ src/warchron/controller/closure_workflow.py | 33 ++++- src/warchron/controller/dtos.py | 8 -- src/warchron/controller/ranking_icon.py | 5 - src/warchron/controller/war_controller.py | 12 ++ src/warchron/model/tie_manager.py | 130 +++++++++++++++++- src/warchron/view/tie_dialog.py | 8 +- 8 files changed, 194 insertions(+), 17 deletions(-) diff --git a/src/warchron/constants.py b/src/warchron/constants.py index 8da62f9..37c4a01 100644 --- a/src/warchron/constants.py +++ b/src/warchron/constants.py @@ -15,6 +15,8 @@ RESOURCES_DIR = VIEW_ROOT / "resources" ROLE_TYPE = Qt.ItemDataRole.UserRole ROLE_ID = Qt.ItemDataRole.UserRole + 1 +# TODO use StrEnum and auto() instead of str,Enum and "name" + class IconName(str, Enum): UNDO = "undo" @@ -245,3 +247,4 @@ class ContextType(StrEnum): CAMPAIGN = "campaign" CHOICE = "choice" BATTLE = "battle" + OBJECTIVE = auto() diff --git a/src/warchron/controller/campaign_controller.py b/src/warchron/controller/campaign_controller.py index 88199eb..e0ff8b2 100644 --- a/src/warchron/controller/campaign_controller.py +++ b/src/warchron/controller/campaign_controller.py @@ -171,7 +171,19 @@ class CampaignController: counters=counters, context_type=ContextType.CAMPAIGN, context_id=ctx.context_id, + context_name=None, ) + if ctx.context_type == ContextType.OBJECTIVE: + campaign_id, objective_id = ctx.context_id.split(":") + objective = war.objectives[objective_id] + dialog = TieDialog( + parent=self.app.view, + players=players, + counters=counters, + context_type=ctx.context_type, + context_id=ctx.context_id, + context_name=objective.name, + ) if not dialog.exec(): TieResolver.cancel_tie_break( war, ContextType.CAMPAIGN, ctx.context_id, ctx.score_value diff --git a/src/warchron/controller/closure_workflow.py b/src/warchron/controller/closure_workflow.py index fe368ac..e54f921 100644 --- a/src/warchron/controller/closure_workflow.py +++ b/src/warchron/controller/closure_workflow.py @@ -2,7 +2,6 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from warchron.controller.app_controller import AppController - from warchron.model.war import War from warchron.model.campaign import Campaign from warchron.model.round import Round @@ -45,6 +44,23 @@ class CampaignClosureWorkflow(ClosureWorkflow): TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) TieResolver.resolve_tie_state(war, tie, bids) ties = TieResolver.find_campaign_ties(war, campaign.id) + for objective_id in war.objectives: + ties = TieResolver.find_campaign_objective_ties( + war, + campaign.id, + objective_id, + ) + while ties: + bids_map = self.app.campaigns.resolve_ties(war, ties) + for tie in ties: + bids = bids_map[(tie.context_type, tie.context_id, tie.score_value)] + TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) + TieResolver.resolve_tie_state(war, tie, bids) + ties = TieResolver.find_campaign_objective_ties( + war, + campaign.id, + objective_id, + ) ClosureService.finalize_campaign(campaign) @@ -60,4 +76,19 @@ class WarClosureWorkflow(ClosureWorkflow): TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) TieResolver.resolve_tie_state(war, tie, bids) ties = TieResolver.find_war_ties(war) + for objective_id in war.objectives: + ties = TieResolver.find_war_objective_ties( + war, + objective_id, + ) + while ties: + bids_map = self.app.wars.resolve_ties(war, ties) + for tie in ties: + bids = bids_map[(tie.context_type, tie.context_id, tie.score_value)] + TieResolver.apply_bids(war, tie.context_type, tie.context_id, bids) + TieResolver.resolve_tie_state(war, tie, bids) + ties = TieResolver.find_war_objective_ties( + war, + objective_id, + ) ClosureService.finalize_war(war) diff --git a/src/warchron/controller/dtos.py b/src/warchron/controller/dtos.py index dcbf05e..84bc0c4 100644 --- a/src/warchron/controller/dtos.py +++ b/src/warchron/controller/dtos.py @@ -107,14 +107,6 @@ class BattleDTO: player2_tooltip: str | None = None -@dataclass(frozen=True, slots=True) -class ParticipantScoreDTO: - participant_id: str - player_name: str - victory_points: int - narrative_points: Dict[str, int] - - @dataclass(frozen=True, slots=True) class CampaignParticipantScoreDTO: campaign_participant_id: str diff --git a/src/warchron/controller/ranking_icon.py b/src/warchron/controller/ranking_icon.py index bb7d914..06f1b3f 100644 --- a/src/warchron/controller/ranking_icon.py +++ b/src/warchron/controller/ranking_icon.py @@ -21,11 +21,6 @@ class RankingIcon: context_id: str, scores: Dict[str, ParticipantScore], ) -> Dict[str, QIcon]: - # scores = ScoreService.compute_scores( - # war, - # context_type, - # context_id, - # ) ranking = ResultChecker.get_effective_ranking( war, context_type, context_id, scores ) diff --git a/src/warchron/controller/war_controller.py b/src/warchron/controller/war_controller.py index 84977e9..dd516ae 100644 --- a/src/warchron/controller/war_controller.py +++ b/src/warchron/controller/war_controller.py @@ -155,7 +155,19 @@ class WarController: counters=counters, context_type=ContextType.WAR, context_id=ctx.context_id, + context_name=None, ) + if ctx.context_type == ContextType.OBJECTIVE: + _, objective_id = ctx.context_id.split(":") + objective = war.objectives[objective_id] + dialog = TieDialog( + parent=self.app.view, + players=players, + counters=counters, + context_type=ctx.context_type, + context_id=ctx.context_id, + context_name=objective.name, + ) if not dialog.exec(): TieResolver.cancel_tie_break( war, ContextType.WAR, ctx.context_id, ctx.score_value diff --git a/src/warchron/model/tie_manager.py b/src/warchron/model/tie_manager.py index e174e8e..028001e 100644 --- a/src/warchron/model/tie_manager.py +++ b/src/warchron/model/tie_manager.py @@ -6,7 +6,7 @@ from warchron.constants import ContextType from warchron.model.exception import ForbiddenOperation from warchron.model.war import War from warchron.model.war_event import InfluenceSpent, TieResolved -from warchron.model.score_service import ScoreService +from warchron.model.score_service import ScoreService, ParticipantScore @dataclass @@ -85,6 +85,61 @@ class TieResolver: ) return ties + @staticmethod + def find_campaign_objective_ties( + war: War, + campaign_id: str, + objective_id: str, + ) -> List[TieContext]: + base_scores = ScoreService.compute_scores( + war, + ContextType.CAMPAIGN, + campaign_id, + ) + scores = TieResolver._build_objective_scores( + base_scores, + objective_id, + ) + buckets: DefaultDict[int, List[str]] = defaultdict(list) + for pid, score in scores.items(): + buckets[score.victory_points].append(pid) + ties: List[TieContext] = [] + context_id = f"{campaign_id}:{objective_id}" + for score_value, participants in buckets.items(): + if len(participants) <= 1: + continue + if TieResolver.is_tie_resolved( + war, + ContextType.OBJECTIVE, + context_id, + score_value, + ): + continue + if not TieResolver.can_tie_be_resolved( + war, + ContextType.OBJECTIVE, + context_id, + participants, + ): + war.events.append( + TieResolved( + None, + ContextType.OBJECTIVE, + context_id, + score_value, + ) + ) + continue + ties.append( + TieContext( + context_type=ContextType.OBJECTIVE, + context_id=context_id, + participants=participants, + score_value=score_value, + ) + ) + return ties + @staticmethod def find_war_ties(war: War) -> List[TieContext]: from warchron.model.result_checker import ResultChecker @@ -115,6 +170,79 @@ class TieResolver: ) return ties + @staticmethod + def find_war_objective_ties( + war: War, + objective_id: str, + ) -> List[TieContext]: + from warchron.model.result_checker import ResultChecker + + base_scores = ScoreService.compute_scores( + war, + ContextType.WAR, + war.id, + ) + scores = TieResolver._build_objective_scores( + base_scores, + objective_id, + ) + ranking = ResultChecker.get_effective_ranking( + war, + ContextType.OBJECTIVE, + f"{war.id}:{objective_id}", + scores, + ) + ties: List[TieContext] = [] + for _, group, _ in ranking: + if len(group) <= 1: + continue + score_value = scores[group[0]].victory_points + context_id = f"{war.id}:{objective_id}" + if TieResolver.is_tie_resolved( + war, + ContextType.OBJECTIVE, + context_id, + score_value, + ): + continue + if not TieResolver.can_tie_be_resolved( + war, + ContextType.OBJECTIVE, + context_id, + group, + ): + war.events.append( + TieResolved( + None, + ContextType.OBJECTIVE, + context_id, + score_value, + ) + ) + continue + ties.append( + TieContext( + context_type=ContextType.OBJECTIVE, + context_id=context_id, + participants=group, + score_value=score_value, + ) + ) + return ties + + @staticmethod + def _build_objective_scores( + base_scores: Dict[str, ParticipantScore], + objective_id: str, + ) -> Dict[str, ParticipantScore]: + return { + pid: ParticipantScore( + victory_points=score.narrative_points.get(objective_id, 0), + narrative_points={}, + ) + for pid, score in base_scores.items() + } + @staticmethod def apply_bids( war: War, diff --git a/src/warchron/view/tie_dialog.py b/src/warchron/view/tie_dialog.py index 971a5ec..41a17ff 100644 --- a/src/warchron/view/tie_dialog.py +++ b/src/warchron/view/tie_dialog.py @@ -26,6 +26,7 @@ class TieDialog(QDialog): counters: List[int], context_type: ContextType, context_id: str, + context_name: str | None = None, ) -> None: super().__init__(parent) self._context_id = context_id @@ -33,7 +34,7 @@ class TieDialog(QDialog): self.ui: Ui_tieDialog = Ui_tieDialog() self.ui.setupUi(self) # type: ignore self.setWindowIcon(Icons.get(IconName.WARCHRONICO)) - self.ui.tieContext.setText(self._get_context_title(context_type)) + self.ui.tieContext.setText(self._get_context_title(context_type, context_name)) grid = self.ui.playersGridLayout icon_path = (RESOURCES_DIR / Icons._paths[IconName.TOKENS]).as_posix() token_html = ( @@ -72,11 +73,14 @@ class TieDialog(QDialog): return {pid: checkbox.isChecked() for pid, checkbox in self._checkboxes.items()} @staticmethod - def _get_context_title(context_type: ContextType) -> str: + def _get_context_title( + context_type: ContextType, context_name: str | None = None + ) -> str: titles = { ContextType.BATTLE: "Battle tie", ContextType.CAMPAIGN: "Campaign tie", ContextType.WAR: "War tie", ContextType.CHOICE: "Choice tie", + ContextType.OBJECTIVE: f"Objective tie: {context_name}", } return titles.get(context_type, "Tie") From f55106c260874a1243cf0548fd0b4c9b65b7daa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20R=C3=A9aux?= Date: Tue, 3 Mar 2026 15:39:30 +0100 Subject: [PATCH 2/2] display objective awards in participant tables --- src/warchron/constants.py | 65 ++++++++++++++++++- .../controller/campaign_controller.py | 22 ++++++- src/warchron/controller/dtos.py | 4 +- src/warchron/controller/ranking_icon.py | 34 ++++++++-- src/warchron/controller/war_controller.py | 25 ++++++- src/warchron/model/result_checker.py | 23 ++++--- src/warchron/model/tie_manager.py | 7 +- src/warchron/view/view.py | 4 ++ 8 files changed, 160 insertions(+), 24 deletions(-) diff --git a/src/warchron/constants.py b/src/warchron/constants.py index 37c4a01..241ff64 100644 --- a/src/warchron/constants.py +++ b/src/warchron/constants.py @@ -64,15 +64,30 @@ class IconName(str, Enum): VPNTHDRAW = auto() VPNTHBREAK = auto() VPNTHTIEDRAW = auto() + NP1STDRAW = auto() + NP1STBREAK = auto() + NP1STTIEDRAW = auto() + NP2NDDRAW = auto() + NP2NDBREAK = auto() + NP2NDTIEDRAW = auto() + NP3RDDRAW = auto() + NP3RDBREAK = auto() + NP3RDTIEDRAW = auto() -RANK_TO_ICON = { +VP_RANK_TO_ICON = { 1: IconName.VP1ST, 2: IconName.VP2ND, 3: IconName.VP3RD, 4: IconName.VPNTH, } +NP_RANK_TO_ICON = { + 1: IconName.NP1ST, + 2: IconName.NP2ND, + 3: IconName.NP3RD, +} + class Icons: _icon_cache: Dict[IconName, QIcon] = {} @@ -194,6 +209,54 @@ class Icons: cls.get_pixmap(IconName.DRAW), cls.get_pixmap(IconName.TOKEN), ) + elif name == IconName.NP1STDRAW: + pix = cls._compose( + cls.get_pixmap(IconName.NP1ST), + cls.get_pixmap(IconName.DRAW), + ) + elif name == IconName.NP1STBREAK: + pix = cls._compose( + cls.get_pixmap(IconName.NP1ST), + cls.get_pixmap(IconName.TOKEN), + ) + elif name == IconName.NP1STTIEDRAW: + pix = cls._compose( + cls.get_pixmap(IconName.NP1ST), + cls.get_pixmap(IconName.DRAW), + cls.get_pixmap(IconName.TOKEN), + ) + elif name == IconName.NP2NDDRAW: + pix = cls._compose( + cls.get_pixmap(IconName.NP2ND), + cls.get_pixmap(IconName.DRAW), + ) + elif name == IconName.NP2NDBREAK: + pix = cls._compose( + cls.get_pixmap(IconName.NP2ND), + cls.get_pixmap(IconName.TOKEN), + ) + elif name == IconName.NP2NDTIEDRAW: + pix = cls._compose( + cls.get_pixmap(IconName.NP2ND), + cls.get_pixmap(IconName.DRAW), + cls.get_pixmap(IconName.TOKEN), + ) + elif name == IconName.NP3RDDRAW: + pix = cls._compose( + cls.get_pixmap(IconName.NP3RD), + cls.get_pixmap(IconName.DRAW), + ) + elif name == IconName.NP3RDBREAK: + pix = cls._compose( + cls.get_pixmap(IconName.NP3RD), + cls.get_pixmap(IconName.TOKEN), + ) + elif name == IconName.NP3RDTIEDRAW: + pix = cls._compose( + cls.get_pixmap(IconName.NP3RD), + cls.get_pixmap(IconName.DRAW), + cls.get_pixmap(IconName.TOKEN), + ) else: path = RESOURCES_DIR / cls._paths[name] pix = QPixmap(path.as_posix()) diff --git a/src/warchron/controller/campaign_controller.py b/src/warchron/controller/campaign_controller.py index e0ff8b2..00ffa3d 100644 --- a/src/warchron/controller/campaign_controller.py +++ b/src/warchron/controller/campaign_controller.py @@ -1,6 +1,7 @@ from typing import List, Dict, Tuple, TYPE_CHECKING from PyQt6.QtWidgets import QMessageBox, QDialog +from PyQt6.QtGui import QIcon from warchron.constants import ( RefreshScope, @@ -57,16 +58,30 @@ class CampaignController: self.app.view.display_campaign_sectors(sectors_for_display) scores = ScoreService.compute_scores(war, ContextType.CAMPAIGN, campaign_id) rows: List[CampaignParticipantScoreDTO] = [] - icon_map = {} + vp_icon_map: Dict[str, QIcon] = {} + objective_icon_maps: Dict[str, Dict[str, QIcon]] = {} if camp.is_over: - icon_map = RankingIcon.compute_icons( + vp_icon_map = RankingIcon.compute_icons( war, ContextType.CAMPAIGN, campaign_id, scores ) + for obj in war.get_all_objectives(): + objective_icon_maps[obj.id] = RankingIcon.compute_icons( + war, + ContextType.CAMPAIGN, + f"{camp.id}:{obj.id}", + scores, + objective_id=obj.id, + ) for camp_part in camp.get_all_campaign_participants(): war_part_id = camp_part.war_participant_id war_part = war.get_war_participant(war_part_id) player_name = self.app.model.get_player_name(war_part.player_id) score = scores[war_part_id] + objective_icons = { + obj_id: icon_map[war_part_id] + for obj_id, icon_map in objective_icon_maps.items() + if war_part_id in icon_map + } rows.append( CampaignParticipantScoreDTO( campaign_participant_id=camp_part.id, @@ -77,7 +92,8 @@ class CampaignController: victory_points=score.victory_points, narrative_points=dict(score.narrative_points), tokens=war.get_influence_tokens(war_part.id), - rank_icon=icon_map.get(war_part_id), + rank_icon=vp_icon_map.get(war_part_id), + objective_icons=objective_icons, ) ) objectives = [ diff --git a/src/warchron/controller/dtos.py b/src/warchron/controller/dtos.py index 84bc0c4..9451bc6 100644 --- a/src/warchron/controller/dtos.py +++ b/src/warchron/controller/dtos.py @@ -1,5 +1,5 @@ from typing import List, Dict -from dataclasses import dataclass +from dataclasses import dataclass, field from PyQt6.QtGui import QIcon @@ -118,6 +118,7 @@ class CampaignParticipantScoreDTO: narrative_points: Dict[str, int] tokens: int rank_icon: QIcon | None = None + objective_icons: Dict[str, QIcon] = field(default_factory=dict) @dataclass(frozen=True, slots=True) @@ -130,3 +131,4 @@ class WarParticipantScoreDTO: narrative_points: Dict[str, int] tokens: int rank_icon: QIcon | None = None + objective_icons: Dict[str, QIcon] = field(default_factory=dict) diff --git a/src/warchron/controller/ranking_icon.py b/src/warchron/controller/ranking_icon.py index 06f1b3f..27024d5 100644 --- a/src/warchron/controller/ranking_icon.py +++ b/src/warchron/controller/ranking_icon.py @@ -6,7 +6,8 @@ from warchron.constants import ( ContextType, Icons, IconName, - RANK_TO_ICON, + VP_RANK_TO_ICON, + NP_RANK_TO_ICON, ) from warchron.model.war import War from warchron.model.score_service import ParticipantScore @@ -20,16 +21,37 @@ class RankingIcon: context_type: ContextType, context_id: str, scores: Dict[str, ParticipantScore], + *, + objective_id: str | None = None, ) -> Dict[str, QIcon]: + + if objective_id is None: + + def value_getter(score: ParticipantScore) -> int: + return score.victory_points + + icon_ranking = VP_RANK_TO_ICON + else: + + def value_getter(score: ParticipantScore) -> int: + return score.narrative_points.get(objective_id, 0) + + icon_ranking = NP_RANK_TO_ICON ranking = ResultChecker.get_effective_ranking( - war, context_type, context_id, scores + war, context_type, context_id, scores, value_getter=value_getter ) - icon_map = {} + icon_map: Dict[str, QIcon] = {} for rank, group, token_map in ranking: - base_icon = RANK_TO_ICON.get(rank, IconName.VPNTH) - vp = scores[group[0]].victory_points + if objective_id and rank not in icon_ranking: + continue + base_icon = icon_ranking.get( + rank, IconName.VPNTH if objective_id is None else None + ) + if base_icon is None: + continue + value = value_getter(scores[group[0]]) original_group_size = sum( - 1 for s in scores.values() if s.victory_points == vp + 1 for s in scores.values() if value_getter(s) == value ) for pid in group: spent = token_map.get(pid, 0) diff --git a/src/warchron/controller/war_controller.py b/src/warchron/controller/war_controller.py index dd516ae..ddec9ff 100644 --- a/src/warchron/controller/war_controller.py +++ b/src/warchron/controller/war_controller.py @@ -1,6 +1,7 @@ from typing import List, Tuple, TYPE_CHECKING, Dict from PyQt6.QtWidgets import QMessageBox, QDialog +from PyQt6.QtGui import QIcon from warchron.constants import ( RefreshScope, @@ -53,12 +54,28 @@ class WarController: self.app.view.display_war_objectives(objectives_for_display) scores = ScoreService.compute_scores(war, ContextType.WAR, war.id) rows: List[WarParticipantScoreDTO] = [] - icon_map = {} + vp_icon_map: dict[str, QIcon] = {} + objective_icon_maps: Dict[str, Dict[str, QIcon]] = {} if war.is_over: - icon_map = RankingIcon.compute_icons(war, ContextType.WAR, war_id, scores) + vp_icon_map = RankingIcon.compute_icons( + war, ContextType.WAR, war_id, scores + ) + for obj in war.get_all_objectives(): + objective_icon_maps[obj.id] = RankingIcon.compute_icons( + war, + ContextType.WAR, + f"{war.id}:{obj.id}", + scores, + objective_id=obj.id, + ) for war_part in war.get_all_war_participants(): player_name = self.app.model.get_player_name(war_part.player_id) score = scores[war_part.id] + objective_icons = { + obj_id: icon_map[war_part.id] + for obj_id, icon_map in objective_icon_maps.items() + if war_part.id in icon_map + } rows.append( WarParticipantScoreDTO( war_participant_id=war_part.id, @@ -68,7 +85,8 @@ class WarController: victory_points=score.victory_points, narrative_points=dict(score.narrative_points), tokens=war.get_influence_tokens(war_part.id), - rank_icon=icon_map.get(war_part.id), + rank_icon=vp_icon_map.get(war_part.id), + objective_icons=objective_icons, ) ) self.app.view.display_war_participants(rows, objectives_for_display) @@ -133,6 +151,7 @@ class WarController: RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id ) + # FIXME tie dialog with all participant even without tie def resolve_ties( self, war: War, contexts: List[TieContext] ) -> Dict[Tuple[ContextType, str, int | None], Dict[str, bool]]: diff --git a/src/warchron/model/result_checker.py b/src/warchron/model/result_checker.py index 6430ef6..b06ecb8 100644 --- a/src/warchron/model/result_checker.py +++ b/src/warchron/model/result_checker.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import List, Tuple, Dict, TYPE_CHECKING +from typing import List, Tuple, Dict, TYPE_CHECKING, Callable from collections import defaultdict from warchron.constants import ContextType @@ -36,15 +36,16 @@ class ResultChecker: context_type: ContextType, context_id: str, scores: Dict[str, ParticipantScore], + value_getter: Callable[[ParticipantScore], int], ) -> List[Tuple[int, List[str], Dict[str, int]]]: - vp_buckets: Dict[int, List[str]] = defaultdict(list) + buckets: Dict[int, List[str]] = defaultdict(list) for pid, score in scores.items(): - vp_buckets[score.victory_points].append(pid) - sorted_vps = sorted(vp_buckets.keys(), reverse=True) + buckets[value_getter(score)].append(pid) + sorted_vps = sorted(buckets.keys(), reverse=True) ranking: List[Tuple[int, List[str], Dict[str, int]]] = [] current_rank = 1 - for vp in sorted_vps: - participants = vp_buckets[vp] + for value in sorted_vps: + participants = buckets[value] if context_type == ContextType.WAR and len(participants) > 1: subgroups = ResultChecker._secondary_sorting_war(war, participants) for subgroup in subgroups: @@ -57,7 +58,7 @@ class ResultChecker: continue # normal tie-break if tie persists if not TieResolver.is_tie_resolved( - war, context_type, context_id, vp + war, context_type, context_id, value ): ranking.append( (current_rank, subgroup, {pid: 0 for pid in subgroup}) @@ -80,7 +81,7 @@ class ResultChecker: continue # no tie if len(participants) == 1 or not TieResolver.is_tie_resolved( - war, context_type, context_id, vp + war, context_type, context_id, value ): ranking.append( (current_rank, participants, {pid: 0 for pid in participants}) @@ -118,7 +119,11 @@ class ResultChecker: war, ContextType.CAMPAIGN, campaign.id ) ranking = ResultChecker.get_effective_ranking( - war, ContextType.CAMPAIGN, campaign.id, scores + war, + ContextType.CAMPAIGN, + campaign.id, + scores, + lambda s: s.victory_points, ) for rank, group, _ in ranking: if pid in group: diff --git a/src/warchron/model/tie_manager.py b/src/warchron/model/tie_manager.py index 028001e..13a220b 100644 --- a/src/warchron/model/tie_manager.py +++ b/src/warchron/model/tie_manager.py @@ -146,7 +146,11 @@ class TieResolver: scores = ScoreService.compute_scores(war, ContextType.WAR, war.id) ranking = ResultChecker.get_effective_ranking( - war, ContextType.WAR, war.id, scores + war, + ContextType.WAR, + war.id, + scores, + value_getter=lambda s: s.victory_points, ) ties: List[TieContext] = [] for _, group, _ in ranking: @@ -191,6 +195,7 @@ class TieResolver: ContextType.OBJECTIVE, f"{war.id}:{objective_id}", scores, + value_getter=lambda s: s.narrative_points.get(objective_id, 0), ) ties: List[TieContext] = [] for _, group, _ in ranking: diff --git a/src/warchron/view/view.py b/src/warchron/view/view.py index 012c2bf..160719a 100644 --- a/src/warchron/view/view.py +++ b/src/warchron/view/view.py @@ -413,6 +413,8 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): for obj in objectives: value = part.narrative_points.get(obj.id, 0) NP_item = QtWidgets.QTableWidgetItem(str(value)) + if part.objective_icons.get(obj.id): + NP_item.setIcon(part.objective_icons[obj.id]) table.setItem(row, col, NP_item) col += 1 table.setItem(row, col, token_item) @@ -545,6 +547,8 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): for obj in objectives: value = part.narrative_points.get(obj.id, 0) NP_item = QtWidgets.QTableWidgetItem(str(value)) + if part.objective_icons.get(obj.id): + NP_item.setIcon(part.objective_icons[obj.id]) table.setItem(row, col, NP_item) col += 1 table.setItem(row, col, token_item)