diff --git a/changelog.txt b/changelog.txt index 11b572b..dad1345 100644 --- a/changelog.txt +++ b/changelog.txt @@ -102,3 +102,6 @@ Version 1.5.1 - Update to 2.1.6 Enhancements, 1.2.8 Global Aircraft stats. +Version 1.5.2 + +- Update to 1.2.9 Global Aircraft Stats diff --git a/readme.txt b/readme.txt index 956b983..d36a519 100644 --- a/readme.txt +++ b/readme.txt @@ -15,7 +15,7 @@ This bundle version is designed to work with version 1.2.50 of IL-2 stats. Disconnect mod version: 1.6 Tank mod version: 3.0.1 Stats Enhancements version : 2.1.6 -Global Aircraft Stats mod version : 1.2.8 +Global Aircraft Stats mod version : 1.2.9 DISCLAIMER: This module is NOT (currently) retroactive, it will only split the stats of new sorties. diff --git a/src/mod_stats_by_aircraft/aircraft_mod_models.py b/src/mod_stats_by_aircraft/aircraft_mod_models.py index 1330f13..835f099 100644 --- a/src/mod_stats_by_aircraft/aircraft_mod_models.py +++ b/src/mod_stats_by_aircraft/aircraft_mod_models.py @@ -600,6 +600,8 @@ class AircraftKillboard(models.Model): # Helper fields in order to detect corrupted data. # This field is only relevant for killboards without Player reset_kills_turret_bug = models.BooleanField(default=False, db_index=True) + # This field is only relevant for killboards with Player. + reset_player_loses = models.BooleanField(default=False, db_index=True) class Meta: # The long table name is to avoid any conflicts with new tables defined in the main branch of IL2 Stats. @@ -621,6 +623,7 @@ class SortieAugmentation(models.Model): player_stats_processed = models.BooleanField(default=False, db_index=True) fixed_aa_accident_stats = models.BooleanField(default=False, db_index=True) fixed_doubled_turret_killboards = models.BooleanField(default=False, db_index=True) + added_player_kb_losses = models.BooleanField(default=False, db_index=True) class Meta: # The long table name is to avoid any conflicts with new tables defined in the main branch of IL2 Stats. diff --git a/src/mod_stats_by_aircraft/background_jobs/background_job.py b/src/mod_stats_by_aircraft/background_jobs/background_job.py index 3324b11..af11464 100644 --- a/src/mod_stats_by_aircraft/background_jobs/background_job.py +++ b/src/mod_stats_by_aircraft/background_jobs/background_job.py @@ -37,7 +37,7 @@ def log_done(self): return "[mod_stats_by_aircraft]: WARNING: Programming error, unimplemented logs done method." - def reset_relevant_fields(self): + def reset_relevant_fields(self, tour_cutoff): """ Optional method. diff --git a/src/mod_stats_by_aircraft/background_jobs/fix_corrupted_aa_accident.py b/src/mod_stats_by_aircraft/background_jobs/fix_corrupted_aa_accident.py index 96a802e..66c800f 100644 --- a/src/mod_stats_by_aircraft/background_jobs/fix_corrupted_aa_accident.py +++ b/src/mod_stats_by_aircraft/background_jobs/fix_corrupted_aa_accident.py @@ -5,13 +5,13 @@ class FixCorruptedAaAccidents(BackgroundJob): """ - A bug in the early versions of 1.2.X the retroactive compute to double accidental/aa deaths. + A bug in the early versions of 1.2.X caused the retroactive compute to double accidental/aa deaths. This job resets the accidental/aa deaths, and recomputes them. Note that a simple halving would not work here, since missions processed after the bugged update did not double accidental/aa deaths. """ - def reset_relevant_fields(self): + def reset_relevant_fields(self, tour_cutoff): AircraftBucket.objects.filter(reset_accident_aa_stats=False).update( deaths_to_accident=0, deaths_to_aa=0, diff --git a/src/mod_stats_by_aircraft/background_jobs/fix_no_deaths_player_kb.py b/src/mod_stats_by_aircraft/background_jobs/fix_no_deaths_player_kb.py new file mode 100644 index 0000000..9fd2c4c --- /dev/null +++ b/src/mod_stats_by_aircraft/background_jobs/fix_no_deaths_player_kb.py @@ -0,0 +1,70 @@ +from .background_job import BackgroundJob +from stats.models import Sortie +from ..aircraft_mod_models import AircraftBucket, AircraftKillboard + + +class FixNoDeathsPlayerKB(BackgroundJob): + """ + A bug in the early versions of 1.2.X the retroactive compute to not count player deaths in aircraft overview + killboards. + + This job resets the loses, and recomputes them. Not that the reset is necessary, since the normal, not retroactive + compute does not have this issue. + """ + + def reset_relevant_fields(self, tour_cutoff): + AircraftKillboard.objects.filter( + aircraft_1__player__isnull=False, + reset_player_loses=False, + tour__id__gte=tour_cutoff + ).update( + aircraft_2_kills=0, + aircraft_2_shotdown=0, + aircraft_2_assists=0, + aircraft_2_pk_assists=0, + aircraft_2_distinct_hits=0, + reset_player_loses=True + ) + + AircraftKillboard.objects.filter( + aircraft_2__player__isnull=False, + reset_player_loses=False, + tour__id__gte=tour_cutoff + ).update( + aircraft_1_kills=0, + aircraft_1_shotdown=0, + aircraft_1_assists=0, + aircraft_1_pk_assists=0, + aircraft_1_distinct_hits=0, + reset_player_loses=True + ) + + def query_find_sorties(self, tour_cutoff): + return (Sortie.objects.filter(SortieAugmentation_MOD_STATS_BY_AIRCRAFT__added_player_kb_losses=False, + aircraft__cls_base='aircraft', tour__id__gte=tour_cutoff) + .order_by('-tour__id')) + + def compute_for_sortie(self, sortie): + from ..stats_whore import process_log_entries, get_sortie_type + + buckets = [(AircraftBucket.objects.get_or_create(tour=sortie.tour, aircraft=sortie.aircraft, + filter_type='NO_FILTER', player=None))[0]] + filter_type = get_sortie_type(sortie) + has_subtype = filter_type != 'NO_FILTER' + if has_subtype: + buckets.append((AircraftBucket.objects.get_or_create(tour=sortie.tour, aircraft=sortie.aircraft, + filter_type=filter_type, player=None))[0]) + + for bucket in buckets: + process_log_entries(bucket, sortie, has_subtype, bucket.filter_type != 'NO_FILTER', + compute_only_pure_killboard_stats=True, stop_update_primary_bucket=True) + + sortie.SortieAugmentation_MOD_STATS_BY_AIRCRAFT.added_player_kb_losses = True + sortie.SortieAugmentation_MOD_STATS_BY_AIRCRAFT.save() + + def log_update(self, to_compute): + return '[mod_stats_by_aircraft]: Adding loses in player aircraft killboards {} sorties left to process.' \ + .format(to_compute) + + def log_done(self): + return '[mod_stats_by_aircraft]: Completed adding loses in player aircraft killboards.' diff --git a/src/mod_stats_by_aircraft/background_jobs/fix_turret_killboards.py b/src/mod_stats_by_aircraft/background_jobs/fix_turret_killboards.py index 1cb8d58..2393a95 100644 --- a/src/mod_stats_by_aircraft/background_jobs/fix_turret_killboards.py +++ b/src/mod_stats_by_aircraft/background_jobs/fix_turret_killboards.py @@ -11,7 +11,7 @@ class FixTurretKillboards(BackgroundJob): This job resets all the killoboards, Elos and lethalities,, and recomputes them. """ - def reset_relevant_fields(self): + def reset_relevant_fields(self, tour_cutoff): AircraftBucket.objects.filter(player=None, reset_elo=False).update( elo=1500, pilot_kills=0, @@ -41,7 +41,8 @@ def compute_for_sortie(self, sortie): filter_type=filter_type, player=None))[0]) for bucket in buckets: process_log_entries(bucket, sortie, has_subtype, bucket.filter_type != 'NO_FILTER', - compute_only_pure_killboard_stats=True) + compute_only_pure_killboard_stats=True, + do_not_use_pilot_kbs=True) sortie.SortieAugmentation_MOD_STATS_BY_AIRCRAFT.fixed_doubled_turret_killboards = True sortie.SortieAugmentation_MOD_STATS_BY_AIRCRAFT.save() diff --git a/src/mod_stats_by_aircraft/background_jobs/player_retro_compute.py b/src/mod_stats_by_aircraft/background_jobs/player_retro_compute.py index 3b5e8fc..e825553 100644 --- a/src/mod_stats_by_aircraft/background_jobs/player_retro_compute.py +++ b/src/mod_stats_by_aircraft/background_jobs/player_retro_compute.py @@ -24,25 +24,24 @@ def compute_for_sortie(self, sortie): process_aircraft_stats(sortie, sortie.player) - if sortie.is_lost_aircraft: - bucket = (AircraftBucket.objects.get_or_create(tour=sortie.tour, aircraft=sortie.aircraft, - filter_type='NO_FILTER', player=None))[0] - filter_type = get_sortie_type(sortie) - has_subtype = filter_type != 'NO_FILTER' + bucket = (AircraftBucket.objects.get_or_create(tour=sortie.tour, aircraft=sortie.aircraft, + filter_type='NO_FILTER', player=None))[0] + filter_type = get_sortie_type(sortie) + has_subtype = filter_type != 'NO_FILTER' + + # To update killboards of buckets with Player shotdown in this sortie, + # and also AA/accident shotdowns/deaths + process_log_entries(bucket, sortie, has_subtype, False, stop_update_primary_bucket=True) + bucket.save() + if has_subtype: + bucket = (AircraftBucket.objects.get_or_create(tour=sortie.tour, aircraft=sortie.aircraft, + filter_type=filter_type, player=None))[0] # To update killboards of buckets with Player shotdown in this sortie, # and also AA/accident shotdowns/deaths - process_log_entries(bucket, sortie, has_subtype, False, stop_update_primary_bucket=True) + process_log_entries(bucket, sortie, True, True, stop_update_primary_bucket=True) bucket.save() - if has_subtype: - bucket = (AircraftBucket.objects.get_or_create(tour=sortie.tour, aircraft=sortie.aircraft, - filter_type=filter_type, player=None))[0] - # To update killboards of buckets with Player shotdown in this sortie, - # and also AA/accident shotdowns/deaths - process_log_entries(bucket, sortie, True, True, stop_update_primary_bucket=True) - bucket.save() - def log_update(self, to_compute): return '[mod_stats_by_aircraft]: Retroactively computing player aircraft stats. {} sorties left to process.' \ .format(to_compute) diff --git a/src/mod_stats_by_aircraft/background_jobs/run_background_jobs.py b/src/mod_stats_by_aircraft/background_jobs/run_background_jobs.py index d50d4e0..d7c5c40 100644 --- a/src/mod_stats_by_aircraft/background_jobs/run_background_jobs.py +++ b/src/mod_stats_by_aircraft/background_jobs/run_background_jobs.py @@ -5,12 +5,14 @@ from .player_retro_compute import PlayerRetroCompute from .fix_corrupted_aa_accident import FixCorruptedAaAccidents from .fix_turret_killboards import FixTurretKillboards +from .fix_no_deaths_player_kb import FixNoDeathsPlayerKB from stats.models import Tour from stats.logger import logger import config # Subclasses of BackgroundJob, see background_job.py -jobs = [FullRetroCompute(), PlayerRetroCompute(), FixCorruptedAaAccidents(), FixTurretKillboards()] +jobs = [FullRetroCompute(), PlayerRetroCompute(), FixCorruptedAaAccidents(), FixTurretKillboards(), + FixNoDeathsPlayerKB()] LOG_COUNTER = 0 LOGGING_INTERVAL = 5 # How many batches are run before an update log is produced. @@ -29,8 +31,12 @@ def reset_corrupted_data(): Note this must be done before any new mission is processed, otherwise the new data would be overwritten if reset later after the mission is processed. """ + tour_cutoff = __get_tour_cutoff() + if tour_cutoff is None: + return + for job in jobs: - job.reset_relevant_fields() + job.reset_relevant_fields(tour_cutoff) @transaction.atomic @@ -40,11 +46,10 @@ def run_background_jobs(): @returns True if some work was done, False if there is no more work left to do. """ - max_id = Tour.objects.aggregate(Max('id'))['id__max'] - if max_id is None: # Edge case: No tour yet - return False - tour_cutoff = max_id - RETRO_COMPUTE_FOR_LAST_TOURS + tour_cutoff = __get_tour_cutoff() + if tour_cutoff is None: + return False for job in jobs: work_done = __run_background_job(job, tour_cutoff) @@ -74,3 +79,11 @@ def __run_background_job(job, tour_cutoff): LOG_COUNTER = 0 return True + + +def __get_tour_cutoff(): + max_id = Tour.objects.aggregate(Max('id'))['id__max'] + if max_id is None: # Edge case: No tour yet + return None + + return max_id - RETRO_COMPUTE_FOR_LAST_TOURS diff --git a/src/mod_stats_by_aircraft/migrations/0005_fix_player_killboards_losses.py b/src/mod_stats_by_aircraft/migrations/0005_fix_player_killboards_losses.py new file mode 100644 index 0000000..7248e66 --- /dev/null +++ b/src/mod_stats_by_aircraft/migrations/0005_fix_player_killboards_losses.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2021-05-06 18:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mod_stats_by_aircraft', '0004_fix_killboard_stats'), + ] + + operations = [ + migrations.AddField( + model_name='aircraftkillboard', + name='reset_player_loses', + field=models.BooleanField(db_index=True, default=False), + ), + migrations.AddField( + model_name='sortieaugmentation', + name='added_player_kb_losses', + field=models.BooleanField(db_index=True, default=False), + ), + ] diff --git a/src/mod_stats_by_aircraft/stats_whore.py b/src/mod_stats_by_aircraft/stats_whore.py index caff60a..5a5cb3a 100644 --- a/src/mod_stats_by_aircraft/stats_whore.py +++ b/src/mod_stats_by_aircraft/stats_whore.py @@ -366,6 +366,7 @@ def stats_whore(m_report_file): # ======================== MODDED PART BEGIN +# TODO: Refactor these functions into a new file. # This should be run after the other objects have been saved, otherwise it will not work. def process_aircraft_stats(sortie, player=None): if not sortie.aircraft.cls_base == "aircraft": @@ -440,11 +441,12 @@ def process_bucket(bucket, sortie, has_subtype, is_subtype): sortie_augmentation.player_stats_processed = True sortie_augmentation.fixed_aa_accident_stats = True sortie_augmentation.fixed_doubled_turret_killboards = True + sortie_augmentation.added_player_kb_losses = True sortie_augmentation.save() def process_log_entries(bucket, sortie, has_subtype, is_subtype, stop_update_primary_bucket=False, - compute_only_pure_killboard_stats=False): + compute_only_pure_killboard_stats=False, do_not_use_pilot_kbs=False): events = (LogEntry.objects .select_related('act_object', 'act_sortie', 'cact_object', 'cact_sortie') .filter(Q(act_sortie_id=sortie.id), @@ -471,9 +473,7 @@ def process_log_entries(bucket, sortie, has_subtype, is_subtype, stop_update_pri enemies_killed.add(enemy_plane_sortie_pair) use_pilot_kbs = bucket.player is None - if compute_only_pure_killboard_stats: - # This is True while we're recomputing corrupted killboards which don't have players. - # So we don't want to update deaths to turret for players. + if do_not_use_pilot_kbs: use_pilot_kbs = False enemy_buckets, kbs = update_from_entries(bucket, enemies_damaged, enemies_killed, enemies_shotdown, has_subtype, is_subtype, use_pilot_kbs, @@ -481,6 +481,7 @@ def process_log_entries(bucket, sortie, has_subtype, is_subtype, stop_update_pri for killboard in kbs.values(): killboard.reset_kills_turret_bug = True + killboard.reset_player_loses = True killboard.save() for enemy_bucket in enemy_buckets.values(): enemy_bucket.update_derived_fields() @@ -545,9 +546,7 @@ def process_log_entries(bucket, sortie, has_subtype, is_subtype, stop_update_pri if stop_update_primary_bucket: update_primary_bucket = False use_pilot_kbs = bucket.player is not None - if compute_only_pure_killboard_stats: - # This is True while we're recomputing corrupted killboards which don't have players. - # So we don't want to update deaths to turret for players. + if do_not_use_pilot_kbs: use_pilot_kbs = False buckets, kbs = update_from_entries(turret_bucket, enemy_damaged, enemy_killed, enemy_shotdown, @@ -564,6 +563,7 @@ def process_log_entries(bucket, sortie, has_subtype, is_subtype, stop_update_pri for kb in kbs.values(): kb.reset_kills_turret_bug = True + kb.reset_player_loses = True kb.save() @@ -934,3 +934,4 @@ def turret_to_aircraft_bucket(turret_name, tour, player=None): logger.info("[mod_stats_by_aircraft] WARNING: Could not find aircraft for turret " + turret_name) return None # ======================== MODDED PART END +