Merge branch 'master' into hugo
This commit is contained in:
@@ -24,6 +24,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./requirements/svelte/api_front/src:/usr/app/src/
|
- ./requirements/svelte/api_front/src:/usr/app/src/
|
||||||
- ./requirements/svelte/api_front/public:/usr/app/public/
|
- ./requirements/svelte/api_front/public:/usr/app/public/
|
||||||
|
ports:
|
||||||
|
- "35729:35729"
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
60
srcs/requirements/nestjs/api_back/package-lock.json
generated
60
srcs/requirements/nestjs/api_back/package-lock.json
generated
@@ -3886,9 +3886,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dezalgo": {
|
"node_modules/dezalgo": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||||
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
|
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asap": "^2.0.0",
|
"asap": "^2.0.0",
|
||||||
@@ -4823,25 +4823,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/formidable": {
|
"node_modules/formidable": {
|
||||||
"version": "2.0.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
|
||||||
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
|
"integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dezalgo": "1.0.3",
|
"dezalgo": "^1.0.4",
|
||||||
"hexoid": "1.0.0",
|
"hexoid": "^1.0.0",
|
||||||
"once": "1.4.0",
|
"once": "^1.4.0",
|
||||||
"qs": "6.9.3"
|
"qs": "^6.11.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/formidable/node_modules/qs": {
|
"node_modules/formidable/node_modules/qs": {
|
||||||
"version": "6.9.3",
|
"version": "6.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||||
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
|
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.4"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
},
|
},
|
||||||
@@ -12651,9 +12654,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"dezalgo": {
|
"dezalgo": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||||
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
|
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asap": "^2.0.0",
|
"asap": "^2.0.0",
|
||||||
@@ -13380,22 +13383,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"formidable": {
|
"formidable": {
|
||||||
"version": "2.0.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
|
||||||
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
|
"integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"dezalgo": "1.0.3",
|
"dezalgo": "^1.0.4",
|
||||||
"hexoid": "1.0.0",
|
"hexoid": "^1.0.0",
|
||||||
"once": "1.4.0",
|
"once": "^1.4.0",
|
||||||
"qs": "6.9.3"
|
"qs": "^6.11.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"qs": {
|
"qs": {
|
||||||
"version": "6.9.3",
|
"version": "6.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||||
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
|
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"side-channel": "^1.0.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ConfigModule } from '@nestjs/config';
|
|||||||
import { FriendshipsModule } from './friendship/friendships.module';
|
import { FriendshipsModule } from './friendship/friendships.module';
|
||||||
import { AuthenticationModule } from './auth/42/authentication.module';
|
import { AuthenticationModule } from './auth/42/authentication.module';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
|
// import { GameModule } from './game/game/game.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UsersModule,
|
imports: [UsersModule,
|
||||||
@@ -26,6 +27,7 @@ import { PassportModule } from '@nestjs/passport';
|
|||||||
//avec une classe pour le module
|
//avec une classe pour le module
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
}),
|
}),
|
||||||
|
// GameModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { AuthenticationService } from './authentication.service';
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { TwoFaDto } from './dto/2fa.dto';
|
import { TwoFaDto } from './dto/2fa.dto';
|
||||||
import { UsersService } from 'src/users/users.service';
|
import { UsersService } from 'src/users/users.service';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthenticationController {
|
export class AuthenticationController {
|
||||||
@@ -33,13 +34,17 @@ export class AuthenticationController {
|
|||||||
async redirect(@Res() response : Response, @Req() request) {
|
async redirect(@Res() response : Response, @Req() request) {
|
||||||
console.log('ON EST DANS REDIRECT AUTH CONTROLLER');
|
console.log('ON EST DANS REDIRECT AUTH CONTROLLER');
|
||||||
console.log('On redirige');
|
console.log('On redirige');
|
||||||
if (request.user.isEnabledTwoFactorAuth === false)
|
const user : User = request.user
|
||||||
return response.status(200).redirect('http://transcendance:8080');
|
if (user.isEnabledTwoFactorAuth === false || user.isTwoFactorAuthenticated === true){
|
||||||
|
console.log('ON VA VERS PROFILE');
|
||||||
|
return response.status(200).redirect('http://transcendance:8080/#/profile');
|
||||||
|
}
|
||||||
|
console.log('ON VA VERS 2FA')
|
||||||
return response.status(200).redirect('http://transcendance:8080/#/2fa');
|
return response.status(200).redirect('http://transcendance:8080/#/2fa');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/v2/auth/logout
|
* POST /api/v2/auth/logout
|
||||||
* Route pour déconnecter l'utilisateur
|
* Route pour déconnecter l'utilisateur
|
||||||
*/
|
*/
|
||||||
@Post('logout')
|
@Post('logout')
|
||||||
@@ -58,22 +63,26 @@ export class AuthenticationController {
|
|||||||
@Post('2fa/generate')
|
@Post('2fa/generate')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
async register(@Req() request, @Res() response){
|
async register(@Req() request, @Res() response){
|
||||||
console.log('ON EST DANS REGISTER POUR 2FA AUTH CONTROLLER')
|
const user : User = request.user;
|
||||||
const { otpauth } = await this.authService.generate2FaSecret(request.user);
|
if (user.isEnabledTwoFactorAuth === true)
|
||||||
return this.authService.pipeQrCodeStream(response, otpauth);
|
{
|
||||||
|
console.log('ON EST DANS REGISTER POUR 2FA AUTH CONTROLLER')
|
||||||
|
const { otpauth } = await this.authService.generate2FaSecret(request.user);
|
||||||
|
return this.authService.pipeQrCodeStream(response, otpauth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('2fa/turn-on')
|
|
||||||
|
@Post('2fa/check')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
async verify(@Req() request, @Body() {twoFaCode} : TwoFaDto, @Res() response){
|
async verify(@Req() request, @Body() {twoFaCode} : TwoFaDto, @Res() response){
|
||||||
|
const user : User = request.user;
|
||||||
console.log('ON EST DANS VERIFY POUR 2FA AUTH CONTROLLER')
|
console.log('ON EST DANS VERIFY POUR 2FA AUTH CONTROLLER')
|
||||||
const isCodeIsValid = await this.authService.verify2FaCode(request.user, twoFaCode);
|
const isCodeIsValid = await this.authService.verify2FaCode(request.user, twoFaCode);
|
||||||
if (isCodeIsValid === false)
|
if (isCodeIsValid === false)
|
||||||
{
|
|
||||||
throw new UnauthorizedException('Wrong Code.');
|
throw new UnauthorizedException('Wrong Code.');
|
||||||
}
|
await this.userService.authenticateUserWith2FA(request.user.id);
|
||||||
await this.userService.enableTwoFactorAuth(request.user.id);
|
|
||||||
console.log('ON REDIRIGE');
|
console.log('ON REDIRIGE');
|
||||||
return response.status(200);
|
return response.status(200).redirect('http://transcendance:8080/#/profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,15 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verify2FaCode(user : User, code : string) {
|
async verify2FaCode(user : User, code : string) {
|
||||||
|
console.log("User : " + user.username);
|
||||||
return authenticator.verify({ token: code, secret: user.secretTwoFactorAuth });
|
return authenticator.verify({ token: code, secret: user.secretTwoFactorAuth });
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate2FaSecret(user : User) {
|
async generate2FaSecret(user : User) {
|
||||||
const secret = authenticator.generateSecret();
|
let secret : string;
|
||||||
|
secret = user.secretTwoFactorAuth;
|
||||||
|
if (!user.secretTwoFactorAuth)
|
||||||
|
secret = authenticator.generateSecret();
|
||||||
const otpauth = authenticator.keyuri(user.email, process.env.TWO_FACTOR_AUTHENTICATION_APP_NAME, secret);
|
const otpauth = authenticator.keyuri(user.email, process.env.TWO_FACTOR_AUTHENTICATION_APP_NAME, secret);
|
||||||
await this.userService.setAuthenticatorSecret(user.id, secret);
|
await this.userService.setAuthenticatorSecret(user.id, secret);
|
||||||
return { secret, otpauth };
|
return { secret, otpauth };
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { IsEnum, IsString } from 'class-validator';
|
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||||
import { FriendshipStatus } from '../entities/friendship.entity';
|
import { FriendshipStatus } from '../entities/friendship.entity';
|
||||||
|
|
||||||
export class CreateFriendshipDto {
|
export class CreateFriendshipDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
readonly requesterId: string;
|
@IsNotEmpty()
|
||||||
@IsString()
|
readonly receiverUsername: string;
|
||||||
readonly addresseeId: string;
|
|
||||||
@IsEnum(FriendshipStatus)
|
@IsEnum(FriendshipStatus)
|
||||||
readonly status: FriendshipStatus;
|
readonly status: FriendshipStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,17 @@ export class Friendship {
|
|||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
date : Date;
|
date : Date;
|
||||||
|
|
||||||
@Column()
|
@ManyToOne(type => User, user => user.username)
|
||||||
@ManyToOne(type => User, user => user.requesterId)
|
sender: User;
|
||||||
requesterId: string;
|
|
||||||
|
@ManyToOne(type => User, user => user.username)
|
||||||
|
receiver: User;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
@ManyToOne(type => User, user => user.addresseeId)
|
senderUsername : string;
|
||||||
addresseeId: string;
|
|
||||||
|
@Column()
|
||||||
|
receiverUsername : string;
|
||||||
|
|
||||||
@Column({ type: 'enum', enum: FriendshipStatus, default: FriendshipStatus.REQUESTED})
|
@Column({ type: 'enum', enum: FriendshipStatus, default: FriendshipStatus.REQUESTED})
|
||||||
status: FriendshipStatus;
|
status: FriendshipStatus;
|
||||||
|
|||||||
@@ -18,12 +18,29 @@ export class FriendshipController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GET http://transcendance:8080/api/v2/network/myfriends/relationshipId
|
// GET http://transcendance:8080/api/v2/network/myfriends/relationshipId
|
||||||
@Get('myfriends/:relationshipId')
|
@Get('myfriend/:relationshipId')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
findOneFriend(@Param('relationshipId') relationshipId: string, @Req() req) {
|
findOneFriend(@Param('relationshipId') relationshipId: string, @Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findOneFriend(relationshipId, user.id);
|
return this.friendshipService.findOneFriend(relationshipId, user.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET http://transcendance:8080/api/v2/network/blocked
|
||||||
|
@Get('blocked')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
findAllBlocked(@Req() req) {
|
||||||
|
const user = req.user;
|
||||||
|
return this.friendshipService.findAllBlockedFriends(user.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('blocked/:relationshipId')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
findOneBlocked(@Param('relationshipId') relationshipId: string, @Req() req) {
|
||||||
|
const user = req.user;
|
||||||
|
return this.friendshipService.findOneBlocked(relationshipId, user.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST http://transcendance:8080/api/v2/network/myfriends
|
// POST http://transcendance:8080/api/v2/network/myfriends
|
||||||
@@ -33,38 +50,46 @@ export class FriendshipController {
|
|||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
create(@Body() createFriendshipDto: CreateFriendshipDto, @Req() req) {
|
create(@Body() createFriendshipDto: CreateFriendshipDto, @Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
console.log(`User id: ${user.id}\nFriend id: ${createFriendshipDto.requesterId}\nStatus: ${createFriendshipDto.status}`);
|
if (user.username !== createFriendshipDto.receiverUsername)
|
||||||
if (user.id === +createFriendshipDto.requesterId)
|
|
||||||
return this.friendshipService.create(createFriendshipDto, user);
|
return this.friendshipService.create(createFriendshipDto, user);
|
||||||
return new HttpException('You can\'t request a frienship for another user', HttpStatus.FORBIDDEN);
|
return new HttpException('You can\'t request a frienship to yourself', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PATCH http://transcendance:8080/api/v2/network/myfriends/relationshipId?status=A
|
// PATCH http://transcendance:8080/api/v2/network/myfriends/relationshipId/accept
|
||||||
@Patch('myfriends/:relationshipId')
|
@Patch('myfriends/:relationshipId/accept')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
update(@Param('relationshipId') relationshipId: string, @Query('status') status : string, @Req() req)
|
updateAccept(@Param('relationshipId') relationshipId: string, @Req() req)
|
||||||
{
|
{
|
||||||
const user : User = req.user;
|
const user : User = req.user;
|
||||||
return this.friendshipService.updateFriendship(relationshipId, user, status);
|
return this.friendshipService.acceptFriendship(relationshipId, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('myfriends/:relationshipId/decline')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
updateDecline(@Param('relationshipId') relationshipId: string, @Req() req)
|
||||||
|
{
|
||||||
|
const user : User = req.user;
|
||||||
|
return this.friendshipService.declineFriendship(relationshipId, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('myfriends/:relationshipId/block')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
updateBlock(@Param('relationshipId') relationshipId: string, @Req() req)
|
||||||
|
{
|
||||||
|
const user : User = req.user;
|
||||||
|
return this.friendshipService.blockFriendship(relationshipId, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE http://transcendance:8080/api/v2/network/myfriends/relationshipId
|
// DELETE http://transcendance:8080/api/v2/network/myfriends/relationshipId
|
||||||
@Delete('myfriends/:relationshipId')
|
@Delete(':relationshipId')
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
remove(@Param('relationshipId') relationshipId: string) {
|
remove(@Param('relationshipId') relationshipId: string, @Req() req) {
|
||||||
return this.friendshipService.removeFriendship(relationshipId);
|
const user : User = req.user;
|
||||||
}
|
return this.friendshipService.removeFriendship(relationshipId, user);
|
||||||
|
|
||||||
|
|
||||||
// GET http://transcendance:8080/api/v2/network/blocked
|
|
||||||
@Get('blocked')
|
|
||||||
@UseGuards(AuthenticateGuard)
|
|
||||||
@UseGuards(TwoFactorGuard)
|
|
||||||
findAllBlocked(@Req() req) {
|
|
||||||
const user = req.user;
|
|
||||||
return this.friendshipService.findAllBlockedFriends(user.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET http://transcendance:8080/api/v2/network/pending
|
// GET http://transcendance:8080/api/v2/network/pending
|
||||||
@@ -73,7 +98,7 @@ export class FriendshipController {
|
|||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
findAllPendantFriendshipRequested(@Req() req) {
|
findAllPendantFriendshipRequested(@Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findAllPendantRequestsForFriendship(user.id);
|
return this.friendshipService.findAllPendantRequestsForFriendship(user.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET http://transcendance:8080/api/v2/network/received
|
// GET http://transcendance:8080/api/v2/network/received
|
||||||
@@ -82,6 +107,6 @@ export class FriendshipController {
|
|||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
findAllPendantFriendshipReceived(@Req() req) {
|
findAllPendantFriendshipReceived(@Req() req) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return this.friendshipService.findAllReceivedRequestsForFriendship(user.id);
|
return this.friendshipService.findAllReceivedRequestsForFriendship(user.username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { User } from 'src/users/entities/user.entity';
|
import { User } from 'src/users/entities/user.entity';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { CreateFriendshipDto } from './dto/create-friendship.dto';
|
import { CreateFriendshipDto } from './dto/create-friendship.dto';
|
||||||
import { UpdateFriendshipDto } from './dto/update-friendship.dto';
|
|
||||||
import { Friendship, FriendshipStatus } from './entities/friendship.entity';
|
import { Friendship, FriendshipStatus } from './entities/friendship.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -17,26 +16,26 @@ export class FriendshipService {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
async findOneFriend(friendshipId: string, userId: string) {
|
async findOneFriend(friendshipId: string, username: string) {
|
||||||
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, requesterId: userId, status: FriendshipStatus.ACCEPTED } });
|
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, senderUsername: username, status: FriendshipStatus.ACCEPTED } });
|
||||||
if (!friendship)
|
if (!friendship)
|
||||||
throw new HttpException(`The requested friend not found.`, HttpStatus.NOT_FOUND);
|
throw new HttpException(`The requested friend not found.`, HttpStatus.NOT_FOUND);
|
||||||
return friendship;
|
return friendship;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneBlocked(friendshipId: string) {
|
async findOneBlocked(friendshipId: string, username: string) {
|
||||||
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, status: FriendshipStatus.BLOCKED } });
|
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, senderUsername: username, status: FriendshipStatus.BLOCKED } });
|
||||||
if (!friendship)
|
if (!friendship)
|
||||||
throw new HttpException(`The requested user not found.`, HttpStatus.NOT_FOUND);
|
throw new HttpException(`The requested blocked not found.`, HttpStatus.NOT_FOUND);
|
||||||
return friendship;
|
return friendship;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllFriends(userId: string) {
|
async findAllFriends(username: string) {
|
||||||
const friendship = await this.friendshipRepository
|
const friendship = await this.friendshipRepository
|
||||||
.createQueryBuilder('friendship')
|
.createQueryBuilder('friendship')
|
||||||
.where('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
|
.where('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
|
||||||
.andWhere('friendship.addresseeId = :addressee', { addressee: userId })
|
.andWhere('friendship.receiverUsername = :addressee', { addressee: username })
|
||||||
.orWhere('friendship.requesterId = :requester', { requester: userId })
|
.orWhere('friendship.senderUsername = :requester', { requester: username })
|
||||||
.andWhere('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
|
.andWhere('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
|
||||||
.getMany();
|
.getMany();
|
||||||
for (const friend of friendship)
|
for (const friend of friendship)
|
||||||
@@ -44,40 +43,56 @@ export class FriendshipService {
|
|||||||
return friendship;
|
return friendship;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllBlockedFriends(userId: string) {
|
async findAllBlockedFriends(username: string) {
|
||||||
return await this.friendshipRepository
|
const friendships : Friendship[] = await this.friendshipRepository
|
||||||
.createQueryBuilder('friendship')
|
.createQueryBuilder('friendship')
|
||||||
.where('friendship.requesterId = :requestee', { requestee: userId })
|
.where('friendship.senderUsername = :requestee', { requestee: username })
|
||||||
.orWhere('friendship.addresseeId = :addressee', { addressee: userId })
|
|
||||||
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
|
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
|
||||||
.getMany();
|
.getMany();
|
||||||
|
let partialFriendship : Partial<Friendship>[] = [];
|
||||||
|
for (const friendship of friendships) {
|
||||||
|
partialFriendship.push({id: friendship.id, date: friendship.date, senderUsername: friendship.senderUsername, receiverUsername: friendship.receiverUsername, status: friendship.status});
|
||||||
|
}
|
||||||
|
return partialFriendship;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllPendantRequestsForFriendship(userId: string) {
|
async findAllPendantRequestsForFriendship(username: string) {
|
||||||
return await this.friendshipRepository
|
const friendship = await this.friendshipRepository
|
||||||
.createQueryBuilder('friendship')
|
.createQueryBuilder('friendship')
|
||||||
.where('friendship.requesterId = :requestee', { requestee: userId })
|
.where('friendship.senderUsername = :requestee', { requestee: username })
|
||||||
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
|
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
|
||||||
.getMany();
|
.getMany();
|
||||||
|
let partialFriendship : Partial<Friendship>[] = [];
|
||||||
|
for (const friend of friendship) {
|
||||||
|
console.log("FRIENDSHIP : " + friend);
|
||||||
|
partialFriendship.push({id: friend.id, senderUsername: friend.senderUsername, receiverUsername: friend.receiverUsername, status: friend.status});
|
||||||
|
}
|
||||||
|
console.log("Pendant requests : " + partialFriendship);
|
||||||
|
return partialFriendship;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAllReceivedRequestsForFriendship(userId: string) {
|
async findAllReceivedRequestsForFriendship(username: string) {
|
||||||
return await this.friendshipRepository
|
const friendship = await this.friendshipRepository
|
||||||
.createQueryBuilder('friendship')
|
.createQueryBuilder('friendship')
|
||||||
.where('friendship.addresseeId = :addressee', { addressee: userId })
|
.where('friendship.receiverUsername = :addressee', { addressee: username })
|
||||||
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
|
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
|
||||||
.getMany();
|
.getMany();
|
||||||
|
let partialFriendship : Partial<Friendship>[] = [];
|
||||||
|
for (const friend of friendship) {
|
||||||
|
partialFriendship.push({id: friend.id, senderUsername: friend.senderUsername, receiverUsername: friend.receiverUsername, status: friend.status});
|
||||||
|
}
|
||||||
|
return partialFriendship;
|
||||||
}
|
}
|
||||||
//GROS CHANTIER
|
|
||||||
async create(createFriendshipDto: CreateFriendshipDto, creator : User) {
|
async create(createFriendshipDto: CreateFriendshipDto, creator : User) : Promise <Partial<Friendship>> {
|
||||||
const addressee = await this.userRepository.findOneBy({ id: +createFriendshipDto.addresseeId });
|
console.log("DTO : \n")
|
||||||
if (!addressee)
|
console.log({...createFriendshipDto})
|
||||||
|
const receiver = await this.userRepository.findOneBy({username: createFriendshipDto.receiverUsername});
|
||||||
|
if (!receiver)
|
||||||
throw new HttpException(`The addressee does not exist.`, HttpStatus.NOT_FOUND);
|
throw new HttpException(`The addressee does not exist.`, HttpStatus.NOT_FOUND);
|
||||||
if (creator.id === addressee.id)
|
|
||||||
throw new HttpException(`You can't add yourself.`, HttpStatus.FORBIDDEN);
|
|
||||||
if (createFriendshipDto.status !== FriendshipStatus.REQUESTED && createFriendshipDto.status !== FriendshipStatus.BLOCKED)
|
if (createFriendshipDto.status !== FriendshipStatus.REQUESTED && createFriendshipDto.status !== FriendshipStatus.BLOCKED)
|
||||||
throw new HttpException(`The status is not valid.`, HttpStatus.NOT_FOUND);
|
throw new HttpException(`The status is not valid.`, HttpStatus.NOT_FOUND);
|
||||||
const friendship = await this.friendshipRepository.findOneBy({ requesterId: createFriendshipDto.requesterId, addresseeId: createFriendshipDto.addresseeId });
|
const friendship = await this.friendshipRepository.findOneBy({ sender: creator, receiver: receiver });
|
||||||
if (friendship) {
|
if (friendship) {
|
||||||
if (friendship.status && friendship.status === FriendshipStatus.ACCEPTED)
|
if (friendship.status && friendship.status === FriendshipStatus.ACCEPTED)
|
||||||
throw new HttpException(`The friendship request has already been accepted.`, HttpStatus.OK);
|
throw new HttpException(`The friendship request has already been accepted.`, HttpStatus.OK);
|
||||||
@@ -88,46 +103,99 @@ export class FriendshipService {
|
|||||||
else if (friendship.status && friendship.status === FriendshipStatus.DECLINED)
|
else if (friendship.status && friendship.status === FriendshipStatus.DECLINED)
|
||||||
throw new HttpException(`The request has been declined.`, HttpStatus.OK);
|
throw new HttpException(`The request has been declined.`, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
const newFriendship = this.friendshipRepository.create(createFriendshipDto);
|
const newFriendship = new Friendship();
|
||||||
return this.friendshipRepository.save(newFriendship);
|
newFriendship.sender = creator;
|
||||||
|
newFriendship.senderUsername = creator.username;
|
||||||
|
newFriendship.receiver = receiver;
|
||||||
|
newFriendship.receiverUsername = receiver.username;
|
||||||
|
newFriendship.status = createFriendshipDto.status;
|
||||||
|
const savedFriendship = this.friendshipRepository.save(newFriendship);
|
||||||
|
const partialFriendship : Partial<Friendship> = {
|
||||||
|
id : (await savedFriendship).id,
|
||||||
|
date : (await savedFriendship).date,
|
||||||
|
receiverUsername: (await savedFriendship).receiverUsername,
|
||||||
|
status : (await savedFriendship).status
|
||||||
|
}
|
||||||
|
console.log({...partialFriendship})
|
||||||
|
return partialFriendship;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateFriendship(relationshipId: string, user: User, status: string) {
|
async acceptFriendship(relationshipId: string, user: User) {
|
||||||
const relation = await this.friendshipRepository.findOneBy({ id: +relationshipId });
|
const relation = await this.friendshipRepository.findOneBy({ id: +relationshipId });
|
||||||
if (!relation)
|
if (!relation)
|
||||||
throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
|
throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
|
||||||
if (+relation.requesterId === user.id) {
|
if (relation.sender.id === user.id) {
|
||||||
throw new HttpException(`You can't accept your own request.`, HttpStatus.NOT_FOUND);
|
throw new HttpException(`You can't accept your own request.`, HttpStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
if (status === FriendshipStatus.ACCEPTED)
|
relation.status = FriendshipStatus.ACCEPTED;
|
||||||
relation.status = FriendshipStatus.ACCEPTED;
|
const savedFriendship = this.friendshipRepository.save(relation);
|
||||||
else if (status === FriendshipStatus.DECLINED)
|
const partialFriendship : Partial<Friendship> = {
|
||||||
relation.status = FriendshipStatus.DECLINED;
|
id : (await savedFriendship).id,
|
||||||
else if (status === FriendshipStatus.BLOCKED)
|
date : (await savedFriendship).date,
|
||||||
relation.status = FriendshipStatus.BLOCKED;
|
receiverUsername: (await savedFriendship).receiverUsername,
|
||||||
else
|
status : (await savedFriendship).status
|
||||||
throw new HttpException(`The status is not valid.`, HttpStatus.NOT_FOUND);
|
}
|
||||||
if (relation.status !== status)
|
return partialFriendship;
|
||||||
throw new HttpException(`We could not update the status.`, HttpStatus.OK);
|
|
||||||
return this.friendshipRepository.save(relation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeFriendship(relationshipId: string) {
|
async declineFriendship(relationshipId: string, user: User) {
|
||||||
|
const relation = await this.friendshipRepository.findOneBy({ id: +relationshipId });
|
||||||
|
if (!relation)
|
||||||
|
throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
|
||||||
|
if (relation.sender.id === user.id) {
|
||||||
|
throw new HttpException(`You can't accept your own request.`, HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
relation.status = FriendshipStatus.DECLINED;
|
||||||
|
const savedFriendship = this.friendshipRepository.save(relation);
|
||||||
|
const partialFriendship : Partial<Friendship> = {
|
||||||
|
id : (await savedFriendship).id,
|
||||||
|
date : (await savedFriendship).date,
|
||||||
|
receiverUsername: (await savedFriendship).receiverUsername,
|
||||||
|
status : (await savedFriendship).status
|
||||||
|
}
|
||||||
|
return partialFriendship
|
||||||
|
}
|
||||||
|
|
||||||
|
async blockFriendship(relationshipId: string, user: User) {
|
||||||
|
const relation = await this.friendshipRepository.findOneBy({ id: +relationshipId });
|
||||||
|
if (!relation)
|
||||||
|
throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
|
||||||
|
if (relation.sender.id === user.id) {
|
||||||
|
throw new HttpException(`You can't accept your own request.`, HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
relation.status = FriendshipStatus.BLOCKED;
|
||||||
|
const savedFriendship = this.friendshipRepository.save(relation);
|
||||||
|
const partialFriendship : Partial<Friendship> = {
|
||||||
|
id : (await savedFriendship).id,
|
||||||
|
date : (await savedFriendship).date,
|
||||||
|
receiverUsername: (await savedFriendship).receiverUsername,
|
||||||
|
status : (await savedFriendship).status
|
||||||
|
}
|
||||||
|
return partialFriendship
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeFriendship(relationshipId: string, user : User) {
|
||||||
const friendship = await this.friendshipRepository.findOneBy({ id: +relationshipId });
|
const friendship = await this.friendshipRepository.findOneBy({ id: +relationshipId });
|
||||||
if (!friendship)
|
if (!friendship)
|
||||||
throw new HttpException(`Your friend could not be deleted.`, HttpStatus.NOT_FOUND);
|
throw new HttpException(`Your friend could not be deleted.`, HttpStatus.NOT_FOUND);
|
||||||
|
if (friendship.sender.id !== user.id || friendship.receiver.id !== user.id) {
|
||||||
|
throw new HttpException(`You can't do that.`, HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
return this.friendshipRepository.remove(friendship);
|
return this.friendshipRepository.remove(friendship);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findIfUserIsBlockedOrHasBlocked(userConnectedId: string, userToCheckId: string) {
|
async findIfUserIsBlockedOrHasBlocked(userConnectedId: string, userToFindId: string) {
|
||||||
|
console.log("finding if user is blocked")
|
||||||
const friendship = await this.friendshipRepository
|
const friendship = await this.friendshipRepository
|
||||||
.createQueryBuilder('friendship')
|
.createQueryBuilder('friendship')
|
||||||
.where('friendship.requesterId = :requestee', { requestee: userConnectedId })
|
.where('friendship.senderUsername = :requestee', { requestee: userConnectedId })
|
||||||
.orWhere('friendship.requesterId = :requesteeBis', { requesteeBis: userToCheckId })
|
.orWhere('friendship.senderUsername = :requesteeBis', { requesteeBis: userToFindId })
|
||||||
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
|
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
|
||||||
.getOne();
|
.getOne();
|
||||||
if (friendship)
|
if (friendship) {
|
||||||
|
console.log('we are blocked in friendship.service')
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { IsBoolean, IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateUsersDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
readonly username: string;
|
||||||
|
readonly fortyTwoId: string;
|
||||||
|
@IsEmail()
|
||||||
|
readonly email: string;
|
||||||
|
@IsString()
|
||||||
|
readonly image_url: string;
|
||||||
|
@IsString()
|
||||||
|
readonly status: string;
|
||||||
|
@IsBoolean()
|
||||||
|
readonly isEnabledTwoFactorAuth: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Entity('gameParty')
|
||||||
|
export class gameParty {
|
||||||
|
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
playerOne: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
playerTwo: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
resultOfTheMatch: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
gameServerIdOfTheMatch: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GameController } from './game.controller';
|
||||||
|
|
||||||
|
describe('GameController', () => {
|
||||||
|
let controller: GameController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [GameController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<GameController>(GameController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { Controller } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Controller('game')
|
||||||
|
export class GameController {}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { GameController } from './game.controller';
|
||||||
|
import { GameService } from './game.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [GameController],
|
||||||
|
providers: [GameService]
|
||||||
|
})
|
||||||
|
export class GameModule {}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { GameService } from './game.service';
|
||||||
|
|
||||||
|
describe('GameService', () => {
|
||||||
|
let service: GameService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [GameService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<GameService>(GameService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GameService {}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { IsBoolean, IsEmail, IsOptional, IsString } from 'class-validator';
|
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
import { isSet } from 'util/types';
|
||||||
|
|
||||||
export class CreateUsersDto {
|
export class CreateUsersDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
readonly username: string;
|
readonly username: string;
|
||||||
@IsString()
|
|
||||||
readonly fortyTwoId: string;
|
readonly fortyTwoId: string;
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
readonly email: string;
|
readonly email: string;
|
||||||
|
|||||||
@@ -43,13 +43,11 @@ export class User {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
secretTwoFactorAuth: string;
|
secretTwoFactorAuth: string;
|
||||||
|
|
||||||
@JoinTable()
|
@OneToMany(type => Friendship , (friendship) => friendship.sender)
|
||||||
@OneToMany(type => Friendship , (friendship) => friendship.requesterId)
|
sentFriendRequest: Friendship[];
|
||||||
requesterId: Friendship[];
|
|
||||||
|
|
||||||
@JoinTable()
|
@OneToMany(type => Friendship , (friendship) => friendship.receiver)
|
||||||
@OneToMany(type => Friendship , (friendship) => friendship.addresseeId)
|
receivedFriendRequest: Friendship[];
|
||||||
addresseeId: Friendship[];
|
|
||||||
|
|
||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
@OneToOne(() => UserStats, { cascade: true })
|
@OneToOne(() => UserStats, { cascade: true })
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post, Query, Req, Res, UploadedFile, UseGuards, UseInterceptors
|
Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post, Query, Redirect, Req, Res, UploadedFile, UseGuards, UseInterceptors
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { Response } from 'express';
|
||||||
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
|
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
|
||||||
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
||||||
import { ValidationPipe } from 'src/common/validation/validation.pipe';
|
import { ValidationPipe } from 'src/common/validation/validation.pipe';
|
||||||
@@ -10,6 +11,7 @@ import { UsersService } from './users.service';
|
|||||||
import { User } from './entities/user.entity';
|
import { User } from './entities/user.entity';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { storageForAvatar } from 'src/common/constants/constants';
|
import { storageForAvatar } from 'src/common/constants/constants';
|
||||||
|
import { use } from 'passport';
|
||||||
|
|
||||||
|
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
@@ -34,11 +36,26 @@ export class UsersController {
|
|||||||
* car un utilisateur est crée à la première connexion avec l'Oauth de 42.
|
* car un utilisateur est crée à la première connexion avec l'Oauth de 42.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// @UseGuards(AuthenticateGuard)
|
||||||
|
// @UseGuards(TwoFactorGuard)
|
||||||
|
// @Get()
|
||||||
|
// findOne(@Req() req) {
|
||||||
|
// console.log("Backend Getting current user");
|
||||||
|
// return this.usersService.findOne(req.user.id);
|
||||||
|
// }
|
||||||
|
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
@Get()
|
@Get()
|
||||||
findOne(@Req() req) {
|
findOne(@Query('username') username: string, @Req() req) {
|
||||||
return this.usersService.findOne(req.user.id);
|
if (username === undefined) {
|
||||||
|
console.log("Backend Getting current user");
|
||||||
|
return this.usersService.findOne(req.user.id);
|
||||||
|
} else {
|
||||||
|
const user : User = req.user;
|
||||||
|
console.log('we have a query: ' + username)
|
||||||
|
return this.usersService.findOneByUsername(user.id.toString(),username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET http://transcendance:8080/user?username=NomDuUser
|
// GET http://transcendance:8080/user?username=NomDuUser
|
||||||
@@ -47,9 +64,11 @@ export class UsersController {
|
|||||||
@Get('search')
|
@Get('search')
|
||||||
findOneByUsername(@Query('username') username: string, @Req() req) {
|
findOneByUsername(@Query('username') username: string, @Req() req) {
|
||||||
const user : User = req.user;
|
const user : User = req.user;
|
||||||
|
console.log('searching for user' + user.username);
|
||||||
return this.usersService.findOneByUsername(user.id.toString(),username);
|
return this.usersService.findOneByUsername(user.id.toString(),username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
@Get('stats')
|
@Get('stats')
|
||||||
@@ -61,9 +80,19 @@ export class UsersController {
|
|||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
@Patch()
|
@Patch()
|
||||||
update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto) {
|
async update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto, @Res() response : Response) {
|
||||||
console.log("DANS PATCH USERS");
|
console.log("DANS PATCH USERS");
|
||||||
return this.usersService.update(req.user.id, usersUpdateDto);
|
const user = await this.usersService.update(req.user.id, usersUpdateDto);
|
||||||
|
// const user : User = req.user;
|
||||||
|
if (user.isEnabledTwoFactorAuth === false && user.isTwoFactorAuthenticated === true)
|
||||||
|
this.usersService.setIsTwoFactorAuthenticatedWhenLogout(user.id);
|
||||||
|
console.log ("Enbale 2FA " + user.isEnabledTwoFactorAuth + " Is authenticated " + user.isTwoFactorAuthenticated);
|
||||||
|
if (user.isEnabledTwoFactorAuth === true && user.isTwoFactorAuthenticated === false)
|
||||||
|
{
|
||||||
|
response.status(201).send('2FA redirect')
|
||||||
|
}
|
||||||
|
console.log("ON RETOURNE 200\n")
|
||||||
|
response.status(200).send("OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthenticateGuard)
|
@UseGuards(AuthenticateGuard)
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ export class UsersService {
|
|||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
console.log(`FIND ONE USER SERVICE Find user ${id}`);
|
console.log(`FIND ONE USER SERVICE Find user ${id}`);
|
||||||
const user = await this.userRepository.createQueryBuilder('user')
|
const user = await this.userRepository.createQueryBuilder('user')
|
||||||
.leftJoinAndSelect('user.stats', 'stats')
|
.leftJoinAndSelect('user.stats', 'stats')
|
||||||
.where('user.id = :id', { id: +id })
|
.where('user.id = :id', { id: +id })
|
||||||
.getOne();
|
.getOne();
|
||||||
if (!user)
|
if (!user)
|
||||||
throw new NotFoundException(`The requested user not found.`);
|
throw new NotFoundException(`The requested user not found.`);
|
||||||
console.log(`FIND ONE USER SERVICE The requested user found.`
|
console.log(`FIND ONE USER SERVICE The requested user found.` + user.username
|
||||||
+ user.stats.id + user.stats.winGame + user.stats.loseGame + user.stats.drawGame + user.stats.totalGame);
|
+ user.stats.id + user.stats.winGame + user.stats.loseGame + user.stats.drawGame + user.stats.totalGame);
|
||||||
const partialUser : Partial<User> = {
|
const partialUser : Partial<User> = {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
@@ -49,6 +49,7 @@ export class UsersService {
|
|||||||
status: user.status,
|
status: user.status,
|
||||||
stats: user.stats,
|
stats: user.stats,
|
||||||
};
|
};
|
||||||
|
console.log(`Returned Partial User.` + partialUser.username + user.username);
|
||||||
return partialUser;
|
return partialUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,21 +60,26 @@ export class UsersService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByUsername(userConnectedId : string, username: string) {
|
async findOneByUsername(userConnectedId : string, usernameToFind: string) {
|
||||||
const user : User = await this.userRepository.createQueryBuilder('user')
|
const userToFind : User = await this.userRepository.createQueryBuilder('user')
|
||||||
.leftJoinAndSelect('user.stats', 'stats')
|
.leftJoinAndSelect('user.stats', 'stats')
|
||||||
.where('user.username = :username', { username: username })
|
.where('user.username = :username', { username: usernameToFind })
|
||||||
.getOne();
|
.getOne();
|
||||||
console.log('USERNAME OF FOUND USER : ' + user.username);
|
console.log('USERNAME OF FOUND USER : ' + userToFind.username);
|
||||||
if (!user)
|
// console.log({...user})
|
||||||
throw new HttpException(`The user could not be found.`,HttpStatus.NOT_FOUND);
|
// you can't do that, you need to do user === undefined
|
||||||
if (this.friendshipService.findIfUserIsBlockedOrHasBlocked(userConnectedId, user.id.toString()))
|
// if (user === undefined)
|
||||||
throw new HttpException(`The user could not be found.`,HttpStatus.NOT_FOUND);
|
if (!userToFind)
|
||||||
|
throw new HttpException(`The user could not be found 1.`,HttpStatus.NOT_FOUND);
|
||||||
|
if (await this.friendshipService.findIfUserIsBlockedOrHasBlocked(userConnectedId, userToFind.id.toString())) {
|
||||||
|
console.log('we are blocked in user.service')
|
||||||
|
throw new HttpException(`The user could not be found 2.`,HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
const partialUser : Partial<User> = {
|
const partialUser : Partial<User> = {
|
||||||
username: user.username,
|
username: userToFind.username,
|
||||||
image_url: user.image_url,
|
image_url: userToFind.image_url,
|
||||||
status: user.status,
|
status: userToFind.status,
|
||||||
stats: user.stats,
|
stats: userToFind.stats,
|
||||||
};
|
};
|
||||||
return partialUser;
|
return partialUser;
|
||||||
}
|
}
|
||||||
@@ -115,7 +121,11 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async enableTwoFactorAuth(id: string) {
|
async enableTwoFactorAuth(id: string) {
|
||||||
return this.userRepository.update(id, {isEnabledTwoFactorAuth: true, isTwoFactorAuthenticated: true});
|
return this.userRepository.update(id, {isEnabledTwoFactorAuth: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticateUserWith2FA(id: string) {
|
||||||
|
return this.userRepository.update(id, { isTwoFactorAuthenticated: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
async setIsTwoFactorAuthenticatedWhenLogout(id: number) {
|
async setIsTwoFactorAuthenticatedWhenLogout(id: number) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import LoginPage from "./LoginPage.svelte";
|
import LoginPage from "./LoginPage.svelte";
|
||||||
import UserPage from "../UserPage.svelte";
|
import UserPage from "../UserPage.svelte";
|
||||||
import NotFound from "../NotFound.svelte";
|
import NotFound from "../pages/NotFound.svelte";
|
||||||
|
|
||||||
// Ideally fuck all this shit in the long run
|
// Ideally fuck all this shit in the long run
|
||||||
let pages = ['login', 'user', 'account'];
|
let pages = ['login', 'user', 'account'];
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
import { location } from 'svelte-spa-router';
|
||||||
|
import GenerateUserDisplay from './GenerateUserDisplay.svelte';
|
||||||
|
|
||||||
|
// using location won't work cuz i do a fetch but i don't change the page fragment, so means nothing to location...
|
||||||
|
|
||||||
|
// this is how you access /:first for example
|
||||||
|
// export let params = {}
|
||||||
|
// <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
|
||||||
|
|
||||||
|
// If i export these vars, maybe as an nice tidy object, i could pass whatever i like to them
|
||||||
|
// The current user, some other user, whatever, and thus reuse this Componente for the user and their friends or whatever
|
||||||
|
// will have to coordinate with Back, will know more once the Game stats are in the back
|
||||||
|
// wait maybe this won't work, cuz like it's still going through a route, i would have to update a Store Var each time...
|
||||||
|
// not sure if that's what i want...
|
||||||
|
|
||||||
|
|
||||||
|
// maybe the rank is determined dynamically just in the front based on win loss ratio or something no one cares about
|
||||||
|
// why bother storing that shit in the back...
|
||||||
|
// maybe i need a Rank.svelte component
|
||||||
|
// ohhh i could make above a certain rank glitter! like that CSS tutorial showed me!
|
||||||
|
|
||||||
|
export let aUsername;
|
||||||
|
|
||||||
|
let user;
|
||||||
|
let rank = '';
|
||||||
|
let avatar;
|
||||||
|
|
||||||
|
// i think i don't need to do this once i sort out the {wrap} conditions: in theory i could pass values to the Route
|
||||||
|
// once the async authentication check is done
|
||||||
|
onMount( async() => {
|
||||||
|
console.log('Display aUser username: '+ aUsername)
|
||||||
|
// http://transcendance:8080/api/v2/user?username=NomDuUserATrouver
|
||||||
|
user = await fetch(`http://transcendance:8080/api/v2/user?username=${aUsername}`)
|
||||||
|
.then( (x) => x.json() );
|
||||||
|
|
||||||
|
console.log('Display a user: ' + user.username)
|
||||||
|
|
||||||
|
|
||||||
|
// console.log('profile display did my fetch')
|
||||||
|
// should i be updating the userStore or is that unnecessary?
|
||||||
|
|
||||||
|
if (user.loseGame > user.winGame) {
|
||||||
|
rank = 'Bitch Ass Loser!'
|
||||||
|
} else if (user.loseGame === user.winGame) {
|
||||||
|
rank = 'Fine i guess...'
|
||||||
|
} else {
|
||||||
|
rank = 'Yea you da Boss!'
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"})
|
||||||
|
.then(response => {return response.blob()})
|
||||||
|
.then(data => {
|
||||||
|
const url = URL.createObjectURL(data);
|
||||||
|
avatar = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
// tmp
|
||||||
|
// console.log('mounted Profile Display')
|
||||||
|
// console.log(user);
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// Glittery Stars and such for Rank
|
||||||
|
|
||||||
|
let index = 0, interval = 1000;
|
||||||
|
|
||||||
|
const rand = (min, max) =>
|
||||||
|
Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
|
||||||
|
// it's unhappy that "star" isn't typeset, no idea what to do about it...
|
||||||
|
const animate = (star) => {
|
||||||
|
// the if seems to have fixed the type issue
|
||||||
|
if (star) {
|
||||||
|
star.style.setProperty("--star-left", `${rand(-10, 100)}%`);
|
||||||
|
star.style.setProperty("--star-top", `${rand(-40, 80)}%`);
|
||||||
|
|
||||||
|
star.style.animation = "none";
|
||||||
|
star.offsetHeight;
|
||||||
|
star.style.animation = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the part i invented, it was kinda a fucking nightmare...
|
||||||
|
let stars = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
animate(stars[i]);
|
||||||
|
|
||||||
|
setInterval(() => animate(stars[i]), 1000);
|
||||||
|
}, index++ * (interval / 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if user !== undefined}
|
||||||
|
<GenerateUserDisplay {user}/>
|
||||||
|
{:else}
|
||||||
|
<h2>Sorry</h2>
|
||||||
|
<div>Failed to load user {aUsername}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
import Footer from "../components/Footer.svelte";
|
import Footer from "../components/Footer.svelte";
|
||||||
import Login from "./Login.svelte";
|
import Login from "./Login.svelte";
|
||||||
import Tabs from "../shared/Tabs.svelte"
|
import Tabs from "../shared/Tabs.svelte"
|
||||||
import Card from "../shared/Card.svelte"
|
import Card from "../pieces/Card.svelte"
|
||||||
// tmp
|
// tmp
|
||||||
let login = { username: '', password: ''};
|
let login = { username: '', password: ''};
|
||||||
// let's us track any errors in a submited form
|
// let's us track any errors in a submited form
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { now } from "svelte/internal";
|
import { now } from "svelte/internal";
|
||||||
import Card from "../shared/Card.svelte";
|
import Card from "../pieces/Card.svelte";
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
// import UserStore from './stores/UserStore';
|
// import UserStore from './stores/UserStore';
|
||||||
|
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
// import Header from "./Header.svelte";
|
// import Header from "./Header.svelte";
|
||||||
import Footer from "../components/Footer.svelte";
|
import Footer from "../components/Footer.svelte";
|
||||||
import Tabs from "../shared/Tabs.svelte";
|
import Tabs from "../shared/Tabs.svelte";
|
||||||
import Card from "../shared/Card.svelte";
|
import Card from "../pieces/Card.svelte";
|
||||||
import Canvas from "../components/Canvas.svelte";
|
import Canvas from "../pieces/Canvas.svelte";
|
||||||
import ScrollTo from "../shared/ScrollTo.svelte";
|
import ScrollTo from "../shared/ScrollTo.svelte";
|
||||||
import UserStore from "./UserStore.js";
|
import UserStore from "./UserStore.js";
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
import { location } from 'svelte-spa-router';
|
||||||
|
// this is how you access /:first for example
|
||||||
|
// export let params = {}
|
||||||
|
// <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
|
||||||
|
|
||||||
|
// If i export these vars, maybe as an nice tidy object, i could pass whatever i like to them
|
||||||
|
// The current user, some other user, whatever, and thus reuse this Componente for the user and their friends or whatever
|
||||||
|
// will have to coordinate with Back, will know more once the Game stats are in the back
|
||||||
|
// wait maybe this won't work, cuz like it's still going through a route, i would have to update a Store Var each time...
|
||||||
|
// not sure if that's what i want...
|
||||||
|
|
||||||
|
|
||||||
|
// maybe the rank is determined dynamically just in the front based on win loss ratio or something no one cares about
|
||||||
|
// why bother storing that shit in the back...
|
||||||
|
// maybe i need a Rank.svelte component
|
||||||
|
// ohhh i could make above a certain rank glitter! like that CSS tutorial showed me!
|
||||||
|
|
||||||
|
let user;
|
||||||
|
let rank = '';
|
||||||
|
let avatar;
|
||||||
|
|
||||||
|
// i think i don't need to do this once i sort out the {wrap} conditions: in theory i could pass values to the Route
|
||||||
|
// once the async authentication check is done
|
||||||
|
onMount( async() => {
|
||||||
|
// console.log('mounting profile display')
|
||||||
|
user = await fetch('http://transcendance:8080/api/v2/user')
|
||||||
|
.then( (x) => x.json() );
|
||||||
|
|
||||||
|
// console.log('profile display did my fetch')
|
||||||
|
// should i be updating the userStore or is that unnecessary?
|
||||||
|
|
||||||
|
if (user.loseGame > user.winGame) {
|
||||||
|
rank = 'Bitch Ass Loser!'
|
||||||
|
} else if (user.loseGame === user.winGame) {
|
||||||
|
rank = 'Fine i guess...'
|
||||||
|
} else {
|
||||||
|
rank = 'Yea you da Boss!'
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"})
|
||||||
|
.then(response => {return response.blob()})
|
||||||
|
.then(data => {
|
||||||
|
const url = URL.createObjectURL(data);
|
||||||
|
avatar = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
// tmp
|
||||||
|
// console.log('mounted Profile Display')
|
||||||
|
// console.log(user);
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// Glittery Stars and such for Rank
|
||||||
|
|
||||||
|
let index = 0, interval = 1000;
|
||||||
|
|
||||||
|
const rand = (min, max) =>
|
||||||
|
Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
|
||||||
|
// it's unhappy that "star" isn't typeset, no idea what to do about it...
|
||||||
|
const animate = (star) => {
|
||||||
|
// the if seems to have fixed the type issue
|
||||||
|
if (star) {
|
||||||
|
star.style.setProperty("--star-left", `${rand(-10, 100)}%`);
|
||||||
|
star.style.setProperty("--star-top", `${rand(-40, 80)}%`);
|
||||||
|
|
||||||
|
star.style.animation = "none";
|
||||||
|
star.offsetHeight;
|
||||||
|
star.style.animation = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the part i invented, it was kinda a fucking nightmare...
|
||||||
|
let stars = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
animate(stars[i]);
|
||||||
|
|
||||||
|
setInterval(() => animate(stars[i]), 1000);
|
||||||
|
}, index++ * (interval / 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- is this if excessive? -->
|
||||||
|
<div class="outer">
|
||||||
|
{#if user !== undefined}
|
||||||
|
<main>
|
||||||
|
<!-- <img class="icon" src="img/default_user_icon.png" alt="default user icon"> -->
|
||||||
|
<!-- <img class="icon" src="{user.image_url}" alt="default user icon"> -->
|
||||||
|
<img class="avatar" src="{avatar}" alt="default user icon">
|
||||||
|
<div class="username">{user.username}</div>
|
||||||
|
<div class="rank">Rank:
|
||||||
|
<span class="glitter">
|
||||||
|
<span bind:this={stars[0]} class="glitter-star">
|
||||||
|
<svg viewBox="0 0 512 512">
|
||||||
|
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span bind:this={stars[1]} class="glitter-star">
|
||||||
|
<svg viewBox="0 0 512 512">
|
||||||
|
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span bind:this={stars[2]} class="glitter-star">
|
||||||
|
<svg viewBox="0 0 512 512">
|
||||||
|
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="glitter-text">{rank}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<section class="main-stats">
|
||||||
|
<h4>Match Statistics</h4>
|
||||||
|
<p>Total: {user.stats.totalGame}</p>
|
||||||
|
<p>Victories: {user.stats.winGame}</p>
|
||||||
|
<p>Losses: {user.stats.loseGame}</p>
|
||||||
|
<p>Draws: {user.stats.drawGame}</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
<div>testing when there's tons of stuff</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
div.outer{
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 40px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The main part */
|
||||||
|
main{
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 40px auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normal CSS stuff */
|
||||||
|
.avatar{
|
||||||
|
max-width: 150px;
|
||||||
|
/* padding: 5px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The variable rich section */
|
||||||
|
section.main-stats{
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 40px auto;
|
||||||
|
text-align: center;
|
||||||
|
/* i think i want to use a grid? */
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
/* not sure about this, maybe top should be larger? */
|
||||||
|
grid-template-rows: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the stuff in the grid*/
|
||||||
|
section.main-stats h4{
|
||||||
|
grid-column: 1 / span 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.username{
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.rank {
|
||||||
|
/* color: black; */
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Glittery Star Stuff */
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--purple: rgb(123, 31, 162);
|
||||||
|
--violet: rgb(103, 58, 183);
|
||||||
|
--pink: rgb(244, 143, 177);
|
||||||
|
/* make shit gold? */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes background-pan {
|
||||||
|
from {
|
||||||
|
background-position: 0% center;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
background-position: -200% center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale {
|
||||||
|
from, to {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div > .glitter {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div > .glitter > .glitter-star {
|
||||||
|
--size: clamp(20px, 1.5vw, 30px);
|
||||||
|
|
||||||
|
animation: scale 700ms ease forwards;
|
||||||
|
display: block;
|
||||||
|
height: var(--size);
|
||||||
|
left: var(--star-left);
|
||||||
|
position: absolute;
|
||||||
|
top: var(--star-top);
|
||||||
|
width: var(--size);
|
||||||
|
}
|
||||||
|
|
||||||
|
div > .glitter > .glitter-star > svg {
|
||||||
|
animation: rotate 1000ms linear infinite;
|
||||||
|
display: block;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
div > .glitter > .glitter-star > svg > path {
|
||||||
|
fill: var(--violet);
|
||||||
|
}
|
||||||
|
|
||||||
|
div > .glitter > .glitter-text {
|
||||||
|
animation: background-pan 3s linear infinite;
|
||||||
|
/* background-image: linear-gradient( */
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--purple),
|
||||||
|
var(--violet),
|
||||||
|
var(--pink),
|
||||||
|
var(--purple)
|
||||||
|
);
|
||||||
|
background-size: 200%;
|
||||||
|
|
||||||
|
/* Keep these for Safari and chrome */
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
|
||||||
|
/* These are for Firefox */
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { loginStatus } from '../stores/loginStatusStore';
|
||||||
|
|
||||||
|
// Cherif's code
|
||||||
|
|
||||||
|
let qrCodeImg;
|
||||||
|
let qrCode = "";
|
||||||
|
let wrongCode = "";
|
||||||
|
let maxTry = 3;
|
||||||
|
// const fetchQrCodeImg = (async() => {
|
||||||
|
// await fetch("http://transcendance:8080/api/v2/auth/2fa/generate",
|
||||||
|
// {
|
||||||
|
// method: 'POST',
|
||||||
|
// })
|
||||||
|
// .then(response => {return response.blob()})
|
||||||
|
// .then(blob => {
|
||||||
|
// const url = URL.createObjectURL(blob);
|
||||||
|
// qrCodeImg = url;
|
||||||
|
// });
|
||||||
|
// })()
|
||||||
|
|
||||||
|
// $: submit = async() => {
|
||||||
|
// const response = await fetch("http://transcendance:8080/api/v2/auth/2fa/turn-on",
|
||||||
|
// {
|
||||||
|
// method : 'POST',
|
||||||
|
// headers : {
|
||||||
|
// "Content-Type": "application/json",
|
||||||
|
// },
|
||||||
|
// body : JSON.stringify({
|
||||||
|
// "twoFaCode" : qrCode,
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
|
// if (response.status === 401)
|
||||||
|
// {
|
||||||
|
// qrCode = "";
|
||||||
|
// wrongCode = `Wrong code, please try again. You have ${maxTry} before end session`;
|
||||||
|
// maxTry--;
|
||||||
|
// }
|
||||||
|
// if (maxTry === 0)
|
||||||
|
// {
|
||||||
|
// await fetch("http://transcendance:8080/auth/logout",
|
||||||
|
// {
|
||||||
|
// method : 'POST',
|
||||||
|
// })
|
||||||
|
// .then(response => response.json())
|
||||||
|
// .then(push("/login"));
|
||||||
|
// }
|
||||||
|
// if (response.status === 200)
|
||||||
|
// {
|
||||||
|
// push("/");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// My code
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let auth;
|
||||||
|
// we're expecting secret and otpauth
|
||||||
|
|
||||||
|
onMount( async() => {
|
||||||
|
// auth = await fetch('http://transcendance:8080/api/v2/auth/2fa/generate', {
|
||||||
|
// method: 'POST'
|
||||||
|
// })
|
||||||
|
// .then((resp) => resp.json());
|
||||||
|
// console.log(auth.secret);
|
||||||
|
|
||||||
|
await fetch("http://transcendance:8080/api/v2/auth/2fa/generate", {
|
||||||
|
method: 'POST',
|
||||||
|
})
|
||||||
|
.then(response => {return response.blob()})
|
||||||
|
.then(blob => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
qrCodeImg = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// testing loginStatus Custom Store
|
||||||
|
|
||||||
|
const toggleTFA = () => {
|
||||||
|
loginStatus.toggleTFA();
|
||||||
|
console.log($loginStatus.tfa);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// testing
|
||||||
|
|
||||||
|
let auth2
|
||||||
|
const TFA = async() => {
|
||||||
|
// ok no idea what goes in here...
|
||||||
|
auth2 = await fetch('http://transcendance:8080/api/v2/auth/2fa/generate', {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
// .then((resp) => resp.json());
|
||||||
|
|
||||||
|
// console.log(auth2.secret);
|
||||||
|
console.log(auth2);
|
||||||
|
};
|
||||||
|
|
||||||
|
// if ($loginStatus.tfa && $loginStatus.fortyTwo)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>2FA Test</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button on:click={TFA}>TFA</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#if auth2}
|
||||||
|
<p>{auth2.json()}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img src={qrCodeImg} alt="A QRCodeImg you must scan with google authenticator" id="qrcodeImg" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <p>FortyTwo: {$loginStatus.fortyTwo}</p>
|
||||||
|
<p>TFA: {$loginStatus.tfa}</p>
|
||||||
|
<p>isLogged: {loginStatus.isLogged}</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button on:click={toggleTFA}>toggleTFA</button>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
let clickedLogout = async() => {
|
let clickedLogout = async() => {
|
||||||
console.log('clicked logout');
|
console.log('clicked logout');
|
||||||
await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
||||||
$loginStatus = false;
|
// $loginStatus = false;
|
||||||
// maybe use replace() ?
|
// maybe use replace() ?
|
||||||
push('/');
|
push('/');
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
// an alternative way of doing things where i have a svelte store connected to localStorage
|
||||||
|
|
||||||
|
// do in need to adapt this to work with 2fa?
|
||||||
|
|
||||||
|
let _user = localStorage.getItem('42User');
|
||||||
|
|
||||||
|
// turns out a simple store is actually the easiest :)
|
||||||
|
// export const userStore = writable(_user ? JSON.parse(_user) : null); // we start with no user, but go get one if one exists
|
||||||
|
// export const userStore = writable(null);
|
||||||
|
|
||||||
|
// ok so this will happen no matter what, basically we are telling it what to do if the store containing the user changes
|
||||||
|
userStore.subscribe((value) => {
|
||||||
|
if (value)
|
||||||
|
localStorage.setItem('42User', JSON.stringify(value));
|
||||||
|
else
|
||||||
|
localStorage.removeItem('42User'); // for logout
|
||||||
|
});
|
||||||
|
|
||||||
|
export const userLogout = () => userStore.set(null);
|
||||||
|
|
||||||
|
|
||||||
|
// export const tmpStore = userStore
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
// This is a "Custom Store" see that chapter in the Svelte Tutorial, should be fine
|
||||||
|
// NVM this is definitely overkill
|
||||||
|
// function createLogin() {
|
||||||
|
// const { subscribe, update } = writable(false);
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// subscribe,
|
||||||
|
// login: () => update(s => s = true),
|
||||||
|
// logout: () => update(s => s = false),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// export const loginStatus = createLogin();
|
||||||
|
|
||||||
|
// export const loginStatus = writable({
|
||||||
|
// 42: false,
|
||||||
|
// tfa: false,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// function createLoginStatus() {
|
||||||
|
|
||||||
|
// //ok it really hated all this
|
||||||
|
|
||||||
|
// // const store = writable({
|
||||||
|
// // fortyTwo: false,
|
||||||
|
// // tfa: false,
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// // return {
|
||||||
|
// // ...store,
|
||||||
|
// // subscribe,
|
||||||
|
// // // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
||||||
|
// // toggle42: () => store.update( fortyTwo => !fortyTwo ),
|
||||||
|
// // // toggleTFA: () => update( l => l.tfa = !l.tfa ),
|
||||||
|
// // toggleTFA: () => store.update( tfa => !tfa ),
|
||||||
|
// // isLogged: () => store.fortyTwo && store.tfa,
|
||||||
|
// // // isLogged: this.fortyTwo && this.tfa,
|
||||||
|
// // // it really doesn't like "this."
|
||||||
|
// // // isLogged: () => (this.tfa && this.fortyTwo),
|
||||||
|
// // // this. ? or (l) => l.tfa ... ?
|
||||||
|
// // }
|
||||||
|
|
||||||
|
|
||||||
|
// // doesn't seem to work...
|
||||||
|
// const { subscribe, update } = writable({
|
||||||
|
// fortyTwo: false,
|
||||||
|
// tfa: false,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// subscribe,
|
||||||
|
// // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
||||||
|
// toggle42: () => update( fortyTwo => !fortyTwo ),
|
||||||
|
// // toggleTFA: () => update( l => l.tfa = !l.tfa ),
|
||||||
|
// toggleTFA: () => update( tfa => !tfa ),
|
||||||
|
// // isLogged: () => fortyTwo && tfa,
|
||||||
|
// // isLogged: this.fortyTwo && this.tfa,
|
||||||
|
// // it really doesn't like "this."
|
||||||
|
// // isLogged: () => (this.tfa && this.fortyTwo),
|
||||||
|
// // this. ? or (l) => l.tfa ... ?
|
||||||
|
// isLogged() {
|
||||||
|
// return fortyTwo && tfa;
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // possible other way of doing this
|
||||||
|
|
||||||
|
// // const store = writable({
|
||||||
|
// // fortyTwo: false,
|
||||||
|
// // tfa: false,
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// // return {
|
||||||
|
// // ...store,
|
||||||
|
// // subscribe,
|
||||||
|
// // // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
||||||
|
// // toggle42: () => store.update( l.fortyTwo => !l.fortyTwo ),
|
||||||
|
// // toggleTFA: () => store.update( l => l.tfa = !l.tfa ),
|
||||||
|
// // isLogged: store.fortyTwo && store.tfa,
|
||||||
|
// // // isLogged: () => (this.tfa && this.fortyTwo),
|
||||||
|
// // // this. ? or (l) => l.tfa ... ?
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
function createLoginStatus() {
|
||||||
|
const { subscribe, update } = writable({
|
||||||
|
fortyTwo: false,
|
||||||
|
tfa: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggle42() {
|
||||||
|
update( (old) => ({...old, fortyTwo: !old.fortyTwo}) );
|
||||||
|
};
|
||||||
|
|
||||||
|
function toggleTFA() {
|
||||||
|
// update( () => {
|
||||||
|
// self.tfa = !self.tfa;
|
||||||
|
// return self;
|
||||||
|
// })
|
||||||
|
// console.log("testing");
|
||||||
|
update( (old) => ({...old, tfa: !old.tfa}) );
|
||||||
|
};
|
||||||
|
|
||||||
|
function isLogged() {
|
||||||
|
// return (l) => {l.fortyTwo && l.tfa};
|
||||||
|
// return self.fortyTwo && self.tfa;
|
||||||
|
// return fortyTwo && tfa;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { subscribe, update, toggle42, toggleTFA, isLogged };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loginStatus = createLoginStatus();
|
||||||
|
|
||||||
|
// OK let's try a totally new approach
|
||||||
|
|
||||||
|
// const _loginStatus = writable({
|
||||||
|
// fortyTwo: false,
|
||||||
|
// tfa: false,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const loginStatus = {
|
||||||
|
// subscribe: _loginStatus.subscribe,
|
||||||
|
// set: _loginStatus.set,
|
||||||
|
// update: _loginStatus.update,
|
||||||
|
// toggle42: () =>
|
||||||
|
// }
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import NotFound from "../src/pages/NotFound.svelte";
|
||||||
|
import ProfilePage from "../src/pages/profile/ProfilePage.svelte";
|
||||||
|
import SplashPage from "../src/pages/SplashPage.svelte";
|
||||||
|
import TwoFactorAuthentication from '../src/pages/TwoFactorAuthentication.svelte';
|
||||||
|
import UnauthorizedAccessPage from '../src/pages/UnauthorizedAccessPage.svelte';
|
||||||
|
import { wrap } from 'svelte-spa-router/wrap'
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
|
import TestPage from '../src/pages/TmpTestPage.svelte';
|
||||||
|
import { userStore, userLogout } from "../src/stores/loginStatusStore";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// "/article/:title": Article, // this is how you would do parameters!
|
||||||
|
// "/": LoginPage,
|
||||||
|
|
||||||
|
// TMP not using this cuz need to work out how to authentical both 42 and 2FA from the backend
|
||||||
|
|
||||||
|
// export const primaryRoutes = {
|
||||||
|
// '/': SplashPage,
|
||||||
|
// // '/2fa': TwoFactorAuthentication,
|
||||||
|
// '/2fa': wrap({
|
||||||
|
// component: TwoFactorAuthentication,
|
||||||
|
// conditions: [
|
||||||
|
// (detail) => {
|
||||||
|
// // let loggedIn;
|
||||||
|
// // loginStatus.subscribe(value => {
|
||||||
|
// // loggedIn = value;
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// const { fortyTwo, tfa } = get(loginStatus);
|
||||||
|
|
||||||
|
// console.log('condition in /2fa');
|
||||||
|
// // return (loginStatus.fortyTwo && loginStatus.tfa);
|
||||||
|
// // console.log($loginStatus.fortyTwo)
|
||||||
|
// console.log(fortyTwo);
|
||||||
|
// console.log(tfa);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }),
|
||||||
|
// '/profile': wrap({
|
||||||
|
// component: ProfilePage,
|
||||||
|
// conditions: [
|
||||||
|
// (detail) => {
|
||||||
|
// const { fortyTwo, tfa } = get(loginStatus);
|
||||||
|
// // console.log(fortyTwo);
|
||||||
|
// // console.log(tfa);
|
||||||
|
// // return true;
|
||||||
|
// return (fortyTwo && tfa);
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }),
|
||||||
|
// '/profile/*': wrap({
|
||||||
|
// component: ProfilePage,
|
||||||
|
// conditions: [
|
||||||
|
// (detail) => {
|
||||||
|
// const { fortyTwo, tfa } = get(loginStatus);
|
||||||
|
// // console.log(fortyTwo);
|
||||||
|
// // console.log(tfa);
|
||||||
|
// // return true;
|
||||||
|
// return (fortyTwo && tfa);
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }),
|
||||||
|
// '/profile': wrap({
|
||||||
|
// // Use a dynamically-loaded component for this
|
||||||
|
// asyncComponent: () => import('./ProfilePage.svelte'),
|
||||||
|
// // Adding one pre-condition that's an async function
|
||||||
|
// conditions: [
|
||||||
|
// async (detail) => {
|
||||||
|
// // Make a network request, which are async operations
|
||||||
|
// const response = await fetch('http://transcendance:8080/api/v2/user')
|
||||||
|
// const data = await response.json()
|
||||||
|
// // Return true to continue loading the component, or false otherwise
|
||||||
|
// if (data.isAdmin) {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }),
|
||||||
|
// '/unauthorized-access': UnauthorizedAccessPage,
|
||||||
|
// '*': NotFound
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const primaryRoutes = {
|
||||||
|
"/": SplashPage,
|
||||||
|
'/test': wrap({
|
||||||
|
component: TestPage,
|
||||||
|
conditions: [
|
||||||
|
(detail) => {
|
||||||
|
// const user = get(userStore); // seems like get(store) is not an option
|
||||||
|
// // const user = userStore;
|
||||||
|
// // console.log(fortyTwo);
|
||||||
|
// // console.log(tfa);
|
||||||
|
// console.log('in /test what is in user')
|
||||||
|
// console.log(user)
|
||||||
|
|
||||||
|
// you moron $userStore is a Svelte Abreviation, this is .JS, duh
|
||||||
|
// let user = $userStore;
|
||||||
|
let user;
|
||||||
|
const unsub = userStore.subscribe(value => {
|
||||||
|
user = value;
|
||||||
|
});
|
||||||
|
console.log('in /test what is in userStore directly')
|
||||||
|
console.log(user)
|
||||||
|
|
||||||
|
// return true;
|
||||||
|
// obvi this doesn't work cuz skips to true after no user...
|
||||||
|
// you gotta make the condition the true and the everything else false
|
||||||
|
// if (user && user.statusCode && user.statusCode === 403)
|
||||||
|
// if (user !== null) {
|
||||||
|
if (user && user.username) {
|
||||||
|
unsub();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
unsub();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
// '/test': wrap({
|
||||||
|
// component: TestPage,
|
||||||
|
// conditions: [
|
||||||
|
// async(detail) => {
|
||||||
|
// // THIS SHIT TOTALLY WORKS
|
||||||
|
// const user = await fetch('http://transcendance:8080/api/v2/user')
|
||||||
|
// .then((resp) => resp.json())
|
||||||
|
|
||||||
|
// console.log('in /test what is in user')
|
||||||
|
// console.log(user)
|
||||||
|
|
||||||
|
// if (user && user.username)
|
||||||
|
// return true;
|
||||||
|
// else
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }),
|
||||||
|
'/2fa': TwoFactorAuthentication,
|
||||||
|
"/profile": ProfilePage,
|
||||||
|
"/profile/*": ProfilePage,
|
||||||
|
'/unauthorized-access': UnauthorizedAccessPage,
|
||||||
|
"*": NotFound
|
||||||
|
};
|
||||||
|
|
||||||
|
// export const primaryRoutes = {
|
||||||
|
// "/": SplashPage,
|
||||||
|
// "/profile": ProfilePage,
|
||||||
|
// "/game": GamePage,
|
||||||
|
// "/chat": ChatPage,
|
||||||
|
// "*": NotFound
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
// i might need to add /profile/* and such to make the nested routers work
|
||||||
|
|
||||||
|
// ok maybe these need to be in their own files?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// export const gameRoutes = {
|
||||||
|
// "/": GamePage,
|
||||||
|
// "*": NotFound
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const chatRoutes = {
|
||||||
|
// "/": ChatPage,
|
||||||
|
// "*": NotFound
|
||||||
|
// };
|
||||||
|
|
||||||
|
</script>
|
||||||
BIN
srcs/requirements/svelte/api_front/public/fonts/Bit5x3.woff
Normal file
BIN
srcs/requirements/svelte/api_front/public/fonts/Bit5x3.woff
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/fonts/Bit5x3.woff2
Normal file
BIN
srcs/requirements/svelte/api_front/public/fonts/Bit5x3.woff2
Normal file
Binary file not shown.
16
srcs/requirements/svelte/api_front/public/game/audio.ts
Normal file
16
srcs/requirements/svelte/api_front/public/game/audio.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import * as c from "./constants.js"
|
||||||
|
|
||||||
|
export const soundPongArr: HTMLAudioElement[] = [];
|
||||||
|
export const soundRoblox = new Audio("http://localhost:8080/sound/roblox-oof.ogg");
|
||||||
|
|
||||||
|
export function initAudio(muteFlag: boolean)
|
||||||
|
{
|
||||||
|
for (let i = 0; i <= 32; i++) {
|
||||||
|
soundPongArr.push(new Audio("http://localhost:8080/sound/pong/"+i+".ogg"));
|
||||||
|
soundPongArr[i].volume = c.soundPongVolume;
|
||||||
|
soundPongArr[i].muted = muteFlag;
|
||||||
|
}
|
||||||
|
soundRoblox.volume = c.soundRobloxVolume;
|
||||||
|
soundRoblox.muted = muteFlag;
|
||||||
|
}
|
||||||
107
srcs/requirements/svelte/api_front/public/game/class/Event.ts
Normal file
107
srcs/requirements/svelte/api_front/public/game/class/Event.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
|
||||||
|
import * as en from "../enums.js"
|
||||||
|
|
||||||
|
/* From Server */
|
||||||
|
class ServerEvent {
|
||||||
|
type: en.EventTypes;
|
||||||
|
constructor(type: en.EventTypes = 0) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventAssignId extends ServerEvent {
|
||||||
|
id: string;
|
||||||
|
constructor(id: string) {
|
||||||
|
super(en.EventTypes.assignId);
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventMatchmakingComplete extends ServerEvent {
|
||||||
|
side: en.PlayerSide;
|
||||||
|
constructor(side: en.PlayerSide) {
|
||||||
|
super(en.EventTypes.matchmakingComplete);
|
||||||
|
this.side = side;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventGameUpdate extends ServerEvent {
|
||||||
|
playerLeft = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
playerRight = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
ballsArr: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
dirX: number,
|
||||||
|
dirY: number,
|
||||||
|
speed: number
|
||||||
|
}[] = [];
|
||||||
|
wallTop? = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
wallBottom? = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
lastInputId = 0;
|
||||||
|
constructor() { // TODO: constructor that take GameComponentsServer maybe ?
|
||||||
|
super(en.EventTypes.gameUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventScoreUpdate extends ServerEvent {
|
||||||
|
scoreLeft: number;
|
||||||
|
scoreRight: number;
|
||||||
|
constructor(scoreLeft: number, scoreRight: number) {
|
||||||
|
super(en.EventTypes.scoreUpdate);
|
||||||
|
this.scoreLeft = scoreLeft;
|
||||||
|
this.scoreRight = scoreRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventMatchEnd extends ServerEvent {
|
||||||
|
winner: en.PlayerSide;
|
||||||
|
constructor(winner: en.PlayerSide) {
|
||||||
|
super(en.EventTypes.matchEnd);
|
||||||
|
this.winner = winner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* From Client */
|
||||||
|
class ClientEvent {
|
||||||
|
type: en.EventTypes; // readonly ?
|
||||||
|
constructor(type: en.EventTypes = 0) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClientAnnounce extends ClientEvent {
|
||||||
|
role: en.ClientRole;
|
||||||
|
clientId: string;
|
||||||
|
matchOptions: en.MatchOptions;
|
||||||
|
constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") {
|
||||||
|
super(en.EventTypes.clientAnnounce);
|
||||||
|
this.role = role;
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.matchOptions = matchOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventInput extends ClientEvent {
|
||||||
|
input: en.InputEnum;
|
||||||
|
id: number;
|
||||||
|
constructor(input: en.InputEnum = en.InputEnum.noInput, id: number = 0) {
|
||||||
|
super(en.EventTypes.clientInput);
|
||||||
|
this.input = input;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ServerEvent, EventAssignId, EventMatchmakingComplete,
|
||||||
|
EventGameUpdate, EventScoreUpdate, EventMatchEnd,
|
||||||
|
ClientEvent, ClientAnnounce, EventInput
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
import * as c from ".././constants.js"
|
||||||
|
|
||||||
|
class GameArea {
|
||||||
|
keys: string[] = [];
|
||||||
|
handleInputInterval: number = 0;
|
||||||
|
gameLoopInterval: number = 0;
|
||||||
|
drawLoopInterval: number = 0;
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
constructor() {
|
||||||
|
this.canvas = document.createElement("canvas");
|
||||||
|
this.ctx = this.canvas.getContext("2d") as CanvasRenderingContext2D;
|
||||||
|
this.canvas.width = c.CanvasWidth;
|
||||||
|
this.canvas.height = c.CanvasWidth / c.CanvasRatio;
|
||||||
|
let container = document.getElementById("canvas_container");
|
||||||
|
if (container)
|
||||||
|
container.insertBefore(this.canvas, container.childNodes[0]);
|
||||||
|
}
|
||||||
|
addKey(key: string) {
|
||||||
|
key = key.toLowerCase();
|
||||||
|
var i = this.keys.indexOf(key);
|
||||||
|
if (i == -1)
|
||||||
|
this.keys.push(key);
|
||||||
|
}
|
||||||
|
deleteKey(key: string) {
|
||||||
|
key = key.toLowerCase();
|
||||||
|
var i = this.keys.indexOf(key);
|
||||||
|
if (i != -1) {
|
||||||
|
this.keys.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {GameArea}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
import * as c from "../constants.js"
|
||||||
|
import * as en from "../../shared_js/enums.js"
|
||||||
|
import { VectorInteger } from "./Vector.js";
|
||||||
|
import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js";
|
||||||
|
import { random } from "../utils.js";
|
||||||
|
|
||||||
|
class GameComponents {
|
||||||
|
wallTop: Rectangle | MovingRectangle;
|
||||||
|
wallBottom: Rectangle | MovingRectangle;
|
||||||
|
playerLeft: Racket;
|
||||||
|
playerRight: Racket;
|
||||||
|
ballsArr: Ball[] = [];
|
||||||
|
constructor(options: en.MatchOptions)
|
||||||
|
{
|
||||||
|
const pos = new VectorInteger;
|
||||||
|
|
||||||
|
// Rackets
|
||||||
|
pos.assign(0+c.pw, c.h_mid-c.ph/2);
|
||||||
|
this.playerLeft = new Racket(pos, c.pw, c.ph, c.racketSpeed);
|
||||||
|
pos.assign(c.w-c.pw-c.pw, c.h_mid-c.ph/2);
|
||||||
|
this.playerRight = new Racket(pos, c.pw, c.ph, c.racketSpeed);
|
||||||
|
|
||||||
|
// Balls
|
||||||
|
let ballsCount = 1;
|
||||||
|
if (options & en.MatchOptions.multiBalls) {
|
||||||
|
ballsCount = c.multiBallsCount;
|
||||||
|
}
|
||||||
|
pos.assign(-c.ballSize, -c.ballSize); // ball out =)
|
||||||
|
while (this.ballsArr.length < ballsCount) {
|
||||||
|
this.ballsArr.push(new Ball(pos, c.ballSize, c.ballSpeed, c.ballSpeedIncrease))
|
||||||
|
}
|
||||||
|
this.ballsArr.forEach((ball) => {
|
||||||
|
ball.dir.x = 1;
|
||||||
|
if (random() > 0.5) {
|
||||||
|
ball.dir.x *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ball.dir.y = random(0, 0.2);
|
||||||
|
if (random() > 0.5) {
|
||||||
|
ball.dir.y *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ball.dir = ball.dir.normalized();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Walls
|
||||||
|
if (options & en.MatchOptions.movingWalls) {
|
||||||
|
pos.assign(0, 0);
|
||||||
|
this.wallTop = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed);
|
||||||
|
(<MovingRectangle>this.wallTop).dir.y = -1;
|
||||||
|
pos.assign(0, c.h-c.wallSize);
|
||||||
|
this.wallBottom = new MovingRectangle(pos, c.w, c.wallSize, c.movingWallSpeed);
|
||||||
|
(<MovingRectangle>this.wallBottom).dir.y = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pos.assign(0, 0);
|
||||||
|
this.wallTop = new Rectangle(pos, c.w, c.wallSize);
|
||||||
|
pos.assign(0, c.h-c.wallSize);
|
||||||
|
this.wallBottom = new Rectangle(pos, c.w, c.wallSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {GameComponents}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
|
||||||
|
import * as c from "../constants.js"
|
||||||
|
import * as en from "../../shared_js/enums.js"
|
||||||
|
import { Vector, VectorInteger } from "../../shared_js/class/Vector.js";
|
||||||
|
import { TextElem, TextNumericValue } from "./Text.js";
|
||||||
|
import { RectangleClient, MovingRectangleClient, RacketClient, BallClient, Line } from "./RectangleClient.js";
|
||||||
|
import { GameComponents } from "../../shared_js/class/GameComponents.js";
|
||||||
|
import { MovingRectangle } from "../../shared_js/class/Rectangle.js";
|
||||||
|
|
||||||
|
class GameComponentsExtensionForClient extends GameComponents {
|
||||||
|
wallTop: RectangleClient | MovingRectangleClient;
|
||||||
|
wallBottom: RectangleClient | MovingRectangleClient;
|
||||||
|
playerLeft: RacketClient;
|
||||||
|
playerRight: RacketClient;
|
||||||
|
ballsArr: BallClient[];
|
||||||
|
constructor(options: en.MatchOptions, ctx: CanvasRenderingContext2D)
|
||||||
|
{
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
// Rackets
|
||||||
|
const basePL = this.playerLeft;
|
||||||
|
const basePR = this.playerRight;
|
||||||
|
this.playerLeft = new RacketClient(
|
||||||
|
basePL.pos, basePL.width, basePL.height, basePL.baseSpeed,
|
||||||
|
ctx, "white");
|
||||||
|
this.playerRight = new RacketClient(
|
||||||
|
basePR.pos, basePR.width, basePR.height, basePR.baseSpeed,
|
||||||
|
ctx, "white");
|
||||||
|
|
||||||
|
// Balls
|
||||||
|
const newBallsArr: BallClient[] = [];
|
||||||
|
this.ballsArr.forEach((ball) => {
|
||||||
|
newBallsArr.push(new BallClient(ball.pos, ball.width, ball.baseSpeed, ball.speedIncrease,
|
||||||
|
ctx, "white")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.ballsArr = newBallsArr;
|
||||||
|
|
||||||
|
// Walls
|
||||||
|
if (options & en.MatchOptions.movingWalls)
|
||||||
|
{
|
||||||
|
const baseWT = <MovingRectangle>this.wallTop;
|
||||||
|
const baseWB = <MovingRectangle>this.wallBottom;
|
||||||
|
|
||||||
|
this.wallTop = new MovingRectangleClient(baseWT.pos, baseWT.width, baseWT.height, baseWT.baseSpeed,
|
||||||
|
ctx, "grey");
|
||||||
|
(<MovingRectangleClient>this.wallTop).dir.assign(baseWT.dir.x, baseWT.dir.y);
|
||||||
|
|
||||||
|
this.wallBottom = new MovingRectangleClient(baseWB.pos, baseWB.width, baseWB.height, baseWB.baseSpeed,
|
||||||
|
ctx, "grey");
|
||||||
|
(<MovingRectangleClient>this.wallBottom).dir.assign(baseWB.dir.x, baseWB.dir.y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const baseWT = this.wallTop;
|
||||||
|
const baseWB = this.wallBottom;
|
||||||
|
this.wallTop = new RectangleClient(baseWT.pos, baseWT.width, baseWT.height,
|
||||||
|
ctx, "grey");
|
||||||
|
this.wallBottom = new RectangleClient(baseWB.pos, baseWB.width, baseWB.height,
|
||||||
|
ctx, "grey");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GameComponentsClient extends GameComponentsExtensionForClient {
|
||||||
|
midLine: Line;
|
||||||
|
scoreLeft: TextNumericValue;
|
||||||
|
scoreRight: TextNumericValue;
|
||||||
|
text1: TextElem;
|
||||||
|
|
||||||
|
w_grid_mid: RectangleClient;
|
||||||
|
w_grid_u1: RectangleClient;
|
||||||
|
w_grid_d1: RectangleClient;
|
||||||
|
h_grid_mid: RectangleClient;
|
||||||
|
h_grid_u1: RectangleClient;
|
||||||
|
h_grid_d1: RectangleClient;
|
||||||
|
constructor(options: en.MatchOptions, ctx: CanvasRenderingContext2D)
|
||||||
|
{
|
||||||
|
super(options, ctx);
|
||||||
|
let pos = new VectorInteger;
|
||||||
|
// Scores
|
||||||
|
pos.assign(c.w_mid-c.scoreSize*1.6, c.scoreSize*1.5);
|
||||||
|
this.scoreLeft = new TextNumericValue(pos, c.scoreSize, ctx, "white");
|
||||||
|
pos.assign(c.w_mid+c.scoreSize*1.1, c.scoreSize*1.5);
|
||||||
|
this.scoreRight = new TextNumericValue(pos, c.scoreSize, ctx, "white");
|
||||||
|
this.scoreLeft.value = 0;
|
||||||
|
this.scoreRight.value = 0;
|
||||||
|
|
||||||
|
// Text
|
||||||
|
pos.assign(0, c.h_mid);
|
||||||
|
this.text1 = new TextElem(pos, Math.floor(c.w/8), ctx, "white");
|
||||||
|
|
||||||
|
// Dotted Midline
|
||||||
|
pos.assign(c.w_mid-c.midLineSize/2, 0+c.wallSize);
|
||||||
|
this.midLine = new Line(pos, c.midLineSize, c.h-c.wallSize*2, ctx, "white", 15);
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
pos.assign(0, c.h_mid);
|
||||||
|
this.w_grid_mid = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen");
|
||||||
|
pos.assign(0, c.h/4);
|
||||||
|
this.w_grid_u1 = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen");
|
||||||
|
pos.assign(0, c.h-c.h/4);
|
||||||
|
this.w_grid_d1 = new RectangleClient(pos, c.w, c.gridSize, ctx, "darkgreen");
|
||||||
|
pos.assign(c.w_mid, 0);
|
||||||
|
this.h_grid_mid = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen");
|
||||||
|
pos.assign(c.w/4, 0);
|
||||||
|
this.h_grid_u1 = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen");
|
||||||
|
pos.assign(c.w-c.w/4, 0);
|
||||||
|
this.h_grid_d1 = new RectangleClient(pos, c.gridSize, c.h, ctx, "darkgreen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {GameComponentsClient}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import * as en from "../../shared_js/enums.js"
|
||||||
|
import * as ev from "../../shared_js/class/Event.js"
|
||||||
|
|
||||||
|
class InputHistory {
|
||||||
|
input: en.InputEnum;
|
||||||
|
id: number;
|
||||||
|
deltaTime: number;
|
||||||
|
constructor(inputState: ev.EventInput, deltaTime: number) {
|
||||||
|
this.input = inputState.input;
|
||||||
|
this.id = inputState.id;
|
||||||
|
this.deltaTime = deltaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {InputHistory}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
|
||||||
|
import { Vector, VectorInteger } from "./Vector.js";
|
||||||
|
import { Component, Moving } from "./interface.js";
|
||||||
|
import * as c from "../constants.js"
|
||||||
|
|
||||||
|
class Rectangle implements Component {
|
||||||
|
pos: VectorInteger;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
constructor(pos: VectorInteger, width: number, height: number) {
|
||||||
|
this.pos = new VectorInteger(pos.x, pos.y);
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
collision(collider: Rectangle): boolean {
|
||||||
|
const thisLeft = this.pos.x;
|
||||||
|
const thisRight = this.pos.x + this.width;
|
||||||
|
const thisTop = this.pos.y;
|
||||||
|
const thisBottom = this.pos.y + this.height;
|
||||||
|
const colliderLeft = collider.pos.x;
|
||||||
|
const colliderRight = collider.pos.x + collider.width;
|
||||||
|
const colliderTop = collider.pos.y;
|
||||||
|
const colliderBottom = collider.pos.y + collider.height;
|
||||||
|
if ((thisBottom < colliderTop)
|
||||||
|
|| (thisTop > colliderBottom)
|
||||||
|
|| (thisRight < colliderLeft)
|
||||||
|
|| (thisLeft > colliderRight)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MovingRectangle extends Rectangle implements Moving {
|
||||||
|
dir: Vector = new Vector(0,0);
|
||||||
|
speed: number;
|
||||||
|
readonly baseSpeed: number;
|
||||||
|
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
|
||||||
|
super(pos, width, height);
|
||||||
|
this.baseSpeed = baseSpeed;
|
||||||
|
this.speed = baseSpeed;
|
||||||
|
}
|
||||||
|
move(delta: number) { // Math.floor WIP until VectorInteger debug
|
||||||
|
// console.log(`delta: ${delta}, speed: ${this.speed}, speed*delta: ${this.speed * delta}`);
|
||||||
|
// this.pos.x += Math.floor(this.dir.x * this.speed * delta);
|
||||||
|
// this.pos.y += Math.floor(this.dir.y * this.speed * delta);
|
||||||
|
this.pos.x += this.dir.x * this.speed * delta;
|
||||||
|
this.pos.y += this.dir.y * this.speed * delta;
|
||||||
|
}
|
||||||
|
moveAndCollide(delta: number, colliderArr: Rectangle[]) {
|
||||||
|
this._moveAndCollideAlgo(delta, colliderArr);
|
||||||
|
}
|
||||||
|
protected _moveAndCollideAlgo(delta: number, colliderArr: Rectangle[]) {
|
||||||
|
let oldPos = new VectorInteger(this.pos.x, this.pos.y);
|
||||||
|
this.move(delta);
|
||||||
|
if (colliderArr.some(this.collision, this)) {
|
||||||
|
this.pos = oldPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Racket extends MovingRectangle {
|
||||||
|
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
|
||||||
|
super(pos, width, height, baseSpeed);
|
||||||
|
}
|
||||||
|
moveAndCollide(delta: number, colliderArr: Rectangle[]) {
|
||||||
|
// let oldPos = new VectorInteger(this.pos.x, this.pos.y); // debug
|
||||||
|
this._moveAndCollideAlgo(delta, colliderArr);
|
||||||
|
// console.log(`y change: ${this.pos.y - oldPos.y}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ball extends MovingRectangle {
|
||||||
|
readonly speedIncrease: number;
|
||||||
|
ballInPlay: boolean = false;
|
||||||
|
constructor(pos: VectorInteger, size: number, baseSpeed: number, speedIncrease: number) {
|
||||||
|
super(pos, size, size, baseSpeed);
|
||||||
|
this.speedIncrease = speedIncrease;
|
||||||
|
}
|
||||||
|
bounce(collider?: Rectangle) {
|
||||||
|
this._bounceAlgo(collider);
|
||||||
|
}
|
||||||
|
protected _bounceAlgo(collider?: Rectangle) {
|
||||||
|
/* Could be more generic, but testing only Racket is enough,
|
||||||
|
because in Pong collider can only be Racket or Wall. */
|
||||||
|
if (collider instanceof Racket) {
|
||||||
|
this._bounceRacket(collider);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._bounceWall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moveAndBounce(delta: number, colliderArr: Rectangle[]) {
|
||||||
|
this.move(delta);
|
||||||
|
let i = colliderArr.findIndex(this.collision, this);
|
||||||
|
if (i != -1)
|
||||||
|
{
|
||||||
|
this.bounce(colliderArr[i]);
|
||||||
|
this.move(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected _bounceWall() { // Should be enough for Wall
|
||||||
|
this.dir.y = this.dir.y * -1;
|
||||||
|
}
|
||||||
|
protected _bounceRacket(racket: Racket) {
|
||||||
|
this._bounceRacketAlgo(racket);
|
||||||
|
}
|
||||||
|
protected _bounceRacketAlgo(racket: Racket) {
|
||||||
|
this.speed += this.speedIncrease;
|
||||||
|
|
||||||
|
let x = this.dir.x * -1;
|
||||||
|
|
||||||
|
const angleFactorDegree = 60;
|
||||||
|
const angleFactor = angleFactorDegree / 90;
|
||||||
|
const racketHalf = racket.height/2;
|
||||||
|
const ballMid = this.pos.y + this.height/2;
|
||||||
|
const racketMid = racket.pos.y + racketHalf;
|
||||||
|
|
||||||
|
let impact = ballMid - racketMid;
|
||||||
|
const horizontalMargin = racketHalf * 0.15;
|
||||||
|
if (impact < horizontalMargin && impact > -horizontalMargin) {
|
||||||
|
impact = 0;
|
||||||
|
}
|
||||||
|
else if (impact > 0) {
|
||||||
|
impact = impact - horizontalMargin;
|
||||||
|
}
|
||||||
|
else if (impact < 0) {
|
||||||
|
impact = impact + horizontalMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
let y = impact / (racketHalf - horizontalMargin) * angleFactor;
|
||||||
|
|
||||||
|
this.dir.assign(x, y);
|
||||||
|
// Normalize Vector (for consistency in speed independent of direction)
|
||||||
|
if (c.normalizedSpeed) {
|
||||||
|
this.dir = this.dir.normalized();
|
||||||
|
}
|
||||||
|
// console.log(`x: ${this.dir.x}, y: ${this.dir.y}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Rectangle, MovingRectangle, Racket, Ball}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
|
||||||
|
import { Vector, VectorInteger } from "../../shared_js/class/Vector.js";
|
||||||
|
import { Component, GraphicComponent, Moving } from "../../shared_js/class/interface.js";
|
||||||
|
import { Rectangle, MovingRectangle, Racket, Ball } from "../../shared_js/class/Rectangle.js";
|
||||||
|
import { soundPongArr } from "../audio.js"
|
||||||
|
import { random } from "../utils.js";
|
||||||
|
|
||||||
|
function updateRectangle(this: RectangleClient) {
|
||||||
|
this.ctx.fillStyle = this.color;
|
||||||
|
this.ctx.fillRect(this.pos.x, this.pos.y, this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearRectangle(this: RectangleClient, pos?: VectorInteger) {
|
||||||
|
if (pos)
|
||||||
|
this.ctx.clearRect(pos.x, pos.y, this.width, this.height);
|
||||||
|
else
|
||||||
|
this.ctx.clearRect(this.pos.x, this.pos.y, this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RectangleClient extends Rectangle implements GraphicComponent {
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
color: string;
|
||||||
|
update: () => void;
|
||||||
|
clear: (pos?: VectorInteger) => void;
|
||||||
|
constructor(pos: VectorInteger, width: number, height: number,
|
||||||
|
ctx: CanvasRenderingContext2D, color: string)
|
||||||
|
{
|
||||||
|
super(pos, width, height);
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.color = color;
|
||||||
|
this.update = updateRectangle;
|
||||||
|
this.clear = clearRectangle;
|
||||||
|
}
|
||||||
|
// update() {
|
||||||
|
// this.ctx.fillStyle = this.color;
|
||||||
|
// this.ctx.fillRect(this.pos.x, this.pos.y, this.width, this.height);
|
||||||
|
// }
|
||||||
|
// clear(pos?: VectorInteger) {
|
||||||
|
// if (pos)
|
||||||
|
// this.ctx.clearRect(pos.x, pos.y, this.width, this.height);
|
||||||
|
// else
|
||||||
|
// this.ctx.clearRect(this.pos.x, this.pos.y, this.width, this.height);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MovingRectangleClient extends MovingRectangle implements GraphicComponent {
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
color: string;
|
||||||
|
update: () => void;
|
||||||
|
clear: (pos?: VectorInteger) => void;
|
||||||
|
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number,
|
||||||
|
ctx: CanvasRenderingContext2D, color: string)
|
||||||
|
{
|
||||||
|
super(pos, width, height, baseSpeed);
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.color = color;
|
||||||
|
this.update = updateRectangle;
|
||||||
|
this.clear = clearRectangle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RacketClient extends Racket implements GraphicComponent {
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
color: string;
|
||||||
|
update: () => void;
|
||||||
|
clear: (pos?: VectorInteger) => void;
|
||||||
|
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number,
|
||||||
|
ctx: CanvasRenderingContext2D, color: string)
|
||||||
|
{
|
||||||
|
super(pos, width, height, baseSpeed);
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.color = color;
|
||||||
|
this.update = updateRectangle;
|
||||||
|
this.clear = clearRectangle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BallClient extends Ball implements GraphicComponent {
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
color: string;
|
||||||
|
update: () => void;
|
||||||
|
clear: (pos?: VectorInteger) => void;
|
||||||
|
constructor(pos: VectorInteger, size: number, baseSpeed: number, speedIncrease: number,
|
||||||
|
ctx: CanvasRenderingContext2D, color: string)
|
||||||
|
{
|
||||||
|
super(pos, size, baseSpeed, speedIncrease);
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.color = color;
|
||||||
|
this.update = updateRectangle;
|
||||||
|
this.clear = clearRectangle;
|
||||||
|
}
|
||||||
|
bounce(collider?: Rectangle) {
|
||||||
|
this._bounceAlgo(collider);
|
||||||
|
soundPongArr[ Math.floor(random(0, soundPongArr.length)) ].play();
|
||||||
|
}
|
||||||
|
/* protected _bounceRacket(collider: Racket) {
|
||||||
|
this._bounceRacketAlgo(collider);
|
||||||
|
soundRoblox.play();
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLine(this: Line) {
|
||||||
|
this.ctx.fillStyle = this.color;
|
||||||
|
let pos: VectorInteger = new VectorInteger;
|
||||||
|
let i = 0;
|
||||||
|
while (i < this.segmentCount)
|
||||||
|
{
|
||||||
|
// for Horizontal Line
|
||||||
|
// pos.y = this.pos.y;
|
||||||
|
// pos.x = this.pos.x + this.segmentWidth * i;
|
||||||
|
pos.x = this.pos.x;
|
||||||
|
pos.y = this.pos.y + this.segmentHeight * i;
|
||||||
|
this.ctx.fillRect(pos.x, pos.y, this.segmentWidth, this.segmentHeight);
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Line extends RectangleClient {
|
||||||
|
gapeCount: number = 0;
|
||||||
|
segmentCount: number;
|
||||||
|
segmentWidth: number;
|
||||||
|
segmentHeight: number;
|
||||||
|
constructor(pos: VectorInteger, width: number, height: number,
|
||||||
|
ctx: CanvasRenderingContext2D, color: string, gapeCount?: number)
|
||||||
|
{
|
||||||
|
super(pos, width, height, ctx, color);
|
||||||
|
this.update = updateLine;
|
||||||
|
if (gapeCount)
|
||||||
|
this.gapeCount = gapeCount;
|
||||||
|
this.segmentCount = this.gapeCount * 2 + 1;
|
||||||
|
|
||||||
|
this.segmentWidth = this.width;
|
||||||
|
this.segmentHeight = this.height / this.segmentCount;
|
||||||
|
|
||||||
|
// for Horizontal Line
|
||||||
|
// this.segmentWidth = this.width / this.segmentCount;
|
||||||
|
// this.segmentHeight = this.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {RectangleClient, MovingRectangleClient, RacketClient, BallClient, Line}
|
||||||
58
srcs/requirements/svelte/api_front/public/game/class/Text.ts
Normal file
58
srcs/requirements/svelte/api_front/public/game/class/Text.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
import { Vector, VectorInteger } from "../../shared_js/class/Vector.js";
|
||||||
|
import { Component } from "../../shared_js/class/interface.js";
|
||||||
|
|
||||||
|
// conflict with Text
|
||||||
|
class TextElem implements Component {
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
pos: VectorInteger;
|
||||||
|
color: string;
|
||||||
|
size: number;
|
||||||
|
font: string;
|
||||||
|
text: string = "";
|
||||||
|
constructor(pos: VectorInteger, size: number,
|
||||||
|
ctx: CanvasRenderingContext2D, color: string, font: string = "Bit5x3")
|
||||||
|
{
|
||||||
|
// this.pos = Object.assign({}, pos); // create bug, Uncaught TypeError: X is not a function
|
||||||
|
this.pos = new VectorInteger(pos.x, pos.y);
|
||||||
|
this.size = size;
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.color = color;
|
||||||
|
this.font = font;
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
this.ctx.font = this.size + "px" + " " + this.font;
|
||||||
|
this.ctx.fillStyle = this.color;
|
||||||
|
this.ctx.fillText(this.text, this.pos.x, this.pos.y);
|
||||||
|
}
|
||||||
|
clear() {
|
||||||
|
// clear no very accurate for Text
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics
|
||||||
|
let textMetric = this.ctx.measureText(this.text);
|
||||||
|
// console.log("textMetric.width = "+textMetric.width);
|
||||||
|
// console.log("size = "+this.size);
|
||||||
|
// console.log("x = "+this.pos.x);
|
||||||
|
// console.log("y = "+this.pos.y);
|
||||||
|
this.ctx.clearRect(this.pos.x - 1, this.pos.y-this.size + 1, textMetric.width, this.size);
|
||||||
|
// +1 and -1 because float imprecision (and Math.floor() with VectorInteger dont work for the moment)
|
||||||
|
// (or maybe its textMetric imprecision ?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextNumericValue extends TextElem {
|
||||||
|
private _value: number = 0;
|
||||||
|
constructor(pos: VectorInteger, size: number,
|
||||||
|
ctx: CanvasRenderingContext2D, color: string, font?: string)
|
||||||
|
{
|
||||||
|
super(pos, size, ctx, color, font);
|
||||||
|
}
|
||||||
|
get value() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
set value(v: number) {
|
||||||
|
this._value = v;
|
||||||
|
this.text = v.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {TextElem, TextNumericValue}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
class Vector {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
constructor(x: number = 0, y: number = 0) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
assign(x: number, y: number) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
normalized() : Vector {
|
||||||
|
const normalizationFactor = Math.abs(this.x) + Math.abs(this.y);
|
||||||
|
return new Vector(this.x/normalizationFactor, this.y/normalizationFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VectorInteger extends Vector {
|
||||||
|
// PLACEHOLDER
|
||||||
|
// VectorInteger with set/get dont work (No draw on the screen). Why ?
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
class VectorInteger {
|
||||||
|
// private _x: number = 0;
|
||||||
|
// private _y: number = 0;
|
||||||
|
// constructor(x: number = 0, y: number = 0) {
|
||||||
|
// this._x = x;
|
||||||
|
// this._y = y;
|
||||||
|
// }
|
||||||
|
// get x(): number {
|
||||||
|
// return this._x;
|
||||||
|
// }
|
||||||
|
// set x(v: number) {
|
||||||
|
// // this._x = Math.floor(v);
|
||||||
|
// this._x = v;
|
||||||
|
// }
|
||||||
|
// get y(): number {
|
||||||
|
// return this._y;
|
||||||
|
// }
|
||||||
|
// set y(v: number) {
|
||||||
|
// // this._y = Math.floor(v);
|
||||||
|
// this._y = v;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {Vector, VectorInteger}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
import { Vector, VectorInteger } from "./Vector.js";
|
||||||
|
|
||||||
|
interface Component {
|
||||||
|
pos: VectorInteger;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraphicComponent extends Component {
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
color: string;
|
||||||
|
update: () => void;
|
||||||
|
clear: (pos?: VectorInteger) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Moving {
|
||||||
|
dir: Vector;
|
||||||
|
speed: number; // pixel per second
|
||||||
|
move(delta: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {Component, GraphicComponent, Moving}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
export const CanvasWidth = 1500;
|
||||||
|
export const CanvasRatio = 1.66666;
|
||||||
|
/* ratio 5/3 (1.66) */
|
||||||
|
|
||||||
|
export const w = CanvasWidth;
|
||||||
|
export const h = CanvasWidth / CanvasRatio;
|
||||||
|
export const w_mid = Math.floor(w/2);
|
||||||
|
export const h_mid = Math.floor(h/2);
|
||||||
|
export const pw = Math.floor(w*0.017);
|
||||||
|
export const ph = pw*6;
|
||||||
|
export const ballSize = pw;
|
||||||
|
export const wallSize = Math.floor(w*0.01);
|
||||||
|
export const racketSpeed = Math.floor(w*0.66); // pixel per second
|
||||||
|
export const ballSpeed = Math.floor(w*0.66); // pixel per second
|
||||||
|
export const ballSpeedIncrease = Math.floor(ballSpeed*0.05); // pixel per second
|
||||||
|
|
||||||
|
export const normalizedSpeed = false; // for consistency in speed independent of direction
|
||||||
|
|
||||||
|
export const matchStartDelay = 3000; // millisecond
|
||||||
|
export const newRoundDelay = 1500; // millisecond
|
||||||
|
|
||||||
|
// Game Variantes
|
||||||
|
export const multiBallsCount = 3;
|
||||||
|
export const movingWallPosMax = Math.floor(w*0.12);
|
||||||
|
export const movingWallSpeed = Math.floor(w*0.08);
|
||||||
18
srcs/requirements/svelte/api_front/public/game/constants.ts
Normal file
18
srcs/requirements/svelte/api_front/public/game/constants.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import { w } from "../shared_js/constants.js"
|
||||||
|
export * from "../shared_js/constants.js"
|
||||||
|
|
||||||
|
export const midLineSize = Math.floor(w/150);
|
||||||
|
export const scoreSize = Math.floor(w/16);
|
||||||
|
export const gridSize = Math.floor(w/500);
|
||||||
|
|
||||||
|
// min interval on Firefox seems to be 15. Chrome can go lower.
|
||||||
|
export const handleInputIntervalMS = 15; // millisecond
|
||||||
|
export const sendLoopIntervalMS = 15; // millisecond // unused
|
||||||
|
export const gameLoopIntervalMS = 15; // millisecond
|
||||||
|
export const drawLoopIntervalMS = 15; // millisecond
|
||||||
|
|
||||||
|
export const fixedDeltaTime = gameLoopIntervalMS/1000; // second
|
||||||
|
|
||||||
|
export const soundRobloxVolume = 0.3; // between 0 and 1
|
||||||
|
export const soundPongVolume = 0.3; // between 0 and 1
|
||||||
51
srcs/requirements/svelte/api_front/public/game/draw.ts
Normal file
51
srcs/requirements/svelte/api_front/public/game/draw.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
import { pong, gc } from "./global.js"
|
||||||
|
import * as c from "./constants.js"
|
||||||
|
import * as en from "../shared_js/enums.js"
|
||||||
|
import { gridDisplay } from "./handleInput.js";
|
||||||
|
|
||||||
|
function drawLoop()
|
||||||
|
{
|
||||||
|
pong.clear();
|
||||||
|
|
||||||
|
if (gridDisplay) {
|
||||||
|
drawGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawStatic();
|
||||||
|
|
||||||
|
gc.text1.update();
|
||||||
|
|
||||||
|
drawDynamic();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDynamic()
|
||||||
|
{
|
||||||
|
gc.scoreLeft.update();
|
||||||
|
gc.scoreRight.update();
|
||||||
|
gc.playerLeft.update();
|
||||||
|
gc.playerRight.update();
|
||||||
|
gc.ballsArr.forEach((ball) => {
|
||||||
|
ball.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawStatic()
|
||||||
|
{
|
||||||
|
gc.midLine.update();
|
||||||
|
gc.wallTop.update();
|
||||||
|
gc.wallBottom.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawGrid()
|
||||||
|
{
|
||||||
|
gc.w_grid_mid.update();
|
||||||
|
gc.w_grid_u1.update();
|
||||||
|
gc.w_grid_d1.update();
|
||||||
|
|
||||||
|
gc.h_grid_mid.update();
|
||||||
|
gc.h_grid_u1.update();
|
||||||
|
gc.h_grid_d1.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
export {drawLoop}
|
||||||
47
srcs/requirements/svelte/api_front/public/game/enums.ts
Normal file
47
srcs/requirements/svelte/api_front/public/game/enums.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
enum EventTypes {
|
||||||
|
// Class Implemented
|
||||||
|
gameUpdate = 1,
|
||||||
|
scoreUpdate,
|
||||||
|
matchEnd,
|
||||||
|
assignId,
|
||||||
|
matchmakingComplete,
|
||||||
|
|
||||||
|
// Generic
|
||||||
|
matchmakingInProgress,
|
||||||
|
matchStart,
|
||||||
|
matchNewRound, // unused
|
||||||
|
matchPause, // unused
|
||||||
|
matchResume, // unused
|
||||||
|
|
||||||
|
// Client
|
||||||
|
clientAnnounce,
|
||||||
|
clientPlayerReady,
|
||||||
|
clientInput,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InputEnum {
|
||||||
|
noInput = 0,
|
||||||
|
up = 1,
|
||||||
|
down,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PlayerSide {
|
||||||
|
left = 1,
|
||||||
|
right
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientRole {
|
||||||
|
player = 1,
|
||||||
|
spectator
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MatchOptions {
|
||||||
|
// binary flags, can be mixed
|
||||||
|
noOption = 0b0,
|
||||||
|
multiBalls = 1 << 0,
|
||||||
|
movingWalls = 1 << 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}
|
||||||
49
srcs/requirements/svelte/api_front/public/game/gameLoop.ts
Normal file
49
srcs/requirements/svelte/api_front/public/game/gameLoop.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
import * as c from "./constants.js";
|
||||||
|
import * as en from "../shared_js/enums.js"
|
||||||
|
import { gc, matchOptions, clientInfo } from "./global.js";
|
||||||
|
import { wallsMovements } from "../shared_js/wallsMovement.js";
|
||||||
|
|
||||||
|
let actual_time: number = Date.now();
|
||||||
|
let last_time: number;
|
||||||
|
let delta_time: number;
|
||||||
|
|
||||||
|
function gameLoop()
|
||||||
|
{
|
||||||
|
/* last_time = actual_time;
|
||||||
|
actual_time = Date.now();
|
||||||
|
delta_time = (actual_time - last_time) / 1000; */
|
||||||
|
|
||||||
|
delta_time = c.fixedDeltaTime;
|
||||||
|
// console.log(`delta_gameLoop: ${delta_time}`);
|
||||||
|
|
||||||
|
// interpolation
|
||||||
|
// console.log(`dir.y: ${clientInfo.opponent.dir.y}, pos.y: ${clientInfo.opponent.pos.y}, opponentNextPos.y: ${clientInfo.opponentNextPos.y}`);
|
||||||
|
if (clientInfo.opponent.dir.y != 0 ) {
|
||||||
|
opponentInterpolation(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// client prediction
|
||||||
|
gc.ballsArr.forEach((ball) => {
|
||||||
|
ball.moveAndBounce(delta_time, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchOptions & en.MatchOptions.movingWalls) {
|
||||||
|
wallsMovements(delta_time, gc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function opponentInterpolation(delta: number)
|
||||||
|
{
|
||||||
|
// interpolation
|
||||||
|
clientInfo.opponent.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
|
||||||
|
|
||||||
|
if ((clientInfo.opponent.dir.y > 0 && clientInfo.opponent.pos.y > clientInfo.opponentNextPos.y)
|
||||||
|
|| (clientInfo.opponent.dir.y < 0 && clientInfo.opponent.pos.y < clientInfo.opponentNextPos.y))
|
||||||
|
{
|
||||||
|
clientInfo.opponent.dir.y = 0;
|
||||||
|
clientInfo.opponent.pos.y = clientInfo.opponentNextPos.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {gameLoop}
|
||||||
3
srcs/requirements/svelte/api_front/public/game/global.ts
Normal file
3
srcs/requirements/svelte/api_front/public/game/global.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
export {pong, gc, matchOptions} from "./pong.js"
|
||||||
|
export {socket, clientInfo} from "./ws.js"
|
||||||
110
srcs/requirements/svelte/api_front/public/game/handleInput.ts
Normal file
110
srcs/requirements/svelte/api_front/public/game/handleInput.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
|
||||||
|
import { pong, gc, socket, clientInfo } from "./global.js"
|
||||||
|
import * as ev from "../shared_js/class/Event.js"
|
||||||
|
import * as en from "../shared_js/enums.js"
|
||||||
|
import { InputHistory } from "./class/InputHistory.js"
|
||||||
|
import * as c from "./constants.js";
|
||||||
|
|
||||||
|
export let gridDisplay = false;
|
||||||
|
|
||||||
|
let actual_time: number = Date.now();
|
||||||
|
let last_time: number;
|
||||||
|
let delta_time: number;
|
||||||
|
|
||||||
|
const inputState: ev.EventInput = new ev.EventInput();
|
||||||
|
const inputHistoryArr: InputHistory[] = [];
|
||||||
|
|
||||||
|
// test
|
||||||
|
/* export function sendLoop()
|
||||||
|
{
|
||||||
|
socket.send(JSON.stringify(inputState));
|
||||||
|
} */
|
||||||
|
|
||||||
|
function handleInput()
|
||||||
|
{
|
||||||
|
/* last_time = actual_time;
|
||||||
|
actual_time = Date.now();
|
||||||
|
delta_time = (actual_time - last_time) / 1000; */
|
||||||
|
|
||||||
|
delta_time = c.fixedDeltaTime;
|
||||||
|
// console.log(`delta_time: ${delta_time}`);
|
||||||
|
|
||||||
|
inputState.id = Date.now();
|
||||||
|
inputState.input = en.InputEnum.noInput;
|
||||||
|
|
||||||
|
const keys = pong.keys;
|
||||||
|
if (keys.length !== 0)
|
||||||
|
{
|
||||||
|
if (keys.indexOf("g") != -1)
|
||||||
|
{
|
||||||
|
gridDisplay = !gridDisplay;
|
||||||
|
pong.deleteKey("g");
|
||||||
|
}
|
||||||
|
playerMovements(delta_time, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.send(JSON.stringify(inputState));
|
||||||
|
// setTimeout(testInputDelay, 100);
|
||||||
|
inputHistoryArr.push(new InputHistory(inputState, delta_time));
|
||||||
|
|
||||||
|
// client prediction
|
||||||
|
if (inputState.input !== en.InputEnum.noInput) {
|
||||||
|
// TODO: peut-etre le mettre dans game loop ?
|
||||||
|
// Attention au delta time dans ce cas !
|
||||||
|
playerMovePrediction(delta_time, inputState.input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerMovements(delta: number, keys: string[])
|
||||||
|
{
|
||||||
|
if (keys.indexOf("w") !== -1 || keys.indexOf("ArrowUp".toLowerCase()) !== -1)
|
||||||
|
{
|
||||||
|
if (keys.indexOf("s") === -1 && keys.indexOf("ArrowDown".toLowerCase()) === -1) {
|
||||||
|
inputState.input = en.InputEnum.up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (keys.indexOf("s") !== -1 || keys.indexOf("ArrowDown".toLowerCase()) !== -1) {
|
||||||
|
inputState.input = en.InputEnum.down;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testInputDelay() {
|
||||||
|
socket.send(JSON.stringify(inputState));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function playerMovePrediction(delta: number, input: en.InputEnum)
|
||||||
|
{
|
||||||
|
// client prediction
|
||||||
|
const racket = clientInfo.racket;
|
||||||
|
if (input === en.InputEnum.up) {
|
||||||
|
racket.dir.y = -1;
|
||||||
|
}
|
||||||
|
else if (input === en.InputEnum.down) {
|
||||||
|
racket.dir.y = 1;
|
||||||
|
}
|
||||||
|
racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function repeatInput(lastInputId: number)
|
||||||
|
{
|
||||||
|
// server reconciliation
|
||||||
|
let i = inputHistoryArr.findIndex((value: InputHistory) => {
|
||||||
|
if (value.id === lastInputId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log(`inputHistory total: ${inputHistoryArr.length}` );
|
||||||
|
inputHistoryArr.splice(0, i+1);
|
||||||
|
// console.log(`inputHistory left: ${inputHistoryArr.length}` );
|
||||||
|
|
||||||
|
inputHistoryArr.forEach((value: InputHistory) => {
|
||||||
|
if (value.input !== en.InputEnum.noInput) {
|
||||||
|
playerMovePrediction(value.deltaTime, value.input);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {handleInput, repeatInput}
|
||||||
99
srcs/requirements/svelte/api_front/public/game/pong.ts
Normal file
99
srcs/requirements/svelte/api_front/public/game/pong.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
|
||||||
|
initDom();
|
||||||
|
function initDom() {
|
||||||
|
document.getElementById("play_pong_button").addEventListener("click", init);
|
||||||
|
}
|
||||||
|
|
||||||
|
import * as c from "./constants.js"
|
||||||
|
import * as en from "../shared_js/enums.js"
|
||||||
|
import { GameArea } from "./class/GameArea.js";
|
||||||
|
import { GameComponentsClient } from "./class/GameComponentsClient.js";
|
||||||
|
import { handleInput } from "./handleInput.js";
|
||||||
|
// import { sendLoop } from "./handleInput.js";
|
||||||
|
import { gameLoop } from "./gameLoop.js"
|
||||||
|
import { drawLoop } from "./draw.js";
|
||||||
|
import { countdown } from "./utils.js";
|
||||||
|
import { initWebSocket } from "./ws.js";
|
||||||
|
import { initAudio } from "./audio.js";
|
||||||
|
|
||||||
|
|
||||||
|
/* Keys
|
||||||
|
Racket: W/S OR Up/Down
|
||||||
|
Grid On-Off: G
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
|
||||||
|
export let pong: GameArea;
|
||||||
|
export let gc: GameComponentsClient;
|
||||||
|
export let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
|
||||||
|
|
||||||
|
function init()
|
||||||
|
{
|
||||||
|
console.log("multi_balls:"+(<HTMLInputElement>document.getElementById("multi_balls")).checked);
|
||||||
|
console.log("moving_walls:"+(<HTMLInputElement>document.getElementById("moving_walls")).checked);
|
||||||
|
console.log("sound_on:"+(<HTMLInputElement>document.getElementById("sound_on")).checked);
|
||||||
|
|
||||||
|
let soundMutedFlag = false;
|
||||||
|
if ( (<HTMLInputElement>document.getElementById("sound_off")).checked ) {
|
||||||
|
soundMutedFlag = true;
|
||||||
|
}
|
||||||
|
initAudio(soundMutedFlag);
|
||||||
|
|
||||||
|
if ( (<HTMLInputElement>document.getElementById("multi_balls")).checked ) {
|
||||||
|
matchOptions |= en.MatchOptions.multiBalls;
|
||||||
|
}
|
||||||
|
if ( (<HTMLInputElement>document.getElementById("moving_walls")).checked ) {
|
||||||
|
matchOptions |= en.MatchOptions.movingWalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("div_game_options").hidden = true;
|
||||||
|
|
||||||
|
pong = new GameArea();
|
||||||
|
gc = new GameComponentsClient(matchOptions, pong.ctx);
|
||||||
|
initWebSocket(matchOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchmaking()
|
||||||
|
{
|
||||||
|
console.log("Searching an opponent...");
|
||||||
|
gc.text1.clear();
|
||||||
|
gc.text1.pos.assign(c.w/5, c.h_mid);
|
||||||
|
gc.text1.text = "Searching...";
|
||||||
|
gc.text1.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchmakingComplete()
|
||||||
|
{
|
||||||
|
console.log("Match Found !");
|
||||||
|
gc.text1.clear();
|
||||||
|
gc.text1.pos.assign(c.w/8, c.h_mid);
|
||||||
|
gc.text1.text = "Match Found !";
|
||||||
|
gc.text1.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
gc.text1.pos.assign(c.w_mid, c.h_mid+c.h/4);
|
||||||
|
countdown(c.matchStartDelay/1000, (count: number) => {
|
||||||
|
gc.text1.clear();
|
||||||
|
gc.text1.text = `${count}`;
|
||||||
|
gc.text1.update();
|
||||||
|
}, resumeGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeGame()
|
||||||
|
{
|
||||||
|
gc.text1.text = "";
|
||||||
|
window.addEventListener('keydown', function (e) {
|
||||||
|
pong.addKey(e.key);
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', function (e) {
|
||||||
|
pong.deleteKey(e.key);
|
||||||
|
});
|
||||||
|
pong.handleInputInterval = window.setInterval(handleInput, c.handleInputIntervalMS);
|
||||||
|
// pong.handleInputInterval = window.setInterval(sendLoop, c.sendLoopIntervalMS);
|
||||||
|
pong.gameLoopInterval = window.setInterval(gameLoop, c.gameLoopIntervalMS);
|
||||||
|
pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {matchmaking, matchmakingComplete, startGame}
|
||||||
27
srcs/requirements/svelte/api_front/public/game/utils copy.ts
Normal file
27
srcs/requirements/svelte/api_front/public/game/utils copy.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
import { MovingRectangle } from "./class/Rectangle.js";
|
||||||
|
|
||||||
|
function random(min: number = 0, max: number = 1) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep (ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(n: number, min: number, max: number) : number
|
||||||
|
{
|
||||||
|
if (n < min)
|
||||||
|
n = min;
|
||||||
|
else if (n > max)
|
||||||
|
n = max;
|
||||||
|
return (n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typescript hack, unused
|
||||||
|
function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
|
||||||
|
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {random, sleep, clamp, assertMovingRectangle}
|
||||||
18
srcs/requirements/svelte/api_front/public/game/utils.ts
Normal file
18
srcs/requirements/svelte/api_front/public/game/utils.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
export * from "../shared_js/utils.js"
|
||||||
|
|
||||||
|
function countdown(count: number, callback?: (count: number) => void, endCallback?: () => void)
|
||||||
|
{
|
||||||
|
console.log("countdown ", count);
|
||||||
|
if (count > 0) {
|
||||||
|
if (callback) {
|
||||||
|
callback(count);
|
||||||
|
}
|
||||||
|
setTimeout(countdown, 1000, --count, callback, endCallback);
|
||||||
|
}
|
||||||
|
else if (endCallback) {
|
||||||
|
endCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {countdown}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
import * as c from "./constants.js";
|
||||||
|
import { MovingRectangle } from "../shared_js/class/Rectangle.js";
|
||||||
|
import { GameComponents } from "./class/GameComponents.js";
|
||||||
|
|
||||||
|
function wallsMovements(delta: number, gc: GameComponents)
|
||||||
|
{
|
||||||
|
const wallTop = <MovingRectangle>gc.wallTop;
|
||||||
|
const wallBottom = <MovingRectangle>gc.wallBottom;
|
||||||
|
if (wallTop.pos.y <= 0 || wallTop.pos.y >= c.movingWallPosMax) {
|
||||||
|
wallTop.dir.y *= -1;
|
||||||
|
}
|
||||||
|
if (wallBottom.pos.y >= c.h-c.wallSize || wallBottom.pos.y <= c.h-c.movingWallPosMax) {
|
||||||
|
wallBottom.dir.y *= -1;
|
||||||
|
}
|
||||||
|
wallTop.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
||||||
|
wallBottom.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {wallsMovements}
|
||||||
182
srcs/requirements/svelte/api_front/public/game/ws.ts
Normal file
182
srcs/requirements/svelte/api_front/public/game/ws.ts
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
|
||||||
|
import * as c from "./constants.js"
|
||||||
|
import { gc, matchOptions } from "./global.js"
|
||||||
|
import * as ev from "../shared_js/class/Event.js"
|
||||||
|
import * as en from "../shared_js/enums.js"
|
||||||
|
import { matchmaking, matchmakingComplete, startGame } from "./pong.js";
|
||||||
|
import { RacketClient } from "./class/RectangleClient.js";
|
||||||
|
import { repeatInput } from "./handleInput.js";
|
||||||
|
import { soundRoblox } from "./audio.js"
|
||||||
|
import { sleep } from "./utils.js";
|
||||||
|
import { Vector, VectorInteger } from "../shared_js/class/Vector.js";
|
||||||
|
|
||||||
|
class ClientInfo {
|
||||||
|
id = "";
|
||||||
|
side: en.PlayerSide;
|
||||||
|
racket: RacketClient;
|
||||||
|
opponent: RacketClient;
|
||||||
|
opponentNextPos: VectorInteger;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsPort = 8042;
|
||||||
|
const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong";
|
||||||
|
export let socket: WebSocket; /* TODO: A way to still use "const" not "let" ? */
|
||||||
|
export const clientInfo = new ClientInfo();
|
||||||
|
|
||||||
|
export function initWebSocket(options: en.MatchOptions)
|
||||||
|
{
|
||||||
|
socket = new WebSocket(wsUrl, "json");
|
||||||
|
socket.addEventListener("open", (event) => {
|
||||||
|
socket.send(JSON.stringify( new ev.ClientAnnounce(en.ClientRole.player, options, clientInfo.id) ));
|
||||||
|
});
|
||||||
|
// socket.addEventListener("message", logListener); // for testing purpose
|
||||||
|
socket.addEventListener("message", preMatchListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logListener(this: WebSocket, event: MessageEvent) {
|
||||||
|
console.log("%i: " + event.data, Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
function preMatchListener(this: WebSocket, event: MessageEvent)
|
||||||
|
{
|
||||||
|
const data: ev.ServerEvent = JSON.parse(event.data);
|
||||||
|
switch (data.type) {
|
||||||
|
case en.EventTypes.assignId:
|
||||||
|
clientInfo.id = (<ev.EventAssignId>data).id;
|
||||||
|
break;
|
||||||
|
case en.EventTypes.matchmakingInProgress:
|
||||||
|
matchmaking();
|
||||||
|
break;
|
||||||
|
case en.EventTypes.matchmakingComplete:
|
||||||
|
clientInfo.side = (<ev.EventMatchmakingComplete>data).side;
|
||||||
|
if (clientInfo.side === en.PlayerSide.left)
|
||||||
|
{
|
||||||
|
clientInfo.racket = gc.playerLeft;
|
||||||
|
clientInfo.opponent = gc.playerRight;
|
||||||
|
}
|
||||||
|
else if (clientInfo.side === en.PlayerSide.right)
|
||||||
|
{
|
||||||
|
clientInfo.racket = gc.playerRight;
|
||||||
|
clientInfo.opponent = gc.playerLeft;
|
||||||
|
}
|
||||||
|
clientInfo.opponentNextPos = new VectorInteger(clientInfo.opponent.pos.x, clientInfo.opponent.pos.y);
|
||||||
|
clientInfo.racket.color = "darkgreen"; // for testing purpose
|
||||||
|
socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientPlayerReady) )); // TODO: set an interval/timeout to resend until matchStart response (in case of network problem)
|
||||||
|
matchmakingComplete();
|
||||||
|
break;
|
||||||
|
case en.EventTypes.matchStart:
|
||||||
|
socket.removeEventListener("message", preMatchListener);
|
||||||
|
socket.addEventListener("message", inGameListener);
|
||||||
|
startGame();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inGameListener(event: MessageEvent)
|
||||||
|
{
|
||||||
|
const data: ev.ServerEvent = JSON.parse(event.data);
|
||||||
|
switch (data.type) {
|
||||||
|
case en.EventTypes.gameUpdate:
|
||||||
|
// setTimeout(gameUpdate, 500, data as ev.EventGameUpdate); // artificial latency for testing purpose
|
||||||
|
gameUpdate(data as ev.EventGameUpdate);
|
||||||
|
break;
|
||||||
|
case en.EventTypes.scoreUpdate:
|
||||||
|
scoreUpdate(data as ev.EventScoreUpdate);
|
||||||
|
break;
|
||||||
|
case en.EventTypes.matchEnd:
|
||||||
|
matchEnd(data as ev.EventMatchEnd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameUpdate(data: ev.EventGameUpdate)
|
||||||
|
{
|
||||||
|
console.log("gameUpdate");
|
||||||
|
|
||||||
|
if (matchOptions & en.MatchOptions.movingWalls) {
|
||||||
|
gc.wallTop.pos.y = data.wallTop.y;
|
||||||
|
gc.wallBottom.pos.y = data.wallBottom.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.ballsArr.forEach((ball, i) => {
|
||||||
|
gc.ballsArr[i].pos.assign(ball.x, ball.y);
|
||||||
|
gc.ballsArr[i].dir.assign(ball.dirX, ball.dirY);
|
||||||
|
gc.ballsArr[i].speed = ball.speed;
|
||||||
|
});
|
||||||
|
/* // Equivalent to
|
||||||
|
gc.ballsArr.forEach((ball, i) => {
|
||||||
|
ball.pos.assign(data.ballsArr[i].x, data.ballsArr[i].y);
|
||||||
|
ball.dir.assign(data.ballsArr[i].dirX, data.ballsArr[i].dirY);
|
||||||
|
ball.speed = data.ballsArr[i].speed;
|
||||||
|
}); */
|
||||||
|
|
||||||
|
const predictionPos = new VectorInteger(clientInfo.racket.pos.x, clientInfo.racket.pos.y); // debug
|
||||||
|
|
||||||
|
if (clientInfo.side === en.PlayerSide.left) {
|
||||||
|
clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerLeft.y);
|
||||||
|
}
|
||||||
|
else if (clientInfo.side === en.PlayerSide.right) {
|
||||||
|
clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerRight.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolation
|
||||||
|
clientInfo.opponent.pos.assign(clientInfo.opponentNextPos.x, clientInfo.opponentNextPos.y);
|
||||||
|
if (clientInfo.side === en.PlayerSide.left) {
|
||||||
|
clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerRight.y);
|
||||||
|
}
|
||||||
|
else if (clientInfo.side === en.PlayerSide.right) {
|
||||||
|
clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerLeft.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
clientInfo.opponent.dir = new Vector(
|
||||||
|
clientInfo.opponentNextPos.x - clientInfo.opponent.pos.x,
|
||||||
|
clientInfo.opponentNextPos.y - clientInfo.opponent.pos.y
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Math.abs(clientInfo.opponent.dir.x) + Math.abs(clientInfo.opponent.dir.y) !== 0) {
|
||||||
|
clientInfo.opponent.dir = clientInfo.opponent.dir.normalized();
|
||||||
|
}
|
||||||
|
|
||||||
|
// server reconciliation
|
||||||
|
repeatInput(data.lastInputId);
|
||||||
|
|
||||||
|
// debug
|
||||||
|
if (clientInfo.racket.pos.y > predictionPos.y + 1
|
||||||
|
|| clientInfo.racket.pos.y < predictionPos.y - 1)
|
||||||
|
{
|
||||||
|
console.log(
|
||||||
|
`Reconciliation error:
|
||||||
|
server y: ${data.playerLeft.y}
|
||||||
|
reconciliation y: ${clientInfo.racket.pos.y}
|
||||||
|
prediction y: ${predictionPos.y}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoreUpdate(data: ev.EventScoreUpdate)
|
||||||
|
{
|
||||||
|
// console.log("scoreUpdate");
|
||||||
|
if (clientInfo.side === en.PlayerSide.left && data.scoreRight > gc.scoreRight.value) {
|
||||||
|
soundRoblox.play();
|
||||||
|
}
|
||||||
|
else if (clientInfo.side === en.PlayerSide.right && data.scoreLeft > gc.scoreLeft.value) {
|
||||||
|
soundRoblox.play();
|
||||||
|
}
|
||||||
|
gc.scoreLeft.value = data.scoreLeft;
|
||||||
|
gc.scoreRight.value = data.scoreRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchEnd(data: ev.EventMatchEnd)
|
||||||
|
{
|
||||||
|
if (data.winner === clientInfo.side) {
|
||||||
|
gc.text1.pos.assign(c.w*0.415, c.h_mid);
|
||||||
|
gc.text1.text = "WIN";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gc.text1.pos.assign(c.w*0.383, c.h_mid);
|
||||||
|
gc.text1.text = "LOSE";
|
||||||
|
}
|
||||||
|
// matchEnded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export let matchEnded = false;
|
||||||
@@ -12,6 +12,11 @@ body {
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
/* tmp? */
|
/* tmp? */
|
||||||
background: bisque;
|
background: bisque;
|
||||||
|
|
||||||
|
/* overflow-x: hidden; */
|
||||||
|
/* This seems to have fixed my pages that are too long */
|
||||||
|
/* but now i can't scroll anywhere ... */
|
||||||
|
/* overflow-y: hidden; */
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
BIN
srcs/requirements/svelte/api_front/public/sound/pong/0.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/0.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/1.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/1.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/10.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/10.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/11.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/11.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/12.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/12.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/13.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/13.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/14.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/14.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/15.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/15.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/16.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/16.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/17.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/17.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/18.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/18.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/19.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/19.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/2.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/2.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/20.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/20.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/21.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/21.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/22.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/22.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/23.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/23.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/24.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/24.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/25.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/25.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/26.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/26.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/27.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/27.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/28.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/28.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/29.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/29.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/3.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/3.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/30.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/30.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/31.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/31.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/32.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/32.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/4.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/4.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/5.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/5.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/6.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/6.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/7.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/7.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/8.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/8.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/pong/9.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/pong/9.ogg
Normal file
Binary file not shown.
BIN
srcs/requirements/svelte/api_front/public/sound/roblox-oof.ogg
Normal file
BIN
srcs/requirements/svelte/api_front/public/sound/roblox-oof.ogg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user