diff --git a/backend/database.db b/backend/database.db index e5224fb..8f87ded 100644 Binary files a/backend/database.db and b/backend/database.db differ diff --git a/backend/server.js b/backend/server.js index a02b803..e3942d0 100644 --- a/backend/server.js +++ b/backend/server.js @@ -6,14 +6,20 @@ const bodyParser = require('body-parser'); const crypto = require('crypto'); const multer = require('multer'); // Add this to your dependencies const storage = multer.memoryStorage(); -const upload = multer({ storage: storage }); +const upload = multer({ + storage: storage, + limits: { + fileSize: 50 * 1024 * 1024 // 50MB limit + } +}); const app = express(); const port = 5000; // Middleware app.use(cors()); -app.use(bodyParser.json()); +app.use(bodyParser.json({limit: '50mb'})); // Increase JSON payload limit +app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); // Increase URL-encoded payload limit // SQLite database setup const db = new sqlite3.Database('./database.db', (err) => { @@ -195,31 +201,46 @@ app.get('/games/:gameId/playerchars', (req, res) => { // Fetch game NPCs app.get('/games/:gameId/npcs', (req, res) => { const gameId = req.params.gameId; - db.all('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, row) => { + db.all('SELECT * FROM NPC 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 Items app.get('/games/:gameId/items', (req, res) => { const gameId = req.params.gameId; db.all(` - SELECT Item.*, PlayerCharacter.CharName as OwnerName - FROM Item - LEFT JOIN PlayerCharacter ON Item.OwnerID = PlayerCharacter.CharID - WHERE Item.GameID = ?`, + SELECT i.*, + COALESCE(pc.CharName, n.Name) as OwnerName + FROM Item i + LEFT JOIN PlayerCharacter pc ON i.OwnerID = pc.CharID + LEFT JOIN NPC n ON i.OwnerID = n.NPCID + WHERE i.GameID = ?`, [gameId], (err, rows) => { if (err) { return res.status(500).json({ error: 'Internal server error' }); } - res.json(rows); + const processedRows = rows.map(row => { + if (row.img) { + row.Img = `data:image/jpeg;base64,${row.img.toString('base64')}`; + } + return row; + }); + res.json(processedRows); } ); -}) +}); // Player Part @@ -365,7 +386,7 @@ app.post('/games/item/create', upload.single('image'), (req, res) => { const stmt = db.prepare(` INSERT INTO Item ( ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability, - GoldValue, Abilities, GameID, OwnerID, img, AP + GoldValue, Abilities, GameID, OwnerID, img, AP ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?) `); @@ -398,6 +419,165 @@ app.post('/games/:charId/:itemId/owner', (req, res) => { }); }) +// Update Item details including owner +app.put('/games/item/:itemId', async (req, res) => { + const itemId = req.params.itemId; + const { + ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability, + GoldValue, Abilities, OwnerID, AP + } = req.body; + + const stmt = db.prepare(` + UPDATE Item + SET ItemName = ?, Type = ?, Art = ?, Rarity = ?, + MaxDurability = ?, CurrentDurability = ?, GoldValue = ?, + Abilities = ?, OwnerID = ?, AP = ? + WHERE ItemID = ? + `); + + stmt.run([ + ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability, + GoldValue, Abilities, OwnerID, AP, itemId + ], function(err) { + if (err) { + console.error('Database error:', err); + return res.status(500).json({ error: 'Internal server error' }); + } + if (this.changes === 0) { + return res.status(404).json({ error: 'Item not found' }); + } + res.json({ message: 'Item updated successfully!' }); + }); + stmt.finalize(); +}); + +//NPC part + +// Create NPC +app.post('/games/npc/create', upload.single('image'), (req, res) => { + const { + Name, Race, Sex, Age, Job, Description, + MaxHealth, MaxMana, Strength, Dexterity, + Agility, Endurance, Allied, Level, GameID + } = req.body; + + const imageBuffer = req.file ? req.file.buffer : null; + + const stmt = db.prepare(` + INSERT INTO NPC ( + GameID, Name, Race, Sex, Age, Job, Description, + MaxHealth, CurrentHealth, MaxMana, CurrentMana, + Strength, Dexterity, Agility, Endurance, + Level, Allied, Img + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + stmt.run([ + GameID, Name, Race, Sex, Age, Job, Description, + MaxHealth, MaxHealth, MaxMana, MaxMana, + Strength, Dexterity, Agility, Endurance, + Level, Allied, imageBuffer + ], function(err) { + if (err) { + console.error('Database error:', err); + return res.status(500).json({ error: 'Internal server error' }); + } + res.status(201).json({ + message: 'NPC created successfully!', + npcId: this.lastID + }); + }); + stmt.finalize(); +}); + +// Update NPC details +app.put('/games/npc/:npcId', upload.single('image'), (req, res) => { + const npcId = req.params.npcId; + const { + Name, Race, Sex, Age, Job, Description, + MaxHealth, CurrentHealth, MaxMana, CurrentMana, + Strength, Dexterity, Agility, Endurance, + Level, Allied + } = req.body; + + let updateQuery = ` + UPDATE NPC SET + Name = ?, Race = ?, Sex = ?, Age = ?, Job = ?, + Description = ?, MaxHealth = ?, CurrentHealth = ?, + MaxMana = ?, CurrentMana = ?, Strength = ?, + Dexterity = ?, Agility = ?, Endurance = ?, + Level = ?, Allied = ? + `; + + let params = [ + Name, Race, Sex, Age, Job, Description, + MaxHealth, CurrentHealth, MaxMana, CurrentMana, + Strength, Dexterity, Agility, Endurance, + Level, Allied + ]; + + // If new image is uploaded, add it to the update + if (req.file) { + updateQuery += `, Img = ?`; + params.push(req.file.buffer); + } + + updateQuery += ` WHERE NPCID = ?`; + params.push(npcId); + + const stmt = db.prepare(updateQuery); + stmt.run(params, function(err) { + if (err) { + console.error('Database error:', err); + return res.status(500).json({ error: 'Internal server error' }); + } + if (this.changes === 0) { + return res.status(404).json({ error: 'NPC not found' }); + } + res.json({ message: 'NPC updated successfully!' }); + }); + stmt.finalize(); +}); + +// Get NPC details +app.get('/games/npc/:npcId', (req, res) => { + const npcId = req.params.npcId; + db.get('SELECT * FROM NPC WHERE NPCID = ?', [npcId], (err, row) => { + if (err) { + return res.status(500).json({ error: 'Internal server error' }); + } + if (!row) { + return res.status(404).json({ error: 'NPC not found' }); + } + + // Convert BLOB to base64 string if image exists + if (row.Img) { + row.Img = `data:image/jpeg;base64,${row.Img.toString('base64')}`; + } + + res.json(row); + }); +}); + +// Update NPC image +app.put('/games/npc/:npcId/image', upload.single('image'), (req, res) => { + const npcId = req.params.npcId; + const imageBuffer = req.file.buffer; + + const stmt = db.prepare('UPDATE NPC SET Img = ? WHERE NPCID = ?'); + stmt.run([imageBuffer, npcId], function(err) { + if (err) { + console.error('Database error:', err); + return res.status(500).json({ error: 'Internal server error' }); + } + if (this.changes === 0) { + return res.status(404).json({ error: 'NPC not found' }); + } + res.json({ message: 'NPC image updated successfully!' }); + }); + stmt.finalize(); +}); + diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 08b941e..8da0223 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -13,6 +13,7 @@ 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 CreateNpc from './pages/createNpc.jsx'; import { UserProvider } from './context/UserContext.jsx'; function App() { @@ -79,7 +80,11 @@ function App() { path='/create-item' element={} /> - + } + /> + diff --git a/frontend/src/pages/createItem.jsx b/frontend/src/pages/createItem.jsx index a132c1d..ad00689 100644 --- a/frontend/src/pages/createItem.jsx +++ b/frontend/src/pages/createItem.jsx @@ -89,6 +89,18 @@ const CreateItem = () => { return ( + +
{/* Left Column - Image and Basic Info */} diff --git a/frontend/src/pages/createNpc.jsx b/frontend/src/pages/createNpc.jsx index e69de29..f6bd2f5 100644 --- a/frontend/src/pages/createNpc.jsx +++ b/frontend/src/pages/createNpc.jsx @@ -0,0 +1,417 @@ +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 the same icons as games.jsx +import PaidIcon from '@mui/icons-material/Paid'; +import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; +import PetsIcon from '@mui/icons-material/Pets'; +import WcIcon from '@mui/icons-material/Wc'; +import WorkIcon from '@mui/icons-material/Work'; +import HeartIcon from '@mui/icons-material/Favorite'; +import WaterDropIcon from '@mui/icons-material/WaterDrop'; +import KeyboardDoubleArrowUpIcon from '@mui/icons-material/KeyboardDoubleArrowUp'; +import SecurityIcon from '@mui/icons-material/Security'; + +import defaultCharacterImage from '../assets/default-character.png'; + +const CreateNpc = () => { + const navigate = useNavigate(); + const location = useLocation(); + const { userId } = useContext(UserContext); + const gameId = new URLSearchParams(location.search).get('gameId'); + + const [formData, setFormData] = useState({ + Name: '', + Race: '', + Sex: '', + Age: '', + Job: '', + Description: '', + MaxHealth: 100, + MaxMana: 100, + Strength: 10, + Dexterity: 10, + Agility: 10, + Endurance: 10, + Allied: 0, // 0 = Allied, 1 = Neutral, 2 = Enemy + Level: 1, + 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(); + + Object.keys(formData).forEach(key => { + formDataToSend.append(key, formData[key]); + }); + + if (selectedImage) { + formDataToSend.append('image', selectedImage); + } + + try { + const response = await axios.post( + 'http://localhost:5000/games/npc/create', + formDataToSend, + { + headers: { + 'Content-Type': 'multipart/form-data' + } + } + ); + + if (response.status === 201) { + navigate(`/games/${gameId}/master`); + } + } catch (error) { + console.error('Error creating NPC:', 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 ( + + + + + {/* NPC Image and Basic Info */} + + + + {/* Image Upload Section - Similar to CreateCharacter */} + {imagePreview ? ( + + + + + ) : ( + + + + + )} + + + + + + + + + + + + + + + + + Geschlecht + + + + + + Status + + + + + + + + + + + + + + + + + + + {/* Right Column - Stats and Description */} + + {/* Health/Mana Bars */} + + + + + Maximale Gesundheit + + + + + + + Maximales Mana + + + + + + {/* Description */} + + Beschreibung + + + + + + {/* Stats */} + + Attribute + + + + + + + + + + + + + + + + + + + {/* Button Container */} + + + + + + + + + ); +}; + +export default CreateNpc; \ No newline at end of file diff --git a/frontend/src/pages/gameMasterPage.jsx b/frontend/src/pages/gameMasterPage.jsx index 008d659..477cb0f 100644 --- a/frontend/src/pages/gameMasterPage.jsx +++ b/frontend/src/pages/gameMasterPage.jsx @@ -2,7 +2,7 @@ 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 { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, FormControl, InputLabel, Select, MenuItem, Tooltip } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import defaultCharacterImage from '../assets/default-character.png'; @@ -14,6 +14,12 @@ const GameMasterPage = () => { const [playerCharacters, setPlayerCharacters] = useState([]); const [npcs, setNpcs] = useState([]); const [items, setItems] = useState([]); + const [editModalOpen, setEditModalOpen] = useState(false); + const [selectedItem, setSelectedItem] = useState(null); + const [editType, setEditType] = useState(''); // 'item', 'npc', or 'character' + const [isEditing, setIsEditing] = useState(false); + const [allOwners, setAllOwners] = useState([]); + const [formData, setFormData] = useState(null); // Fix useEffect data fetching useEffect(() => { @@ -39,10 +45,400 @@ const GameMasterPage = () => { }; fetchData(); - const interval = setInterval(fetchData, 5000); - return () => clearInterval(interval); + let interval; + if (!isEditing && !editModalOpen) { // Add editModalOpen check + interval = setInterval(fetchData, 10000); + } + return () => { + if (interval) clearInterval(interval); + }; + }, [gameId, isEditing, editModalOpen]); // Add editModalOpen dependency + + useEffect(() => { + const fetchOwners = async () => { + try { + // Only fetch player characters + const pcsResponse = await axios.get(`http://localhost:5000/games/${gameId}/playerchars`); + + const pcs = pcsResponse.data.map(pc => ({ + id: pc.CharID, + name: pc.CharName, + type: 'Player Character' + })); + + setAllOwners([ + { id: null, name: 'Unassigned', type: 'None' }, + ...pcs + ]); + } catch (error) { + console.error('Error fetching potential owners:', error); + } + }; + + fetchOwners(); }, [gameId]); + const handleItemClick = (item) => { + setSelectedItem(item); + setFormData({...item}); + setEditType('item'); + setEditModalOpen(true); + setIsEditing(true); + }; + + const handleNpcClick = (npc) => { + setSelectedItem(npc); + setFormData({...npc}); + setEditType('npc'); + setEditModalOpen(true); + setIsEditing(true); + }; + + const handleCharacterClick = (character) => { + setSelectedItem(character); + setFormData({...character}); + setEditType('character'); + setEditModalOpen(true); + setIsEditing(true); + }; + + const handleUpdate = async () => { + try { + let response; + if (editType === 'item') { + response = await axios.put(`http://localhost:5000/games/item/${selectedItem.ItemID}`, formData); + } else if (editType === 'npc') { + response = await axios.put(`http://localhost:5000/games/npc/${selectedItem.NPCID}`, formData); + } else if (editType === 'character') { + response = await axios.put(`http://localhost:5000/games/character/${selectedItem.CharID}`, formData); + } + + if (response.status === 200) { + setEditModalOpen(false); + setIsEditing(false); + setFormData(null); + fetchData(); + } + } catch (error) { + console.error('Error updating:', error); + } + }; + + const handleModalClose = () => { + setEditModalOpen(false); + setIsEditing(false); + setFormData(null); + }; + + // First, define the common input styles at component level + const commonStyles = { + dialog: { + '& .MuiPaper-root': { + backgroundColor: '#1e1e2f', + color: '#fff', + border: '1px solid #444', + borderRadius: '8px', + minWidth: '500px' + } + }, + input: { + '& .MuiOutlinedInput-root': { + color: '#fff', + '& fieldset': { borderColor: '#444' }, + '&:hover fieldset': { borderColor: '#764ACB' }, + '&.Mui-focused fieldset': { borderColor: '#764ACB' }, + }, + '& .MuiInputLabel-root': { + color: '#fff', + '&.Mui-focused': { + color: '#764ACB' + } + }, + '& .MuiInputBase-input': { color: '#fff' } + }, + select: { + color: '#fff', + '.MuiInputLabel-root': { + color: '#fff', + '&.Mui-focused': { + color: '#764ACB' + } + }, + '.MuiSelect-select': { + color: '#fff', + backgroundColor: '#1e1e2f' + }, + '.MuiOutlinedInput-root': { + color: '#fff', + backgroundColor: '#1e1e2f', + '& fieldset': { borderColor: '#444' }, + '&:hover fieldset': { borderColor: '#764ACB' }, + '&.Mui-focused fieldset': { borderColor: '#764ACB' } + }, + '.MuiSelect-icon': { color: '#fff' } + }, + menuItem: { + backgroundColor: '#1e1e2f', + color: '#fff', + '&:hover': { + backgroundColor: '#2e2e3f' + }, + '&.Mui-selected': { + backgroundColor: '#764ACB', + '&:hover': { + backgroundColor: '#9865f7' + } + } + } + }; + + // Update the EditModal component + const EditModal = () => { + const nonEditableFields = [ + 'GameID', 'CharID', 'ItemID', 'NPCID', 'Img', 'img', + 'GameId', 'NpcID', 'itemID', 'PlayerID', 'OwnerName' + ]; + + const renderField = (key) => { + if (key === 'Allied') { + return ( + + Status + + + ); + } + + if (key === 'Sex') { + return ( + + Sex + + + ); + } + + if (key === 'Rarity') { + return ( + + Rarity + + + ); + } + + if (key === 'Type') { + return ( + + Type + + + ); + } + + if (key === 'Art') { + return ( + + Art + + + ); + } + + if (key === 'OwnerID') { + return ( + + Owner + + + ); + } + + return ( + setFormData({ + ...formData, + [key]: e.target.value + })} + sx={{ ...commonStyles.input, mb: 2 }} + /> + ); + }; + + return ( + + + {editType === 'item' ? 'Edit Item' : editType === 'npc' ? 'Edit NPC' : 'Edit Character'} + + + {formData && ( + + {Object.keys(formData) + .filter(key => !nonEditableFields.includes(key)) + .map(key => renderField(key))} + + )} + + + + + + + ); + }; + const Section = ({ title, items, createPath, createText }) => ( { {items.map((item, index) => ( - - - - - - - {item.ItemName} - - - Type: {item.Type} - - - Value: {item.GoldValue} - - - Owner: {item.OwnerName || 'Unassigned'} - - - + + handleItemClick(item)} + sx={{ + backgroundColor: '#1e1e2f', + color: '#fff', + border: '1px solid #444', + height: '95%', + cursor: 'pointer', + transition: 'border-color 0.2s', + '&:hover': { + borderColor: '#764ACB' + } + }} + > + + + + + + {item.ItemName} + + + Type: {item.Type} + + + Value: {item.GoldValue} + + + Owner: {item.OwnerName || 'Unassigned'} + + + + ))} @@ -147,12 +553,20 @@ const GameMasterPage = () => { {playerCharacters.map((character, index) => ( - + handleCharacterClick(character)} + sx={{ + backgroundColor: '#1e1e2f', + color: '#fff', + border: '1px solid #444', + height: '100%', + cursor: 'pointer', + transition: 'border-color 0.2s', + '&:hover': { + borderColor: '#764ACB' + } + }} + > { {npcs.map((npc, index) => ( - + handleNpcClick(npc)} + sx={{ + backgroundColor: '#1e1e2f', + color: '#fff', + border: '1px solid #444', + height: '100%', + cursor: 'pointer', + transition: 'border-color 0.2s', + '&:hover': { + borderColor: '#764ACB' + } + }} + > { component="img" height="140" image={npc.Img || defaultCharacterImage} - alt={npc.CharName} + alt={npc.Name || 'NPC'} sx={{ objectFit: 'contain', width: '140px', @@ -276,6 +698,7 @@ const GameMasterPage = () => { createPath="/create-item" createText="Create Item" /> + ); };