Fixing achievement notifications
This commit is contained in:
parent
67a80cdca1
commit
a0d6aea811
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ node-modules/
|
|||||||
game-assets/
|
game-assets/
|
||||||
backup/
|
backup/
|
||||||
update_users.py
|
update_users.py
|
||||||
|
updateusers.py
|
||||||
skinimporter.py
|
skinimporter.py
|
||||||
package.json
|
package.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|||||||
@ -43,6 +43,10 @@ app = Flask(__name__)
|
|||||||
app.config['SECRET_KEY'] = secret_key
|
app.config['SECRET_KEY'] = secret_key
|
||||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||||
|
|
||||||
|
socket_to_user_id = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# MongoDB setup
|
# MongoDB setup
|
||||||
client = MongoClient('mongodb://localhost:27017/')
|
client = MongoClient('mongodb://localhost:27017/')
|
||||||
db = client['resonance_rumble']
|
db = client['resonance_rumble']
|
||||||
@ -56,6 +60,44 @@ active_sockets_collection = db['active_sockets']
|
|||||||
oauth_states = db['oauth_states']
|
oauth_states = db['oauth_states']
|
||||||
achievements_collection = db['achievements']
|
achievements_collection = db['achievements']
|
||||||
|
|
||||||
|
def update_user_stat(user_id, stat_name, value, operation='inc'):
|
||||||
|
if user_id is None:
|
||||||
|
print("Cannot update stats for guest or unknown user")
|
||||||
|
return
|
||||||
|
|
||||||
|
update = {}
|
||||||
|
if operation == 'inc':
|
||||||
|
update = {'$inc': {f'stats.{stat_name}': value}}
|
||||||
|
elif operation == 'max':
|
||||||
|
update = {'$max': {f'stats.{stat_name}': value}}
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid operation")
|
||||||
|
|
||||||
|
result = users_collection.update_one({'_id': user_id}, update)
|
||||||
|
if result.modified_count > 0:
|
||||||
|
check_achievements(user_id)
|
||||||
|
else:
|
||||||
|
print(f"Failed to update stat for user_id: {user_id}")
|
||||||
|
|
||||||
|
def check_achievements(user_id):
|
||||||
|
user = users_collection.find_one({'_id': user_id})
|
||||||
|
if not user:
|
||||||
|
print(f"User not found for id: {user_id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
user_stats = user.get('stats', {})
|
||||||
|
|
||||||
|
achievements_to_check = [
|
||||||
|
{'id': 'kill_10_enemies', 'name': 'Killing spree', 'condition': lambda s: s['enemies_killed'] >= 10},
|
||||||
|
{'id': 'kill_100_enemies', 'name': 'Killing frenzy', 'condition': lambda s: s['enemies_killed'] >= 100},
|
||||||
|
{'id': 'collect_50_coins', 'name': 'Coin collector', 'condition': lambda s: s['coins_collected'] >= 50},
|
||||||
|
{'id': 'reach_level_10', 'name': 'Ding!', 'condition': lambda s: s['highest_level'] >= 10},
|
||||||
|
]
|
||||||
|
|
||||||
|
for achievement in achievements_to_check:
|
||||||
|
if achievement['id'] not in user['achievements'] and achievement['condition'](user_stats):
|
||||||
|
award_achievement(user_id, achievement['id'])
|
||||||
|
|
||||||
def decode_token(token):
|
def decode_token(token):
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
|
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||||
@ -124,6 +166,20 @@ def award_achievement(user_id, achievement_id):
|
|||||||
{'$push': {'achievements': achievement_id}}
|
{'$push': {'achievements': achievement_id}}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
achievement = achievements_collection.find_one({'id': achievement_id})
|
||||||
|
if achievement:
|
||||||
|
# Find the socket ID for this user
|
||||||
|
player_socket_id = next((sid for sid, player in game_state['players'].items() if player.name == user['username']), None)
|
||||||
|
|
||||||
|
if player_socket_id:
|
||||||
|
socketio.emit('achievement_unlocked', {
|
||||||
|
'name': achievement['name'],
|
||||||
|
'description': achievement['description'],
|
||||||
|
'image': achievement.get('image', '')
|
||||||
|
}, room=player_socket_id)
|
||||||
|
else:
|
||||||
|
print(f"Could not find socket ID for user {user['username']}")
|
||||||
|
|
||||||
send_discord_alert(f"🏆 Player {user['username']} has earned the '{achievement['name']}' achievement!", user['username'])
|
send_discord_alert(f"🏆 Player {user['username']} has earned the '{achievement['name']}' achievement!", user['username'])
|
||||||
|
|
||||||
# Send a DM to the user if they have a linked Discord account
|
# Send a DM to the user if they have a linked Discord account
|
||||||
@ -851,7 +907,14 @@ def signup():
|
|||||||
},
|
},
|
||||||
"unlocked_skins": [ObjectId("669e1272813db368f3b19519")], # Glowy Blue
|
"unlocked_skins": [ObjectId("669e1272813db368f3b19519")], # Glowy Blue
|
||||||
"unlocked_classes": default_class_ids,
|
"unlocked_classes": default_class_ids,
|
||||||
"selected_class": default_class_ids[0] if default_class_ids else None
|
"selected_class": default_class_ids[0] if default_class_ids else None,
|
||||||
|
'achievements': [],
|
||||||
|
'stats': {
|
||||||
|
'enemies_killed': 0,
|
||||||
|
'coins_collected': 0,
|
||||||
|
'highest_level': 1,
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
users_collection.insert_one(new_user)
|
users_collection.insert_one(new_user)
|
||||||
|
|
||||||
@ -1025,6 +1088,8 @@ def on_disconnect():
|
|||||||
socketio.emit('player_disconnected', player_id, to=main_room)
|
socketio.emit('player_disconnected', player_id, to=main_room)
|
||||||
send_discord_alert(f"👋 Player {username} has disconnected!", username)
|
send_discord_alert(f"👋 Player {username} has disconnected!", username)
|
||||||
print(f"Player {player_id} disconnected")
|
print(f"Player {player_id} disconnected")
|
||||||
|
if player_id in socket_to_user_id:
|
||||||
|
del socket_to_user_id[player_id]
|
||||||
active_sockets_collection.delete_one({'_id': player_id})
|
active_sockets_collection.delete_one({'_id': player_id})
|
||||||
|
|
||||||
@socketio.on('player_paused')
|
@socketio.on('player_paused')
|
||||||
@ -1053,6 +1118,7 @@ def on_join(data):
|
|||||||
# Get player skin
|
# Get player skin
|
||||||
user = users_collection.find_one({'username': username})
|
user = users_collection.find_one({'username': username})
|
||||||
if user:
|
if user:
|
||||||
|
socket_to_user_id[player_id] = user['_id']
|
||||||
selected_skins = user.get('selected_skins', {})
|
selected_skins = user.get('selected_skins', {})
|
||||||
skin_data = {}
|
skin_data = {}
|
||||||
for skin_type, skin_id in selected_skins.items():
|
for skin_type, skin_id in selected_skins.items():
|
||||||
@ -1161,6 +1227,8 @@ def on_collect_synth_coin(data):
|
|||||||
if math.hypot(coin.x - coin_x, coin.y - coin_y) < 0.1:
|
if math.hypot(coin.x - coin_x, coin.y - coin_y) < 0.1:
|
||||||
player.synth_coins += 100
|
player.synth_coins += 100
|
||||||
game_state['synth_coins'].remove(coin)
|
game_state['synth_coins'].remove(coin)
|
||||||
|
if player_id in socket_to_user_id:
|
||||||
|
update_user_stat(socket_to_user_id[player_id], 'coins_collected', 1)
|
||||||
print(f"Player {player.name} collected a coin. New count: {player.synth_coins}")
|
print(f"Player {player.name} collected a coin. New count: {player.synth_coins}")
|
||||||
socketio.emit('coin_collected', {
|
socketio.emit('coin_collected', {
|
||||||
'player_id': player_id,
|
'player_id': player_id,
|
||||||
@ -1303,6 +1371,8 @@ def add_experience(player, amount):
|
|||||||
player.level += 1
|
player.level += 1
|
||||||
player.experience -= player.max_experience
|
player.experience -= player.max_experience
|
||||||
player.max_experience = int(player.max_experience * 1.2)
|
player.max_experience = int(player.max_experience * 1.2)
|
||||||
|
# if player_id in socket_to_user_id:
|
||||||
|
# update_user_stat(socket_to_user_id[player_id], 'highest_level', player.level, operation='max')
|
||||||
|
|
||||||
# Generate upgrade options and store them
|
# Generate upgrade options and store them
|
||||||
player_id = next(pid for pid, p in game_state['players'].items() if p == player)
|
player_id = next(pid for pid, p in game_state['players'].items() if p == player)
|
||||||
@ -1458,6 +1528,8 @@ def game_loop():
|
|||||||
|
|
||||||
if enemy['current_health'] <= 0:
|
if enemy['current_health'] <= 0:
|
||||||
game_state['enemies'].remove(enemy)
|
game_state['enemies'].remove(enemy)
|
||||||
|
if player_id in socket_to_user_id:
|
||||||
|
update_user_stat(socket_to_user_id[player_id], 'enemies_killed', 1)
|
||||||
|
|
||||||
coin = SynthCoin(enemy['x'], enemy['y'])
|
coin = SynthCoin(enemy['x'], enemy['y'])
|
||||||
game_state['synth_coins'].append(coin)
|
game_state['synth_coins'].append(coin)
|
||||||
|
|||||||
@ -75,10 +75,6 @@ let lastUpdateTime = Date.now();
|
|||||||
let impactEffects = [];
|
let impactEffects = [];
|
||||||
let currentSongName = '';
|
let currentSongName = '';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationSystem {
|
class NotificationSystem {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.notificationContainer = null;
|
this.notificationContainer = null;
|
||||||
@ -91,19 +87,25 @@ class NotificationSystem {
|
|||||||
document.body.appendChild(this.notificationContainer);
|
document.body.appendChild(this.notificationContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
showNotification(message, image = null) {
|
showNotification(message, image) {
|
||||||
|
console.log('showNotification called with message:', message, 'and image:', image);
|
||||||
const notificationElement = document.createElement('div');
|
const notificationElement = document.createElement('div');
|
||||||
notificationElement.className = 'custom-notification draggable';
|
notificationElement.className = 'custom-notification draggable';
|
||||||
|
|
||||||
let content = `<div class="notification-content">`;
|
let content = `<div class="notification-content">`;
|
||||||
if (image) {
|
if (image && typeof image === 'string') {
|
||||||
content += `<img src="${image}" alt="Achievement" class="notification-image">`;
|
console.log('Adding image to notification:', image);
|
||||||
|
content += `<img src="${image}" alt="Achievement" class="notification-image" onerror="console.error('Failed to load image:', this.src);">`;
|
||||||
|
} else {
|
||||||
|
console.log('No valid image provided for notification');
|
||||||
}
|
}
|
||||||
content += `
|
content += `
|
||||||
<p>${message}</p>
|
<p>${message}</p>
|
||||||
<button class="close-notification">Close</button>
|
<button class="close-notification">Close</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
console.log('Notification content:', content);
|
||||||
|
|
||||||
notificationElement.innerHTML = content;
|
notificationElement.innerHTML = content;
|
||||||
|
|
||||||
const closeButton = notificationElement.querySelector('.close-notification');
|
const closeButton = notificationElement.querySelector('.close-notification');
|
||||||
@ -113,24 +115,45 @@ class NotificationSystem {
|
|||||||
setTimeout(() => notificationElement.classList.add('show'), 10);
|
setTimeout(() => notificationElement.classList.add('show'), 10);
|
||||||
|
|
||||||
makeDraggable(notificationElement);
|
makeDraggable(notificationElement);
|
||||||
|
|
||||||
|
// Add auto-hide after 3 seconds
|
||||||
|
setTimeout(() => this.closeNotification(notificationElement), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
closeNotification(notificationElement) {
|
closeNotification(notificationElement) {
|
||||||
notificationElement.classList.remove('show');
|
notificationElement.classList.remove('show');
|
||||||
setTimeout(() => notificationElement.remove(), 300);
|
setTimeout(() => notificationElement.remove(), 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const notificationSystem = new NotificationSystem();
|
const notificationSystem = new NotificationSystem();
|
||||||
|
|
||||||
function handleAchievementUnlock(data) {
|
function preloadImage(src) {
|
||||||
const message = `Achievement Unlocked: ${data.name}\n${data.description}`;
|
return new Promise((resolve, reject) => {
|
||||||
notificationSystem.showNotification(message, data.image);
|
const img = new Image();
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = reject;
|
||||||
|
img.src = src;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleAchievementUnlock(data) {
|
||||||
|
console.log('Achievement unlocked (full data):', JSON.stringify(data));
|
||||||
|
if (!notificationSystem) {
|
||||||
|
console.error('NotificationSystem not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = `Achievement Unlocked: ${data.name}\n${data.description}`;
|
||||||
|
console.log('Showing notification:', message);
|
||||||
|
console.log('Achievement image path:', data.image);
|
||||||
|
console.log('Calling showNotification with message:', message, 'and image:', data.image);
|
||||||
|
notificationSystem.showNotification(message, data.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function fetchUserAchievements() {
|
function fetchUserAchievements() {
|
||||||
const achievementsGrid = document.getElementById('achievementsGrid');
|
const achievementsGrid = document.getElementById('achievementsGrid');
|
||||||
const achievementInfoBox = document.getElementById('achievementInfoBox');
|
const achievementInfoBox = document.getElementById('achievementInfoBox');
|
||||||
@ -2199,7 +2222,10 @@ function startGame() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
socket.on('achievement_unlocked', handleAchievementUnlock);
|
socket.on('achievement_unlocked', (data) => {
|
||||||
|
console.log('Received achievement_unlocked event:', data);
|
||||||
|
handleAchievementUnlock(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
socket.on('reconnect', (attemptNumber) => {
|
socket.on('reconnect', (attemptNumber) => {
|
||||||
|
|||||||
@ -1460,7 +1460,7 @@ body {
|
|||||||
top: 20px;
|
top: 20px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 2000;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.achievements-grid {
|
.achievements-grid {
|
||||||
@ -1668,6 +1668,12 @@ body {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-notification.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.custom-notification .notification-content {
|
.custom-notification .notification-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -1683,13 +1689,13 @@ body {
|
|||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
|
|
||||||
font-family: 'Orbitron', sans-serif;
|
font-family: 'Orbitron', sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
cursor: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-notification .close-notification:hover {
|
.custom-notification .close-notification:hover {
|
||||||
@ -1699,6 +1705,13 @@ body {
|
|||||||
box-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff;
|
box-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notification-image {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user