Skip to content

Commit

Permalink
Merge pull request #42 from kosmotive/dev/fix-badges
Browse files Browse the repository at this point in the history
Award Carrier/Peach/Quad-kill/Ace also to non-registered squad members
  • Loading branch information
kostrykin authored Oct 6, 2024
2 parents 76d9fb4 + 5af485a commit 7a4133d
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 76 deletions.
3 changes: 0 additions & 3 deletions django/accounts/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,6 @@ def test_one_missing(self, mock__SquadMembership__update_stats):

self.assertEqual(len(ScheduledNotification.objects.all()), 1)
notification_text = ScheduledNotification.objects.get().text
print('-' * 80)
print(notification_text)
print('-' * 80)
self.assertEqual(
notification_text,
'We have changes in the 30-days leaderboard! 🎆' '\n'
Expand Down
4 changes: 4 additions & 0 deletions django/static/base.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.nowrap {
white-space: nowrap;
}

a:link, a:visited, a:active, a:hover {
color: #3633fb;
text-decoration: underline;
Expand Down
24 changes: 24 additions & 0 deletions django/stats/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ class MatchParticipationInline(admin.TabularInline):
ordering = ('team', '-adr')


@admin.action(description='Award missing badges')
def award_missing_badges(modeladmin, request, queryset):
for pmatch in queryset.all():
pmatch.award_badges()


@admin.action(description='Re-award badges')
def reaward_badges(modeladmin, request, queryset):
for pmatch in queryset.all():
MatchBadge.objects.filter(
participation__pmatch = pmatch,
).exclude(
badge_type__slug = 'surpass-yourself',
).delete()
pmatch.award_badges(mute_discord = True)


@admin.register(Match)
class MatchAdmin(admin.ModelAdmin):

Expand All @@ -36,6 +53,13 @@ def has_add_permission(self, request, obj = None):
MatchParticipationInline,
]

actions = [
award_missing_badges,
reaward_badges,
]

list_max_show_all = 1000

@admin.display(description='Session')
def session_list(self, pmatch):
sessions = pmatch.sessions.all()
Expand Down
102 changes: 79 additions & 23 deletions django/stats/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,18 @@ def create_from_data(data: dict) -> Self:
squad = Squad.objects.get(uuid = squad_id)
squad.handle_new_match(m)

m.award_badges()
return m

def award_badges(self, mute_discord = False):
"""
Award the badges for all who participated in this match.
This does not include badges which require the previous match history.
"""
for mp in self.matchparticipation_set.all():
MatchBadge.award(mp, mute_discord = mute_discord)

def __str__(self):
return f'{self.map_name} ({self.date_and_time})'

Expand Down Expand Up @@ -729,10 +739,6 @@ def get_or_none(qs, **kwargs):
except ObjectDoesNotExist:
return None
except MultipleObjectsReturned:
print('-' * 10)
for obj in qs.filter(**kwargs).all():
print(obj)
print('-' * 10)
raise


Expand Down Expand Up @@ -1037,13 +1043,18 @@ class MatchBadge(models.Model):
frequency = models.PositiveSmallIntegerField(null = False, default = 1)

@staticmethod
def award(participation, old_participations):
def award(participation, **kwargs):
MatchBadge.award_kills_in_one_round_badges(participation, 5, 'ace', **kwargs)
MatchBadge.award_kills_in_one_round_badges(participation, 4, 'quad-kill', **kwargs)
MatchBadge.award_margin_badge(participation, 'carrier', order = '-adr', margin = 1.8, emoji = '🍆', **kwargs)
MatchBadge.award_margin_badge(
participation, 'peach', order = 'adr', margin = 0.67, emoji = '🍑', max_adr = 50, max_kd = 0.5, **kwargs,
)

@staticmethod
def award_with_history(participation, old_participations):
if len(old_participations) >= 10:
MatchBadge.award_surpass_yourself_badge(participation, old_participations[-20:])
MatchBadge.award_kills_in_one_round_badges(participation, 5, 'ace')
MatchBadge.award_kills_in_one_round_badges(participation, 4, 'quad-kill')
MatchBadge.award_margin_badge(participation, 'carrier', order = '-adr', margin = 1.8, emoji = '🍆')
MatchBadge.award_margin_badge(participation, 'peach', order = 'adr', margin = 0.75, emoji = '🍑')

@staticmethod
def award_surpass_yourself_badge(participation, old_participations):
Expand All @@ -1068,7 +1079,7 @@ def award_surpass_yourself_badge(participation, old_participations):
m.squad.notify_on_discord(text)

@staticmethod
def award_kills_in_one_round_badges(participation, kill_number, badge_type_slug):
def award_kills_in_one_round_badges(participation, kill_number, badge_type_slug, mute_discord = False):
badge_type = MatchBadgeType.objects.get(slug = badge_type_slug)
if MatchBadge.objects.filter(badge_type=badge_type, participation=participation).exists():
return
Expand All @@ -1081,33 +1092,55 @@ def award_kills_in_one_round_badges(participation, kill_number, badge_type_slug)
f'<{participation.player.steamid}> has achieved **{badge_type.name}**{frequency} on '
f'*{participation.pmatch.map_name}* recently!'
)
for m in participation.player.squad_memberships.all():
m.squad.notify_on_discord(text)
if not mute_discord:
for m in participation.player.squad_memberships.all():
m.squad.notify_on_discord(text)

@staticmethod
def award_margin_badge(participation, badge_type_slug, order, margin, emoji):
def award_margin_badge(participation, badge_type_slug, order, margin, emoji, mute_discord = False, **bounds):
kpi = order[1:] if order[0] in '+-' else order
badge_type = MatchBadgeType.objects.get(slug = badge_type_slug)
if MatchBadge.objects.filter(badge_type=badge_type, participation=participation).exists():
return
teammates = participation.pmatch.matchparticipation_set.filter(team = participation.team).order_by(order)

