fix mismatch part_id in choice event + fix revert events on sector/participant removal
This commit is contained in:
parent
42ad708e77
commit
db78c6dacc
6 changed files with 145 additions and 53 deletions
|
|
@ -23,6 +23,12 @@ class Campaign:
|
||||||
self.is_over = False
|
self.is_over = False
|
||||||
self._war: War | None = None # private link
|
self._war: War | None = None # private link
|
||||||
|
|
||||||
|
@property
|
||||||
|
def war(self) -> "War":
|
||||||
|
if self._war is None:
|
||||||
|
raise RuntimeError("Campaign is not linked to a War")
|
||||||
|
return self._war
|
||||||
|
|
||||||
def set_id(self, new_id: str) -> None:
|
def set_id(self, new_id: str) -> None:
|
||||||
self.id = new_id
|
self.id = new_id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ class Model:
|
||||||
self.players[player.id] = player
|
self.players[player.id] = player
|
||||||
for w in data.get("wars", []):
|
for w in data.get("wars", []):
|
||||||
war = War.fromDict(w)
|
war = War.fromDict(w)
|
||||||
|
war.relink()
|
||||||
self.wars[war.id] = war
|
self.wars[war.id] = war
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
raise RuntimeError("Data file is corrupted")
|
raise RuntimeError("Data file is corrupted")
|
||||||
|
|
|
||||||
|
|
@ -186,12 +186,13 @@ class Pairing:
|
||||||
campaign = war.get_campaign_by_round(round.id)
|
campaign = war.get_campaign_by_round(round.id)
|
||||||
if campaign is None:
|
if campaign is None:
|
||||||
raise DomainError("Campaign not found for round {round.id}")
|
raise DomainError("Campaign not found for round {round.id}")
|
||||||
|
war_participants = [
|
||||||
|
campaign.campaign_to_war_part_id(pid) for pid in participants
|
||||||
|
]
|
||||||
context = TieContext(
|
context = TieContext(
|
||||||
context_type=ContextType.CHOICE,
|
context_type=ContextType.CHOICE,
|
||||||
context_id=round.id,
|
context_id=round.id,
|
||||||
participants=[
|
participants=war_participants,
|
||||||
campaign.campaign_to_war_part_id(pid) for pid in participants
|
|
||||||
],
|
|
||||||
score_value=score_value,
|
score_value=score_value,
|
||||||
score_kind=ScoreKind.VP,
|
score_kind=ScoreKind.VP,
|
||||||
sector_id=sector_id,
|
sector_id=sector_id,
|
||||||
|
|
@ -212,6 +213,7 @@ class Pairing:
|
||||||
score_kind=context.score_kind,
|
score_kind=context.score_kind,
|
||||||
sector_id=context.sector_id,
|
sector_id=context.sector_id,
|
||||||
)
|
)
|
||||||
|
# natural or unbreakable draw
|
||||||
if not TieResolver.can_tie_be_resolved(
|
if not TieResolver.can_tie_be_resolved(
|
||||||
war, context, current_context.participants
|
war, context, current_context.participants
|
||||||
):
|
):
|
||||||
|
|
@ -220,7 +222,7 @@ class Pairing:
|
||||||
None,
|
None,
|
||||||
context.context_type,
|
context.context_type,
|
||||||
context.context_id,
|
context.context_id,
|
||||||
participants,
|
participants=context.participants,
|
||||||
tie_id=tie_id,
|
tie_id=tie_id,
|
||||||
score_value=score_value,
|
score_value=score_value,
|
||||||
sector_id=sector_id,
|
sector_id=sector_id,
|
||||||
|
|
@ -254,7 +256,6 @@ class Pairing:
|
||||||
for group in ranked_groups:
|
for group in ranked_groups:
|
||||||
shuffled_group = list(group)
|
shuffled_group = list(group)
|
||||||
# TODO improve tie break with history parsing
|
# TODO improve tie break with history parsing
|
||||||
# TODO avoid rematch
|
|
||||||
random.shuffle(shuffled_group)
|
random.shuffle(shuffled_group)
|
||||||
ordered.extend(
|
ordered.extend(
|
||||||
campaign.war_to_campaign_part_id(pid) for pid in shuffled_group
|
campaign.war_to_campaign_part_id(pid) for pid in shuffled_group
|
||||||
|
|
@ -266,6 +267,7 @@ class Pairing:
|
||||||
round: Round,
|
round: Round,
|
||||||
remaining: List[str],
|
remaining: List[str],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# TODO avoid rematch
|
||||||
for pid in list(remaining):
|
for pid in list(remaining):
|
||||||
available = round.get_battles_with_places()
|
available = round.get_battles_with_places()
|
||||||
if not available:
|
if not available:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from typing import Any, Dict, List, TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from warchron.model.campaign import Campaign
|
from warchron.model.campaign import Campaign
|
||||||
|
from warchron.model.war import War
|
||||||
from warchron.model.exception import ForbiddenOperation
|
from warchron.model.exception import ForbiddenOperation
|
||||||
from warchron.model.choice import Choice
|
from warchron.model.choice import Choice
|
||||||
from warchron.model.battle import Battle
|
from warchron.model.battle import Battle
|
||||||
|
|
@ -17,6 +18,16 @@ class Round:
|
||||||
self.is_over: bool = False
|
self.is_over: bool = False
|
||||||
self._campaign: Campaign | None = None # private link
|
self._campaign: Campaign | None = None # private link
|
||||||
|
|
||||||
|
@property
|
||||||
|
def campaign(self) -> "Campaign":
|
||||||
|
if self._campaign is None:
|
||||||
|
raise RuntimeError("Round is not linked to a Campaign")
|
||||||
|
return self._campaign
|
||||||
|
|
||||||
|
@property
|
||||||
|
def war(self) -> "War":
|
||||||
|
return self.campaign.war
|
||||||
|
|
||||||
def set_id(self, new_id: str) -> None:
|
def set_id(self, new_id: str) -> None:
|
||||||
self.id = new_id
|
self.id = new_id
|
||||||
|
|
||||||
|
|
@ -91,7 +102,6 @@ class Round:
|
||||||
choice.set_secondary(secondary_sector_id)
|
choice.set_secondary(secondary_sector_id)
|
||||||
choice.set_comment(comment)
|
choice.set_comment(comment)
|
||||||
|
|
||||||
# FIXME remove corresponding InfluenceSpent and TieResolved
|
|
||||||
def clear_sector_references(self, sector_id: str) -> None:
|
def clear_sector_references(self, sector_id: str) -> None:
|
||||||
for choice in self.choices.values():
|
for choice in self.choices.values():
|
||||||
trigger_revert_ties = False
|
trigger_revert_ties = False
|
||||||
|
|
@ -102,18 +112,19 @@ class Round:
|
||||||
choice.secondary_sector_id = None
|
choice.secondary_sector_id = None
|
||||||
trigger_revert_ties = True
|
trigger_revert_ties = True
|
||||||
if trigger_revert_ties:
|
if trigger_revert_ties:
|
||||||
if self._campaign and self._campaign._war:
|
self.war.revert_choice_ties(self.id, sector_id=sector_id)
|
||||||
self._campaign._war.revert_choice_ties(self.id, sector_id=sector_id)
|
|
||||||
|
|
||||||
def remove_choice(self, participant_id: str) -> None:
|
def remove_choice(self, participant_id: str) -> None:
|
||||||
|
if participant_id not in self.choices:
|
||||||
|
return
|
||||||
if self.is_over:
|
if self.is_over:
|
||||||
# TODO catch me if you can (inner)
|
# TODO catch me if you can (inner)
|
||||||
raise ForbiddenOperation("Can't remove choice in a closed round.")
|
raise ForbiddenOperation("Can't remove choice in a closed round.")
|
||||||
# TODO prevent if battles already assigned
|
# TODO prevent if battles already assigned
|
||||||
if self._campaign and self._campaign._war:
|
self.war.revert_choice_ties(
|
||||||
self._campaign._war.revert_choice_ties(
|
self.id,
|
||||||
self.id, participants=[participant_id]
|
participants=[self.campaign.campaign_to_war_part_id(participant_id)],
|
||||||
)
|
)
|
||||||
del self.choices[participant_id]
|
del self.choices[participant_id]
|
||||||
|
|
||||||
# Battle methods
|
# Battle methods
|
||||||
|
|
@ -187,10 +198,16 @@ class Round:
|
||||||
if battle.winner_id == participant_id:
|
if battle.winner_id == participant_id:
|
||||||
battle.winner_id = None
|
battle.winner_id = None
|
||||||
if trigger_revert_ties:
|
if trigger_revert_ties:
|
||||||
if self._campaign and self._campaign._war:
|
self.war.revert_battle_ties(
|
||||||
self._campaign._war.revert_battle_ties(battle.sector_id)
|
self.id,
|
||||||
|
participants=[
|
||||||
|
self.campaign.campaign_to_war_part_id(participant_id)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def remove_battle(self, sector_id: str) -> None:
|
def remove_battle(self, sector_id: str) -> None:
|
||||||
|
if sector_id not in self.battles:
|
||||||
|
return
|
||||||
if self.is_over:
|
if self.is_over:
|
||||||
# TODO catch me if you can
|
# TODO catch me if you can
|
||||||
raise ForbiddenOperation("Can't remove battle in a closed round.")
|
raise ForbiddenOperation("Can't remove battle in a closed round.")
|
||||||
|
|
@ -198,6 +215,5 @@ class Round:
|
||||||
if bat and bat.is_finished():
|
if bat and bat.is_finished():
|
||||||
# TODO catch me if you can
|
# TODO catch me if you can
|
||||||
raise ForbiddenOperation("Can't remove finished battle.")
|
raise ForbiddenOperation("Can't remove finished battle.")
|
||||||
if self._campaign and self._campaign._war:
|
self.war.revert_battle_ties(self.id, sector_id=sector_id)
|
||||||
self._campaign._war.revert_battle_ties(sector_id)
|
|
||||||
del self.battles[sector_id]
|
del self.battles[sector_id]
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,12 @@ class War:
|
||||||
war.set_state(data.get("is_over", False))
|
war.set_state(data.get("is_over", False))
|
||||||
return war
|
return war
|
||||||
|
|
||||||
|
def relink(self) -> None:
|
||||||
|
for campaign in self.campaigns:
|
||||||
|
campaign._war = self
|
||||||
|
for rnd in campaign.rounds:
|
||||||
|
rnd._campaign = campaign
|
||||||
|
|
||||||
# Objective methods
|
# Objective methods
|
||||||
|
|
||||||
def add_objective(self, name: str, description: str | None) -> Objective:
|
def add_objective(self, name: str, description: str | None) -> Objective:
|
||||||
|
|
@ -552,12 +558,60 @@ class War:
|
||||||
)
|
)
|
||||||
return gained - spent
|
return gained - spent
|
||||||
|
|
||||||
def get_events_by_ties_session(self, tie_id: str) -> List[WarEvent]:
|
def get_events_by_tie_id(self, tie_id: str) -> List[WarEvent]:
|
||||||
return [ev for ev in self.events if ev.tie_id == tie_id]
|
return [ev for ev in self.events if ev.tie_id == tie_id]
|
||||||
|
|
||||||
def remove_ties_session(self, tie_id: str) -> None:
|
def remove_events_by_tie_id(self, tie_id: str) -> None:
|
||||||
self.events = [ev for ev in self.events if ev.tie_id != tie_id]
|
self.events = [ev for ev in self.events if ev.tie_id != tie_id]
|
||||||
|
|
||||||
|
def find_ties(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
context_type: str,
|
||||||
|
context_id: str,
|
||||||
|
sector_id: str | None = None,
|
||||||
|
participants: List[str] | None = None,
|
||||||
|
) -> Set[str]:
|
||||||
|
ties = set()
|
||||||
|
for ev in self.events:
|
||||||
|
if not isinstance(ev, TieResolved):
|
||||||
|
continue
|
||||||
|
if ev.context_type != context_type:
|
||||||
|
continue
|
||||||
|
if ev.context_id != context_id:
|
||||||
|
continue
|
||||||
|
if sector_id and ev.sector_id != sector_id:
|
||||||
|
continue
|
||||||
|
if participants and not any(p in ev.participants for p in participants):
|
||||||
|
continue
|
||||||
|
if ev.tie_id:
|
||||||
|
ties.add(ev.tie_id)
|
||||||
|
return ties
|
||||||
|
|
||||||
|
def get_draws(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
context_type: str,
|
||||||
|
context_id: str,
|
||||||
|
sector_id: str | None = None,
|
||||||
|
participants: List[str] | None = None,
|
||||||
|
) -> List[WarEvent]:
|
||||||
|
draws: List[WarEvent] = list()
|
||||||
|
for ev in self.events:
|
||||||
|
if not isinstance(ev, TieResolved):
|
||||||
|
continue
|
||||||
|
if ev.context_type != context_type:
|
||||||
|
continue
|
||||||
|
if ev.context_id != context_id:
|
||||||
|
continue
|
||||||
|
if sector_id and ev.sector_id != sector_id:
|
||||||
|
continue
|
||||||
|
if participants and not any(p in ev.participants for p in participants):
|
||||||
|
continue
|
||||||
|
if ev.tie_id is None:
|
||||||
|
draws.append(ev)
|
||||||
|
return draws
|
||||||
|
|
||||||
def revert_choice_ties(
|
def revert_choice_ties(
|
||||||
self,
|
self,
|
||||||
round_id: str,
|
round_id: str,
|
||||||
|
|
@ -565,33 +619,45 @@ class War:
|
||||||
sector_id: str | None = None,
|
sector_id: str | None = None,
|
||||||
participants: List[str] | None = None,
|
participants: List[str] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
removed_ties: Set[str] = set()
|
removed_ties = self.find_ties(
|
||||||
for ev in self.events:
|
context_type=ContextType.CHOICE,
|
||||||
if (
|
context_id=round_id,
|
||||||
isinstance(ev, TieResolved)
|
sector_id=sector_id,
|
||||||
and ev.context_type == ContextType.CHOICE
|
participants=participants,
|
||||||
and ev.context_id == round_id
|
)
|
||||||
):
|
draws = self.get_draws(
|
||||||
if (
|
context_type=ContextType.CHOICE,
|
||||||
sector_id is None
|
context_id=round_id,
|
||||||
or ev.sector_id == sector_id
|
sector_id=sector_id,
|
||||||
or participants is None
|
participants=participants,
|
||||||
or any(p in ev.participants for p in participants)
|
)
|
||||||
):
|
self.events = [
|
||||||
if ev.tie_id:
|
ev
|
||||||
removed_ties.add(ev.tie_id)
|
|
||||||
self.events = [ev for ev in self.events if ev.tie_id not in removed_ties]
|
|
||||||
|
|
||||||
def revert_battle_ties(self, sector_id: str) -> None:
|
|
||||||
removed_ties = {
|
|
||||||
ev.tie_id
|
|
||||||
for ev in self.events
|
for ev in self.events
|
||||||
if isinstance(ev, TieResolved)
|
if ev.tie_id not in removed_ties and ev not in draws
|
||||||
and ev.context_type == ContextType.BATTLE
|
]
|
||||||
and ev.context_id == sector_id
|
|
||||||
and ev.tie_id
|
|
||||||
}
|
|
||||||
self.events = [ev for ev in self.events if ev.tie_id not in removed_ties]
|
|
||||||
|
|
||||||
def revert_tie(self, tie_id: str) -> None:
|
def revert_battle_ties(
|
||||||
self.events = [ev for ev in self.events if ev.tie_id != tie_id]
|
self,
|
||||||
|
round_id: str,
|
||||||
|
*,
|
||||||
|
sector_id: str | None = None,
|
||||||
|
participants: List[str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
removed_ties = self.find_ties(
|
||||||
|
context_type=ContextType.BATTLE,
|
||||||
|
context_id=round_id,
|
||||||
|
sector_id=sector_id,
|
||||||
|
participants=participants,
|
||||||
|
)
|
||||||
|
draws = self.get_draws(
|
||||||
|
context_type=ContextType.BATTLE,
|
||||||
|
context_id=round_id,
|
||||||
|
sector_id=sector_id,
|
||||||
|
participants=participants,
|
||||||
|
)
|
||||||
|
self.events = [
|
||||||
|
ev
|
||||||
|
for ev in self.events
|
||||||
|
if ev.tie_id not in removed_ties and ev not in draws
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -325,25 +325,26 @@
|
||||||
"events": [
|
"events": [
|
||||||
{
|
{
|
||||||
"type": "InfluenceGained",
|
"type": "InfluenceGained",
|
||||||
"id": "8f20b587-0ff3-42c9-9965-88112e2a4e74",
|
"id": "096802e2-5833-4c6d-941d-c91d98957fc1",
|
||||||
"participant_id": "accc25f2-43a0-4d41-804f-3c8aec853c97",
|
"participant_id": "accc25f2-43a0-4d41-804f-3c8aec853c97",
|
||||||
"context_type": "battle",
|
"context_type": "battle",
|
||||||
"context_id": "4548997e-50e5-493a-b751-483f5bcccc00",
|
"context_id": "4548997e-50e5-493a-b751-483f5bcccc00",
|
||||||
"timestamp": "2026-02-26T16:10:54.125407",
|
"timestamp": "2026-03-18T09:25:03.746744",
|
||||||
|
"tie_id": null,
|
||||||
"amount": 1
|
"amount": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "TieResolved",
|
"type": "TieResolved",
|
||||||
"id": "c83a9d4e-4620-47de-93c8-d29d6e119c59",
|
"id": "ec6b8932-b24f-4042-a8f4-566a42d0857d",
|
||||||
"participant_id": null,
|
"participant_id": null,
|
||||||
"context_type": "battle",
|
"context_type": "battle",
|
||||||
"context_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
|
"context_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
|
||||||
|
"timestamp": "2026-03-18T09:25:08.578539",
|
||||||
|
"tie_id": "07aa41e5-1696-4b9f-931b-2540e213c7ef",
|
||||||
"participants": [
|
"participants": [
|
||||||
"602e2eaf-297e-490b-b0e9-efec818e466a",
|
"accc25f2-43a0-4d41-804f-3c8aec853c97",
|
||||||
"1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de"
|
"179bdab6-8630-4a92-8fb6-d2637562c66c"
|
||||||
],
|
],
|
||||||
"timestamp": "2026-02-26T16:11:44.346337",
|
|
||||||
"tie_id": null,
|
|
||||||
"score_value": null,
|
"score_value": null,
|
||||||
"objective_id": null,
|
"objective_id": null,
|
||||||
"sector_id": null
|
"sector_id": null
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue