+
This commit is contained in:
Binary file not shown.
+191
-11
@@ -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
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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,6 +80,10 @@ function App() {
|
||||
path='/create-item'
|
||||
element={<CreateItem isLoggedIn={isLoggedIn} />}
|
||||
/>
|
||||
<Route
|
||||
path='/create-npc'
|
||||
element={<CreateNpc isLoggedIn={isLoggedIn} />}
|
||||
/>
|
||||
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
@@ -89,6 +89,18 @@ const CreateItem = () => {
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
|
||||
<Button
|
||||
onClick={() => navigate(`/games/${gameId}/master`)}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#764ACB',
|
||||
'&:hover': { backgroundColor: '#9865f7' },
|
||||
mb: 3
|
||||
}}
|
||||
>
|
||||
Back to Game
|
||||
</Button>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Grid2 container spacing={3}>
|
||||
{/* Left Column - Image and Basic Info */}
|
||||
|
||||
@@ -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 (
|
||||
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Grid2 container spacing={3}>
|
||||
{/* NPC Image and Basic Info */}
|
||||
<Grid2 item xs={12} md={4}>
|
||||
<Box sx={{ border: '1px solid #444', borderRadius: '3px', p: 2, backgroundColor: '#2e2e3f' }}>
|
||||
<Card sx={{ width: '400px', backgroundColor: '#1e1e2f', color: '#fff', height: '635px' }}>
|
||||
{/* Image Upload Section - Similar to CreateCharacter */}
|
||||
{imagePreview ? (
|
||||
<Box component="label" htmlFor="image-upload" sx={{ cursor: 'pointer', position: 'relative' }}>
|
||||
<input
|
||||
accept="image/*"
|
||||
type="file"
|
||||
id="image-upload"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="300"
|
||||
image={imagePreview}
|
||||
alt="NPC Preview"
|
||||
sx={{ borderRadius: '3px', objectFit: 'contain', margin: '0 auto', borderBottom: '1px solid #444' }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
component="label"
|
||||
htmlFor="image-upload"
|
||||
sx={{
|
||||
height: 300,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#2e2e3f',
|
||||
borderRadius: '3px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<input
|
||||
accept="image/*"
|
||||
type="file"
|
||||
id="image-upload"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleImageChange}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
component="span"
|
||||
sx={{
|
||||
backgroundColor: '#764ACB',
|
||||
'&:hover': { backgroundColor: '#9865f7' }
|
||||
}}
|
||||
>
|
||||
Bild Hochladen
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<CardContent sx={{ p: 3 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Name des NPCs"
|
||||
name="Name"
|
||||
value={formData.Name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={{ ...inputStyles, mb: 2, width: '347px'}}
|
||||
/>
|
||||
|
||||
<Grid2 container spacing={2}>
|
||||
<Grid2 item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Alter"
|
||||
name="Age"
|
||||
type="number"
|
||||
value={formData.Age}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={{ ...inputStyles, width: '165px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Rasse"
|
||||
name="Race"
|
||||
value={formData.Race}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={{ ...inputStyles, width: '165px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 container spacing={2} sx={{ mt: 1 }}>
|
||||
<Grid2 item xs={6}>
|
||||
<FormControl sx={{ width: '165px' }}>
|
||||
<InputLabel sx={{ color: '#fff' }}>Geschlecht</InputLabel>
|
||||
<Select
|
||||
name="Sex"
|
||||
value={formData.Sex}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={{
|
||||
color: '#fff',
|
||||
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
|
||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
|
||||
}}
|
||||
>
|
||||
<MenuItem value="Male">Männlich</MenuItem>
|
||||
<MenuItem value="Female">Weiblich</MenuItem>
|
||||
<MenuItem value="Other">Divers</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid2>
|
||||
<Grid2 item xs={6}>
|
||||
<FormControl sx={{ width: '165px' }}>
|
||||
<InputLabel sx={{ color: '#fff' }}>Status</InputLabel>
|
||||
<Select
|
||||
name="Allied"
|
||||
value={formData.Allied}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={{
|
||||
color: '#fff',
|
||||
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
|
||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
|
||||
}}
|
||||
>
|
||||
<MenuItem value={0}>Verbündet</MenuItem>
|
||||
<MenuItem value={1}>Neutral</MenuItem>
|
||||
<MenuItem value={2}>Feindlich</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
|
||||
<Grid2 container spacing={2} sx={{ mt: 1 }}>
|
||||
<Grid2 item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Beruf/Klasse"
|
||||
name="Job"
|
||||
value={formData.Job}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={{ ...inputStyles, width: '165px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 item xs={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Level"
|
||||
name="Level"
|
||||
type="number"
|
||||
value={formData.Level}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={{ ...inputStyles, width: '165px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Grid2>
|
||||
|
||||
{/* Right Column - Stats and Description */}
|
||||
<Grid2 item xs={12} md={8}>
|
||||
{/* Health/Mana Bars */}
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 4, width: '810px' }}>
|
||||
<Box sx={{ flex: 1, backgroundColor: '#2e2e3f', borderRadius: '8px', p: 2, border: '1px solid #444' }}>
|
||||
<Typography variant="body1" sx={{ display: 'flex', justifyContent: 'space-between', color: '#fff' }}>
|
||||
<HeartIcon sx={{ color: "red" }} />
|
||||
<span>Maximale Gesundheit</span>
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="MaxHealth"
|
||||
type="number"
|
||||
value={formData.MaxHealth}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={inputStyles}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ flex: 1, backgroundColor: '#2e2e3f', borderRadius: '8px', p: 2, border: '1px solid #444' }}>
|
||||
<Typography variant="body1" sx={{ display: 'flex', justifyContent: 'space-between', color: '#fff' }}>
|
||||
<WaterDropIcon sx={{ color: 'blue' }} />
|
||||
<span>Maximales Mana</span>
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="MaxMana"
|
||||
type="number"
|
||||
value={formData.MaxMana}
|
||||
onChange={handleChange}
|
||||
required
|
||||
sx={inputStyles}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Description */}
|
||||
<Box sx={{ mb: 4, width: '810px' }}>
|
||||
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Beschreibung</Typography>
|
||||
<Box sx={{ backgroundColor: '#2e2e3f', p: 2, borderRadius: '8px', border: '1px solid #444' }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={4}
|
||||
name="Description"
|
||||
value={formData.Description}
|
||||
onChange={handleChange}
|
||||
sx={inputStyles}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Stats */}
|
||||
<Box sx={{ mb: 4, width: '810px' }}>
|
||||
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Attribute</Typography>
|
||||
<Box sx={{
|
||||
backgroundColor: '#2e2e3f',
|
||||
p: 2,
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #444'
|
||||
}}>
|
||||
<Grid2 container spacing={2} justifyContent="space-between">
|
||||
<Grid2 item>
|
||||
<TextField
|
||||
label="Stärke"
|
||||
name="Strength"
|
||||
type="number"
|
||||
value={formData.Strength}
|
||||
onChange={handleChange}
|
||||
sx={{ ...inputStyles, width: '180px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 item>
|
||||
<TextField
|
||||
label="Geschicklichkeit"
|
||||
name="Dexterity"
|
||||
type="number"
|
||||
value={formData.Dexterity}
|
||||
onChange={handleChange}
|
||||
sx={{ ...inputStyles, width: '180px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 item>
|
||||
<TextField
|
||||
label="Beweglichkeit"
|
||||
name="Agility"
|
||||
type="number"
|
||||
value={formData.Agility}
|
||||
onChange={handleChange}
|
||||
sx={{ ...inputStyles, width: '180px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
<Grid2 item>
|
||||
<TextField
|
||||
label="Ausdauer"
|
||||
name="Endurance"
|
||||
type="number"
|
||||
value={formData.Endurance}
|
||||
onChange={handleChange}
|
||||
sx={{ ...inputStyles, width: '180px' }}
|
||||
/>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Button Container */}
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2,
|
||||
width: '800px'
|
||||
}}>
|
||||
<Button
|
||||
onClick={() => navigate(`/games/${gameId}/master`)}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#764ACB',
|
||||
'&:hover': { backgroundColor: '#9865f7' },
|
||||
width: '200px'
|
||||
}}
|
||||
>
|
||||
Zurück ohne Speichern
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#764ACB',
|
||||
'&:hover': { backgroundColor: '#9865f7' },
|
||||
width: '580px'
|
||||
}}
|
||||
>
|
||||
NPC Erstellen
|
||||
</Button>
|
||||
</Box>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateNpc;
|
||||
@@ -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 (
|
||||
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||
<InputLabel>Status</InputLabel>
|
||||
<Select
|
||||
value={formData[key] || 0}
|
||||
label="Status"
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
[key]: e.target.value
|
||||
})}
|
||||
>
|
||||
<MenuItem value={0}>Allied</MenuItem>
|
||||
<MenuItem value={1}>Neutral</MenuItem>
|
||||
<MenuItem value={2}>Enemy</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'Sex') {
|
||||
return (
|
||||
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||
<InputLabel>Sex</InputLabel>
|
||||
<Select
|
||||
value={formData[key] || ''}
|
||||
label="Sex"
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
[key]: e.target.value
|
||||
})}
|
||||
>
|
||||
<MenuItem value="Male">Male</MenuItem>
|
||||
<MenuItem value="Female">Female</MenuItem>
|
||||
<MenuItem value="Other">Other</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'Rarity') {
|
||||
return (
|
||||
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||
<InputLabel>Rarity</InputLabel>
|
||||
<Select
|
||||
value={formData[key] || 1}
|
||||
label="Rarity"
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
[key]: e.target.value
|
||||
})}
|
||||
>
|
||||
<MenuItem value={1}>Poor</MenuItem>
|
||||
<MenuItem value={2}>Common</MenuItem>
|
||||
<MenuItem value={3}>Uncommon</MenuItem>
|
||||
<MenuItem value={4}>Rare</MenuItem>
|
||||
<MenuItem value={5}>Epic</MenuItem>
|
||||
<MenuItem value={6}>Legendary</MenuItem>
|
||||
<MenuItem value={7}>Artifact</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'Type') {
|
||||
return (
|
||||
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||
<InputLabel>Type</InputLabel>
|
||||
<Select
|
||||
value={formData[key] || ''}
|
||||
label="Type"
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
[key]: e.target.value
|
||||
})}
|
||||
>
|
||||
<MenuItem value="Weapon">Weapon</MenuItem>
|
||||
<MenuItem value="Armor">Armor</MenuItem>
|
||||
<MenuItem value="Consumable">Consumable</MenuItem>
|
||||
<MenuItem value="Quest">Quest Item</MenuItem>
|
||||
<MenuItem value="Other">Other</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'Art') {
|
||||
return (
|
||||
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||
<InputLabel>Art</InputLabel>
|
||||
<Select
|
||||
value={formData[key] || ''}
|
||||
label="Art"
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
[key]: e.target.value
|
||||
})}
|
||||
>
|
||||
<MenuItem value="Sword">Sword</MenuItem>
|
||||
<MenuItem value="Axe">Axe</MenuItem>
|
||||
<MenuItem value="Bow">Bow</MenuItem>
|
||||
<MenuItem value="Shield">Shield</MenuItem>
|
||||
<MenuItem value="Staff">Staff</MenuItem>
|
||||
<MenuItem value="Potion">Potion</MenuItem>
|
||||
<MenuItem value="Other">Other</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'OwnerID') {
|
||||
return (
|
||||
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||
<InputLabel sx={{ color: '#fff' }}>Owner</InputLabel>
|
||||
<Select
|
||||
value={formData[key] || ''}
|
||||
label="Owner"
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
[key]: e.target.value
|
||||
})}
|
||||
sx={{
|
||||
color: '#fff',
|
||||
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
|
||||
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
|
||||
}}
|
||||
>
|
||||
{allOwners.map((owner) => (
|
||||
<MenuItem
|
||||
key={owner.id}
|
||||
value={owner.id || ''}
|
||||
sx={{
|
||||
...commonStyles.menuItem,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between'
|
||||
}}
|
||||
>
|
||||
<span>{owner.name}</span>
|
||||
{owner.type !== 'None' && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
ml: 2,
|
||||
color: '#764ACB'
|
||||
}}
|
||||
>
|
||||
{owner.type}
|
||||
</Typography>
|
||||
)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TextField
|
||||
key={key}
|
||||
fullWidth
|
||||
label={key}
|
||||
value={formData[key] || ''}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
[key]: e.target.value
|
||||
})}
|
||||
sx={{ ...commonStyles.input, mb: 2 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={editModalOpen}
|
||||
onClose={handleModalClose}
|
||||
sx={commonStyles.dialog}
|
||||
>
|
||||
<DialogTitle sx={{
|
||||
borderBottom: '1px solid #444',
|
||||
color: '#fff',
|
||||
backgroundColor: '#2e2e3f',
|
||||
padding: '16px 24px'
|
||||
}}>
|
||||
{editType === 'item' ? 'Edit Item' : editType === 'npc' ? 'Edit NPC' : 'Edit Character'}
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{
|
||||
p: 3,
|
||||
backgroundColor: '#1e1e2f'
|
||||
}}>
|
||||
{formData && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
{Object.keys(formData)
|
||||
.filter(key => !nonEditableFields.includes(key))
|
||||
.map(key => renderField(key))}
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{
|
||||
borderTop: '1px solid #444',
|
||||
p: 2,
|
||||
backgroundColor: '#2e2e3f',
|
||||
'& .MuiButton-root': {
|
||||
textTransform: 'none',
|
||||
fontSize: '1rem',
|
||||
padding: '6px 16px'
|
||||
}
|
||||
}}>
|
||||
<Button
|
||||
onClick={handleModalClose}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
color: '#fff',
|
||||
borderColor: '#444',
|
||||
'&:hover': {
|
||||
borderColor: '#764ACB',
|
||||
backgroundColor: 'rgba(118, 74, 203, 0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleUpdate}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: '#764ACB',
|
||||
color: '#fff',
|
||||
'&:hover': {
|
||||
backgroundColor: '#9865f7'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const Section = ({ title, items, createPath, createText }) => (
|
||||
<Box sx={{
|
||||
mb: 4,
|
||||
@@ -70,12 +466,21 @@ const GameMasterPage = () => {
|
||||
<Grid2 container spacing={2}>
|
||||
{items.map((item, index) => (
|
||||
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||
<Card sx={{
|
||||
<Tooltip title={item.Abilities || 'No description available'} arrow>
|
||||
<Card
|
||||
onClick={() => handleItemClick(item)}
|
||||
sx={{
|
||||
backgroundColor: '#1e1e2f',
|
||||
color: '#fff',
|
||||
height: '95%', // Reduced height
|
||||
border: '1px solid #444',
|
||||
}}>
|
||||
height: '95%',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 0.2s',
|
||||
'&:hover': {
|
||||
borderColor: '#764ACB'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -85,7 +490,7 @@ const GameMasterPage = () => {
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="135" // Reduced from 140
|
||||
image={item.Img || defaultItemImage}
|
||||
image={item.img || defaultItemImage} // Changed from item.img to item.Img
|
||||
alt={item.ItemName}
|
||||
className={`rarity-${item.Rarity} rarity-image`}
|
||||
sx={{
|
||||
@@ -113,6 +518,7 @@ const GameMasterPage = () => {
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
</Grid2>
|
||||
))}
|
||||
</Grid2>
|
||||
@@ -147,12 +553,20 @@ const GameMasterPage = () => {
|
||||
<Grid2 container spacing={2}>
|
||||
{playerCharacters.map((character, index) => (
|
||||
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||
<Card sx={{
|
||||
<Card
|
||||
onClick={() => handleCharacterClick(character)}
|
||||
sx={{
|
||||
backgroundColor: '#1e1e2f',
|
||||
color: '#fff',
|
||||
border: '1px solid #444',
|
||||
height: '100%'
|
||||
}}>
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 0.2s',
|
||||
'&:hover': {
|
||||
borderColor: '#764ACB'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -215,12 +629,20 @@ const GameMasterPage = () => {
|
||||
<Grid2 container spacing={2}>
|
||||
{npcs.map((npc, index) => (
|
||||
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||
<Card sx={{
|
||||
<Card
|
||||
onClick={() => handleNpcClick(npc)}
|
||||
sx={{
|
||||
backgroundColor: '#1e1e2f',
|
||||
color: '#fff',
|
||||
border: '1px solid #444',
|
||||
height: '100%'
|
||||
}}>
|
||||
height: '100%',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 0.2s',
|
||||
'&:hover': {
|
||||
borderColor: '#764ACB'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -231,7 +653,7 @@ const GameMasterPage = () => {
|
||||
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"
|
||||
/>
|
||||
<EditModal />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user