-
-- + +
diff --git a/app/bloonsa_api/urls.py b/app/bloonsa_api/urls.py index 5033254..8f8466b 100644 --- a/app/bloonsa_api/urls.py +++ b/app/bloonsa_api/urls.py @@ -1,6 +1,8 @@ from django.urls import path, include -from bloonsa_api.views import LoadLevel, RandomLevel, CompleteLevel, RateLevel +from bloonsa_api.views import (LoadLevel, RandomLevel, + CompleteLevel, RateLevel, + GetStatusData) app_name = "bloonsa_api" @@ -8,5 +10,7 @@ urlpatterns = [ path("load_level", LoadLevel.as_view(), name="api-load_level"), path("get_random_level", RandomLevel.as_view(), name="api-get_random_level"), path("complete_level", CompleteLevel.as_view(), name="api-complete_level"), - path("rate_level", RateLevel.as_view(), name="api-rate_level") + path("rate_level", RateLevel.as_view(), name="api-rate_level"), + + path("get_status_data", GetStatusData.as_view(), name="api-get_status_data") ] diff --git a/app/bloonsa_api/views.py b/app/bloonsa_api/views.py index 203a490..ffa64d2 100644 --- a/app/bloonsa_api/views.py +++ b/app/bloonsa_api/views.py @@ -1,5 +1,9 @@ +import json + +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render, HttpResponse -from django.http import HttpResponseBadRequest +from django.http import HttpResponseBadRequest, JsonResponse from django.views.generic import TemplateView from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt @@ -21,94 +25,127 @@ class LoadLevel(CSRFexemptTemplateView): if level_id is None or not level_id.isdigit(): return HttpResponseBadRequest() - level = Level.objects.filter(levelId=int(level_id)).first() + level: Level = Level.objects.filter(level_id=int(level_id)).first() if level is None: return HttpResponseBadRequest() if request.user.is_authenticated: bloonsa_util.tag_player(request=request) - player = Player.objects.get(user=request.user) + player: Player = Player.objects.get(user=request.user) player.bloonsa_levels_played.add(level) bloonsa_util.log(player=player, action=actions.bloonsa_load_level_by_id, - note=level.levelId) + note=level.level_id) - flashVars = level.getFlashVars(seperator="&") + flashVars = level.get_flash_vars(seperator="&") return HttpResponse(flashVars) class RandomLevel(CSRFexemptTemplateView): def post(self, request, *args, **kwargs): - level = Level.objects.order_by("?").first() - flashVars = level.getFlashVars(seperator="&") + level: Level = Level.objects.order_by("?").first() + flashVars = level.get_flash_vars(seperator="&") if request.user.is_authenticated: bloonsa_util.tag_player(request=request) - player = Player.objects.get(user=request.user) + player: Player = Player.objects.get(user=request.user) level.played_by.add(player) bloonsa_util.log(player=player, action=actions.bloonsa_load_random_level, - note=level.levelId) + note=level.level_id) return HttpResponse(flashVars) def get(self, request, *args, **kwargs): return self.post(request=request, *args, **kwargs) + class CompleteLevel(CSRFexemptTemplateView): + def post(self, request, *args, **kwargs): - if request.user.is_authenticated: - bloonsa_util.tag_player(request=request) - level_id = int(request.POST.get("level_id")) - darts_left = int(request.POST.get("darts_left")) - pops = int(request.POST.get("pops")) + if not request.user.is_authenticated: + return HttpResponse(content="GG", status=200) - level = Level.objects.get(levelId=level_id) - player = Player.objects.get(user=request.user) + bloonsa_util.tag_player(request=request) + level_id = int(request.POST.get("level_id")) + darts_left = int(request.POST.get("darts_left")) + pops = int(request.POST.get("pops")) - prevScore = player.bloonsa_level_scores.first() - if prevScore is None \ - or pops > prevScore.pops \ - or pops == prevScore.pops and darts_left > prevScore.darts_left: - score = LevelScore.objects.create(level=level, - clear=True, - darts_left=darts_left, - pops=pops) - if prevScore: - player.bloonsa_level_scores.remove(prevScore) + level: Level = Level.objects.get(level_id=level_id) + player: Player = Player.objects.get(user=request.user) - player.bloonsa_level_scores.add(score) - score.save() - player.save() - bloonsa_util.log(player=player, - action=actions.bloonsa_submit_score, - note=level.levelId) + prevScore = player.bloonsa_level_scores.first() + if prevScore is None \ + or pops > prevScore.pops \ + or pops == prevScore.pops and darts_left > prevScore.darts_left: + score = LevelScore.objects.create(level=level, + clear=True, + darts_left=darts_left, + pops=pops) + if prevScore: + player.bloonsa_level_scores.remove(prevScore) + + player.bloonsa_level_scores.add(score) + score.save() + player.save() + bloonsa_util.log(player=player, + action=actions.bloonsa_submit_score, + note=level.level_id) # Sending empty content means error in as3 return HttpResponse(content="GG", status=200) class RateLevel(CSRFexemptTemplateView): - def post(self, request, *args, **kwargs): - if request.user.is_authenticated: - bloonsa_util.tag_player(request=request) - rating = int(request.POST.get("rating")) - level_id = int(request.POST.get("level_id")) - level = Level.objects.get(levelId=level_id) - player = Player.objects.get(user=request.user) - ratingObject = Player.objects.filter(bloonsa_level_ratings__level=level).first() - bloonsa_util.log(player=player, - action=actions.bloonsa_rate_level, - note=level.levelId) - if ratingObject: - ratingObject.rating = rating - ratingObject.save() - return HttpResponse(content="OK", status=200) - rating = LevelRating.objects.create(level=level, - rating=rating) - player.bloonsa_level_ratings.add(rating) - rating.save() - player.save() + def post(self, request, *args, **kwargs): + if not request.user.is_authenticated: return HttpResponse(content="OK", status=200) - return HttpResponse(status=400) + bloonsa_util.tag_player(request=request) + rating = int(request.POST.get("rating")) + level_id = int(request.POST.get("level_id")) + level: Level = Level.objects.get(level_id=level_id) + player: Player = Player.objects.get(user=request.user) + ratingObject = player.bloonsa_level_ratings.filter(level=level).first() + bloonsa_util.log(player=player, + action=actions.bloonsa_rate_level, + note=level.level_id) + if ratingObject: + ratingObject.rating = rating + ratingObject.save() + return HttpResponse(content="OK", status=200) + + rating = LevelRating.objects.create(level=level, + rating=rating) + player.bloonsa_level_ratings.add(rating) + rating.save() + player.save() + return HttpResponse(content="OK", status=200) + + + +class GetStatusData(CSRFexemptTemplateView): + + def post(self, request, *args, **kwargs): + bloonsa_util.tag_player(request=request) + level_id = int(json.loads(request.body).get("level_id")) + level = Level.objects.get(level_id=level_id) + player = Player.objects.get(user=request.user) + total_levels = Level.objects.count() + level_cleared = bool(player.bloonsa_level_scores.filter()) + + jdata = { + "bloonsa_levels_played": player.bloonsa_levels_played_count, + "bloonsa_levels_beaten": player.bloonsa_levels_beaten_count, + "bloonsa_total_levels": total_levels, + + "level_author_name": level.author.name, + "level_author_id": level.author.id, + "level_title": level.title, + "level_id": level.level_id, + "level_legacy_plays": level.plays, + "level_plays": level.play_count, + "level_rating": level.rating, + "level_cleared": level_cleared, + } + return JsonResponse(jdata) \ No newline at end of file diff --git a/app/bloonsa_game/migrations/0018_rename_authorid_author_author_id_and_more.py b/app/bloonsa_game/migrations/0018_rename_authorid_author_author_id_and_more.py new file mode 100644 index 0000000..7a72f7c --- /dev/null +++ b/app/bloonsa_game/migrations/0018_rename_authorid_author_author_id_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.6 on 2025-02-14 13:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bloonsa_game', '0017_alter_levelrating_rating'), + ] + + operations = [ + migrations.RenameField( + model_name='author', + old_name='authorId', + new_name='author_id', + ), + migrations.RenameField( + model_name='level', + old_name='levelId', + new_name='level_id', + ), + ] diff --git a/app/bloonsa_game/models.py b/app/bloonsa_game/models.py index ab32376..b2d75b6 100644 --- a/app/bloonsa_game/models.py +++ b/app/bloonsa_game/models.py @@ -6,36 +6,39 @@ from users.models import Player class Author(models.Model): name = models.CharField(max_length=255) - authorId = models.IntegerField() + author_id = models.IntegerField() def __str__(self): - return f"{self.authorId} - {self.name}" + return f"{self.author_id} - {self.name}" class Level(models.Model): author = models.ForeignKey(Author, on_delete=models.CASCADE) title = models.CharField(max_length=16) - levelId = models.IntegerField() + level_id = models.IntegerField() darts = models.SmallIntegerField() target = models.SmallIntegerField() plays = models.IntegerField() - beats = models.IntegerField() + beats = models.IntegerField() # TODO can be removed? rating = models.FloatField(null=True, blank=True) data = models.CharField(max_length=288) played_by = models.ManyToManyField(Player, blank=True, related_name="bloonsa_levels_played") + @property + def play_count(self): + return self.played_by.count() + def __str__(self): return f"{self.author} - {self.title}" - - def getFlashVars(self, seperator): + def get_flash_vars(self, seperator): flashVars = { - "levelNum": self.levelId, + "levelNum": self.level_id, "title": self.title, "darts": self.darts, "target": self.target, "data": self.data, - "authorID": self.author.authorId, + "authorID": self.author.author_id, "rating": self.rating if self.rating else 0.0, } flashVarsStr = f"{seperator}".join([f"{k}={v}" diff --git a/app/bloonsa_game/static/bloonsa_game/css/append.css b/app/bloonsa_game/static/bloonsa_game/css/append.css index 67b67ce..a0d7234 100644 --- a/app/bloonsa_game/static/bloonsa_game/css/append.css +++ b/app/bloonsa_game/static/bloonsa_game/css/append.css @@ -26,4 +26,41 @@ .wide.centered { white-space: nowrap; +} + +#level-title, +#level-id, +#level-author, +#level-author-id +#level-clear-state { + white-space: break-spaces; + float: left; + padding-left: 0px; + padding-right: 10px; +} + +#level-id, +#level-author-id { + color: #59b1ff; +} + +#current-level-container { + height: 110px; +} + +#levels_played, #levels_beaten, #total_levels, .profile_text { + white-space: break-spaces; + float: left; + padding-left: 0px; + padding-right: 0px; +} + +#logout_button { + float: left; + display: block; + font-size: 24px; +} + +#level-clear-state { + } \ No newline at end of file diff --git a/app/bloonsa_game/static/bloonsa_game/js/flash_handler.js b/app/bloonsa_game/static/bloonsa_game/js/flash_handler.js index 3d42abd..2ce2274 100644 --- a/app/bloonsa_game/static/bloonsa_game/js/flash_handler.js +++ b/app/bloonsa_game/static/bloonsa_game/js/flash_handler.js @@ -1,7 +1,46 @@ -function bloonsa_new_level_started(id) { - console.log("new level started! id=" + id) +function bloonsa_new_level_started(level_id) { + console.log("call to new_level_started"); + bloonsa_update_data(level_id); } -function bloonsa_level_completed(id) { - console.log("level completed! id=" + id) +function bloonsa_level_completed(level_id) { + bloonsa_update_data(level_id) +} + +function bloonsa_update_data(level_id) { + const xhr = new XMLHttpRequest(); + console.log("call to update_data"); + xhr.open("POST", "/bloonsa_api/get_status_data", true); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4 && xhr.status === 200) { + try { + var r = JSON.parse(xhr.response); + + bloonsa_update_html(r); + + } catch (error) { + console.error("Error:", error); + } + } + }; + xhr.send(JSON.stringify({ + level_id: level_id + })); +} + +function bloonsa_update_html(r) { + document.getElementById("level-title").textContent = r.level_title; + document.getElementById("level-id").textContent = `(${r.level_id})`; + document.getElementById("level-author").textContent = `by ${r.level_author_name}`; + document.getElementById("level-author-id").textContent = `(${r.level_author_id})`; + if (r.level_cleared) { + document.getElementById("level-clear-state").textContent = `✅`; + } + document.getElementById("levels_played").textContent = `${r.bloonsa_levels_played}`; + document.getElementById("levels_beaten").textContent = `${r.bloonsa_levels_beaten}`; + document.getElementById("total_levels").textContent = `${r.bloonsa_total_levels}`; + + + } \ No newline at end of file diff --git a/app/bloonsa_game/static/bloonsa_game/misc/bloons_unlimited.swf b/app/bloonsa_game/static/bloonsa_game/misc/bloons_unlimited.swf index 5d0c062..487ff00 100644 Binary files a/app/bloonsa_game/static/bloonsa_game/misc/bloons_unlimited.swf and b/app/bloonsa_game/static/bloonsa_game/misc/bloons_unlimited.swf differ diff --git a/app/bloonsa_game/templates/bloonsa_game/base.html b/app/bloonsa_game/templates/bloonsa_game/base.html index 95287d8..cd3e669 100644 --- a/app/bloonsa_game/templates/bloonsa_game/base.html +++ b/app/bloonsa_game/templates/bloonsa_game/base.html @@ -68,11 +68,10 @@
-
-