vh-bombsquad-modded-server-.../dist/ba_data/python/ba/_ads.py
2024-02-20 23:04:51 +05:30

225 lines
8.5 KiB
Python

# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to ads."""
from __future__ import annotations
import time
from typing import TYPE_CHECKING
import _ba
from ba import _internal
if TYPE_CHECKING:
from typing import Callable, Any
class AdsSubsystem:
"""Subsystem for ads functionality in the app.
Category: **App Classes**
Access the single shared instance of this class at 'ba.app.ads'.
"""
def __init__(self) -> None:
self.last_ad_network = 'unknown'
self.last_ad_network_set_time = time.time()
self.ad_amt: float | None = None
self.last_ad_purpose = 'invalid'
self.attempted_first_ad = False
self.last_in_game_ad_remove_message_show_time: float | None = None
self.last_ad_completion_time: float | None = None
self.last_ad_was_short = False
def do_remove_in_game_ads_message(self) -> None:
"""(internal)"""
from ba._language import Lstr
from ba._generated.enums import TimeType
# Print this message once every 10 minutes at most.
tval = _ba.time(TimeType.REAL)
if self.last_in_game_ad_remove_message_show_time is None or (
tval - self.last_in_game_ad_remove_message_show_time > 60 * 10
):
self.last_in_game_ad_remove_message_show_time = tval
with _ba.Context('ui'):
_ba.timer(
1.0,
lambda: _ba.screenmessage(
Lstr(
resource='removeInGameAdsText',
subs=[
(
'${PRO}',
Lstr(resource='store.bombSquadProNameText'),
),
('${APP_NAME}', Lstr(resource='titleText')),
],
),
color=(1, 1, 0),
),
timetype=TimeType.REAL,
)
def show_ad(
self, purpose: str, on_completion_call: Callable[[], Any] | None = None
) -> None:
"""(internal)"""
self.last_ad_purpose = purpose
_ba.show_ad(purpose, on_completion_call)
def show_ad_2(
self,
purpose: str,
on_completion_call: Callable[[bool], Any] | None = None,
) -> None:
"""(internal)"""
self.last_ad_purpose = purpose
_ba.show_ad_2(purpose, on_completion_call)
def call_after_ad(self, call: Callable[[], Any]) -> None:
"""Run a call after potentially showing an ad."""
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
from ba._generated.enums import TimeType
app = _ba.app
show = True
# No ads without net-connections, etc.
if not _ba.can_show_ad():
show = False
if app.accounts_v1.have_pro():
show = False # Pro disables interstitials.
try:
session = _ba.get_foreground_host_session()
assert session is not None
is_tournament = session.tournament_id is not None
except Exception:
is_tournament = False
if is_tournament:
show = False # Never show ads during tournaments.
if show:
interval: float | None
launch_count = app.config.get('launchCount', 0)
# If we're seeing short ads we may want to space them differently.
interval_mult = (
_internal.get_v1_account_misc_read_val(
'ads.shortIntervalMult', 1.0
)
if self.last_ad_was_short
else 1.0
)
if self.ad_amt is None:
if launch_count <= 1:
self.ad_amt = _internal.get_v1_account_misc_read_val(
'ads.startVal1', 0.99
)
else:
self.ad_amt = _internal.get_v1_account_misc_read_val(
'ads.startVal2', 1.0
)
interval = None
else:
# So far we're cleared to show; now calc our
# ad-show-threshold and see if we should *actually* show
# (we reach our threshold faster the longer we've been
# playing).
base = 'ads' if _ba.has_video_ads() else 'ads2'
min_lc = _internal.get_v1_account_misc_read_val(
base + '.minLC', 0.0
)
max_lc = _internal.get_v1_account_misc_read_val(
base + '.maxLC', 5.0
)
min_lc_scale = _internal.get_v1_account_misc_read_val(
base + '.minLCScale', 0.25
)
max_lc_scale = _internal.get_v1_account_misc_read_val(
base + '.maxLCScale', 0.34
)
min_lc_interval = _internal.get_v1_account_misc_read_val(
base + '.minLCInterval', 360
)
max_lc_interval = _internal.get_v1_account_misc_read_val(
base + '.maxLCInterval', 300
)
if launch_count < min_lc:
lc_amt = 0.0
elif launch_count > max_lc:
lc_amt = 1.0
else:
lc_amt = (float(launch_count) - min_lc) / (max_lc - min_lc)
incr = (1.0 - lc_amt) * min_lc_scale + lc_amt * max_lc_scale
interval = (
1.0 - lc_amt
) * min_lc_interval + lc_amt * max_lc_interval
self.ad_amt += incr
assert self.ad_amt is not None
if self.ad_amt >= 1.0:
self.ad_amt = self.ad_amt % 1.0
self.attempted_first_ad = True
# After we've reached the traditional show-threshold once,
# try again whenever its been INTERVAL since our last successful
# show.
elif self.attempted_first_ad and (
self.last_ad_completion_time is None
or (
interval is not None
and _ba.time(TimeType.REAL) - self.last_ad_completion_time
> (interval * interval_mult)
)
):
# Reset our other counter too in this case.
self.ad_amt = 0.0
else:
show = False
# If we're *still* cleared to show, actually tell the system to show.
if show:
# As a safety-check, set up an object that will run
# the completion callback if we've returned and sat for 10 seconds
# (in case some random ad network doesn't properly deliver its
# completion callback).
class _Payload:
def __init__(self, pcall: Callable[[], Any]):
self._call = pcall
self._ran = False
def run(self, fallback: bool = False) -> None:
"""Run fallback call (and issue a warning about it)."""
if not self._ran:
if fallback:
print(
(
'ERROR: relying on fallback ad-callback! '
'last network: '
+ app.ads.last_ad_network
+ ' (set '
+ str(
int(
time.time()
- app.ads.last_ad_network_set_time
)
)
+ 's ago); purpose='
+ app.ads.last_ad_purpose
)
)
_ba.pushcall(self._call)
self._ran = True
payload = _Payload(call)
with _ba.Context('ui'):
_ba.timer(
5.0,
lambda: payload.run(fallback=True),
timetype=TimeType.REAL,
)
self.show_ad('between_game', on_completion_call=payload.run)
else:
_ba.pushcall(call) # Just run the callback without the ad.