Compare commits

..

4 commits

Author SHA1 Message Date
Maxime Réaux
789756d586 fix forbidden sector add/update/remove in closed rounds 2026-02-24 11:07:06 +01:00
Maxime Réaux
097823fab0 fix canceled tie spent tokens 2026-02-24 10:04:16 +01:00
Maxime Réaux
31a2ebb9dc fix disable end war button 2026-02-24 09:11:37 +01:00
Maxime Réaux
81626171c8 display tokens in participant tables 2026-02-24 09:06:13 +01:00
7 changed files with 65 additions and 10 deletions

View file

@ -111,6 +111,7 @@ class CampaignController:
theme=camp_part.theme or "", theme=camp_part.theme or "",
victory_points=score.victory_points, victory_points=score.victory_points,
narrative_points=dict(score.narrative_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=icon_map.get(war_part_id),
) )
) )
@ -206,6 +207,7 @@ class CampaignController:
context_id=ctx.context_id, context_id=ctx.context_id,
) )
if not dialog.exec(): if not dialog.exec():
TieResolver.cancel_tie_break(war, ContextType.CAMPAIGN, ctx.context_id)
raise ForbiddenOperation("Tie resolution cancelled") raise ForbiddenOperation("Tie resolution cancelled")
bids_map[ctx.context_id] = dialog.get_bids() bids_map[ctx.context_id] = dialog.get_bids()
return bids_map return bids_map

View file

@ -124,6 +124,7 @@ class CampaignParticipantScoreDTO:
theme: str theme: str
victory_points: int victory_points: int
narrative_points: Dict[str, int] narrative_points: Dict[str, int]
tokens: int
rank_icon: QIcon | None = None rank_icon: QIcon | None = None
@ -135,4 +136,5 @@ class WarParticipantScoreDTO:
faction: str faction: str
victory_points: int victory_points: int
narrative_points: Dict[str, int] narrative_points: Dict[str, int]
tokens: int
rank_icon: QIcon | None = None rank_icon: QIcon | None = None

View file

@ -96,11 +96,12 @@ class WarController:
faction=war_part.faction or "", faction=war_part.faction or "",
victory_points=score.victory_points, victory_points=score.victory_points,
narrative_points=dict(score.narrative_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=icon_map.get(war_part.id),
) )
) )
self.app.view.display_war_participants(rows, objectives_for_display) self.app.view.display_war_participants(rows, objectives_for_display)
self.app.view.endCampaignBtn.setEnabled(not war.is_over) self.app.view.endWarBtn.setEnabled(not war.is_over)
def _validate_war_inputs(self, name: str, year: int) -> bool: def _validate_war_inputs(self, name: str, year: int) -> bool:
if not name.strip(): if not name.strip():
@ -160,6 +161,7 @@ class WarController:
RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id
) )
# TODO fix ignored campaign tie-breaks
def resolve_ties( def resolve_ties(
self, war: War, contexts: List[TieContext] self, war: War, contexts: List[TieContext]
) -> Dict[str, Dict[str, bool]]: ) -> Dict[str, Dict[str, bool]]:
@ -181,6 +183,7 @@ class WarController:
context_id=ctx.context_id, context_id=ctx.context_id,
) )
if not dialog.exec(): if not dialog.exec():
TieResolver.cancel_tie_break(war, ContextType.WAR, ctx.context_id)
raise ForbiddenOperation("Tie resolution cancelled") raise ForbiddenOperation("Tie resolution cancelled")
bids_map[ctx.context_id] = dialog.get_bids() bids_map[ctx.context_id] = dialog.get_bids()
return bids_map return bids_map

View file

@ -107,7 +107,7 @@ class Campaign:
def remove_campaign_participant(self, participant_id: str) -> None: def remove_campaign_participant(self, participant_id: str) -> None:
if self.is_over: if self.is_over:
raise ForbiddenOperation("Can't remove participant in a closed campaign.") raise ForbiddenOperation("Can't remove participant in a closed campaign.")
rounds_blocking: list[Round] = [] rounds_blocking: List[Round] = []
for rnd in self.rounds: for rnd in self.rounds:
if rnd.has_choice_with_participant( if rnd.has_choice_with_participant(
participant_id participant_id
@ -158,6 +158,10 @@ class Campaign:
) -> Sector: ) -> Sector:
if self.is_over: if self.is_over:
raise ForbiddenOperation("Can't add sector in a closed campaign.") raise ForbiddenOperation("Can't add sector in a closed campaign.")
if round_id is not None:
round = self.get_round(round_id)
if round.is_over:
raise ForbiddenOperation("Can't add sector with a closed round.")
sect = Sector( sect = Sector(
name, round_id, major_id, minor_id, influence_id, mission, description name, round_id, major_id, minor_id, influence_id, mission, description
) )
@ -187,11 +191,18 @@ class Campaign:
mission: str | None, mission: str | None,
description: str | None, description: str | None,
) -> None: ) -> None:
# TODO raise error if sector used in a closed round (potential tokens)
if self.is_over: if self.is_over:
raise ForbiddenOperation("Can't update sector in a closed campaign.") raise ForbiddenOperation("Can't update sector in a closed campaign.")
sect = self.get_sector(sector_id) sect = self.get_sector(sector_id)
old_round_id = sect.round_id old_round_id = sect.round_id
if old_round_id is not None:
old_round = self.get_round(old_round_id)
if old_round.is_over:
raise ForbiddenOperation("Can't update sector used in a closed round.")
if round_id is not None:
new_round = self.get_round(round_id)
if new_round.is_over:
raise ForbiddenOperation("Can't update sector with a closed round.")
def apply_update() -> None: def apply_update() -> None:
sect.set_name(name) sect.set_name(name)
@ -205,7 +216,7 @@ class Campaign:
if old_round_id == round_id: if old_round_id == round_id:
apply_update() apply_update()
return return
affected_rounds: list[Round] = [] affected_rounds: List[Round] = []
for rnd in self.rounds: for rnd in self.rounds:
if rnd.id == old_round_id and ( if rnd.id == old_round_id and (
rnd.has_choice_with_sector(sector_id) rnd.has_choice_with_sector(sector_id)
@ -235,7 +246,14 @@ class Campaign:
def remove_sector(self, sector_id: str) -> None: def remove_sector(self, sector_id: str) -> None:
if self.is_over: if self.is_over:
raise ForbiddenOperation("Can't remove sector in a closed campaign.") raise ForbiddenOperation("Can't remove sector in a closed campaign.")
rounds_blocking: list[Round] = [] sect = self.get_sector(sector_id)
round_id = sect.round_id
if round_id is not None:
round = self.get_round(round_id)
if round.is_over:
raise ForbiddenOperation("Can't remove sector used in a closed round.")
rounds_blocking: List[Round] = []
for rnd in self.rounds: for rnd in self.rounds:
if rnd.has_battle_with_sector(sector_id) or rnd.has_choice_with_sector( if rnd.has_battle_with_sector(sector_id) or rnd.has_choice_with_sector(
sector_id sector_id

View file

@ -28,7 +28,6 @@ class ResultChecker:
and ev.context_id == context_id and ev.context_id == context_id
): ):
return ev.participant_id # None if confirmed draw return ev.participant_id # None if confirmed draw
return None return None
@staticmethod @staticmethod

View file

@ -130,6 +130,29 @@ class TieResolver:
) )
) )
@staticmethod
def cancel_tie_break(
war: War,
context_type: ContextType,
context_id: str,
) -> None:
war.events = [
ev
for ev in war.events
if not (
(
isinstance(ev, InfluenceSpent)
and ev.context_type == context_type
and ev.context_id == context_id
)
or (
isinstance(ev, TieResolved)
and ev.context_type == context_type
and ev.context_id == context_id
)
)
]
@staticmethod @staticmethod
def rank_by_tokens( def rank_by_tokens(
war: War, war: War,

View file

@ -369,8 +369,10 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
) -> None: ) -> None:
table = self.warParticipantsTable table = self.warParticipantsTable
table.clearContents() table.clearContents()
base_cols = ["Player", "Faction", "Victory"] base_cols = ["Player", "Faction", "Victory pts"]
headers = base_cols + [obj.name for obj in objectives] headers = (
base_cols + [str(obj.name + " pts") for obj in objectives] + ["Tokens"]
)
table.setColumnCount(len(headers)) table.setColumnCount(len(headers))
table.setHorizontalHeaderLabels(headers) table.setHorizontalHeaderLabels(headers)
table.setRowCount(len(participants)) table.setRowCount(len(participants))
@ -382,6 +384,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
faction_item = QtWidgets.QTableWidgetItem(part.faction) faction_item = QtWidgets.QTableWidgetItem(part.faction)
VP_item = QtWidgets.QTableWidgetItem(str(part.victory_points)) VP_item = QtWidgets.QTableWidgetItem(str(part.victory_points))
name_item.setData(Qt.ItemDataRole.UserRole, part.war_participant_id) name_item.setData(Qt.ItemDataRole.UserRole, part.war_participant_id)
token_item = QtWidgets.QTableWidgetItem(str(part.tokens))
table.setItem(row, 0, name_item) table.setItem(row, 0, name_item)
table.setItem(row, 1, faction_item) table.setItem(row, 1, faction_item)
table.setItem(row, 2, VP_item) table.setItem(row, 2, VP_item)
@ -391,6 +394,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
NP_item = QtWidgets.QTableWidgetItem(str(value)) NP_item = QtWidgets.QTableWidgetItem(str(value))
table.setItem(row, col, NP_item) table.setItem(row, col, NP_item)
col += 1 col += 1
table.setItem(row, col, token_item)
table.resizeColumnsToContents() table.resizeColumnsToContents()
def _on_major_changed(self, value: int) -> None: def _on_major_changed(self, value: int) -> None:
@ -489,8 +493,10 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
) -> None: ) -> None:
table = self.campaignParticipantsTable table = self.campaignParticipantsTable
table.clearContents() table.clearContents()
base_cols = ["Player", "Leader", "Theme", "Victory"] base_cols = ["Player", "Leader", "Theme", "Victory pts"]
headers = base_cols + [obj.name for obj in objectives] headers = (
base_cols + [str(obj.name + " pts") for obj in objectives] + ["Tokens"]
)
table.setColumnCount(len(headers)) table.setColumnCount(len(headers))
table.setHorizontalHeaderLabels(headers) table.setHorizontalHeaderLabels(headers)
table.setRowCount(len(participants)) table.setRowCount(len(participants))
@ -502,6 +508,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
lead_item = QtWidgets.QTableWidgetItem(part.leader) lead_item = QtWidgets.QTableWidgetItem(part.leader)
theme_item = QtWidgets.QTableWidgetItem(part.theme) theme_item = QtWidgets.QTableWidgetItem(part.theme)
VP_item = QtWidgets.QTableWidgetItem(str(part.victory_points)) VP_item = QtWidgets.QTableWidgetItem(str(part.victory_points))
token_item = QtWidgets.QTableWidgetItem(str(part.tokens))
name_item.setData(Qt.ItemDataRole.UserRole, part.campaign_participant_id) name_item.setData(Qt.ItemDataRole.UserRole, part.campaign_participant_id)
table.setItem(row, 0, name_item) table.setItem(row, 0, name_item)
table.setItem(row, 1, lead_item) table.setItem(row, 1, lead_item)
@ -513,6 +520,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
NP_item = QtWidgets.QTableWidgetItem(str(value)) NP_item = QtWidgets.QTableWidgetItem(str(value))
table.setItem(row, col, NP_item) table.setItem(row, col, NP_item)
col += 1 col += 1
table.setItem(row, col, token_item)
table.resizeColumnsToContents() table.resizeColumnsToContents()
# Round page # Round page