diff --git a/backend/database.db b/backend/database.db
index 662d7f6..71c46ba 100644
Binary files a/backend/database.db and b/backend/database.db differ
diff --git a/backend/server.js b/backend/server.js
index 3ee6d7b..a02b803 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -95,6 +95,21 @@ app.get('/games/:userId', (req, res) => {
});
+// Fetch game details including game master
+app.get('/games/:gameId/master', (req, res) => {
+ const gameId = req.params.gameId;
+ db.get('SELECT * FROM games WHERE game_id = ?', [gameId], (err, row) => {
+ if (err) {
+ return res.status(500).json({ error: 'Internal server error' });
+ }
+ if (!row) {
+ return res.status(404).json({ error: 'Game not found' });
+ }
+ // Convert game_master_id to number for consistent comparison
+ res.json(row);
+ });
+ });
+
// Join Game
app.post('/joinGame/:gameId', (req, res) => {
const gameId = req.params.gameId;
@@ -162,18 +177,25 @@ app.post('/createGame', (req, res) => {
// Fetch game Participant Characters
app.get('/games/:gameId/playerchars', (req, res) => {
const gameId = req.params.gameId;
- db.get('SELECT * FROM PlayerCharacter WHERE GameID = ?', [gameId], (err, row) => {
+ db.all('SELECT * FROM PlayerCharacter WHERE GameID = ?', [gameId], (err, rows) => {
if (err) {
return res.status(500).json({ error: 'Internal server error' });
}
- res.json(row);
+ // Process each row and convert BLOB to base64 if image exists
+ const processedRows = rows.map(row => {
+ if (row.Img) {
+ row.Img = `data:image/jpeg;base64,${row.Img.toString('base64')}`;
+ }
+ return row;
+ });
+ res.json(processedRows);
});
-})
+});
// Fetch game NPCs
app.get('/games/:gameId/npcs', (req, res) => {
const gameId = req.params.gameId;
- db.get('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, row) => {
+ db.all('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, row) => {
if (err) {
return res.status(500).json({ error: 'Internal server error' });
}
@@ -184,12 +206,19 @@ app.get('/games/:gameId/npcs', (req, res) => {
// Fetch game Items
app.get('/games/:gameId/items', (req, res) => {
const gameId = req.params.gameId;
- db.get('SELECT * FROM Item WHERE GameID = ?', [gameId], (err, row) => {
- if (err) {
- return res.status(500).json({ error: 'Internal server error' });
+ db.all(`
+ SELECT Item.*, PlayerCharacter.CharName as OwnerName
+ FROM Item
+ LEFT JOIN PlayerCharacter ON Item.OwnerID = PlayerCharacter.CharID
+ WHERE Item.GameID = ?`,
+ [gameId],
+ (err, rows) => {
+ if (err) {
+ return res.status(500).json({ error: 'Internal server error' });
+ }
+ res.json(rows);
}
- res.json(row);
- });
+ );
})
@@ -324,6 +353,38 @@ app.get('/games/:gameId/:charId/items', (req, res) => {
});
// Item Part
+// Create Item
+app.post('/games/item/create', upload.single('image'), (req, res) => {
+ const {
+ ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability,
+ GoldValue, Abilities, GameID, AP
+ } = req.body;
+
+ const imageBuffer = req.file ? req.file.buffer : null;
+
+ const stmt = db.prepare(`
+ INSERT INTO Item (
+ ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability,
+ GoldValue, Abilities, GameID, OwnerID, img, AP
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
+ `);
+
+ stmt.run([
+ ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability,
+ GoldValue, Abilities, GameID, imageBuffer, AP
+ ], function(err) {
+ if (err) {
+ console.error('Database error:', err);
+ return res.status(500).json({ error: 'Internal server error' });
+ }
+ res.status(201).json({
+ message: 'Item created successfully!',
+ itemId: this.lastID
+ });
+ });
+ stmt.finalize();
+});
+
// Set Item Owner
app.post('/games/:charId/:itemId/owner', (req, res) => {
const gameId = req.params.gameId;
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 96bd4d5..08b941e 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -10,6 +10,8 @@ import JoinGame from './pages/joinGame';
import StartGame from './pages/startGame';
import Profile from './pages/profile';
import Games from './pages/games.jsx';
+import GameMasterPage from './pages/gameMasterPage.jsx';
+import CreateItem from './pages/createItem.jsx';
import CreateCharacter from './pages/createCharacter.jsx';
import { UserProvider } from './context/UserContext.jsx';
@@ -61,6 +63,10 @@ function App() {
path="/profile"
element={}
/>
+ }
+ />
}
@@ -69,6 +75,11 @@ function App() {
path='/create-Character'
element={}
/>
+ }
+ />
+
diff --git a/frontend/src/pages/createItem.jsx b/frontend/src/pages/createItem.jsx
new file mode 100644
index 0000000..a132c1d
--- /dev/null
+++ b/frontend/src/pages/createItem.jsx
@@ -0,0 +1,308 @@
+import React, { useState, useContext } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import { UserContext } from '../context/UserContext';
+import axios from 'axios';
+import { Box, TextField, Button, Typography, Select, MenuItem, FormControl, InputLabel, Grid2, Card, CardContent, CardMedia } from '@mui/material';
+
+import defaultItemImage from '../assets/default-item.png';
+
+const CreateItem = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { userId } = useContext(UserContext);
+ const gameId = new URLSearchParams(location.search).get('gameId');
+
+ const [formData, setFormData] = useState({
+ ItemName: '', // Match DB column names exactly
+ Type: '',
+ Art: '',
+ Rarity: 1,
+ MaxDurability: 100,
+ CurrentDurability: 100,
+ GoldValue: 0,
+ Abilities: '',
+ AP: 0,
+ GameID: gameId
+ });
+
+ const [selectedImage, setSelectedImage] = useState(null);
+ const [imagePreview, setImagePreview] = useState(null);
+
+ const handleChange = (e) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value
+ });
+ };
+
+ const handleImageChange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ setSelectedImage(file);
+ setImagePreview(URL.createObjectURL(file));
+ }
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const formDataToSend = new FormData();
+
+ // Add all form fields with exact column names
+ Object.keys(formData).forEach(key => {
+ // Convert null values to empty strings to prevent SQL issues
+ formDataToSend.append(key, formData[key] === null ? '' : formData[key]);
+ });
+
+ // Add image if present
+ if (selectedImage) {
+ formDataToSend.append('image', selectedImage);
+ }
+
+ try {
+ const response = await axios.post(
+ 'http://localhost:5000/games/item/create',
+ formDataToSend,
+ {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ }
+ );
+
+ if (response.status === 201) {
+ navigate(`/games/${gameId}/master`);
+ }
+ } catch (error) {
+ console.error('Error creating item:', error);
+ }
+};
+
+ const inputStyles = {
+ '& .MuiOutlinedInput-root': {
+ '& fieldset': { borderColor: '#444' },
+ '&:hover fieldset': { borderColor: '#764ACB' },
+ '&.Mui-focused fieldset': { borderColor: '#764ACB' },
+ },
+ '& .MuiInputLabel-root': { color: '#fff' },
+ '& .MuiInputBase-input': { color: '#fff' }
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default CreateItem;
\ No newline at end of file
diff --git a/frontend/src/pages/createNpc.jsx b/frontend/src/pages/createNpc.jsx
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/pages/gameMasterPage.jsx b/frontend/src/pages/gameMasterPage.jsx
new file mode 100644
index 0000000..68d570a
--- /dev/null
+++ b/frontend/src/pages/gameMasterPage.jsx
@@ -0,0 +1,267 @@
+import React, { useState, useEffect, useContext } from 'react';
+import { Link, useParams } from 'react-router-dom';
+import { UserContext } from '../context/UserContext';
+import axios from 'axios';
+import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton } from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+
+import defaultCharacterImage from '../assets/default-character.png';
+import defaultItemImage from '../assets/default-item.png';
+
+const GameMasterPage = () => {
+ const { userId } = useContext(UserContext);
+ const { gameId } = useParams();
+ const [playerCharacters, setPlayerCharacters] = useState([]);
+ const [npcs, setNpcs] = useState([]);
+ const [items, setItems] = useState([]);
+
+ // Fix useEffect data fetching
+ useEffect(() => {
+ const fetchData = async () => {
+ try {
+ // Fetch player characters
+ const pcsResponse = await axios.get(`http://localhost:5000/games/${gameId}/playerchars`);
+ const processedPCs = Array.isArray(pcsResponse.data) ? pcsResponse.data : [pcsResponse.data];
+ setPlayerCharacters(processedPCs.filter(pc => pc !== null));
+
+ // Fetch NPCs with different structure
+ const npcsResponse = await axios.get(`http://localhost:5000/games/${gameId}/npcs`);
+ const processedNPCs = Array.isArray(npcsResponse.data) ? npcsResponse.data : [npcsResponse.data];
+ setNpcs(processedNPCs.filter(npc => npc !== null));
+
+ // Fetch Items
+ const itemsResponse = await axios.get(`http://localhost:5000/games/${gameId}/items`);
+ const processedItems = Array.isArray(itemsResponse.data) ? itemsResponse.data : [itemsResponse.data];
+ setItems(processedItems.filter(item => item !== null));
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ };
+
+ fetchData();
+ const interval = setInterval(fetchData, 5000);
+ return () => clearInterval(interval);
+ }, [gameId]);
+
+ const Section = ({ title, items, createPath, createText }) => (
+
+
+ {title}
+ }
+ sx={{
+ backgroundColor: '#764ACB',
+ '&:hover': { backgroundColor: '#9865f7' }
+ }}
+ >
+ {createText}
+
+
+
+ {items.map((item, index) => (
+
+
+
+
+
+
+
+ {item.ItemName}
+
+
+ Type: {item.Type}
+
+
+ Value: {item.GoldValue}
+
+
+ Owner: {item.OwnerName || 'Unassigned'}
+
+
+
+
+ ))}
+
+
+ );
+
+ return (
+ // Update main container Box styling
+
+
+ Game Master Dashboard
+
+
+ {/* Player Characters Section */}
+
+ Player Characters
+
+ {playerCharacters.map((character, index) => (
+
+
+
+
+
+
+
+ {character.CharName}
+
+
+ {character.Race} - Level {character.Level}
+
+
+ {character.Job}
+
+
+
+
+ ))}
+
+
+
+ {/* NPCs Section */}
+
+
+ NPCs
+ }
+ sx={{
+ backgroundColor: '#764ACB',
+ '&:hover': { backgroundColor: '#9865f7' }
+ }}
+ >
+ Create NPC
+
+
+
+ {npcs.map((npc, index) => (
+
+
+
+
+
+
+
+ {npc.CharName}
+
+
+ {npc.Race}
+
+
+ {npc.Job}
+
+
+
+
+ ))}
+
+
+
+ {/* Items Section */}
+
+
+ );
+};
+
+export default GameMasterPage;
\ No newline at end of file
diff --git a/frontend/src/pages/games.jsx b/frontend/src/pages/games.jsx
index 4fffb95..9889544 100644
--- a/frontend/src/pages/games.jsx
+++ b/frontend/src/pages/games.jsx
@@ -1,5 +1,5 @@
import { useState, useEffect, useContext } from 'react';
-import { Link, useParams } from 'react-router-dom';
+import { Link, useParams, useNavigate } from 'react-router-dom';
import axios from 'axios';
import { UserContext } from '../context/UserContext';
import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from '@mui/material';
@@ -21,6 +21,7 @@ import './games.css';
const GamesPage = () => {
const { userId } = useContext(UserContext);
const { gameId } = useParams();
+ const navigate = useNavigate();
const [character, setCharacter] = useState(null);
const [inventory, setInventory] = useState([]);
const [isEditOpen, setIsEditOpen] = useState(false);
@@ -68,6 +69,29 @@ const GamesPage = () => {
return () => clearInterval(interval);
}, [character]);
+ useEffect(() => {
+ const checkGameMaster = async () => {
+ if (!userId || !gameId) return;
+
+ try {
+ console.log('Checking if user is game master...');
+ const response = await axios.get(`http://localhost:5000/games/${gameId}/master`);
+ console.log('Game data:', response.data);
+ console.log('User ID:', userId);
+ console.log('Game master ID:', response.data.game_master_id);
+
+ if (parseInt(userId) === parseInt(response.data.game_master_id)) {
+ console.log('User is game master, redirecting...');
+ navigate(`/games/${gameId}/master`, { replace: true });
+ }
+ } catch (error) {
+ console.error('Error checking game master:', error);
+ }
+ };
+
+ checkGameMaster();
+ }, [userId, gameId, navigate]);
+
const handleEditOpen = () => {
setNewDescription(character.description);
setIsEditOpen(true);