# Released under the MIT License. See LICENSE for details.
#
"""Defines Actor Type(s)."""
from __future__ import annotations
from typing import TYPE_CHECKING, override
import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Callable
[docs]
class OnScreenCountdown(bs.Actor):
"""A Handy On-Screen Timer.
Useful for time-based games that count down to zero.
"""
def __init__(self, duration: int, endcall: Callable[[], Any] | None = None):
"""Duration is provided in seconds."""
super().__init__()
self._timeremaining = duration
self._ended = False
self._endcall = endcall
self.node = bs.newnode(
'text',
attrs={
'v_attach': 'top',
'h_attach': 'center',
'h_align': 'center',
'color': (1, 1, 0.5, 1),
'flatness': 0.5,
'shadow': 0.5,
'position': (0, -70),
'scale': 1.4,
'text': '',
},
)
self.inputnode = bs.newnode(
'timedisplay',
attrs={
'time2': duration * 1000,
'timemax': duration * 1000,
'timemin': 0,
},
)
self.inputnode.connectattr('output', self.node, 'text')
self._countdownsounds = {
10: bs.getsound('announceTen'),
9: bs.getsound('announceNine'),
8: bs.getsound('announceEight'),
7: bs.getsound('announceSeven'),
6: bs.getsound('announceSix'),
5: bs.getsound('announceFive'),
4: bs.getsound('announceFour'),
3: bs.getsound('announceThree'),
2: bs.getsound('announceTwo'),
1: bs.getsound('announceOne'),
}
self._timer: bs.Timer | None = None
[docs]
def start(self) -> None:
"""Start the timer."""
globalsnode = bs.getactivity().globalsnode
globalsnode.connectattr('time', self.inputnode, 'time1')
self.inputnode.time2 = (
globalsnode.time + (self._timeremaining + 1) * 1000
)
self._timer = bs.Timer(1.0, self._update, repeat=True)
[docs]
@override
def on_expire(self) -> None:
super().on_expire()
# Release callbacks/refs.
self._endcall = None
def _update(self, forcevalue: int | None = None) -> None:
if forcevalue is not None:
tval = forcevalue
else:
self._timeremaining = max(0, self._timeremaining - 1)
tval = self._timeremaining
# If there's a countdown sound for this time that we
# haven't played yet, play it.
if tval == 10:
assert self.node
assert isinstance(self.node.scale, float)
self.node.scale *= 1.2
cmb = bs.newnode('combine', owner=self.node, attrs={'size': 4})
cmb.connectattr('output', self.node, 'color')
bs.animate(cmb, 'input0', {0: 1.0, 0.15: 1.0}, loop=True)
bs.animate(cmb, 'input1', {0: 1.0, 0.15: 0.5}, loop=True)
bs.animate(cmb, 'input2', {0: 0.1, 0.15: 0.0}, loop=True)
cmb.input3 = 1.0
if tval <= 10 and not self._ended:
bs.getsound('tick').play()
if tval in self._countdownsounds:
self._countdownsounds[tval].play()
if tval <= 0 and not self._ended:
self._ended = True
if self._endcall is not None:
self._endcall()
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum