Character Creator

Added the character creation page.
This commit is contained in:
Marces Zastrow
2025-01-10 12:05:05 +01:00
parent b56b73754c
commit 9c45795e80
6 changed files with 385 additions and 344 deletions
Binary file not shown.
+37
View File
@@ -192,7 +192,44 @@ app.get('/games/:gameId/items', (req, res) => {
}); });
}) })
// Player Part // Player Part
// Create Player Character
app.post('/games/character/create', upload.single('image'), (req, res) => {
const {
charName, race, sex, age, job, description,
maxHealth, maxMana, strength, dexterity, agility, endurance,
gameId, playerId
} = req.body;
const imageBuffer = req.file ? req.file.buffer : null;
const stmt = db.prepare(`
INSERT INTO PlayerCharacter (
GameID, PlayerID, CharName, Race, Sex, Age, Job,
description, maxHealth, currentHealth, maxMana, currentMana,
Strength, Dexterity, Agility, Endurance, Level, Gold, Img
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, ?)
`);
stmt.run([
gameId, playerId, charName, race, sex, age, job,
description, maxHealth, maxHealth, maxMana, maxMana,
strength, dexterity, agility, endurance, imageBuffer
], function(err) {
if (err) {
console.error('Database error:', err);
return res.status(500).json({ error: 'Internal server error' });
}
res.status(201).json({
message: 'Character created successfully!',
characterId: this.lastID
});
});
stmt.finalize();
});
// Fetch all Player Characters of Player // Fetch all Player Characters of Player
app.get('/games/:userId/characters', (req, res) => { app.get('/games/:userId/characters', (req, res) => {
const userId = req.params.userId; const userId = req.params.userId;
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

+239 -235
View File
@@ -2,17 +2,19 @@ import React, { useState, useContext } from 'react';
import { useNavigate, useLocation } from 'react-router-dom'; import { useNavigate, useLocation } from 'react-router-dom';
import { UserContext } from '../context/UserContext'; import { UserContext } from '../context/UserContext';
import axios from 'axios'; import axios from 'axios';
import { import { Box, TextField, Button, Typography, Select, MenuItem, FormControl, InputLabel, Grid2, Card, CardContent, CardMedia, Menu } from '@mui/material';
Box,
TextField, // Import the same icons as in games.jsx
Button, import PaidIcon from '@mui/icons-material/Paid';
Typography, import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
Select, import PetsIcon from '@mui/icons-material/Pets';
MenuItem, import WcIcon from '@mui/icons-material/Wc';
FormControl, import WorkIcon from '@mui/icons-material/Work';
InputLabel, import HeartIcon from '@mui/icons-material/Favorite';
Grid2 import WaterDropIcon from '@mui/icons-material/WaterDrop';
} from '@mui/material'; import KeyboardDoubleArrowUpIcon from '@mui/icons-material/KeyboardDoubleArrowUp';
import defaultCharacterImage from '../assets/default-character.png';
const CreateCharacter = () => { const CreateCharacter = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -57,18 +59,23 @@ const CreateCharacter = () => {
e.preventDefault(); e.preventDefault();
const formDataToSend = new FormData(); const formDataToSend = new FormData();
// Append all form data // Add all form data
Object.keys(formData).forEach(key => { Object.keys(formData).forEach(key => {
formDataToSend.append(key, formData[key]); formDataToSend.append(key, formData[key]);
}); });
// Append image if exists // Add image if selected
if (selectedImage) { if (selectedImage) {
formDataToSend.append('image', selectedImage); formDataToSend.append('image', selectedImage);
} }
// Add gameId and playerId
formDataToSend.append('gameId', gameId);
formDataToSend.append('playerId', userId);
try { try {
await axios.post('http://localhost:5000/games/character/create', const response = await axios.post(
'http://localhost:5000/games/character/create',
formDataToSend, formDataToSend,
{ {
headers: { headers: {
@@ -76,60 +83,88 @@ const CreateCharacter = () => {
} }
} }
); );
if (response.status === 201) {
// Redirect to the game page after successful character creation
navigate(`/games/${gameId}`); navigate(`/games/${gameId}`);
}
} catch (error) { } catch (error) {
console.error('Error creating character:', error); console.error('Error creating character:', error);
// You might want to show an error message to the user here
} }
}; };
return ( const inputStyles = {
<Box sx={{
p: 3,
background: 'rgba(30, 30, 47, 0.9)',
borderRadius: '8px',
marginTop: '40px',
maxWidth: '800px',
margin: '40px auto'
}}>
<Typography variant="h4" sx={{ mb: 4, color: '#fff', textAlign: 'center' }}>
Create New Character
</Typography>
<form onSubmit={handleSubmit}>
<Grid2 container spacing={2}>
{/* Basic Info */}
<Grid2 item xs={12}>
<TextField
fullWidth
label="Character Name"
name="charName"
value={formData.charName}
onChange={handleChange}
required
sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
'& fieldset': { '& fieldset': { borderColor: '#444' },
borderColor: '#444', '&:hover fieldset': { borderColor: '#764ACB' },
'&.Mui-focused fieldset': { borderColor: '#764ACB' },
}, },
'&:hover fieldset': { '& .MuiInputLabel-root': { color: '#fff' },
borderColor: '#764ACB', '& .MuiInputBase-input': { color: '#fff' }
}, };
'&.Mui-focused fieldset': {
borderColor: '#764ACB', return (
}, <Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
}, <form onSubmit={handleSubmit}>
'& .MuiInputLabel-root': { <Grid2 container spacing={3}>
color: '#fff', {/* Character Image and Details */}
}, <Grid2 item xs={12} md={4}>
'& .MuiInputBase-input': { <Box sx={{ border: '1px solid #444', borderRadius: '3px', p: 2, backgroundColor: '#2e2e3f' }}>
<Card sx={{ width: '400px', backgroundColor: '#1e1e2f', color: '#fff', height: '635px' }}>
{imagePreview ? (
<Box
component="label"
htmlFor="image-upload"
sx={{
cursor: 'pointer',
position: 'relative',
'&:hover::after': {
content: '"Change Image"',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(0,0,0,0.5)',
color: '#fff', color: '#fff',
fontSize: '1.2rem',
borderRadius: '3px'
} }
}} }}
/> >
</Grid2> <input
accept="image/*"
{/* Image Upload */} type="file"
<Grid2 item xs={12}> id="image-upload"
style={{ display: 'none' }}
onChange={handleImageChange}
/>
<CardMedia
component="img"
height="300"
image={imagePreview}
alt="Character Preview"
sx={{ borderRadius: '3px', objectFit: 'cover', margin: '0 auto' }}
/>
</Box>
) : (
<Box
sx={{
height: 300,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#2e2e3f',
borderRadius: '3px',
cursor: 'pointer'
}}
component="label"
htmlFor="image-upload"
>
<input <input
accept="image/*" accept="image/*"
type="file" type="file"
@@ -137,86 +172,44 @@ const CreateCharacter = () => {
style={{ display: 'none' }} style={{ display: 'none' }}
onChange={handleImageChange} onChange={handleImageChange}
/> />
<label htmlFor="image-upload">
<Button <Button
variant="contained" variant="contained"
component="span" component="span"
sx={{ sx={{
backgroundColor: '#764ACB', backgroundColor: '#764ACB',
'&:hover': { backgroundColor: '#9865f7' }, '&:hover': { backgroundColor: '#9865f7' }
mb: 2
}} }}
> >
Upload Character Image Upload Character Image
</Button> </Button>
</label>
{imagePreview && (
<Box sx={{ mt: 2, mb: 2 }}>
<img
src={imagePreview}
alt="Preview"
style={{
maxWidth: '200px',
borderRadius: '4px',
border: '1px solid #444'
}}
/>
</Box> </Box>
)} )}
</Grid2> <CardContent sx={{
padding: '4px',
{/* Character Details */} display: 'flex',
<Grid2 container item spacing={2}> flexDirection: 'column',
<Grid2 item xs={12} md={6}> alignItems: 'center'
<FormControl fullWidth sx={{ mb: 2 }}> }}>
<InputLabel sx={{ color: '#fff' }}>Race</InputLabel> <TextField
<Select label="Character Name"
name="race" name="charName"
value={formData.race} value={formData.charName}
onChange={handleChange} onChange={handleChange}
required required
sx={{ sx={{
color: '#fff', mb: 4,
'& .MuiOutlinedInput-notchedOutline': { width: '315px',
borderColor: '#444', '& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
}, },
'&:hover .MuiOutlinedInput-notchedOutline': { '& .MuiInputLabel-root': { color: '#fff' },
borderColor: '#764ACB', '& .MuiInputBase-input': { color: '#fff', textAlign: 'center' }
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: '#764ACB',
},
'& .MuiSelect-icon': {
color: '#fff',
}
}} }}
> />
<MenuItem value="Human">Human</MenuItem>
<MenuItem value="Elf">Elf</MenuItem>
<MenuItem value="Dwarf">Dwarf</MenuItem>
<MenuItem value="Orc">Orc</MenuItem>
</Select>
</FormControl>
</Grid2>
<Grid2 item xs={12} md={6}> <Grid2 container spacing={2} sx={{ maxWidth: '600px', justifyContent: 'center' }}>
<FormControl fullWidth> <Grid2 item xs={5}>
<InputLabel sx={{ color: '#fff' }}>Sex</InputLabel>
<Select
name="sex"
value={formData.sex}
onChange={handleChange}
required
sx={{ color: '#fff', '& fieldset': { borderColor: '#444' } }}
>
<MenuItem value="Male">Male</MenuItem>
<MenuItem value="Female">Female</MenuItem>
<MenuItem value="Other">Other</MenuItem>
</Select>
</FormControl>
</Grid2>
<Grid2 item xs={12} md={6}>
<TextField <TextField
fullWidth fullWidth
label="Age" label="Age"
@@ -225,18 +218,42 @@ const CreateCharacter = () => {
value={formData.age} value={formData.age}
onChange={handleChange} onChange={handleChange}
required required
sx={{ sx={{ ...inputStyles, width: '150px' }}
'& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' }
}}
/> />
</Grid2> </Grid2>
<Grid2 item xs={5}>
<Grid2 item xs={12} md={6}> <TextField
fullWidth
label="Race"
name="race"
value={formData.race}
onChange={handleChange}
required
sx={{ ...inputStyles, width: '150px' }}
/>
</Grid2>
<Grid2 item xs={5}>
<FormControl fullWidth>
<InputLabel sx={{ color: '#fff' }}>Sex</InputLabel>
<Select
name="sex"
value={formData.sex}
onChange={handleChange}
required
sx={{
color: '#fff',
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
width: '150px'
}}
>
<MenuItem value="Male">Male</MenuItem>
<MenuItem value="Female">Female</MenuItem>
<MenuItem value="Other">Other</MenuItem>
</Select>
</FormControl>
</Grid2>
<Grid2 item xs={5}>
<TextField <TextField
fullWidth fullWidth
label="Job/Class" label="Job/Class"
@@ -244,22 +261,25 @@ const CreateCharacter = () => {
value={formData.job} value={formData.job}
onChange={handleChange} onChange={handleChange}
required required
sx={{ sx={{ ...inputStyles, width: '150px' }}
'& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' }
}}
/> />
</Grid2> </Grid2>
</Grid2>
</CardContent>
</Card>
</Box>
</Grid2 >
{/* Stats */} <Grid2 item xs={12} md={8}>
<Grid2 item xs={12} md={6}> {/* Health/Mana Bars - Keep existing */}
<Box sx={{ display: 'flex', gap: 2, mb: 4, width: '885px' }}>
<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>Max Health</span>
</Typography>
<TextField <TextField
fullWidth fullWidth
label="Max Health"
name="maxHealth" name="maxHealth"
type="number" type="number"
value={formData.maxHealth} value={formData.maxHealth}
@@ -270,16 +290,17 @@ const CreateCharacter = () => {
'& fieldset': { borderColor: '#444' }, '& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' }, '&:hover fieldset': { borderColor: '#764ACB' },
}, },
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' } '& .MuiInputBase-input': { color: '#fff' }
}} }}
/> />
</Grid2> </Box>
<Box sx={{ flex: 1, backgroundColor: '#2e2e3f', borderRadius: '8px', p: 2, border: '1px solid #444' }}>
<Grid2 item xs={12} md={6}> <Typography variant="body1" sx={{ display: 'flex', justifyContent: 'space-between', color: '#fff' }}>
<WaterDropIcon sx={{ color: 'blue' }} />
<span>Max Mana</span>
</Typography>
<TextField <TextField
fullWidth fullWidth
label="Max Mana"
name="maxMana" name="maxMana"
type="number" type="number"
value={formData.maxMana} value={formData.maxMana}
@@ -290,100 +311,21 @@ const CreateCharacter = () => {
'& fieldset': { borderColor: '#444' }, '& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' }, '&:hover fieldset': { borderColor: '#764ACB' },
}, },
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' } '& .MuiInputBase-input': { color: '#fff' }
}} }}
/> />
</Grid2> </Box>
</Box>
<Grid2 item xs={12} md={6}>
<TextField
fullWidth
label="Strength"
name="strength"
type="number"
value={formData.strength}
onChange={handleChange}
required
sx={{
'& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' }
}}
/>
</Grid2>
<Grid2 item xs={12} md={6}>
<TextField
fullWidth
label="Dexterity"
name="dexterity"
type="number"
value={formData.dexterity}
onChange={handleChange}
required
sx={{
'& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' }
}}
/>
</Grid2>
<Grid2 item xs={12} md={6}>
<TextField
fullWidth
label="Agility"
name="agility"
type="number"
value={formData.agility}
onChange={handleChange}
required
sx={{
'& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' }
}}
/>
</Grid2>
<Grid2 item xs={12} md={6}>
<TextField
fullWidth
label="Endurance"
name="endurance"
type="number"
value={formData.endurance}
onChange={handleChange}
required
sx={{
'& .MuiOutlinedInput-root': {
'& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' },
},
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' }
}}
/>
</Grid2>
{/* Description */} {/* Description */}
<Grid2 item xs={12}> <Box sx={{ mb: 4 }}>
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Description</Typography>
<Box sx={{ backgroundColor: '#2e2e3f', p: 2, borderRadius: '8px', border: '1px solid #444' }}>
<TextField <TextField
fullWidth fullWidth
label="Description"
name="description"
multiline multiline
rows={4} rows={4}
name="description"
value={formData.description} value={formData.description}
onChange={handleChange} onChange={handleChange}
sx={{ sx={{
@@ -391,14 +333,74 @@ const CreateCharacter = () => {
'& fieldset': { borderColor: '#444' }, '& fieldset': { borderColor: '#444' },
'&:hover fieldset': { borderColor: '#764ACB' }, '&:hover fieldset': { borderColor: '#764ACB' },
}, },
'& .MuiInputLabel-root': { color: '#fff' },
'& .MuiInputBase-input': { color: '#fff' } '& .MuiInputBase-input': { color: '#fff' }
}} }}
/> />
</Grid2> </Box>
</Grid2> </Box>
</Grid2>
{/* Stats Grid */}
<Box sx={{ mb: 4 }}>
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Character Stats</Typography>
<Box sx={{
backgroundColor: '#2e2e3f',
p: 2,
borderRadius: '8px',
border: '1px solid #444',
maxWidth: '800px',
margin: '0 auto'
}}>
<Grid2 container spacing={3} justifyContent="center">
<Grid2 item xs={10} md={2}>
<TextField
fullWidth
label="Strength"
name="strength"
type="number"
value={formData.strength}
onChange={handleChange}
sx={{ ...inputStyles }}
/>
</Grid2>
<Grid2 item xs={10} md={2}>
<TextField
fullWidth
label="Dexterity"
name="dexterity"
type="number"
value={formData.dexterity}
onChange={handleChange}
sx={{ ...inputStyles }}
/>
</Grid2>
<Grid2 item xs={10} md={2}>
<TextField
fullWidth
label="Agility"
name="agility"
type="number"
value={formData.agility}
onChange={handleChange}
sx={{ ...inputStyles }}
/>
</Grid2>
<Grid2 item xs={10} md={2}>
<TextField
fullWidth
label="Endurance"
name="endurance"
type="number"
value={formData.endurance}
onChange={handleChange}
sx={{ ...inputStyles }}
/>
</Grid2>
</Grid2>
</Box>
</Box>
{/* Submit Button */}
<Button <Button
type="submit" type="submit"
variant="contained" variant="contained"
@@ -411,6 +413,8 @@ const CreateCharacter = () => {
> >
Create Character Create Character
</Button> </Button>
</Grid2>
</Grid2 >
</form > </form >
</Box > </Box >
); );
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

+2 -2
View File
@@ -27,8 +27,8 @@ async function uploadImage(imagePath, gameId, userId) {
// Example usage: // Example usage:
// Replace these values with your actual gameId and userId // Replace these values with your actual gameId and userId
const gameId = 'eb82723d'; const gameId = 'd7997cfd';
const userId = '1'; const userId = '1';
const imagePath = './female-char.png'; const imagePath = './catiana.png';
uploadImage(imagePath, gameId, userId); uploadImage(imagePath, gameId, userId);