awarded = teammates[0].pk == participation.pk and any(
(
order[0] == '-' and getattr(teammates[0], kpi) > margin * getattr(teammates[1], kpi),
order[0] != '-' and getattr(teammates[0], kpi) < margin * getattr(teammates[1], kpi),
)
)
# Define the requirements for the badge
requirements = [
teammates[0].pk == participation.pk,
any(
(
order[0] == '-' and getattr(teammates[0], kpi) > margin * getattr(teammates[1], kpi),
order[0] != '-' and getattr(teammates[0], kpi) < margin * getattr(teammates[1], kpi),
)
),
]

if awarded:
# Add the bound checks to the requirements
req_bounds = list()
for bound_key, bound_val in bounds.items():
func_name, attr_name = bound_key.split('_')
attr = getattr(participation, attr_name)
match func_name:
case 'min':
req_bounds.append(attr >= bound_val)
case 'max':
req_bounds.append(attr <= bound_val)
case _:
raise ValueError(f'Invalid function name: "{func_name}"')
if req_bounds:
requirements.append(any(req_bounds))

# Check the requirements and award the badge
if all(requirements):
log.info(f'{participation.player.name} received the {badge_type.name}')
MatchBadge.objects.create(badge_type = badge_type, participation = participation)
text = (
f'{emoji} <{participation.player.steamid}> has qualified for the **{badge_type.name}** '
f'on *{participation.pmatch.map_name}*!'
)
for m in participation.player.squad_memberships.all():
m.squad.notify_on_discord(text)
if not mute_discord:
for m in participation.player.squad_memberships.all():
m.squad.notify_on_discord(text)

class Meta:
verbose_name = 'Match-based badge'
Expand All @@ -1119,6 +1152,29 @@ class Meta:
)
]

def __str__(self):
return (
f'{self.frequency}x '
f'{self.badge_type.name} for {self.participation.player.name} '
f'({self.participation.player.steamid})'
)

def __eq__(self, other):
return (
isinstance(other, MatchBadge)
and self.participation.pk == other.participation.pk
and self.badge_type.pk == other.badge_type.pk
)

def __hash__(self):
return hash(
(
self.participation.pk,
self.badge_type.pk,
self.frequency,
)
)


class UpdateTask(models.Model):
"""
Expand Down Expand Up @@ -1250,7 +1306,7 @@ def run(self):
continue

participation = pmatch.get_participation(self.account.steam_profile)
MatchBadge.award(participation, old_participations)
MatchBadge.award_with_history(participation, old_participations)

old_participations.append(participation)

Expand Down
20 changes: 14 additions & 6 deletions django/stats/static/stats.css
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ div.squad-members-appendix .legend-item {
vertical-align: middle;
margin-left: 5mm;
position: relative;
width: 70mm;
height: 3.5em;
width: 85mm;
height: 3.1em;
margin-top: 2mm;
margin-bottom: 2mm;
}
Expand All @@ -314,7 +314,7 @@ div.squad-members-appendix .legend-item .badge {
left: 0;
top: 0;
border-right: 1px solid #ccc;
padding-right: 2mm;
padding-right: 3mm;
height: 100%;
background-size: 85%;
background-repeat: no-repeat;
Expand All @@ -326,15 +326,15 @@ div.squad-members-appendix .legend-item .legend-item-label {
vertical-align: middle;
font-size: 100%;
position: absolute;
left: 10mm;
left: 12mm;
white-space: nowrap;
}

div.squad-members-appendix .legend-item .legend-item-description {
display: block;
font-size: 77%;
position: absolute;
left: 10mm;
left: 12mm;
top: 1.8em;
}

Expand Down Expand Up @@ -665,14 +665,22 @@ div.session .match-scoreboard table tr td .player-name {
margin-left: 1mm;
}

div.session .match-scoreboard table tr td .badge-list {
div.session .match-scoreboard table tr.is-not-squad-member td {
opacity: 0.5;
}

div.session .match-scoreboard table tr.is-squad-member td .badge-list {
display: inline-block;
height: 0;
overflow: visible;
transform: translate(0px, -1.05em);
filter: drop-shadow(0 0 1mm #ccc);
}

div.session .match-scoreboard table tr.is-not-squad-member td .badge-list {
display: none;
}

div.session .match-scoreboard table tr td .badge {
width: auto;
height: 2em;
Expand Down
2 changes: 1 addition & 1 deletion django/stats/templates/stats/sessions-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

{% for mp in m.matchparticipation_set.all %}{% if mp.team == team_idx|add:"0" %}

<tr>
<tr class="{% if mp.player.steamid in session.participated_squad_members_steamids %}is-squad-member{% else %}is-not-squad-member{% endif %}">
<td>
<div class="first-item">
<img src="{{ mp.player.avatar_s }}" class="avatar">
Expand Down
2 changes: 1 addition & 1 deletion django/stats/templates/stats/squads.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
<div class="legend-item">
<div style="background-image: url('{% static 'badges' %}/peach.png');" class="badge"></div>
<div class="legend-item-label">Peach Price</div>
<div class="legend-item-description">Be the red lantern in your team, with a substantial deficit in ADR.</div>
<div class="legend-item-description">Be the red lantern of your team with <span class="nowrap">K/D &leq;0.5</span>, <span class="nowrap">ADR &leq;50</span>, and a substantial deficit in ADR.</div>
</div>

</div>
Expand Down
Loading

0 comments on commit 7a4133d

Please sign in to comment.