Skip to content

Commit

Permalink
Updated map ban for bo1 matches, added select captain feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Qwizi committed May 11, 2024
1 parent d74cbd4 commit aa8b8e3
Show file tree
Hide file tree
Showing 11 changed files with 527 additions and 108 deletions.
18 changes: 18 additions & 0 deletions src/matches/migrations/0027_alter_match_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-05-10 14:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('matches', '0026_match_embed'),
]

operations = [
migrations.AlterField(
model_name='match',
name='status',
field=models.CharField(choices=[('CREATED', 'Created'), ('CAPTAINS_SELECT', 'Captains Select'), ('MAP_VETO', 'Map Veto'), ('READY_TO_LOAD', 'Ready To Load'), ('LOADED', 'Loaded'), ('LIVE', 'Live'), ('FINISHED', 'Finished'), ('CANCELLED', 'Cancelled')], default='CREATED', max_length=255),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 5.0.6 on 2024-05-10 16:22

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('matches', '0027_alter_match_status'),
]

operations = [
migrations.AlterField(
model_name='match',
name='last_map_ban',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='matches_last_map_ban', to='matches.mapban'),
),
migrations.AlterField(
model_name='match',
name='last_map_pick',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='matches_last_map_pick', to='matches.mappick'),
),
]
71 changes: 56 additions & 15 deletions src/matches/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

