Progress on js exec stuff

This commit is contained in:
Walter 2025-02-14 16:31:28 +01:00
parent 53a4734cae
commit 919d4acf6f
12 changed files with 229 additions and 86 deletions

View File

@ -1,6 +1,8 @@
from django.urls import path, include 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" app_name = "bloonsa_api"
@ -8,5 +10,7 @@ urlpatterns = [
path("load_level", LoadLevel.as_view(), name="api-load_level"), path("load_level", LoadLevel.as_view(), name="api-load_level"),
path("get_random_level", RandomLevel.as_view(), name="api-get_random_level"), path("get_random_level", RandomLevel.as_view(), name="api-get_random_level"),
path("complete_level", CompleteLevel.as_view(), name="api-complete_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")
] ]

View File

@ -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.shortcuts import render, HttpResponse
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest, JsonResponse
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt 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(): if level_id is None or not level_id.isdigit():
return HttpResponseBadRequest() 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: if level is None:
return HttpResponseBadRequest() return HttpResponseBadRequest()
if request.user.is_authenticated: if request.user.is_authenticated:
bloonsa_util.tag_player(request=request) 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) player.bloonsa_levels_played.add(level)
bloonsa_util.log(player=player, bloonsa_util.log(player=player,
action=actions.bloonsa_load_level_by_id, 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) return HttpResponse(flashVars)
class RandomLevel(CSRFexemptTemplateView): class RandomLevel(CSRFexemptTemplateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
level = Level.objects.order_by("?").first() level: Level = Level.objects.order_by("?").first()
flashVars = level.getFlashVars(seperator="&") flashVars = level.get_flash_vars(seperator="&")
if request.user.is_authenticated: if request.user.is_authenticated:
bloonsa_util.tag_player(request=request) 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) level.played_by.add(player)
bloonsa_util.log(player=player, bloonsa_util.log(player=player,
action=actions.bloonsa_load_random_level, action=actions.bloonsa_load_random_level,
note=level.levelId) note=level.level_id)
return HttpResponse(flashVars) return HttpResponse(flashVars)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return self.post(request=request, *args, **kwargs) return self.post(request=request, *args, **kwargs)
class CompleteLevel(CSRFexemptTemplateView): class CompleteLevel(CSRFexemptTemplateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if request.user.is_authenticated: if not request.user.is_authenticated:
bloonsa_util.tag_player(request=request) return HttpResponse(content="GG", status=200)
level_id = int(request.POST.get("level_id"))
darts_left = int(request.POST.get("darts_left"))
pops = int(request.POST.get("pops"))
level = Level.objects.get(levelId=level_id) bloonsa_util.tag_player(request=request)
player = Player.objects.get(user=request.user) 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() level: Level = Level.objects.get(level_id=level_id)
if prevScore is None \ player: Player = Player.objects.get(user=request.user)
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) prevScore = player.bloonsa_level_scores.first()
score.save() if prevScore is None \
player.save() or pops > prevScore.pops \
bloonsa_util.log(player=player, or pops == prevScore.pops and darts_left > prevScore.darts_left:
action=actions.bloonsa_submit_score, score = LevelScore.objects.create(level=level,
note=level.levelId) 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 # Sending empty content means error in as3
return HttpResponse(content="GG", status=200) return HttpResponse(content="GG", status=200)
class RateLevel(CSRFexemptTemplateView): 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, def post(self, request, *args, **kwargs):
rating=rating) if not request.user.is_authenticated:
player.bloonsa_level_ratings.add(rating)
rating.save()
player.save()
return HttpResponse(content="OK", status=200) 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)

View File

@ -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',
),
]

View File

