|
|
|
@ -5,19 +5,20 @@ from urwid import Pile, Columns, ListBox, SimpleFocusListWalker, Text, WidgetWra |
|
|
|
|
Button |
|
|
|
|
|
|
|
|
|
from model import Character, Clock |
|
|
|
|
from enums import CharacterType, Counter |
|
|
|
|
from enums import CharacterType, Counter, decode_discord, CombatSide |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SessionStateUI(WidgetWrap): |
|
|
|
|
@classmethod |
|
|
|
|
def render_round_timer(cls, rd: int, fp_spent: int, up_spent: int): |
|
|
|
|
return ([('RoundLabel', 'Round'), ': ', ('RoundNumber', str(rd)), ' / '] if rd > 0 else []) + [ |
|
|
|
|
('FabulaLabel', 'FP Used'), |
|
|
|
|
': ', |
|
|
|
|
('FabulaNumber', str(fp_spent)), |
|
|
|
|
] + ([' / ', ('UltimaLabel', 'UP Used'), ': ', ('UltimaNumber', str(up_spent))] if up_spent > 0 else []) |
|
|
|
|
|
|
|
|
|
def __init__(self, rd: int, fp_spent: int, up_spent: int): |
|
|
|
|
@staticmethod |
|
|
|
|
def render_round_timer(rd: int | None, fp_spent: int, up_spent: int): |
|
|
|
|
return ( |
|
|
|
|
([('RoundLabel', 'Round'), ':\u00A0', ('RoundNumber', str(rd)), ' / '] if rd is not None else []) + |
|
|
|
|
([('FabulaLabel', 'FP\u00A0Used'), ':\u00A0', ('FabulaNumber', str(fp_spent))]) + |
|
|
|
|
([' / ', ('UltimaLabel', 'UP\u00A0Used'), ':\u00A0', ('UltimaNumber', str(up_spent))] |
|
|
|
|
if up_spent > 0 else []) |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
def __init__(self, rd: int | None, fp_spent: int, up_spent: int): |
|
|
|
|
self.text = Text(self.render_round_timer(rd=rd, fp_spent=fp_spent, up_spent=up_spent)) |
|
|
|
|
super().__init__(Columns([ |
|
|
|
|
(4, Padding( |
|
|
|
@ -29,7 +30,7 @@ class SessionStateUI(WidgetWrap): |
|
|
|
|
right=1)), |
|
|
|
|
('weight', 1, self.text)])) |
|
|
|
|
|
|
|
|
|
def update_session(self, rd: int, fp_spent: int, up_spent: int): |
|
|
|
|
def update_session(self, rd: int | None, fp_spent: int, up_spent: int): |
|
|
|
|
self.text.set_text(self.render_round_timer(rd=rd, fp_spent=fp_spent, up_spent=up_spent)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -69,27 +70,29 @@ class ClockUI(WidgetWrap): |
|
|
|
|
class ClocksUI(WidgetWrap): |
|
|
|
|
def __init__(self): |
|
|
|
|
self.state = SessionStateUI(rd=0, fp_spent=0, up_spent=0) |
|
|
|
|
self.items = SimpleFocusListWalker([]) |
|
|
|
|
self.items = SimpleFocusListWalker([self.state]) |
|
|
|
|
self.list = ListBox(self.items) |
|
|
|
|
super().__init__(Pile([('pack', self.state), ('weight', 1, self.list)])) |
|
|
|
|
super().__init__(self.list) |
|
|
|
|
|
|
|
|
|
def update_session(self, rd: int, fp_spent: int, up_spent: int): |
|
|
|
|
def update_session(self, rd: int | None, fp_spent: int, up_spent: int): |
|
|
|
|
self.state.update_session(rd=rd, fp_spent=fp_spent, up_spent=up_spent) |
|
|
|
|
|
|
|
|
|
def add_clock(self, clock: Clock, index: int = -1) -> int: |
|
|
|
|
if index == -1: |
|
|
|
|
index = len(self.items) |
|
|
|
|
self.items.insert(index, ClockUI(clock=clock)) |
|
|
|
|
if index < 0: |
|
|
|
|
index = len(self.items) - 1 |
|
|
|
|
self.items.insert(index + 1, ClockUI(clock=clock)) |
|
|
|
|
return index |
|
|
|
|
|
|
|
|
|
def update_clock(self, index: int, clock: Clock): |
|
|
|
|
self.items[index].update(clock) |
|
|
|
|
if index < 0: |
|
|
|
|
return |
|
|
|
|
self.items[index + 1].update(clock) |
|
|
|
|
|
|
|
|
|
def reorder_clock(self, old: int, new: int = -1): |
|
|
|
|
focused = self.list.focus_position == old |
|
|
|
|
item = self.items.pop(old) |
|
|
|
|
new = new if new != -1 else len(self.items) |
|
|
|
|
self.items.insert(new, self.items.pop(item)) |
|
|
|
|
focused = self.list.focus_position == old + 1 |
|
|
|
|
item = self.items.pop(old + 1) |
|
|
|
|
new = new if new >= 0 else len(self.items) |
|
|
|
|
self.items.insert(new + 1, self.items.pop(item)) |
|
|
|
|
if focused: |
|
|
|
|
self.items.set_focus(position=new) |
|
|
|
|
|
|
|
|
@ -98,33 +101,36 @@ class ClocksUI(WidgetWrap): |
|
|
|
|
|
|
|
|
|
def clear(self): |
|
|
|
|
self.items.clear() |
|
|
|
|
self.append(self.state) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CharacterUI(WidgetWrap): |
|
|
|
|
@classmethod |
|
|
|
|
def render_character_text(cls, character: Character): |
|
|
|
|
if character.role == CharacterType.Player: |
|
|
|
|
role_icon = '♥' |
|
|
|
|
role_style = 'CharacterPlayer' |
|
|
|
|
elif character.role == CharacterType.Ally: |
|
|
|
|
if character.side == CombatSide.Heroes: |
|
|
|
|
if character.character_type == CharacterType.PlayerCharacter: |
|
|
|
|
role_icon = '♡' |
|
|
|
|
role_style = 'CharacterPlayer' |
|
|
|
|
else: |
|
|
|
|
role_icon = '♥' |
|
|
|
|
role_style = 'CharacterAlly' |
|
|
|
|
elif character.role == CharacterType.Enemy: |
|
|
|
|
else: |
|
|
|
|
if character.character_type == CharacterType.NonPlayerCharacter: |
|
|
|
|
role_icon = '♤' |
|
|
|
|
role_style = 'CharacterEnemy' |
|
|
|
|
elif character.role == CharacterType.EnemyPlayer: |
|
|
|
|
else: |
|
|
|
|
role_icon = '♠' |
|
|
|
|
role_style = 'CharacterEnemyPlayer' |
|
|
|
|
else: |
|
|
|
|
role_icon = '*' |
|
|
|
|
role_style = 'CharacterUnknown' |
|
|
|
|
if character.max_turns == 0 or character.hp == 0: |
|
|
|
|
if character.max_turns <= 0 or character.hp == 0: |
|
|
|
|
turn_style = 'TurnDisabled' |
|
|
|
|
turn_text = '[✗]' |
|
|
|
|
turn_text = '[✖]' |
|
|
|
|
elif character.turns_left > 9: |
|
|
|
|
turn_style = 'TurnAvailable' |
|
|
|
|
turn_text = f'[#]' |
|
|
|
|
elif character.turns_left > 1: |
|
|
|
|
turn_style = 'TurnAvailable' |
|
|
|
|
turn_text = f'[{character.turns_left}]' |
|
|
|
|
elif character.turns_left == 0: |
|
|
|
|
elif character.turns_left <= 0: |
|
|
|
|
turn_style = 'TurnActed' |
|
|
|
|
turn_text = '[✓]' |
|
|
|
|
elif character.max_turns > 1: |
|
|
|
@ -151,34 +157,41 @@ class CharacterUI(WidgetWrap): |
|
|
|
|
else: |
|
|
|
|
hp_style = 'HPDown' |
|
|
|
|
hp_suffix = '✖' |
|
|
|
|
return ([ |
|
|
|
|
(turn_style, turn_text), |
|
|
|
|
' ', |
|
|
|
|
(role_style, role_icon + character.name), |
|
|
|
|
], |
|
|
|
|
[('HPLabel', hp_suffix + 'HP'), |
|
|
|
|
return ([(turn_style, turn_text), ' ', (role_style, role_icon + character.name)], |
|
|
|
|
['\u00A0\u00A0\u00A0\u00A0', |
|
|
|
|
(hp_style, hp_suffix), |
|
|
|
|
('HPLabel', 'HP'), |
|
|
|
|
'\u00A0', |
|
|
|
|
(hp_style, str(character.hp).rjust(3, '\u00A0')), |
|
|
|
|
'/', |
|
|
|
|
('HPMax', str(character.max_hp).rjust(3, '\u00A0')), |
|
|
|
|
'\u00A0 ', |
|
|
|
|
' \u00A0\u00A0\u00A0\u00A0', |
|
|
|
|
('MPLabel', '\u00A0MP'), |
|
|
|
|
'\u00A0', |
|
|
|
|
('MPValue', str(character.mp).rjust(3, '\u00A0')), |
|
|
|
|
'/', |
|
|
|
|
('MPMax', str(character.max_mp).rjust(3, '\u00A0'))] + ( |
|
|
|
|
['\u00A0 ', |
|
|
|
|
('FPLabel' if character.role in (CharacterType.Player, CharacterType.EnemyPlayer) else 'UPLabel', '\u00A0FP' if character.role in (CharacterType.Player, CharacterType.EnemyPlayer) else '\u00A0UP'), |
|
|
|
|
('MPMax', str(character.max_mp).rjust(3, '\u00A0'))] + ([ |
|
|
|
|
' \u00A0\u00A0\u00A0\u00A0', |
|
|
|
|
('FPLabel' if character.character_type == CharacterType.PlayerCharacter else 'UPLabel', |
|
|
|
|
'\u00A0FP' if character.character_type == CharacterType.PlayerCharacter else '\u00A0UP'), |
|
|
|
|
'\u00A0', |
|
|
|
|
('FPValue' if character.character_type == CharacterType.PlayerCharacter else 'UPValue', |
|
|
|
|
str(character.sp).rjust(3, '\u00A0')), |
|
|
|
|
'\u00A0' * 4 |
|
|
|
|
] if character.sp is not None else []) + ([ |
|
|
|
|
' \u00A0\u00A0\u00A0\u00A0', |
|
|
|
|
('IPLabel', '\u00A0IP'), |
|
|
|
|
'\u00A0', |
|
|
|
|
('FPValue' if character.role in (CharacterType.Player, CharacterType.EnemyPlayer) else 'UPValue', str(character.sp).rjust(3, '\u00A0')), |
|
|
|
|
'\u00A0' * 4] if character.sp is not None else []) + ( |
|
|
|
|
['\u00A0 ', ('IPLabel', '\u00A0IP'), '\u00A0', ('IPValue', str(character.ip).rjust(3, '\u00A0')), '/', |
|
|
|
|
('IPMax', str(character.max_ip).rjust(3, '\u00A0'))] if character.max_ip > 0 else []) + |
|
|
|
|
(['\n ', |
|
|
|
|
('IPValue', str(character.ip).rjust(3, '\u00A0')), |
|
|
|
|
'/', |
|
|
|
|
('IPMax', str(character.max_ip).rjust(3, '\u00A0')) |
|
|
|
|
] if character.max_ip > 0 else []) + ([ |
|
|
|
|
'\n \u00A0\u00A0\u00A0\u00A0', |
|
|
|
|
(('StatusKO', "KO") if character.hp == 0 else |
|
|
|
|
('StatusCrisis', "Crisis") if character.hp * 2 < character.max_hp else ""), |
|
|
|
|
('Statuses', |
|
|
|
|
", ".join(([""] if character.hp * 2 < character.max_hp else []) + |
|
|
|
|
", ".join( |
|
|
|
|
([""] if character.hp * 2 < character.max_hp else []) + |
|
|
|
|
sorted(list(str(s) for s in character.statuses)))), |
|
|
|
|
] if len(character.statuses) > 0 or character.hp * 2 < character.max_hp else [])) |
|
|
|
|
|
|
|
|
@ -187,7 +200,7 @@ class CharacterUI(WidgetWrap): |
|
|
|
|
self.nameText = Text(name_text) |
|
|
|
|
self.conditionText = Text(condition_text) |
|
|
|
|
self.icon = SelectableIcon( |
|
|
|
|
text=f'({character.access_key if character.access_key != "" else "*"})', cursor_position=1) |
|
|
|
|
text=f'({character.access_key if character.access_key != "" else " "})', cursor_position=1) |
|
|
|
|
super().__init__(Columns([ |
|
|
|
|
(4, Padding( |
|
|
|
|
w=AttrMap( |
|
|
|
@ -219,10 +232,8 @@ class LogMessageUI(WidgetWrap): |
|
|
|
|
align='left', |
|
|
|
|
width=3, |
|
|
|
|
right=1)), |
|
|
|
|
('weight', 1, Text([ |
|
|
|
|
('LogBold', v) if index % 2 != 0 else ('LogText', v) |
|
|
|
|
for index, v |
|
|
|
|
in enumerate(text.split("**")) if v != ""]))])) |
|
|
|
|
('weight', 1, Text(decode_discord(text, 'LogText', 'LogBold'))) |
|
|
|
|
])) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LogUI(WidgetWrap): |
|
|
|
@ -260,13 +271,11 @@ class LogUI(WidgetWrap): |
|
|
|
|
class MainUI(WidgetWrap): |
|
|
|
|
def __init__(self, clocks: ClocksUI, characters: CharactersUI, log: LogUI): |
|
|
|
|
super().__init__(Pile([ |
|
|
|
|
('weight', 5, Columns([ |
|
|
|
|
('weight', 5, LineBox(original_widget=characters, title="Character Status", |
|
|
|
|
('weight', 1, LineBox(original_widget=clocks, title="Clocks", |
|
|
|
|
title_attr="FrameTitle", title_align='left')), |
|
|
|
|
('weight', 3, LineBox(original_widget=clocks, title="Clocks", |
|
|
|
|
('weight', 7, LineBox(original_widget=characters, title="Character Status", |
|
|
|
|
title_attr="FrameTitle", title_align='left')), |
|
|
|
|
])), |
|
|
|
|
('weight', 3, LineBox(original_widget=log, title="Combat Log", |
|
|
|
|
('weight', 2, LineBox(original_widget=log, title="Log", |
|
|
|
|
title_attr="FrameTitle", title_align='left')), |
|
|
|
|
])) |
|
|
|
|
|
|
|
|
@ -282,7 +291,7 @@ class AbilityEntryPopup(WidgetWrap): |
|
|
|
|
self.hp_cost_editor = IntEdit(f'Health Points cost (of {user.hp}) ', 0) |
|
|
|
|
editors = [self.ability_name_editor, self.mp_cost_editor, self.ip_cost_editor, self.hp_cost_editor] |
|
|
|
|
if user.sp is not None: |
|
|
|
|
self.sp_cost_editor = IntEdit(f'{user.role.sp_name_abbr} cost (of {user.sp}) ', 0) |
|
|
|
|
self.sp_cost_editor = IntEdit(f'{user.character_type.sp_name_abbr} cost (of {user.sp}) ', 0) |
|
|
|
|
editors.append(self.sp_cost_editor) |
|
|
|
|
else: |
|
|
|
|
self.sp_cost_editor = None |
|
|
|
|