Achievements added. Updates to frontend and backend. Basic testing completed, but may be some bugs, further testing required. Basic achievement added for linking discord account. Reward for achievements added too - Discord linking gives you a Discord Mod Class.
This commit is contained in:
1 parent
6cc8afe4e9
commit
b4ffcbed2a
28 files changed
+526
-4892
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
+268
-64
@@ -89,15 +89,20 @@ class NotificationSystem {
|
||||
document.body.appendChild(this.notificationContainer);
|
||||
}
|
||||
|
||||
showNotification(message) {
|
||||
showNotification(message, image = null) {
|
||||
const notificationElement = document.createElement('div');
|
||||
notificationElement.className = 'custom-notification draggable';
|
||||
notificationElement.innerHTML = `
|
||||
<div class="notification-content">
|
||||
<p>${message}</p>
|
||||
<button class="close-notification">Close</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let content = `<div class="notification-content">`;
|
||||
if (image) {
|
||||
content += `<img src="${image}" alt="Achievement" class="notification-image">`;
|
||||
}
|
||||
content += `
|
||||
<p>${message}</p>
|
||||
<button class="close-notification">Close</button>
|
||||
</div>`;
|
||||
|
||||
notificationElement.innerHTML = content;
|
||||
|
||||
const closeButton = notificationElement.querySelector('.close-notification');
|
||||
closeButton.addEventListener('click', () => this.closeNotification(notificationElement));
|
||||
@@ -107,6 +112,7 @@ class NotificationSystem {
|
||||
|
||||
makeDraggable(notificationElement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
closeNotification(notificationElement) {
|
||||
@@ -115,9 +121,170 @@ class NotificationSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a global instance of the NotificationSystem
|
||||
const notificationSystem = new NotificationSystem();
|
||||
|
||||
function handleAchievementUnlock(data) {
|
||||
const message = `Achievement Unlocked: ${data.name}\n${data.description}`;
|
||||
notificationSystem.showNotification(message, data.image);
|
||||
|
||||
}
|
||||
|
||||
function fetchUserAchievements() {
|
||||
const achievementsGrid = document.getElementById('achievementsGrid');
|
||||
if (!achievementsGrid) {
|
||||
console.error('Achievements grid not found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentUser) {
|
||||
displaySignInMessage(achievementsGrid, 'achievements');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/get-user-achievements', {
|
||||
headers: {
|
||||
'Authorization': getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(achievements => {
|
||||
if (Array.isArray(achievements) && achievements.length > 0) {
|
||||
displayUserAchievements(achievements);
|
||||
} else {
|
||||
displayNoAchievementsMessage(achievementsGrid);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching user achievements:', error);
|
||||
displayErrorMessage(achievementsGrid, 'Failed to load achievements. Please try again later.');
|
||||
});
|
||||
}
|
||||
|
||||
function displaySignInMessage(container, feature) {
|
||||
container.innerHTML = `
|
||||
<div class="sign-in-message">
|
||||
<p>Please sign in to view your ${feature}.</p>
|
||||
<button id="signInButton" class="glowing-button">Sign In</button>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('signInButton').addEventListener('click', showLoginForm);
|
||||
}
|
||||
|
||||
function displayNoAchievementsMessage(container) {
|
||||
container.innerHTML = `
|
||||
<div class="no-achievements-message">
|
||||
<p>You haven't unlocked any achievements yet.</p>
|
||||
<p>Keep playing to earn achievements!</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function displayErrorMessage(container, message) {
|
||||
container.innerHTML = `
|
||||
<div class="error-message">
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
function displayUserAchievements(achievements) {
|
||||
const achievementsGrid = document.getElementById('achievementsGrid');
|
||||
if (!achievementsGrid) {
|
||||
console.error('Achievements grid not found');
|
||||
return;
|
||||
}
|
||||
|
||||
achievementsGrid.innerHTML = '';
|
||||
achievements.forEach(achievement => {
|
||||
const achievementElement = document.createElement('div');
|
||||
achievementElement.classList.add('achievement');
|
||||
achievementElement.innerHTML = `<img src="${achievement.image}" alt="${achievement.name}">`;
|
||||
achievementElement.addEventListener('click', () => showAchievementInfo(achievement));
|
||||
achievementElement.addEventListener('mouseover', () => showAchievementInfo(achievement));
|
||||
achievementsGrid.appendChild(achievementElement);
|
||||
});
|
||||
|
||||
// Show info for the first achievement by default
|
||||
if (achievements.length > 0) {
|
||||
showAchievementInfo(achievements[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function showAchievementInfo(achievement) {
|
||||
const image = document.getElementById('achievementImage');
|
||||
const name = document.getElementById('achievementName');
|
||||
const description = document.getElementById('achievementDescription');
|
||||
const unlockedClasses = document.getElementById('unlockedClasses');
|
||||
const unlockedSkins = document.getElementById('unlockedSkins');
|
||||
|
||||
image.src = achievement.image;
|
||||
name.textContent = achievement.name;
|
||||
description.textContent = achievement.description;
|
||||
|
||||
unlockedClasses.innerHTML = '<h5>Classes:</h5>';
|
||||
if (achievement.unlocked_classes && achievement.unlocked_classes.length > 0) {
|
||||
achievement.unlocked_classes.forEach(c => {
|
||||
unlockedClasses.innerHTML += `<div class="unlock-item"><img src="${c.image}" alt="${c.name}"><span>${c.name}</span></div>`;
|
||||
});
|
||||
} else {
|
||||
unlockedClasses.innerHTML += '<p>No classes unlocked</p>';
|
||||
}
|
||||
|
||||
unlockedSkins.innerHTML = '<h5>Skins:</h5>';
|
||||
if (achievement.unlocked_skins && achievement.unlocked_skins.length > 0) {
|
||||
achievement.unlocked_skins.forEach(s => {
|
||||
unlockedSkins.innerHTML += `<div class="unlock-item"><img src="${s.image}" alt="${s.name}"><span>${s.name}</span></div>`;
|
||||
});
|
||||
} else {
|
||||
unlockedSkins.innerHTML += '<p>No skins unlocked</p>';
|
||||
}
|
||||
|
||||
// For debugging
|
||||
debugLog('Achievement:', achievement);
|
||||
}
|
||||
|
||||
function hideAchievementInfo() {
|
||||
const infoBox = document.getElementById('achievementInfoBox');
|
||||
infoBox.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const achievementsTab = document.getElementById('achievements');
|
||||
if (achievementsTab && !achievementsTab.contains(event.target)) {
|
||||
// Optionally hide or reset achievement info here if needed
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function createAchievementsTab() {
|
||||
const achievementsTab = document.createElement('div');
|
||||
achievementsTab.id = 'achievements';
|
||||
achievementsTab.classList.add('tab-content');
|
||||
achievementsTab.innerHTML = `
|
||||
<div class="achievements-container">
|
||||
<div id="achievementsGrid" class="achievements-grid"></div>
|
||||
<div id="achievementInfoBox" class="achievement-info-box">
|
||||
<img id="achievementImage" src="" alt="Achievement">
|
||||
<h3 id="achievementName"></h3>
|
||||
<p id="achievementDescription"></p>
|
||||
<div id="achievementUnlocks">
|
||||
<h4>Unlocks:</h4>
|
||||
<div id="unlockedClasses"></div>
|
||||
<div id="unlockedSkins"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return achievementsTab;
|
||||
}
|
||||
|
||||
|
||||
function initShop() {
|
||||
let shopButton = document.getElementById('shopButton');
|
||||
if (!shopButton) {
|
||||
@@ -1160,51 +1327,56 @@ function hideClassSelector() {
|
||||
}
|
||||
|
||||
function fetchAndDisplayClasses() {
|
||||
fetch('/get-available-classes')
|
||||
.then(response => response.json())
|
||||
.then(classes => {
|
||||
// Sort classes: unlocked first, then locked
|
||||
classes.sort((a, b) => {
|
||||
if (a.unlocked_by_default === b.unlocked_by_default) return 0;
|
||||
return a.unlocked_by_default ? -1 : 1;
|
||||
});
|
||||
fetch('/get-available-classes', {
|
||||
headers: {
|
||||
'Authorization': getSessionToken()
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(classes => {
|
||||
// Sort classes: unlocked first, then locked
|
||||
classes.sort((a, b) => {
|
||||
if (a.unlocked === b.unlocked) return 0;
|
||||
return a.unlocked ? -1 : 1;
|
||||
});
|
||||
|
||||
const classSelector = document.getElementById('classSelector');
|
||||
classSelector.innerHTML = '';
|
||||
|
||||
classes.forEach(playerClass => {
|
||||
const classOption = document.createElement('div');
|
||||
classOption.classList.add('class-option');
|
||||
if (playerClass._id === selectedClassId) {
|
||||
classOption.classList.add('selected');
|
||||
}
|
||||
classOption.innerHTML = `<img src="${playerClass.image_source}" alt="${playerClass.name}">`;
|
||||
|
||||
if (!playerClass.unlocked_by_default) {
|
||||
classOption.classList.add('locked');
|
||||
const lockOverlay = document.createElement('div');
|
||||
lockOverlay.classList.add('lock-overlay');
|
||||
lockOverlay.innerHTML = '<i class="fas fa-lock"></i>';
|
||||
classOption.appendChild(lockOverlay);
|
||||
}
|
||||
|
||||
classOption.addEventListener('click', () => selectClass(playerClass));
|
||||
classOption.addEventListener('mouseover', () => displayClassInfo(playerClass));
|
||||
classSelector.appendChild(classOption);
|
||||
});
|
||||
|
||||
// Display info for the first unlocked class by default
|
||||
const firstUnlockedClass = classes.find(c => c.unlocked_by_default);
|
||||
if (firstUnlockedClass) {
|
||||
displayClassInfo(firstUnlockedClass);
|
||||
} else if (classes.length > 0) {
|
||||
displayClassInfo(classes[0]);
|
||||
const classSelector = document.getElementById('classSelector');
|
||||
classSelector.innerHTML = '';
|
||||
|
||||
classes.forEach(playerClass => {
|
||||
const classOption = document.createElement('div');
|
||||
classOption.classList.add('class-option');
|
||||
if (playerClass._id === selectedClassId) {
|
||||
classOption.classList.add('selected');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error loading classes:', error));
|
||||
classOption.innerHTML = `<img src="${playerClass.image_source}" alt="${playerClass.name}">`;
|
||||
|
||||
if (!playerClass.unlocked) {
|
||||
classOption.classList.add('locked');
|
||||
const lockOverlay = document.createElement('div');
|
||||
lockOverlay.classList.add('lock-overlay');
|
||||
lockOverlay.innerHTML = '<i class="fas fa-lock"></i>';
|
||||
classOption.appendChild(lockOverlay);
|
||||
}
|
||||
|
||||
classOption.addEventListener('click', () => selectClass(playerClass));
|
||||
classOption.addEventListener('mouseover', () => displayClassInfo(playerClass));
|
||||
classSelector.appendChild(classOption);
|
||||
});
|
||||
|
||||
// Display info for the first unlocked class by default
|
||||
const firstUnlockedClass = classes.find(c => c.unlocked);
|
||||
if (firstUnlockedClass) {
|
||||
displayClassInfo(firstUnlockedClass);
|
||||
} else if (classes.length > 0) {
|
||||
displayClassInfo(classes[0]);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error loading classes:', error));
|
||||
}
|
||||
|
||||
|
||||
|
||||
function displayClassInfo(playerClass) {
|
||||
const classImage = document.getElementById('classImage');
|
||||
const className = document.getElementById('className');
|
||||
@@ -1726,10 +1898,35 @@ document.querySelectorAll('.close-button').forEach(button => {
|
||||
});
|
||||
});
|
||||
|
||||
// Modify the showWidget function
|
||||
function showWidget() {
|
||||
uiWidget.classList.add('visible');
|
||||
openInterfaces.menu = true;
|
||||
|
||||
// Add Achievements tab if it doesn't exist
|
||||
if (!document.getElementById('achievements')) {
|
||||
const achievementsTab = createAchievementsTab();
|
||||
const widgetContent = uiWidget.querySelector('.widget-content');
|
||||
if (widgetContent) {
|
||||
widgetContent.appendChild(achievementsTab);
|
||||
} else {
|
||||
console.error('Widget content container not found');
|
||||
}
|
||||
|
||||
// Add Achievements button to tab buttons
|
||||
const tabButtons = uiWidget.querySelector('.widget-tabs');
|
||||
if (tabButtons) {
|
||||
const achievementsButton = document.createElement('button');
|
||||
achievementsButton.classList.add('tab-button');
|
||||
achievementsButton.setAttribute('data-tab', 'achievements');
|
||||
achievementsButton.textContent = 'Achievements';
|
||||
tabButtons.appendChild(achievementsButton);
|
||||
|
||||
achievementsButton.addEventListener('click', () => showTab('achievements'));
|
||||
} else {
|
||||
console.error('Widget tabs container not found');
|
||||
}
|
||||
}
|
||||
|
||||
if (gameStarted && player) {
|
||||
pausePlayer();
|
||||
showTab('settings'); // Open the Settings tab by default when in-game
|
||||
@@ -1739,6 +1936,7 @@ function showWidget() {
|
||||
updateAccountTab();
|
||||
}
|
||||
|
||||
|
||||
function updateAccountTab() {
|
||||
const accountContent = document.getElementById('accountContent');
|
||||
if (currentUser) {
|
||||
@@ -1935,7 +2133,9 @@ function showTab(tabName) {
|
||||
if (tabName === 'customization') {
|
||||
onCustomizationTabOpen();
|
||||
} else if (tabName === 'account') {
|
||||
updateAccountTab(); // Update account tab content when selected
|
||||
updateAccountTab();
|
||||
} else if (tabName === 'achievements') {
|
||||
fetchUserAchievements();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1966,6 +2166,7 @@ function startGame() {
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('achievement_unlocked', handleAchievementUnlock);
|
||||
|
||||
|
||||
socket.on('reconnect', (attemptNumber) => {
|
||||
@@ -3104,13 +3305,8 @@ function drawGameObjects() {
|
||||
|
||||
|
||||
function selectClass(playerClass) {
|
||||
if (!playerClass.unlocked_by_default && currentUser === null) {
|
||||
notificationSystem.showNotification('You need to be logged in to select this class.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!playerClass.unlocked_by_default) {
|
||||
showUnlockRequirements(playerClass);
|
||||
if (!playerClass.unlocked) {
|
||||
notificationSystem.showNotification('This class is locked. Unlock it to select.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3118,7 +3314,7 @@ function selectClass(playerClass) {
|
||||
const classSelector = document.getElementById('classSelector');
|
||||
const classOptions = classSelector.querySelectorAll('.class-option');
|
||||
classOptions.forEach(option => option.classList.remove('selected'));
|
||||
const selectedOption = classSelector.querySelector(`.class-option:nth-child(${Array.from(classOptions).indexOf(event.currentTarget) + 1})`);
|
||||
const selectedOption = Array.from(classOptions).find(option => option.querySelector('img').alt === playerClass.name);
|
||||
if (selectedOption) {
|
||||
selectedOption.classList.add('selected');
|
||||
}
|
||||
@@ -3129,21 +3325,29 @@ function selectClass(playerClass) {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': getSessionToken()
|
||||
},
|
||||
body: JSON.stringify({ username: currentUser, class_id: selectedClassId }),
|
||||
body: JSON.stringify({ class_id: selectedClassId }),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(data => {
|
||||
throw new Error(data.error || 'Failed to select class');
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
debugLog('Class selected successfully');
|
||||
notificationSystem.showNotification(`${playerClass.name} class selected successfully!`);
|
||||
} else {
|
||||
console.error('Failed to select class:', data.error);
|
||||
notificationSystem.showNotification('Failed to select class. Please try again.');
|
||||
throw new Error(data.error || 'Failed to select class');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error selecting class:', error);
|
||||
notificationSystem.showNotification('An error occurred while selecting the class. Please try again.');
|
||||
console.error('Error selecting class:', error.message);
|
||||
notificationSystem.showNotification(`Failed to select class: ${error.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1296,6 +1296,146 @@ body {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.achievements-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#achievementsContainer {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.achievements-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.achievement {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 2px solid #00ffff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.achievement:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.achievement img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
.achievement-info-box {
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
background-color: rgba(18, 4, 88, 0.8);
|
||||
border-left: 2px solid #00ffff;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.achievement-info-box.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.achievement-info-box img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.achievement-info-box h3 {
|
||||
color: #00ffff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.achievement-info-box p {
|
||||
color: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#achievementUnlocks h4 {
|
||||
color: #00ffff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#achievementUnlocks h5 {
|
||||
color: #00ffff;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sign-in-message,
|
||||
.no-achievements-message,
|
||||
.error-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
}
|
||||
|
||||
.sign-in-message p,
|
||||
.no-achievements-message p,
|
||||
.error-message p {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.glowing-button {
|
||||
background-color: transparent;
|
||||
border: 2px solid #00ffff;
|
||||
color: #00ffff;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 0 10px #00ffff;
|
||||
}
|
||||
|
||||
.glowing-button:hover {
|
||||
background-color: #00ffff;
|
||||
color: #000000;
|
||||
box-shadow: 0 0 20px #00ffff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.unlock-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.unlock-item img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
object-fit: cover;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.unlock-item span {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.custom-notification {
|
||||
background-color: rgba(18, 4, 88, 0.9);
|
||||
color: #00ffff;
|
||||
|
||||
Reference in new issue
Block a user