From eba70f0c8140614856f1c8664df4173941a9275c Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Sat, 17 Oct 2020 18:18:21 -0700 Subject: [PATCH] Separating out ads functionality --- .efrocachemap | 40 ++-- .idea/dictionaries/ericf.xml | 12 +- CHANGELOG.md | 1 + assets/.asset_manifest_public.json | 2 + assets/Makefile | 2 + .../src/ba_data/python/ba/_activitytypes.py | 3 +- assets/src/ba_data/python/ba/_ads.py | 186 ++++++++++++++++++ assets/src/ba_data/python/ba/_app.py | 34 +--- assets/src/ba_data/python/ba/_apputils.py | 130 ------------ assets/src/ba_data/python/ba/_hooks.py | 6 +- assets/src/ba_data/python/ba/_session.py | 4 +- assets/src/ba_data/python/ba/internal.py | 2 +- .../ba_data/python/bastd/ui/getcurrency.py | 3 +- .../ba_data/python/bastd/ui/specialoffer.py | 5 +- .../python/bastd/ui/tournamententry.py | 6 +- docs/ba_module.md | 2 +- 16 files changed, 234 insertions(+), 204 deletions(-) create mode 100644 assets/src/ba_data/python/ba/_ads.py diff --git a/.efrocachemap b/.efrocachemap index 1a98b554..4583763f 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -3932,24 +3932,24 @@ "assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450", "assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e", "assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f", - "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/72/93/a41a9777570bee533ab3259f1597", - "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/2d/40/964c6b36393b12b459433dcda36c", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e3/d6/e286d413e60a2e6a43b5773d6441", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/20/97/d4a3ccde682ec984a67b3d683f54", - "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/82/25/c1d9b277444a9aa46be5f1eec44a", - "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/05/36d55f280f9676e3098ed9aa6a78", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8a/8c/0a3d0b30186fec5b189a5f0407cb", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/30/1c/f554338c290026fb45bd52e9e1a7", - "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/fa/e5/e624e868b0fc00a2413ae4b9432c", - "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e8/1e/418931e11d12072c869808579592", - "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/21/21/de5c6e124de4675b11d0c73ceb28", - "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/11/f3/037c95d8bbb74730bdef686526a5", - "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1a/4a/bd980abb5c7078d1e144d19fa63d", - "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fd/cd/3397d744c7405740df4d4ae567f0", - "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/34/c2/24a9ab7d513acdb8ebaa3251611f", - "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/54/40/8e0cc564f49f8963803834ec7995", - "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/62/55/765d22f045d0d4d31a02aeffec7b", - "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4b/76/aa0554648b65da797eb30b9c4699", - "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/91/dc/c415fc19cfa0395cc200a5a72e2c", - "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cd/d8/ec9c1c0955cc62ea574a4b42a707" + "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/dd/d1/e12c5331256ecffcd7684d6d2963", + "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c5/35/8d811eec1a47f4e70d4b27eb3bf5", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6f/43/d0f61fb34a76e11b9ebd4334038d", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a4/56/614e09d8ab86355f0d65c1ce76fe", + "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f7/c5/f695953295c2d79dfb01555aea89", + "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7b/19/ff1bba3a148b6e0f0823d7d7c7bf", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f7/dd/d5e4d872192060d46821d6911c13", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/64/58/962c2e707ee66d310848f375796a", + "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/db/58/65c487facba1de6ba8ce65f19f01", + "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/40/cb/afd71350fdc9827ca99baab73001", + "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f2/05/e9252b68a96ee418da35dd4d2530", + "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/bb/a2/e1f5b3f561a08bb09cc3ffb2492f", + "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/66/c9/3b04209f599dea8b8ca4be7d3404", + "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0d/3b/b7b46c3131cff8a40dfaa001af38", + "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/57/40/0c1d88af3ce14e0f8870ab9ac7ad", + "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8a/72/02b4eddf662001f05f98288d4ad4", + "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fc/bc/51529aac7531d1a62cf13eb79153", + "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ea/7b/de9ce5284627cc77b2fffc354a66", + "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/96/00/78b64146e33ec35dcde9c278328a", + "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6d/25/0e5918fa1eb7285538d14051761c" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index a30ab2e5..5c65f9f5 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -29,8 +29,8 @@ achname achs acinstance - ack'ed ack + ack'ed acked acks acnt @@ -152,8 +152,8 @@ bacommon badguy bafoundation - ballistica's ballistica + ballistica's ballisticacore ballisticacorecb bamaster @@ -800,8 +800,8 @@ gamedata gameinstance gamemap - gamepad's gamepad + gamepad's gamepadadvanced gamepads gamepadselect @@ -1189,8 +1189,8 @@ lsqlite lssl lstart - lstr's lstr + lstr's lstrs lsval ltex @@ -1823,8 +1823,8 @@ sessionname sessionplayer sessionplayers - sessionteam's sessionteam + sessionteam's sessionteams sessiontype setactivity @@ -2156,8 +2156,8 @@ txtw typeargs typecheck - typechecker's typechecker + typechecker's typedval typeshed typestr diff --git a/CHANGELOG.md b/CHANGELOG.md index eb436088..045f4502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Plugin functionality has been consolidated into a PluginSubsystem obj at ba.app.plugins - Ditto with AccountSubsystem and ba.app.accounts - Ditto with MetadataSubsystem and ba.app.meta +- Ditto with AdsSubsystem and ba.app.ads ### 1.5.26 (20217) - Simplified licensing header on python scripts. diff --git a/assets/.asset_manifest_public.json b/assets/.asset_manifest_public.json index 79e43d36..601eaa84 100644 --- a/assets/.asset_manifest_public.json +++ b/assets/.asset_manifest_public.json @@ -6,6 +6,7 @@ "ba_data/python/ba/__pycache__/_activity.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_activitytypes.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_actor.cpython-38.opt-1.pyc", + "ba_data/python/ba/__pycache__/_ads.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_analytics.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_app.cpython-38.opt-1.pyc", "ba_data/python/ba/__pycache__/_appconfig.cpython-38.opt-1.pyc", @@ -67,6 +68,7 @@ "ba_data/python/ba/_activity.py", "ba_data/python/ba/_activitytypes.py", "ba_data/python/ba/_actor.py", + "ba_data/python/ba/_ads.py", "ba_data/python/ba/_analytics.py", "ba_data/python/ba/_app.py", "ba_data/python/ba/_appconfig.py", diff --git a/assets/Makefile b/assets/Makefile index 36d24841..b38181a3 100644 --- a/assets/Makefile +++ b/assets/Makefile @@ -137,6 +137,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ build/ba_data/python/ba/_activity.py \ build/ba_data/python/ba/_activitytypes.py \ build/ba_data/python/ba/_actor.py \ + build/ba_data/python/ba/_ads.py \ build/ba_data/python/ba/_analytics.py \ build/ba_data/python/ba/_app.py \ build/ba_data/python/ba/_appconfig.py \ @@ -375,6 +376,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ build/ba_data/python/ba/__pycache__/_activity.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_activitytypes.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_actor.cpython-38.opt-1.pyc \ + build/ba_data/python/ba/__pycache__/_ads.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_analytics.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_app.cpython-38.opt-1.pyc \ build/ba_data/python/ba/__pycache__/_appconfig.cpython-38.opt-1.pyc \ diff --git a/assets/src/ba_data/python/ba/_activitytypes.py b/assets/src/ba_data/python/ba/_activitytypes.py index c877adb9..c51655db 100644 --- a/assets/src/ba_data/python/ba/_activitytypes.py +++ b/assets/src/ba_data/python/ba/_activitytypes.py @@ -40,11 +40,10 @@ class EndSessionActivity(Activity[EmptyPlayer, EmptyTeam]): def on_begin(self) -> None: # pylint: disable=cyclic-import from bastd.mainmenu import MainMenuSession - from ba._apputils import call_after_ad from ba._general import Call super().on_begin() _ba.unlock_all_input() - call_after_ad(Call(_ba.new_host_session, MainMenuSession)) + _ba.app.ads.call_after_ad(Call(_ba.new_host_session, MainMenuSession)) class JoinActivity(Activity[EmptyPlayer, EmptyTeam]): diff --git a/assets/src/ba_data/python/ba/_ads.py b/assets/src/ba_data/python/ba/_ads.py new file mode 100644 index 00000000..74939794 --- /dev/null +++ b/assets/src/ba_data/python/ba/_ads.py @@ -0,0 +1,186 @@ +# 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 + +if TYPE_CHECKING: + from typing import Optional, 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: Optional[float] = None + self.last_ad_purpose = 'invalid' + self.attempted_first_ad = False + self.last_in_game_ad_remove_message_show_time: Optional[float] = None + self.last_ad_completion_time: Optional[float] = None + self.last_ad_was_short = False + + def do_remove_in_game_ads_message(self) -> None: + """(internal)""" + from ba._language import Lstr + from ba._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: + """(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: + """(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._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.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: Optional[float] + launch_count = app.config.get('launchCount', 0) + + # If we're seeing short ads we may want to space them differently. + interval_mult = (_ba.get_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 = _ba.get_account_misc_read_val( + 'ads.startVal1', 0.99) + else: + self.ad_amt = _ba.get_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 = _ba.get_account_misc_read_val(base + '.minLC', 0.0) + max_lc = _ba.get_account_misc_read_val(base + '.maxLC', 5.0) + min_lc_scale = (_ba.get_account_misc_read_val( + base + '.minLCScale', 0.25)) + max_lc_scale = (_ba.get_account_misc_read_val( + base + '.maxLCScale', 0.34)) + min_lc_interval = (_ba.get_account_misc_read_val( + base + '.minLCInterval', 360)) + max_lc_interval = (_ba.get_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. diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py index 9d52c9c8..8c4c25cc 100644 --- a/assets/src/ba_data/python/ba/_app.py +++ b/assets/src/ba_data/python/ba/_app.py @@ -3,7 +3,6 @@ """Functionality related to the high level state of the app.""" from __future__ import annotations -import time import random from typing import TYPE_CHECKING @@ -173,6 +172,7 @@ class App: from ba._plugin import PluginSubsystem from ba._account import AccountSubsystem from ba._meta import MetadataSubsystem + from ba._ads import AdsSubsystem # Config. self.config_file_healthy = False @@ -199,12 +199,9 @@ class App: # Misc. self.tips: List[str] = [] self.stress_test_reset_timer: Optional[ba.Timer] = None - self.last_ad_completion_time: Optional[float] = None - self.last_ad_was_short = False self.did_weak_call_warning = False self.ran_on_app_launch = False - self.last_in_game_ad_remove_message_show_time: Optional[float] = None self.log_have_new = False self.log_upload_timer_started = False self._config: Optional[ba.AppConfig] = None @@ -223,13 +220,6 @@ class App: # Server Mode. self.server: Optional[ba.ServerController] = None - # Ads. - self.last_ad_network = 'unknown' - self.last_ad_network_set_time = time.time() - self.ad_amt: Optional[float] = None - self.last_ad_purpose = 'invalid' - self.attempted_first_ad = False - self.meta = MetadataSubsystem() self.accounts = AccountSubsystem() self.plugins = PluginSubsystem() @@ -237,6 +227,7 @@ class App: self.lang = LanguageSubsystem() self.ach = AchievementSubsystem() self.ui = UISubsystem() + self.ads = AdsSubsystem() # Lobby. self.lobby_random_profile_index: int = 1 @@ -552,27 +543,6 @@ class App: _ba.fade_screen(False, endcall=_fade_end) return True - def do_remove_in_game_ads_message(self) -> None: - """(internal)""" - from ba._language import Lstr - from ba._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 on_app_shutdown(self) -> None: """(internal)""" self.music.on_app_shutdown() diff --git a/assets/src/ba_data/python/ba/_apputils.py b/assets/src/ba_data/python/ba/_apputils.py index 0e16a444..b599a7e2 100644 --- a/assets/src/ba_data/python/ba/_apputils.py +++ b/assets/src/ba_data/python/ba/_apputils.py @@ -225,133 +225,3 @@ def print_corrupt_file_error() -> None: _ba.timer(2.0, Call(_ba.playsound, _ba.getsound('error')), timetype=TimeType.REAL) - - -def show_ad(purpose: str, - on_completion_call: Callable[[], Any] = None) -> None: - """(internal)""" - _ba.app.last_ad_purpose = purpose - _ba.show_ad(purpose, on_completion_call) - - -def show_ad_2(purpose: str, - on_completion_call: Callable[[bool], Any] = None) -> None: - """(internal)""" - _ba.app.last_ad_purpose = purpose - _ba.show_ad_2(purpose, on_completion_call) - - -def call_after_ad(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._enums import TimeType - import time - app = _ba.app - show = True - - # No ads without net-connections, etc. - if not _ba.can_show_ad(): - show = False - if app.accounts.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: Optional[float] - launch_count = app.config.get('launchCount', 0) - - # If we're seeing short ads we may want to space them differently. - interval_mult = (_ba.get_account_misc_read_val( - 'ads.shortIntervalMult', 1.0) if app.last_ad_was_short else 1.0) - if app.ad_amt is None: - if launch_count <= 1: - app.ad_amt = _ba.get_account_misc_read_val( - 'ads.startVal1', 0.99) - else: - app.ad_amt = _ba.get_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 = _ba.get_account_misc_read_val(base + '.minLC', 0.0) - max_lc = _ba.get_account_misc_read_val(base + '.maxLC', 5.0) - min_lc_scale = (_ba.get_account_misc_read_val( - base + '.minLCScale', 0.25)) - max_lc_scale = (_ba.get_account_misc_read_val( - base + '.maxLCScale', 0.34)) - min_lc_interval = (_ba.get_account_misc_read_val( - base + '.minLCInterval', 360)) - max_lc_interval = (_ba.get_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) - app.ad_amt += incr - assert app.ad_amt is not None - if app.ad_amt >= 1.0: - app.ad_amt = app.ad_amt % 1.0 - app.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 (app.attempted_first_ad - and (app.last_ad_completion_time is None or - (interval is not None - and _ba.time(TimeType.REAL) - app.last_ad_completion_time > - (interval * interval_mult)))): - # Reset our other counter too in this case. - app.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 the fallback call (and issues a warning about it).""" - if not self._ran: - if fallback: - print(( - 'ERROR: relying on fallback ad-callback! ' - 'last network: ' + app.last_ad_network + ' (set ' + - str(int(time.time() - - app.last_ad_network_set_time)) + - 's ago); purpose=' + app.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) - show_ad('between_game', on_completion_call=payload.run) - else: - _ba.pushcall(call) # Just run the callback without the ad. diff --git a/assets/src/ba_data/python/ba/_hooks.py b/assets/src/ba_data/python/ba/_hooks.py index eed60f95..169a648f 100644 --- a/assets/src/ba_data/python/ba/_hooks.py +++ b/assets/src/ba_data/python/ba/_hooks.py @@ -178,8 +178,8 @@ def submit_analytics_counts(sval: str) -> None: def set_last_ad_network(sval: str) -> None: import time - _ba.app.last_ad_network = sval - _ba.app.last_ad_network_set_time = time.time() + _ba.app.ads.last_ad_network = sval + _ba.app.ads.last_ad_network_set_time = time.time() def no_game_circle_message() -> None: @@ -263,7 +263,7 @@ def quit_window() -> None: def remove_in_game_ads_message() -> None: - _ba.app.do_remove_in_game_ads_message() + _ba.app.ads.do_remove_in_game_ads_message() def telnet_access_request() -> None: diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 72b82c6f..758b2333 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -612,7 +612,7 @@ class Session: def transitioning_out_activity_was_freed( self, can_show_ad_on_death: bool) -> None: """(internal)""" - from ba._apputils import garbage_collect, call_after_ad + from ba._apputils import garbage_collect # Since things should be generally still right now, it's a good time # to run garbage collection to clear out any circular dependency @@ -622,7 +622,7 @@ class Session: with _ba.Context(self): if can_show_ad_on_death: - call_after_ad(self.begin_next_activity) + _ba.app.ads.call_after_ad(self.begin_next_activity) else: _ba.pushcall(self.begin_next_activity) diff --git a/assets/src/ba_data/python/ba/internal.py b/assets/src/ba_data/python/ba/internal.py index 4e7289f8..1d852ee1 100644 --- a/assets/src/ba_data/python/ba/internal.py +++ b/assets/src/ba_data/python/ba/internal.py @@ -18,7 +18,7 @@ from ba._input import (get_device_value, get_input_map_hash, from ba._general import getclass, json_prep, get_type_name from ba._activitytypes import JoinActivity, ScoreScreenActivity from ba._apputils import (is_browser_likely_available, get_remote_app_name, - should_submit_debug_info, show_ad, show_ad_2) + should_submit_debug_info) from ba._benchmark import (run_gpu_benchmark, run_cpu_benchmark, run_media_reload_benchmark, run_stress_test) from ba._campaign import getcampaign diff --git a/assets/src/ba_data/python/bastd/ui/getcurrency.py b/assets/src/ba_data/python/bastd/ui/getcurrency.py index d879a8e5..691fdfe1 100644 --- a/assets/src/ba_data/python/bastd/ui/getcurrency.py +++ b/assets/src/ba_data/python/bastd/ui/getcurrency.py @@ -551,7 +551,6 @@ class GetCurrencyWindow(ba.Window): # actually start the purchase locally.. def _do_purchase(self, item: str) -> None: - from ba.internal import show_ad if item == 'ad': import datetime # if ads are disabled until some time, error.. @@ -568,7 +567,7 @@ class GetCurrencyWindow(ba.Window): resource='getTicketsWindow.unavailableTemporarilyText'), color=(1, 0, 0)) elif self._enable_ad_button: - show_ad('tickets') + _ba.app.ads.show_ad('tickets') else: _ba.purchase(item) diff --git a/assets/src/ba_data/python/bastd/ui/specialoffer.py b/assets/src/ba_data/python/bastd/ui/specialoffer.py index cb11339c..3e3a5421 100644 --- a/assets/src/ba_data/python/bastd/ui/specialoffer.py +++ b/assets/src/ba_data/python/bastd/ui/specialoffer.py @@ -434,8 +434,9 @@ def show_offer() -> bool: # Space things out a bit so we don't hit the poor user with an ad and # then an in-game offer. has_been_long_enough_since_ad = True - if (app.last_ad_completion_time is not None and - (ba.time(ba.TimeType.REAL) - app.last_ad_completion_time < 30.0)): + if (app.ads.last_ad_completion_time is not None and + (ba.time(ba.TimeType.REAL) - app.ads.last_ad_completion_time < + 30.0)): has_been_long_enough_since_ad = False if app.special_offer is not None and has_been_long_enough_since_ad: diff --git a/assets/src/ba_data/python/bastd/ui/tournamententry.py b/assets/src/ba_data/python/bastd/ui/tournamententry.py index b4ee89c0..87772b07 100644 --- a/assets/src/ba_data/python/bastd/ui/tournamententry.py +++ b/assets/src/ba_data/python/bastd/ui/tournamententry.py @@ -525,7 +525,6 @@ class TournamentEntryWindow(popup.PopupWindow): self._launch() def _on_pay_with_ad_press(self) -> None: - from ba.internal import show_ad_2 # If we're already entering, ignore. if self._entering: @@ -547,8 +546,9 @@ class TournamentEntryWindow(popup.PopupWindow): cur_time = ba.time(ba.TimeType.REAL) if cur_time - self._last_ad_press_time > 5.0: self._last_ad_press_time = cur_time - show_ad_2('tournament_entry', - on_completion_call=ba.WeakCall(self._on_ad_complete)) + _ba.app.ads.show_ad_2('tournament_entry', + on_completion_call=ba.WeakCall( + self._on_ad_complete)) def _on_ad_complete(self, actually_showed: bool) -> None: diff --git a/docs/ba_module.md b/docs/ba_module.md index 249c2d76..3c2d804d 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-10-16 for Ballistica version 1.5.27 build 20219

+

last updated on 2020-10-17 for Ballistica version 1.5.27 build 20223

This page documents the Python classes and functions in the 'ba' module, which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!