diff --git a/src/draw/coordinate.entity.ts b/src/draw/coordinate.entity.ts index e9a1b6ff35544fa599220367638fd3ada00fa865..1448b994f7ea26a60ba946597569c9425c1cd789 100644 --- a/src/draw/coordinate.entity.ts +++ b/src/draw/coordinate.entity.ts @@ -1,11 +1,4 @@ -import { - Entity, - Column, - PrimaryGeneratedColumn, - ManyToOne, - CreateDateColumn, - OneToMany, -} from 'typeorm'; +import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; import { GameEntity } from '../game/game.entity'; import { FactionEntity } from '../faction/faction.entity'; @@ -20,7 +13,7 @@ export class MapDrawingEntity { @Column({ type: 'bool', nullable: true }) drawingIsActive: boolean; @Column({ type: 'json', nullable: true }) data: JSON; - // When Faction or game that has the drawing in question is deleted from + // When Faction or game that has the drawing in question is deleted from // the database, the drawing is also deleted @ManyToOne(type => FactionEntity, faction => faction.mapDrawings, { onDelete: 'CASCADE', diff --git a/src/draw/draw.service.ts b/src/draw/draw.service.ts index 5f7566cda9d127173cbba11a8f2f92727a38f7dc..e1c416c70504f0d9b6df4814da79d1aed85a373f 100644 --- a/src/draw/draw.service.ts +++ b/src/draw/draw.service.ts @@ -30,13 +30,7 @@ export class DrawService { drawing.faction = gameperson.faction; const mapDrawing = await this.mapDrawingRepository.insert(drawing); // create a history entity and insert it - const history = await this.mapDrawHistoryRepository.create({ - data: data.data, - drawingIsActive: data.drawingIsActive, - mapdrawing: mapDrawing.identifiers[0]['mapDrawingId'], - timestamp: Date.now(), - }); - await this.mapDrawHistoryRepository.insert(history); + await this.createHistory(data, gameperson, mapDrawing); return mapDrawing.identifiers; } // get ref from db @@ -46,13 +40,7 @@ export class DrawService { }); if (await draw.ownershipCheck(gameperson.faction, gameperson.role)) { // else update the existing instance - const history = await this.mapDrawHistoryRepository.create({ - data: data.data, - drawingIsActive: data.drawingIsActive, - mapdrawing: data.mapDrawingId, - timestamp: Date.now(), - }); - await this.mapDrawHistoryRepository.insert(history); + await this.createHistory(data, gameperson); return await this.mapDrawingRepository.save(drawing); } @@ -62,6 +50,22 @@ export class DrawService { ); } + // used to create mapDrawing history entity entry + private async createHistory(data, gameperson, mapDrawing?) { + // create a history entity and insert it + const history = await this.mapDrawHistoryRepository.create({ + data: data.data, + drawingIsActive: data.drawingIsActive, + mapdrawing: + data.mapDrawingId || mapDrawing.identifiers[0]['mapDrawingId'], + timestamp: Date.now(), + }); + history.data['faction'] = gameperson.faction + ? gameperson.faction.factionName + : 'admin'; + await this.mapDrawHistoryRepository.insert(history); + } + // draw map based on game and gameperson faction async drawMap(gameperson, gameId) { // return all active drawings if admin diff --git a/src/game/game.controller.ts b/src/game/game.controller.ts index 27e3248368c7fa54c76db0cc114e66afdc445118..092f484ffe1576be36a4a6e74d27bb28fbf0e4dc 100644 --- a/src/game/game.controller.ts +++ b/src/game/game.controller.ts @@ -86,6 +86,11 @@ export class GameController { return this.gameservice.listFactions(id); } + @Get('flag-events/:id') + async returnFlagboxInfo(@Param('id') id: GameEntity) { + return this.gameservice.returnObjectivePointInfo(id); + } + @Get('flag/:id') async flagboxQuery(@Param('id') id: string) { return this.gameservice.flagboxQuery(id); diff --git a/src/game/game.dto.ts b/src/game/game.dto.ts index abf1742d2af2bd9e10eed82b264217af11f96d8c..1809314fb6cfcab6b7618f5b42add114505e157a 100644 --- a/src/game/game.dto.ts +++ b/src/game/game.dto.ts @@ -86,16 +86,18 @@ export class FlagboxDTO { objectivePointId: string; @IsString() @IsNotEmpty() - @Length(7) + @Length(7, 7) objectivePointDescription: string; @IsNumber() objectivePointMultiplier: number; + @IsOptional() + data: JSON; } export class FlagboxEventDTO { @IsString() @IsNotEmpty() - @Length(7) + @Length(7, 7) node_id: string; @IsNumber() @Min(0) @@ -109,6 +111,6 @@ export class FlagboxEventDTO { @Min(0) @Max(3) capture: number; // which faction is capturing, same logic as in owner with numbers - oP_HistoryTimestamp?: string; + oP_HistoryTimestamp?: number; objective_point?: ObjectivePointEntity; } diff --git a/src/game/game.entity.ts b/src/game/game.entity.ts index 22e171b67e655917d108a66695dae59f32304e4e..ccc6ea24bbeef81f6a899e862406e1cc6a43d174 100644 --- a/src/game/game.entity.ts +++ b/src/game/game.entity.ts @@ -83,25 +83,30 @@ export class ObjectivePointEntity { @PrimaryGeneratedColumn('uuid') objectivePointId: string; @Column({ type: 'text' }) objectivePointDescription: string; @Column({ type: 'float' }) objectivePointMultiplier: number; + @Column({ type: 'json' }) data: JSON; - // If the MapDrawing or Game where the ObjectivePoint was in is deleted, the ObjectivePoint is also deleted - @ManyToOne(type => MapDrawingEntity, coordinate => coordinate.data, { - onDelete: 'CASCADE', - }) - coordinate: MapDrawingEntity; + // If the Game where the ObjectivePoint was in is deleted, the ObjectivePoint is also deleted @ManyToOne(type => GameEntity, game => game.objective_points, { onDelete: 'CASCADE', }) game: GameEntity; + @OneToMany( + () => ObjectivePoint_HistoryEntity, + history => history.objective_point, + { + onDelete: 'NO ACTION', + }, + ) + history: ObjectivePoint_HistoryEntity[]; } @Entity('ObjectivePoint_History') export class ObjectivePoint_HistoryEntity { @PrimaryGeneratedColumn('uuid') oP_HistoryId: string; - @Column({ type: 'timestamp' }) oP_HistoryTimestamp: Timestamp; + @Column({ type: 'float' }) oP_HistoryTimestamp: number; @Column('float') action: number; - // If the owner Faction, capturer Faction or ObjectivePoint, that has, is trying to have or is the point where + // If the owner Faction, capturer Faction or ObjectivePoint, that has, is trying to have or is the point where // ObjectivePointHistory points to is deleted, the ObjectivePointHistory is also deleted @ManyToOne(type => FactionEntity, factionEntity => factionEntity.factionId, { onDelete: 'CASCADE', diff --git a/src/game/game.module.ts b/src/game/game.module.ts index 8fa0638e4a5ef0a0a72d04a416113c15278fc242..6fffbb0132fd6085fa82adfa93d9bec083559cb2 100644 --- a/src/game/game.module.ts +++ b/src/game/game.module.ts @@ -15,7 +15,7 @@ import { FactionEntity } from '../faction/faction.entity'; import { NotificationModule } from '../notifications/notifications.module'; import { TickService } from './tick.service'; import { ScoreService } from '../score/score.service'; -import { ScoreEntity } from 'src/score/score.entity'; +import { ScoreEntity } from '../score/score.entity'; ///////////////////////////////////////////////////////////////////// /// Game /// diff --git a/src/game/game.service.ts b/src/game/game.service.ts index 86bc61ef43c3255b83914cc08e2f1dcfae61bf90..4863cf067d2133cc428da74bb80b1ed6448bff41 100644 --- a/src/game/game.service.ts +++ b/src/game/game.service.ts @@ -83,6 +83,14 @@ export class GameService { HttpStatus.BAD_REQUEST, ); } + // check that there's location data for each added objective point + gameData.objective_points.forEach(obj => { + if (!obj['data']) + throw new HttpException( + 'Objective Point error. Add location for each Objective Point.', + HttpStatus.BAD_REQUEST, + ); + }); // get factions that have been added previously let factions = await this.factionRepository.find({ game: id }); @@ -121,8 +129,10 @@ export class GameService { ({ objectivePointId }) => objectivePointId, ); flagboxes.map(async flagbox => { - if (!flagboxIds.includes(flagbox.objectivePointDescription)) { - await this.objectivePointRepository.delete(flagbox); + if (!flagboxIds.includes(flagbox.objectivePointId)) { + await this.objectivePointRepository.delete({ + objectivePointId: flagbox.objectivePointId, + }); } }); gameData.objective_points.map(async flagbox => { @@ -131,13 +141,18 @@ export class GameService { game: gameId, }); await this.objectivePointRepository.save(newFlagbox); + // create base status for flagbox + this.flagboxEvent(gameId, { + node_id: flagbox.objectivePointDescription, + owner: 0, + action: 0, + capture: 0, + }); }); } else { await this.objectivePointRepository.delete({ game: id }); } - // TO DO: ADD FLAGBOX LOCATION TO MAPDRAWING ENTITY - return { message: 'Game updated', }; @@ -209,7 +224,7 @@ export class GameService { } // returns information about a game identified by id - async returnGameInfo(id: string) { + async returnGameInfo(id) { const game = await this.gameRepository.findOne({ where: { id: id }, relations: ['factions', 'objective_points'], @@ -221,6 +236,52 @@ export class GameService { return game; } + // returns information about game's flagboxes and their most recent event + async returnObjectivePointInfo(gameId) { + const info = await this.objectivePointRepository.find({ + where: { game: gameId }, + relations: ['history', 'history.owner', 'history.capture'], + }); + let response = await Promise.all( + info.map(async obj => { + let history = obj.history.pop(); + return await { + objectivePointId: obj.objectivePointId, + objectivePointDescription: obj.objectivePointDescription, + objectivePointMultiplier: obj.objectivePointMultiplier, + action: { + status: history.action, + message: { + 0: 'No capture ongoing', + 1: `Captured by ${ + history.owner ? history.owner.factionName : 'neutral' + }`, + 2: `Being captured by ${ + history.capture ? history.capture.factionName : 'neutral' + }`, + }[history.action], + }, + owner: await this.infoHelper(history.owner), + capture: await this.infoHelper(history.capture), + data: obj.data, + }; + }), + ); + return response; + } + + private async infoHelper(obj) { + return (await obj) + ? { + factionName: obj.factionName, + colour: obj.colour, + } + : { + factionName: 'neutral', + colour: '#000000', + }; + } + // returns flagbox settings async flagboxQuery(gameId) { const game = await this.gameRepository.findOne({ id: gameId }); @@ -235,7 +296,7 @@ export class GameService { const objectiveRef = await this.objectivePointRepository.findOne({ where: { objectivePointDescription: data.node_id, game: gameId }, }); - data.oP_HistoryTimestamp = new Date(Date.now()).toLocaleString(); + data.oP_HistoryTimestamp = Date.now(); const eventUpdate = await this.objectivePoint_HistoryRepository.create({ oP_HistoryTimestamp: data.oP_HistoryTimestamp, action: data.action, diff --git a/src/replay/replay.module.ts b/src/replay/replay.module.ts index d4d5dc095bf95a9fd2316870d4e3d57a17af321f..6df113387f4dd010e31128a39592062517a5872f 100644 --- a/src/replay/replay.module.ts +++ b/src/replay/replay.module.ts @@ -22,6 +22,8 @@ import { import { ScoreService } from '../score/score.service'; import { ScoreEntity } from '../score/score.entity'; import { NotificationModule } from 'src/notifications/notifications.module'; +import { GameService } from 'src/game/game.service'; +import { TickService } from 'src/game/tick.service'; ///////////////////////////////////////////////////////////////////// /// Replay /// @@ -51,6 +53,8 @@ import { NotificationModule } from 'src/notifications/notifications.module'; FactionService, TrackingService, ScoreService, + GameService, + TickService, ], }) export class ReplayModule {} diff --git a/src/replay/replay.service.ts b/src/replay/replay.service.ts index bb06399aa59082ce46cd98b49b0ca21412f912eb..330029b4ea2846e9cd5325703520a372f5f93101 100644 --- a/src/replay/replay.service.ts +++ b/src/replay/replay.service.ts @@ -1,10 +1,14 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository, In } from 'typeorm'; +import { Repository } from 'typeorm'; import * as jwt from 'jsonwebtoken'; import { FactionEntity } from '../faction/faction.entity'; -import { GameEntity } from '../game/game.entity'; +import { + GameEntity, + ObjectivePointEntity, + ObjectivePoint_HistoryEntity, +} from '../game/game.entity'; import { TrackingService } from '../tracking/tracking.service'; import { UserService } from '../user/user.service'; import { FactionService } from '../faction/faction.service'; @@ -13,8 +17,9 @@ import { MapDrawingEntity, MapDrawingHistoryEntity, } from '../draw/coordinate.entity'; -import { ScoreService } from 'src/score/score.service'; -import { ScoreEntity } from 'src/score/score.entity'; +import { ScoreService } from '../score/score.service'; +import { ScoreEntity } from '../score/score.entity'; +import { GameService } from '../game/game.service'; @Injectable() export class ReplayService { @@ -31,35 +36,15 @@ export class ReplayService { private mapHistoryRepository: Repository<MapDrawingHistoryEntity>, @InjectRepository(ScoreEntity) private scoreRepository: Repository<ScoreEntity>, + @InjectRepository(ObjectivePointEntity) + private objectivepointRepository: Repository<ObjectivePointEntity>, private trackingService: TrackingService, private userService: UserService, private factionService: FactionService, private scoreService: ScoreService, + private gameService: GameService, ) {} - /* async replayData(gameId) { - let mapDrawingIds = await this.mapdrawingRepository.find({ - where: { gameId: gameId }, - select: ['mapDrawingId'], - // replay data for Factions - async replayData(gameId) { - const replay = await this.factionRepository.find({ - where: { game: gameId }, - relations: ['mapDrawings', 'scores', 'trackdata'], - }); - let drawings = []; - await Promise.all( - mapDrawingIds.map(async mapId => { - drawings.push( - await this.mapHistoryRepository.find({ - mapdrawing: mapId.mapDrawingId, - }), - ); - }), - ); - return drawings; - } */ - async replayData(gameId) { // // this block returns game's initial location @@ -139,6 +124,10 @@ export class ReplayService { }); }), ); + // + // this function returns all flagbox-events from the game + // + let objectivepoints = await this.returnObjectivePointInfo(gameId); return { location: gamelocation, @@ -146,13 +135,73 @@ export class ReplayService { factions: currentFactions, scores: currentScore, drawings: drawData, + objectivepoints: objectivepoints, }; } + + // returns information about game's flagboxes and all of their events + async returnObjectivePointInfo(gameId) { + const info = await this.objectivepointRepository.find({ + where: { game: gameId }, + relations: ['history', 'history.owner', 'history.capture'], + }); + let response = await Promise.all( + info.map(async obj => { + return await { + objectivePointId: obj.objectivePointId, + objectivePointDescription: obj.objectivePointDescription, + objectivePointMultiplier: obj.objectivePointMultiplier, + data: obj.data, + history: await this.parseHistory(obj.history), + }; + }), + ); + return response; + } + + // loops all events in history array and returns an array of objects formatted for replay + private async parseHistory(history: ObjectivePoint_HistoryEntity[]) { + return await Promise.all( + history.map(async event => { + return { + timestamp: event.oP_HistoryTimestamp, + action: { + status: event.action, + message: { + 0: 'No capture ongoing', + 1: `Captured by ${ + event.owner ? event.owner.factionName : 'neutral' + }`, + 2: `Being captured by ${ + event.capture ? event.capture.factionName : 'neutral' + }`, + }[event.action], + }, + owner: await this.infoHelper(event.owner), + capture: await this.infoHelper(event.capture), + }; + }), + ); + } + + // small helper function that formats data for replay + private async infoHelper(obj) { + return (await obj) + ? { + factionName: obj.factionName, + colour: obj.colour, + } + : { + factionName: 'neutral', + colour: '#000000', + }; + } // generate mockdata for a 3 day game // create x amount of players // assign them to two separate factions // assign them to three separate groups // insert x amount of locations for each players around some lat lng area + // insert x amont of flagbox events // insert x amount of score ticks for score mockdata // use the game's initial geojson to draw game area // @@ -165,6 +214,9 @@ export class ReplayService { // set the LAT and LNG for initial location const LAT = 62.24147; const LNG = 25.72088; + // set the x amount of flagbox events + // not used at the moment + const FLAGBOX_EVENTS = 8; // set the score tick amount // not used at the moment const SCORE_TICKS = 10; @@ -188,6 +240,10 @@ export class ReplayService { await game.factions.forEach(async faction => { groups.push(await this.factionService.showGroups(faction.factionId)); }); + // get all objective point refs + const objectivepoints = await this.objectivepointRepository.find({ + game: gameId, + }); // create x amount of users for the mock game with random username for (let i = 0; i < USER_AMOUNT; i++) { let res = await this.userService.register({ @@ -227,6 +283,10 @@ export class ReplayService { let x = 1; // score ticks with players to sync them await this.scoreService.scoreTick(gameId); + // flagbox events with players to sync them + // use helper function to generate a random event + let event = await this.createEvent(objectivepoints); + await this.gameService.flagboxEvent(gameId, event); // add location entry for each gameperson await Promise.all( gamepersons.map(async gameperson => { @@ -252,4 +312,18 @@ export class ReplayService { message: 'all done', }; } + + // creates randomized events for randomly chosen flagbox + // may result in impossible scenarios where owner is capturing their own flagbox + // use only for testing flagbox replay functionalities + private async createEvent(objectivepoints: ObjectivePointEntity[]) { + let point = + objectivepoints[Math.floor(Math.random() * objectivepoints.length)]; + return await { + node_id: point.objectivePointDescription, + owner: Math.round(Math.random()) + 1, + action: Math.round(Math.random()) ? 2 : 0, + capture: Math.round(Math.random()) + 1, + }; + } } diff --git a/src/tracking/tracking.module.ts b/src/tracking/tracking.module.ts index b2a731217fadbda0dca4be40af271b711e604482..98079e9576122273127ce44007bea3fe66276b1d 100644 --- a/src/tracking/tracking.module.ts +++ b/src/tracking/tracking.module.ts @@ -6,6 +6,7 @@ import { TrackingService } from './tracking.service'; import { TrackingEntity } from './tracking.entity'; import { Game_PersonEntity } from '../game/game.entity'; import { PersonEntity } from '../user/user.entity'; +import { FactionEntity } from '../faction/faction.entity'; ///////////////////////////////////////////////////////////////////// /// Tracking /// @@ -13,7 +14,12 @@ import { PersonEntity } from '../user/user.entity'; ///////////////////////////////////////////////////////////////////// @Module({ imports: [ - TypeOrmModule.forFeature([TrackingEntity, Game_PersonEntity, PersonEntity]), + TypeOrmModule.forFeature([ + TrackingEntity, + Game_PersonEntity, + PersonEntity, + FactionEntity, + ]), ], controllers: [TrackingController], providers: [TrackingService], diff --git a/src/tracking/tracking.service.ts b/src/tracking/tracking.service.ts index b366784ec03f8c4f67e0ec99bbc563a292733d6f..c40d5f78cda46a69127463431a2abfb6032a3793 100644 --- a/src/tracking/tracking.service.ts +++ b/src/tracking/tracking.service.ts @@ -5,6 +5,7 @@ import { Repository } from 'typeorm'; import { Game_PersonEntity } from '../game/game.entity'; import { TrackingEntity } from './tracking.entity'; import { GeoDTO } from './geo.dto'; +import { FactionEntity } from 'src/faction/faction.entity'; @Injectable() export class TrackingService { @@ -13,6 +14,8 @@ export class TrackingService { private trackingrepository: Repository<TrackingEntity>, @InjectRepository(Game_PersonEntity) private gamepersonrepository: Repository<Game_PersonEntity>, + @InjectRepository(FactionEntity) + private factionRepository: Repository<FactionEntity>, ) {} private icons = { @@ -59,34 +62,45 @@ export class TrackingService { // get player data while game is running async getPlayers(gameperson, gameId) { - let playerdata; + let playerdata = []; // get playerdata if (gameperson.faction) { - playerdata = await this.trackingrepository.find({ - where: { faction: gameperson.faction }, - relations: ['faction', 'gamepersonId'], - }); + // create an array of the response as frontend maps the response + // to create different clusters for factions + playerdata.push( + await this.trackingrepository.find({ + where: { faction: gameperson.faction }, + relations: ['faction', 'gamepersonId'], + }), + ); } else { - playerdata = await this.trackingrepository.find({ - where: { game: gameId }, - relations: ['faction', 'gamepersonId'], - }); + let factions = await this.factionRepository.find({ game: gameId }); + playerdata = await Promise.all( + factions.map(async faction => { + return await this.trackingrepository.find({ + where: { faction: faction.factionId }, + relations: ['faction', 'gamepersonId'], + }); + }), + ); } - // parse data const currentdata = await Promise.all( - playerdata.map(async player => { - return { - gamepersonId: player['gamepersonId']['gamepersonId'], - gamepersonRole: player['gamepersonId']['role'], - factionId: player['faction']['factionId'], - factionColour: player['faction']['colour'], - icon: player['icon'], - coordinates: player['data'].pop(), - }; + await playerdata.map(async faction => { + return await Promise.all( + faction.map(async player => { + return await { + gamepersonId: player['gamepersonId']['gamepersonId'], + gamepersonRole: player['gamepersonId']['role'], + factionId: player['faction']['factionId'], + factionColour: player['faction']['colour'], + icon: player['icon'], + coordinates: player['data'].pop(), + }; + }), + ); }), ); - return currentdata; }