class MatchStatus(models.TextChoices):
CREATED = "CREATED"
STARTED = "STARTED"
CAPTAINS_SELECT = "CAPTAINS_SELECT"
MAP_VETO = "MAP_VETO"
READY_TO_LOAD = "READY_TO_LOAD"
LOADED = "LOADED"
LIVE = "LIVE"
FINISHED = "FINISHED"
Expand Down Expand Up @@ -133,9 +135,9 @@ class Match(models.Model):
map_picks = models.ManyToManyField(
MapPick, related_name="matches_map_picks", blank=True
)
last_map_ban = models.ForeignKey(MapBan, on_delete=models.CASCADE, related_name="matches_last_map_ban", null=True,
last_map_ban = models.ForeignKey(MapBan, on_delete=models.SET_NULL, related_name="matches_last_map_ban", null=True,
blank=True)
last_map_pick = models.ForeignKey(MapPick, on_delete=models.CASCADE, related_name="matches_last_map_pick",
last_map_pick = models.ForeignKey(MapPick, on_delete=models.SET_NULL, related_name="matches_last_map_pick",
null=True, blank=True)
maplist = models.JSONField(null=True, blank=True)
cvars = models.JSONField(null=True, blank=True)
Expand All @@ -149,6 +151,9 @@ class Match(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"<{self.status} - {self.config.name} - {self.pk}>"

@property
def api_key_header(self):
return "Authorization"
Expand All @@ -157,8 +162,6 @@ def api_key_header(self):
def load_match_command_name(self):
return "matchzy_loadmatch_url"

def __str__(self):
return f"<{self.status} - {self.config.name} - {self.pk}>"

def get_team1_players_dict(self):
return {
Expand Down Expand Up @@ -218,21 +221,26 @@ def ban_map(self, team, map):

if self.config.type == MatchType.BO1:
# 6 bans
map_bans_count = self.map_bans.objects.count()
map_bans_count = self.map_bans.count()
# 7 maps
map_pool_count = self.config.map_pool.maps.count() - 1
if map_bans_count == map_pool_count:
# Select the last map
map_to_select = self.config.map_pool.maps.exclude(map_bans__match=self).first()
self.maplist.append(map_to_select.tag)
map_to_select = self.config.map_pool.maps.exclude(tag__in=self.map_bans.values_list("map__tag", flat=True)).exclude(
tag__in=self.map_picks.values_list("map__tag", flat=True)).first()
self.maplist = [map_to_select.tag]
self.status = MatchStatus.READY_TO_LOAD
elif self.config.type == MatchType.BO3:
# 4 bans
map_bans_count = self.map_bans.objects.count()
map_bans_count = self.map_bans.count()
# 7 maps
map_pool_count = self.config.map_pool.maps.count() - 3
if map_bans_count == map_pool_count:
map_to_select = self.config.map_pool.maps.exclude(map_bans__match=self).first()
map_to_select = self.config.map_pool.maps.exclude(
tag__in=self.map_bans.values_list("map__tag", flat=True)).exclude(
tag__in=self.map_picks.values_list("map__tag", flat=True)).first()
self.maplist.append(map_to_select.tag)
self.status = MatchStatus.READY_TO_LOAD

self.save()
return self
Expand Down Expand Up @@ -267,8 +275,12 @@ def change_teams_name(self):
def add_player_to_match(self, player, team: str = None):
if team:
if team == "team1":
if self.team2.players.filter(id=player.id).exists():
self.team2.players.remove(player)
self.team1.players.add(player)
elif team == "team2":
if self.team1.players.filter(id=player.id).exists():
self.team1.players.remove(player)
self.team2.players.add(player)
else:
if self.team1.players.count() < self.team2.players.count():
Expand Down Expand Up @@ -309,18 +321,47 @@ def remove_player_from_match(self, player):
return self

def start_match(self):
self.status = MatchStatus.STARTED
if self.config.shuffle_teams:
self.shuffle_players()
self.change_teams_name()
self.status = MatchStatus.MAP_VETO
else:
self.team1.leader = self.team1.players.first()
self.status = MatchStatus.CAPTAINS_SELECT

self.save()
return self

def set_team_captain(self, player, team):
if team == "team1":
print("Setting team1 captain")
self.team1.leader = player
self.team1.save()
self.team2.leader = self.team2.players.first()
elif team == "team2":
print("Setting team2 captain")
self.team2.leader = player
self.team2.save()
self.change_teams_name()
if self.team1.leader and self.team2.leader:
self.status = MatchStatus.MAP_VETO
self.change_teams_name()
self.save()
return self

def get_team_by_player(self, player):
if player in self.team1.players.all():
return self.team1
elif player in self.team2.players.all():
return self.team2
return None

def get_maps_left(self):
maps_left = self.config.map_pool.maps.exclude(tag__in=self.map_bans.values_list("map__tag", flat=True)).exclude(
tag__in=self.map_picks.values_list("map__tag", flat=True))
if self.config.type == MatchType.BO1 and len(maps_left) == 1:
return []
return [map.tag for map in maps_left]

def get_next_ban_team(self):
return self.team1 if self.last_map_ban.team == self.team2 else self.team2

@receiver(post_save, sender=Match)
def match_post_save(sender, instance, created, **kwargs):
Expand Down Expand Up @@ -440,7 +481,7 @@ def match_post_save(sender, instance, created, **kwargs):
```
"""
team2_field.save()
if instance.status == MatchStatus.STARTED:
if instance.status == MatchStatus.READY_TO_LOAD:
if instance.server:
server_detail_field = EmbedField.objects.create(
order=instance.embed.fields.last().order + 1,
Expand Down
35 changes: 30 additions & 5 deletions src/matches/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from guilds.models import Guild
from guilds.serializers import GuildSerializer, EmbedSerializer
from matches.models import Map, MapBan, MapPick, Match, MatchType, MatchStatus, MatchConfig, MapPool
from matches.validators import ValidDiscordUser, DiscordUserCanJoinMatch, DiscordUserCanLeaveMatch, TeamCanBeJoined
from matches.validators import ValidDiscordUser, DiscordUserCanJoinMatch, DiscordUserCanLeaveMatch, TeamCanBeJoined, \
DiscordUserIsInMatch, ValidMap, MapCanBeBanned
from players.models import DiscordUser
from players.serializers import TeamSerializer, DiscordUserSerializer
from servers.models import Server
Expand Down Expand Up @@ -76,6 +77,7 @@ class MatchzyConfigSerializer(serializers.Serializer):
)
)
clinch_series = serializers.BooleanField()
wingman = serializers.BooleanField(required=False)
players_per_team = serializers.IntegerField()
cvars = serializers.DictField(required=False)

Expand Down Expand Up @@ -223,20 +225,26 @@ class InteractionUserSerializer(serializers.Serializer):
interaction_user_id = serializers.CharField(required=True, validators=[ValidDiscordUser()])


class MatchBanMapSerializer(InteractionUserSerializer):
map_tag = serializers.CharField(required=True)
class MatchBanMapSerializer(serializers.Serializer):
interaction_user_id = serializers.CharField(required=True, validators=[ValidDiscordUser(), DiscordUserIsInMatch()])
map_tag = serializers.CharField(required=True, validators=[ValidMap(), MapCanBeBanned()])


class MatchPickMapSerializer(MatchBanMapSerializer):
pass


class MatchBanMapResultSerializer(serializers.Serializer):
match = serializers.SerializerMethodField(method_name="get_match")
banned_map = serializers.SerializerMethodField(method_name="get_banned_map")
next_ban_team = serializers.SerializerMethodField(method_name="get_next_ban_team")
maps_left = serializers.ListField(child=serializers.CharField())
map_bans_count = serializers.IntegerField()


def get_match(self, obj) -> MatchSerializer:
return MatchSerializer(self.context["match"], context={"request": self.context["request"]}).data

def get_banned_map(self, obj) -> MapSerializer:
return MapSerializer(self.context["banned_map"]).data

Expand All @@ -260,9 +268,26 @@ def get_next_pick_team(self, obj) -> TeamSerializer:
class MatchPlayerJoin(serializers.Serializer):
interaction_user_id = serializers.CharField(required=True,
validators=[ValidDiscordUser(), DiscordUserCanJoinMatch()])
team = serializers.CharField(required=False, validators=[TeamCanBeJoined(interaction_user_id)])
team = serializers.CharField(required=False, validators=[TeamCanBeJoined()])

def validate_team(self, value):
if value not in ["team1", "team2"]:
raise serializers.ValidationError("Team must be either 'team1' or 'team2'")
return value


class MatchPlayerLeave(serializers.Serializer):
interaction_user_id = serializers.CharField(required=True,
validators=[ValidDiscordUser(), DiscordUserCanLeaveMatch()])
validators=[ValidDiscordUser(), DiscordUserCanLeaveMatch(),
DiscordUserIsInMatch()])


class MatchSelectCaptain(serializers.Serializer):
team = serializers.CharField(required=True)
interaction_user_id = serializers.CharField(required=True, validators=[ValidDiscordUser(),
DiscordUserIsInMatch(), ])

def validate_team(self, value):
if value not in ["team1", "team2"]:
raise serializers.ValidationError("Team must be either 'team1' or 'team2'")
return value
Loading

0 comments on commit aa8b8e3

Please sign in to comment.