@ -6,36 +6,39 @@ from users.models import Player
class Author(models.Model): class Author(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
authorId = models.IntegerField() author_id = models.IntegerField()
def __str__(self): def __str__(self):
return f"{self.authorId} - {self.name}" return f"{self.author_id} - {self.name}"
class Level(models.Model): class Level(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE) author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=16) title = models.CharField(max_length=16)
levelId = models.IntegerField() level_id = models.IntegerField()
darts = models.SmallIntegerField() darts = models.SmallIntegerField()
target = models.SmallIntegerField() target = models.SmallIntegerField()
plays = models.IntegerField() plays = models.IntegerField()
beats = models.IntegerField() beats = models.IntegerField() # TODO can be removed?
rating = models.FloatField(null=True, blank=True) rating = models.FloatField(null=True, blank=True)
data = models.CharField(max_length=288) data = models.CharField(max_length=288)
played_by = models.ManyToManyField(Player, blank=True, related_name="bloonsa_levels_played") 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): def __str__(self):
return f"{self.author} - {self.title}" return f"{self.author} - {self.title}"
def get_flash_vars(self, seperator):
def getFlashVars(self, seperator):
flashVars = { flashVars = {
"levelNum": self.levelId, "levelNum": self.level_id,
"title": self.title, "title": self.title,
"darts": self.darts, "darts": self.darts,
"target": self.target, "target": self.target,
"data": self.data, "data": self.data,
"authorID": self.author.authorId, "authorID": self.author.author_id,
"rating": self.rating if self.rating else 0.0, "rating": self.rating if self.rating else 0.0,
} }
flashVarsStr = f"{seperator}".join([f"{k}={v}" flashVarsStr = f"{seperator}".join([f"{k}={v}"

View File

@ -27,3 +27,40 @@
.wide.centered { .wide.centered {
white-space: nowrap; 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 {
}

View File

@ -1,7 +1,46 @@
function bloonsa_new_level_started(id) { function bloonsa_new_level_started(level_id) {
console.log("new level started! id=" + id) console.log("call to new_level_started");
bloonsa_update_data(level_id);
} }
function bloonsa_level_completed(id) { function bloonsa_level_completed(level_id) {
console.log("level completed! id=" + 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}`;
} }

View File

@ -68,11 +68,10 @@
<div id="content"> <div id="content">
{% block content %}{% endblock content %} {% block content %}{% endblock content %}
<div class="wide left"> <div id="current-level-container" class="wide left">
<p> <p>
<h1 class="level-title-container"></h1> <h1 id="level-title"></h1><h1 id="level-id"></h1><h1 id="level-clear-state"></h1><h1></h1>
<h3 class="level-author-container"></h3><br> <h3 id="level-author"></h3><h3 id="level-author-id"></h3><br>
<p class="level-id-container"></p>
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,7 +1,10 @@
{% load bloonsa_game_tags %} {% load bloonsa_game_tags %}
<div id="profile_box"> <div id="profile_box">
<h3>{{ user }}</h3> <h3 id="username">{{ user }}</h3>
<p>Levels played: {{ player.levels_played }}<br> <div class="profile_text">Levels played: </div>
Levels beaten: {{ player.levels_beaten }} / {{ total_levels }}</p> <div id="levels_played">{{ player.bloonsa_levels_played_count }}</div><br>
<h3><a href="/users/logout">Logout</a></h3> <div class="profile_text">Levels beaten: </div>
<div id="levels_beaten">{{ player.bloonsa_levels_beaten_count }}</div>
<div class="profile_text"> / </div><div id="total_levels">{{ total_levels }}</div><br>
<a href="/users/logout" id="logout_button"><b>Logout</b></a>
</div> </div>

View File

@ -34,7 +34,7 @@ class GameView(TemplateView):
return render(request, "bloonsa_game/level.html", context={ return render(request, "bloonsa_game/level.html", context={
"player": player, "player": player,
"total_levels": total_levels, "total_levels": total_levels,
"flashVars": level.getFlashVars(seperator="&amp;"), "flashVars": level.get_flash_vars(seperator="&amp;"),
"levelTitle": level.title, "levelTitle": level.title,
"levelAuthor": level.author, "levelAuthor": level.author,
}) })

View File

@ -14,16 +14,16 @@ def run():
cursor.execute("SELECT * FROM Levels") cursor.execute("SELECT * FROM Levels")
for (levelNum, title, authorname, authorID, numPlays, numCompleted, rating, target, darts, data) in tqdm(cursor.fetchall()): for (levelNum, title, authorname, authorID, numPlays, numCompleted, rating, target, darts, data) in tqdm(cursor.fetchall()):
authorObj = Author.objects.filter(authorId=authorID).first() authorObj = Author.objects.filter(author_id=authorID).first()
if not authorObj: if not authorObj:
authorObj = Author.objects.create(authorId=authorID, authorObj = Author.objects.create(author_id=authorID,
name=authorname) name=authorname)
authorObj.save() authorObj.save()
levelObj = Level.objects.filter(levelId=levelNum).first() levelObj = Level.objects.filter(level_id=levelNum).first()
if not levelObj: if not levelObj:
levelObj = Level.objects.create(author=authorObj, levelObj = Level.objects.create(author=authorObj,
levelId=levelNum, level_id=levelNum,
title=title, title=title,
plays=numPlays, plays=numPlays,
beats=numCompleted, beats=numCompleted,

View File

@ -18,17 +18,15 @@ class Player(models.Model):
# If bloonsa_levels_played gets replaced then this needs to be updated # If bloonsa_levels_played gets replaced then this needs to be updated
@property @property
def levels_played(self): def bloonsa_levels_played_count(self):
return self.bloonsa_levels_played.count() return self.bloonsa_levels_played.count()
@property @property
def levels_beaten(self): def bloonsa_levels_beaten_count(self):
return self.bloonsa_level_scores.filter(clear=True).count() return self.bloonsa_level_scores.filter(clear=True).count()
@property def has_beaten_level(self, level):
def total_levels(self): bool(self.bloonsa_level_scores.filter(clear=True, level=level).first())
# TODO
return 0
def __str__(self): def __str__(self):
statesDict = { statesDict = {