diff --git a/README.md b/README.md index 9ae3311..49b76af 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,32 @@ _MEAN Application_ ## Installation -## Development +### Setup mongoDB + +https://docs.mongodb.com/manual/administration/install-community/ + +### Setup node and npm + + sudo apt-get install npm nodejs-legacy + +update to latest npm version + + sudo npm install -g npm@latest + +update node to latest version + + sudo npm install -g n@latest + n latest + +check versions + + npm -v + node -v + +## Development and Execution + +### First run in dev mode + +### Run in Production ## License Information diff --git a/api/config/api-url.js b/api/config/api-url.js index 87a179e..e8041c8 100644 --- a/api/config/api-url.js +++ b/api/config/api-url.js @@ -8,6 +8,7 @@ module.exports = { cmdCreateSig: rootRoute + '/cmd/createSignature', command: rootRoute + '/cmd', decorations: rootRoute + '/decorations', + logs: rootRoute + '/logs', overview: rootRoute + '/overview', players: rootRoute + '/players', ranks: rootRoute + '/ranks', diff --git a/api/cron-job/cron.js b/api/cron-job/cron.js index b9c1790..a0ca8b3 100644 --- a/api/cron-job/cron.js +++ b/api/cron-job/cron.js @@ -4,7 +4,7 @@ const cron = require('cron'); const async = require('async'); const {exec} = require('child_process'); const UserModel = require('../models/user'); -const signatureTool = require('../signature-tool/signature-tool'); +const signatureTool = require('../tools/signature-tool'); const createAllSignatures = () => { console.log('\x1b[35m%s\x1b[0m', new Date().toLocaleString() diff --git a/api/middleware/util.js b/api/middleware/util.js deleted file mode 100644 index 1ab04d2..0000000 --- a/api/middleware/util.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; - -const sortCollectionBy = (collection, key) => { - collection.sort((a, b) => { - a = a[key].toLowerCase(); - b = b[key].toLowerCase(); - if (a < b) return -1; - if (a > b) return 1; - return 0; - }); - return collection; -}; - -exports.sortCollection = sortCollectionBy; diff --git a/api/models/logs/budget.js b/api/models/logs/budget.js new file mode 100644 index 0000000..46f93b1 --- /dev/null +++ b/api/models/logs/budget.js @@ -0,0 +1,39 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LogBudgetSchema = new Schema({ + war: { + type: mongoose.Schema.Types.ObjectId, + ref: 'War', + required: true + }, + time: { + type: Date, + required: true + }, + fraction: { + type: String, + enum: ['BLUFOR', 'OPFOR'], + required: true + }, + oldBudget: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + required: true + }, + newBudget: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + required: true + } +}, { + collection: 'logBudget' +}); +// optional more indices +LogBudgetSchema.index({war: 1}); + +module.exports = mongoose.model('LogBudget', LogBudgetSchema); diff --git a/api/models/logs/flag.js b/api/models/logs/flag.js new file mode 100644 index 0000000..13a0eea --- /dev/null +++ b/api/models/logs/flag.js @@ -0,0 +1,35 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LogFlagSchema = new Schema({ + war: { + type: mongoose.Schema.Types.ObjectId, + ref: 'War', + required: true + }, + time: { + type: Date, + required: true + }, + player: { + type: String, + required: true + }, + flagFraction: { + type: String, + enum: ['BLUFOR', 'OPFOR'], + required: true + }, + capture: { + type: Boolean, + required: true + } +}, { + collection: 'logFlag' +}); +// optional more indices +LogFlagSchema.index({war: 1, player: 1}); + +module.exports = mongoose.model('LogFlag', LogFlagSchema); diff --git a/api/models/logs/kill.js b/api/models/logs/kill.js new file mode 100644 index 0000000..b48761e --- /dev/null +++ b/api/models/logs/kill.js @@ -0,0 +1,38 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LogKillSchema = new Schema({ + war: { + type: mongoose.Schema.Types.ObjectId, + ref: 'War', + required: true + }, + time: { + type: Date, + required: true + }, + shooter: { + type: String + }, + target: { + type: String, + required: true + }, + friendlyFire: { + type: Boolean, + required: true + }, + fraction: { + type: String, + enum: ['BLUFOR', 'OPFOR', 'NONE'], + required: true + } +}, { + collection: 'logKill' +}); +// optional more indices +LogKillSchema.index({war: 1, shooter: 1, target: 1}); + +module.exports = mongoose.model('LogKill', LogKillSchema); diff --git a/api/models/logs/points.js b/api/models/logs/points.js new file mode 100644 index 0000000..28aa39b --- /dev/null +++ b/api/models/logs/points.js @@ -0,0 +1,39 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LogKillSchema = new Schema({ + war: { + type: mongoose.Schema.Types.ObjectId, + ref: 'War', + required: true + }, + time: { + type: Date, + required: true + }, + ptBlufor: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + required: true + }, + ptOpfor: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + required: true + }, + fraction: { + type: String, + enum: ['BLUFOR', 'OPFOR', 'NONE'], + required: true + } +}, { + collection: 'logPoints' +}); +// optional more indices +LogKillSchema.index({war: 1, shooter: 1, target: 1}); + +module.exports = mongoose.model('LogPoints', LogKillSchema); diff --git a/api/models/logs/respawn.js b/api/models/logs/respawn.js new file mode 100644 index 0000000..a79486a --- /dev/null +++ b/api/models/logs/respawn.js @@ -0,0 +1,26 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LogRespawnSchema = new Schema({ + war: { + type: mongoose.Schema.Types.ObjectId, + ref: 'War', + required: true + }, + time: { + type: Date, + required: true + }, + player: { + type: String, + required: true + } +}, { + collection: 'logRespawn' +}); +// optional more indices +LogRespawnSchema.index({war: 1, player: 1}); + +module.exports = mongoose.model('LogRespawn', LogRespawnSchema); diff --git a/api/models/logs/revive.js b/api/models/logs/revive.js new file mode 100644 index 0000000..c5c9b10 --- /dev/null +++ b/api/models/logs/revive.js @@ -0,0 +1,39 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LogReviveSchema = new Schema({ + war: { + type: mongoose.Schema.Types.ObjectId, + ref: 'War', + required: true + }, + time: { + type: Date, + required: true + }, + medic: { + type: String, + required: true + }, + patient: { + type: String, + required: true + }, + stabilized: { + type: Boolean, + required: true + }, + fraction: { + type: String, + enum: ['BLUFOR', 'OPFOR'], + required: true + } +}, { + collection: 'logRevive' +}); +// optional more indices +LogReviveSchema.index({war: 1, medic: 1}); + +module.exports = mongoose.model('LogRevive', LogReviveSchema); diff --git a/api/models/logs/transport.js b/api/models/logs/transport.js new file mode 100644 index 0000000..09accf0 --- /dev/null +++ b/api/models/logs/transport.js @@ -0,0 +1,41 @@ +"use strict"; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LogTransportSchema = new Schema({ + war: { + type: mongoose.Schema.Types.ObjectId, + ref: 'War', + required: true + }, + time: { + type: Date, + required: true + }, + driver: { + type: String, + required: true + }, + passenger: { + type: String, + required: true + }, + distance: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + required: true + }, + fraction: { + type: String, + enum: ['BLUFOR', 'OPFOR'], + required: true + } +}, { + collection: 'logTransport' +}); +// optional more indices +LogTransportSchema.index({war: 1}); + +module.exports = mongoose.model('LogTransport', LogTransportSchema); diff --git a/api/models/player.js b/api/models/player.js index fcf4b07..8086056 100644 --- a/api/models/player.js +++ b/api/models/player.js @@ -53,6 +53,11 @@ const PlayerSchema = new Schema({ get: v => Math.round(v), set: v => Math.round(v), required: true + }, + sort: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v) } }, { collection: 'player', diff --git a/api/models/war.js b/api/models/war.js index b89bcae..7d4cf9c 100644 --- a/api/models/war.js +++ b/api/models/war.js @@ -10,19 +10,20 @@ const WarSchema = new Schema({ }, date: { type: Date, + }, + endDate : { + type: Date, required: true }, ptBlufor: { type: Number, get: v => Math.round(v), set: v => Math.round(v), - required: true }, ptOpfor: { type: Number, get: v => Math.round(v), set: v => Math.round(v), - required: true }, playersBlufor: { type: Number, @@ -38,7 +39,32 @@ const WarSchema = new Schema({ }, campaign: { type: mongoose.Schema.Types.ObjectId, - ref: 'Campaign' + ref: 'Campaign', + required: true + }, + budgetBlufor: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + default: 0 + }, + budgetOpfor: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + default: 0 + }, + endBudgetBlufor: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + default: 0 + }, + endBudgetOpfor: { + type: Number, + get: v => Math.round(v), + set: v => Math.round(v), + default: 0 } }, { collection: 'war', diff --git a/api/package.json b/api/package.json index b0a7cd2..15b224b 100644 --- a/api/package.json +++ b/api/package.json @@ -3,6 +3,7 @@ "licence": "CC BY-SA 4.0", "description": "RESTful API for Operation Pandora Trigger Command Center, includes signature generator", "main": "server.js", + "author": "Florian Hartwich ", "private": true, "scripts": { "start": "NODE_ENV=production node server.js", diff --git a/api/routes/command.js b/api/routes/command.js index 5759d07..4439bbe 100644 --- a/api/routes/command.js +++ b/api/routes/command.js @@ -9,7 +9,7 @@ const codes = require('./http-codes'); const routerHandling = require('../middleware/router-handling'); const createAllSignatures = require('../cron-job/cron').createAllSignatures; -const createSignature = require('../signature-tool/signature-tool'); +const createSignature = require('../tools/signature-tool'); const command = express.Router(); diff --git a/api/routes/logs.js b/api/routes/logs.js new file mode 100644 index 0000000..4891d1b --- /dev/null +++ b/api/routes/logs.js @@ -0,0 +1,156 @@ +"use strict"; + +// modules +const express = require('express'); +const async = require('async'); +const logger = require('debug')('cc:logs'); + +const routerHandling = require('../middleware/router-handling'); +const decimalToTimeString = require('../tools/util').decimalToTimeString; + +// Mongoose Model using mongoDB +const LogBudgetModel = require('../models/logs/budget'); +const LogRespawnModel = require('../models/logs/respawn'); +const LogReviveModel = require('../models/logs/revive'); +const LogKillModel = require('../models/logs/kill'); +const LogTransportModel = require('../models/logs/transport'); +const LogFlagModel = require('../models/logs/flag'); +const LogPointsModel = require('../models/logs/points'); + +const logsRouter = express.Router(); + +function processLogRequest(model, filter, res, next) { + model.find(filter, {}, {sort: {time: 1}}, (err, log) => { + if (err) return next(err); + if (!log || log.length === 0) { + const err = new Error('No logs found'); + err.status = require('./http-codes').notfound; + return next(err) + } + res.locals.items = log; + next(); + }) +} + +// routes ********************** +logsRouter.route('/:warId') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + const sort = {sort: {time: 1}}; + + const pointsObjects = LogPointsModel.find(filter, {}, sort); + const budgetObjects = LogBudgetModel.find(filter, {}, sort); + const respawnObjects = LogRespawnModel.find(filter, {}, sort); + const reviveObjects = LogReviveModel.find(filter, {}, sort); + const killObjects = LogKillModel.find(filter, {}, sort); + const transportObjects = LogTransportModel.find(filter, {}, sort); + const flagObjects = LogFlagModel.find(filter, {}, sort); + const resources = { + points: pointsObjects.exec.bind(pointsObjects), + budget: budgetObjects.exec.bind(budgetObjects), + respawn: respawnObjects.exec.bind(respawnObjects), + revive: reviveObjects.exec.bind(reviveObjects), + kill: killObjects.exec.bind(killObjects), + transport: transportObjects.exec.bind(transportObjects), + flag: flagObjects.exec.bind(flagObjects) + }; + + async.parallel(resources, function (error, results){ + if (error) { + res.status(500).send(error); + return; + } + res.locals.items = results; + next(); + }); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.route('/:warId/budget') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + if (req.query.fraction) filter['fraction'] = req.query.fraction; + processLogRequest(LogBudgetModel, filter, res, next); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.route('/:warId/respawn') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + if (req.query.player) filter['player'] = req.query.player; + processLogRequest(LogRespawnModel, filter, res, next); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.route('/:warId/revive') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + if (req.query.medic) filter['medic'] = req.query.medic; + if (req.query.patient) filter['patient'] = req.query.patient; + if (req.query.stabilized) filter['stabilized'] = true; + if (req.query.revive) filter['stabilized'] = false; + if (req.query.fraction) filter['fraction'] = req.query.fraction; + processLogRequest(LogReviveModel, filter, res, next); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.route('/:warId/kills') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + if (req.query.shooter) filter['shooter'] = req.query.shooter; + if (req.query.target) filter['target'] = req.query.target; + if (req.query.friendlyFire) filter['friendlyFire'] = true; + if (req.query.noFriendlyFire) filter['friendlyFire'] = false; + if (req.query.fraction) filter['fraction'] = req.query.fraction; + processLogRequest(LogKillModel, filter, res, next); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.route('/:warId/transport') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + if (req.query.driver) filter['driver'] = req.query.driver; + if (req.query.passenger) filter['passenger'] = req.query.passenger; + if (req.query.fraction) filter['fraction'] = req.query.fraction; + processLogRequest(LogTransportModel, filter, res, next); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.route('/:warId/flag') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + if (req.query.player) filter['player'] = req.query.player; + if (req.query.capture) filter['capture'] = true; + if (req.query.defend) filter['capture'] = false; + if (req.query.fraction) filter['fraction'] = req.query.fraction; + processLogRequest(LogFlagModel, filter, res, next); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.route('/:warId/points') + .get((req, res, next) => { + const filter = {war: req.params.warId}; + if (req.query.fraction) filter['fraction'] = req.query.fraction; + processLogRequest(LogPointsModel, filter, res, next); + }) + .all( + routerHandling.httpMethodNotAllowed + ); + +logsRouter.use(routerHandling.emptyResponse); + +module.exports = logsRouter; diff --git a/api/routes/wars.js b/api/routes/wars.js index a3000ad..7726b68 100644 --- a/api/routes/wars.js +++ b/api/routes/wars.js @@ -3,7 +3,6 @@ // modules const fs = require('fs'); const mkdirp = require("mkdirp"); -const {exec} = require('child_process'); const express = require('express'); const multer = require('multer'); const storage = multer.memoryStorage(); @@ -17,10 +16,19 @@ const apiAuthenticationMiddleware = require('../middleware/auth-middleware'); const checkMT = require('../middleware/permission-check').checkMT; const routerHandling = require('../middleware/router-handling'); +const parseWarLog = require('../tools/log-parse-tool'); + // Mongoose Model using mongoDB const CampaignModel = require('../models/campaign'); const WarModel = require('../models/war'); const PlayerModel = require('../models/player'); +const LogKillModel = require('../models/logs/kill'); +const LogRespawnModel = require('../models/logs/respawn'); +const LogReviveModel = require('../models/logs/revive'); +const LogTransportModel = require('../models/logs/transport'); +const LogFlagModel = require('../models/logs/flag'); +const LogBudgetModel = require('../models/logs/budget'); +const LogPointsModel = require('../models/logs/points'); const wars = express.Router(); @@ -61,70 +69,63 @@ wars.route('/') }) .post(apiAuthenticationMiddleware, checkMT, upload.single('log'), (req, res, next) => { - let body = req.body; - let parts = body.date.split("-"); - body.date = new Date(parseInt(parts[0], 10), - parseInt(parts[1], 10) - 1, - parseInt(parts[2], 10)); - const war = new WarModel(body); + const body = req.body; + const warBody = new WarModel(body); if (req.file) { - war.save((err, war) => { + fs.readFile(req.file.buffer, (file, err) => { if (err) { return next(err); } - const folderName = __dirname + '/../resource/logs/' + war._id; - mkdirp(folderName, function (err) { + const lineArray = file.toString().split("\n"); + const statsResult = parseWarLog(lineArray, warBody); + statsResult.war.save((err, war) => { if (err) { return next(err); } - fs.appendFile(folderName + '/war.log', new Buffer(req.file.buffer), (err) => { + PlayerModel.create(statsResult.players, function (err) { if (err) { return next(err); } - exec(__dirname + '/../war-parser/run.sh ' + folderName + ' ' + war._id, (error, stdout) => { - if (error) { - return next(error); - } - exec(__dirname + '/../war-parser/clean.sh /../' + folderName + ' | tee ' + folderName + '/clean.log', (error) => { - if (error) { - return next(error); - } - let obj = JSON.parse(`${stdout}`); - for (let i = 0; i < obj.length; i++) { - if (!obj[i].fraction) { - obj.splice(i, 1); - } else if (obj[i].fraction === 'BLUFOR') { - war.playersBlufor++; - } else { - war.playersOpfor++; - } - } + LogKillModel.create(statsResult.kills, function () { + LogRespawnModel.create(statsResult.respawn, function () { + LogReviveModel.create(statsResult.revive, function () { + LogFlagModel.create(statsResult.flag, function () { + LogBudgetModel.create(statsResult.budget, function () { + LogTransportModel.create(statsResult.transport, function () { + LogPointsModel.create(statsResult.points, function () { + const folderName = __dirname + '/../resource/logs/' + war._id; + mkdirp(folderName, function (err) { + if (err) return next(err); - WarModel.findByIdAndUpdate(war._id, war, {new: true}, (err, item) => { - if (err) { - err.status = codes.wrongrequest; - } - else if (!item) { - err = new Error("item not found"); - err.status = codes.notfound; - } - else { - PlayerModel.create(obj, function (err) { - if (err) { - return next(err); - } - res.status(codes.created); - res.locals.items = war; - return next(); - }); - } + // save clean log file + const cleanFile = fs.createWriteStream(folderName + '/clean.log'); + statsResult.clean.forEach(cleanLine => { + cleanFile.write(cleanLine + '\n\n') + }); + cleanFile.end(); + + // save raw log file + const rawFile = fs.createWriteStream(folderName + '/war.log'); + lineArray.forEach(rawLine => { + rawFile.write(rawLine + '\n') + }); + rawFile.end(); + + res.status(codes.created); + res.locals.items = war; + next(); + }) + }) + }) + }) + }) }) - }); - }); - }); - }); - }) + }) + }) + }) + }) + }); } else { const err = new Error('no Logfile provided'); diff --git a/api/server.js b/api/server.js index b961de2..9fda98e 100644 --- a/api/server.js +++ b/api/server.js @@ -35,6 +35,7 @@ const signatureRouter = require('./routes/signatures'); const commandRouter = require('./routes/command'); const campaignRouter = require('./routes/campaigns'); const warRouter = require('./routes/wars'); +const logRouter = require('./routes/logs'); // Configuration *********************************** // mongoose promise setup @@ -83,6 +84,7 @@ app.use(urls.awards, awardingRouter); app.use(urls.wars, warRouter); app.use(urls.players, playerRouter); app.use(urls.campaigns,campaignRouter); +app.use(urls.logs,logRouter); app.use(urls.command, apiAuthenticationMiddleware, checkAdmin, commandRouter); app.use(urls.account, apiAuthenticationMiddleware, checkAdmin, accountRouter); diff --git a/api/signature-tool/backplate/blufor.png b/api/tools/backplate/blufor.png similarity index 100% rename from api/signature-tool/backplate/blufor.png rename to api/tools/backplate/blufor.png diff --git a/api/signature-tool/backplate/opfor.png b/api/tools/backplate/opfor.png similarity index 100% rename from api/signature-tool/backplate/opfor.png rename to api/tools/backplate/opfor.png diff --git a/api/signature-tool/font/DEJAVU_SANS_13.fnt b/api/tools/font/DEJAVU_SANS_13.fnt similarity index 100% rename from api/signature-tool/font/DEJAVU_SANS_13.fnt rename to api/tools/font/DEJAVU_SANS_13.fnt diff --git a/api/signature-tool/font/DEJAVU_SANS_13.png b/api/tools/font/DEJAVU_SANS_13.png similarity index 100% rename from api/signature-tool/font/DEJAVU_SANS_13.png rename to api/tools/font/DEJAVU_SANS_13.png diff --git a/api/signature-tool/font/DEVAJU_SANS_19.fnt b/api/tools/font/DEVAJU_SANS_19.fnt similarity index 100% rename from api/signature-tool/font/DEVAJU_SANS_19.fnt rename to api/tools/font/DEVAJU_SANS_19.fnt diff --git a/api/signature-tool/font/DEVAJU_SANS_19.png b/api/tools/font/DEVAJU_SANS_19.png similarity index 100% rename from api/signature-tool/font/DEVAJU_SANS_19.png rename to api/tools/font/DEVAJU_SANS_19.png diff --git a/api/tools/log-parse-tool.js b/api/tools/log-parse-tool.js new file mode 100644 index 0000000..06fa9ec --- /dev/null +++ b/api/tools/log-parse-tool.js @@ -0,0 +1,244 @@ +'use strict'; + +const playerArrayContains = require('./util').playerArrayContains; + +const parseWarLog = (lineArray, war) => { + const nameToLongError = 'Error: ENAMETOOLONG: name too long, open \''; + const stats = { + war: war, + clean: [], + budget: [], + points: [], + kills: [], + respawn: [], + revive: [], + flag: [], + transport: [], + players: [] + }; + + const addPlayersIfNotExists = (inputPlayers) => { + inputPlayers.forEach(player => { + if (player && player.name && player.fraction && !playerArrayContains(stats.players, player)) { + player['warId'] = war._id; + stats.players.push(player); + } + }) + }; + + lineArray.some(line => { + /** + * sanitize nameToLongError coming up in first line + */ + if (line.includes(nameToLongError)) { + line = line.substring(line.indexOf(nameToLongError) + nameToLongError.length); + } + + /** + * KILLS + */ + if (line.includes('Abschuss') && !line.includes('Fahrzeug')) { + stats.clean.push(line); + const shooterString = line.substring(line.lastIndexOf(' von: ') + 6, line.lastIndexOf('. :OPT LOG END')); + const shooter = getPlayerAndFractionFromString(shooterString); + const targetString = line.substring(line.lastIndexOf(' || ') + 4, line.lastIndexOf(' von:')); + const target = getPlayerAndFractionFromString(targetString); + + stats.kills.push({ + war: war._id, + time: getFullTimeDate(war.date, line.split(' ')[5]), + shooter: shooter ? shooter.name : null, + target: target.name, + friendlyFire: shooter ? target.fraction === shooter.fraction : false, + fraction: shooter ? shooter.fraction : 'NONE' + }); + + addPlayersIfNotExists([shooter, target]); + } + + /** + * BUDGET + */ + if (line.includes('Budget')) { + stats.clean.push(line); + const budg = line.split(' '); + if (line.includes('Endbudget')) { + stats.war['endBudgetBlufor'] = transformMoneyString(budg[11]); + stats.war['endBudgetOpfor'] = transformMoneyString(budg[14]); + console.log(budg) + console.log(budg[0].substr(0, budg[0].length - 1).split('/').join('-') + 'T' + budg[5] +'.000+02:00') + war.endDate = new Date(budg[0].substr(0, budg[0].length - 1).split('/').join('-') + 'T0' + budg[5] +'.000+02:00'); + } else if (line.includes('Startbudget')) { + stats.war.date = new Date(budg[0].substr(0, budg[0].length - 1).split('/').join('-') + 'T22:00:00.000+02:00'); + stats.war['budgetBlufor'] = transformMoneyString(budg[11]); + stats.war['budgetOpfor'] = transformMoneyString(budg[14]); + } else { + stats.budget.push(getBudgetEntry(budg, war._id, war.date)); + } + } + + /** + * FLAG + */ + if (line.includes('Fahne')) { + stats.clean.push(line); + const playerName = line.substring(line.lastIndexOf('t von ') + 6, line.lastIndexOf(' :OPT LOG END')); + const flagFraction = line.includes('NATO Flagge') ? 'BLUFOR' : 'OPFOR'; + const capture = !!line.includes('Flagge erobert'); + + stats.flag.push({ + war: war._id, + time: getFullTimeDate(war.date, line.split(' ')[5]), + player: playerName, + flagFraction: flagFraction, + capture: capture + }); + } + + /** + * POINTS + */ + if (line.includes('Punkte')) { + stats.clean.push(line); + const pt = line.split(' '); + + if (line.includes('Endpunktestand')) { + stats.war['ptBlufor'] = parseInt(pt[11]); + stats.war['ptOpfor'] = parseInt(pt[14].slice(0, -1)); + return true; + } else { + stats.points.push(getPointsEntry(pt, line, war._id, war.date)) + } + } + + /** + * RESPAWN + */ + if (line.includes('Respawn')) { + stats.clean.push(line); + const resp = line.split(' '); + const playerName = line.substring(line.lastIndexOf('Spieler:') + 9, line.lastIndexOf('-') - 1); + stats.respawn.push(getRespawnEntry(resp, playerName, war._id, war.date)); + } + + /** + * REVIVE + */ + if (line.includes('Revive')) { + stats.clean.push(line); + const stabilized = !!line.includes('stabilisiert'); + const medicName = line.substring(line.lastIndexOf('wurde von ') + 10, + line.lastIndexOf(stabilized ? ' stabilisiert' : ' wiederbelebt')); + const medic = getPlayerAndFractionFromString(medicName); + const patientName = line.substring(line.lastIndexOf('|| ') + 3, line.lastIndexOf(' wurde von')); + const patient = getPlayerAndFractionFromString(patientName); + + stats.revive.push({ + war: war._id, + time: getFullTimeDate(war.date, line.split(' ')[5]), + stabilized: stabilized, + medic: medic.name, + patient: patient.name, + fraction: medic.fraction + }); + + addPlayersIfNotExists([medic, patient]); + } + + /** + * TRANSPORT + */ + if (line.includes('Transport ||')) { + stats.clean.push(line); + const driverString = line.substring(line.lastIndexOf('wurde von ') + 10, line.lastIndexOf(' eingeflogen')); + const driver = getPlayerAndFractionFromString(driverString); + const passengerString = line.substring(line.lastIndexOf('|| ') + 3, line.lastIndexOf(' wurde von')); + const passenger = getPlayerAndFractionFromString(passengerString); + const distance = parseInt(line.substring(line.lastIndexOf('eingeflogen (') + 13, line.lastIndexOf('m)') - 1)); + + stats.transport.push({ + war: war._id, + time: getFullTimeDate(war.date, line.split(' ')[5]), + driver: driver.name, + passenger: passenger ? passenger.name : null, + fraction: driver.fraction, + distance: distance + }); + + addPlayersIfNotExists([driver, passenger]); + } + }); + + for (let i = 0; i < stats.players.length; i++) { + const playerName = stats.players[i].name; + stats.players[i]['respawn'] = stats.respawn.filter(res => res.player === playerName).length; + stats.players[i]['kill'] = stats.kills.filter(kill => kill.shooter === playerName && !kill.friendlyFire).length; + stats.players[i]['friendlyFire'] = stats.kills.filter(kill => kill.shooter === playerName && kill.friendlyFire).length; + stats.players[i]['death'] = stats.kills.filter(kill => kill.target === playerName).length; + stats.players[i]['revive'] = stats.revive.filter(rev => rev.medic === playerName && !rev.stabilized).length; + stats.players[i]['flagTouch'] = stats.flag.filter(flag => flag.player === playerName).length; + stats.players[i]['sort'] = stats.players[i]['kill'] + stats.players[i]['revive'] + stats.players[i]['flagTouch'] + - stats.players[i]['friendlyFire'] - stats.players[i]['death'] - stats.players[i]['respawn'] + } + + stats.war.playersBlufor = stats.players.filter(player => player.fraction === 'BLUFOR').length; + stats.war.playersOpfor = stats.players.filter(player => player.fraction === 'OPFOR').length; + + return stats; +}; + +const getRespawnEntry = (respawn, playerName, warId, warDate) => { + return { + war: warId, + time: getFullTimeDate(warDate, respawn[5]), + player: playerName + } +}; + +const getPointsEntry = (pt, line, warId, warDate) => { + return { + war: warId, + time: getFullTimeDate(warDate, pt[5]), + ptBlufor: parseInt(pt[12]), + ptOpfor: parseInt(pt[15].slice(0, -1)), + fraction: line.includes('no Domination') ? 'NONE' : line.includes('NATO +1') ? 'BLUFOR' : 'OPFOR' + } +}; + +const getBudgetEntry = (budg, warId, warDate) => { + return { + war: warId, + time: getFullTimeDate(warDate, budg[5]), + fraction: budg[9] === 'NATO' ? 'BLUFOR' : 'OPFOR', + oldBudget: transformMoneyString(budg[11]), + newBudget: transformMoneyString(budg[14]) + } +}; + +const getPlayerAndFractionFromString = (nameAndFractionString) => { + const nameArray = nameAndFractionString.split(' '); + const fraction = nameArray[nameArray.length - 1] !== '(ENEMY)' ? nameArray[nameArray.length - 1] === '(WEST)' ? 'BLUFOR' : 'OPFOR' : undefined; + const name = nameAndFractionString.substring(0, nameAndFractionString.indexOf(nameArray[nameArray.length - 1]) - 1); + // do not return player for 'Selbstverschulden' or 'Error: No unit' + if (name && name !== 'Error: No unit') { + return {name: name, fraction: fraction}; + } +}; + +const transformMoneyString = (budgetString) => { + if (!budgetString.includes('e+')) { + return parseInt(budgetString); + } + const budget = budgetString.split('e+'); + return Math.round(parseFloat(budget[0]) * Math.pow(10, parseInt(budget[1]))); +}; + +const getFullTimeDate = (date, timeString) => { + const returnDate = new Date(date); + const time = timeString.split(':'); + returnDate.setHours(time[0]); + returnDate.setMinutes(time[1]); + return returnDate; +}; + +module.exports = parseWarLog; diff --git a/api/signature-tool/signature-tool.js b/api/tools/signature-tool.js similarity index 100% rename from api/signature-tool/signature-tool.js rename to api/tools/signature-tool.js diff --git a/api/tools/util.js b/api/tools/util.js new file mode 100644 index 0000000..e0ebbe7 --- /dev/null +++ b/api/tools/util.js @@ -0,0 +1,50 @@ +"use strict"; + +const sortCollectionBy = (collection, key) => { + collection.sort((a, b) => { + a = a[key].toLowerCase(); + b = b[key].toLowerCase(); + if (a < b) return -1; + if (a > b) return 1; + return 0; + }); + return collection; +}; + +const playerArrayContains = (arr, item) => { + let i = 0, count = arr.length, matchFound = false; + + for (; i < count; i++) { + if (arr[i].name === item.name && arr[i].fraction === item.fraction) { + matchFound = true; + break; + } + } + + return matchFound; +}; + +const timeStringToDecimal = (timeString) => { + const timeArray = timeString.split(':'); + const hour = parseInt(timeArray[0]); + const sek = parseInt(timeArray[1]) * 60 + parseInt(timeArray[2]); + + return hour + (sek / 3600); +}; + + +const decimalToTimeString = (decimal) => { + const hours = parseInt(decimal.toString().split(".")[0]); + const minutesFloat = ((decimal % 1) * 3600) / 60; + const minutes = parseInt(minutesFloat.toString().split(".")[0]); + const seconds = Math.round((minutesFloat - parseInt(minutes)) * 60); + + return (hours < 10 ? '0' + hours : hours) + ':' + + (minutes < 10 ? '0' + minutes : minutes) + ':' + + (seconds < 10 ? '0' + seconds : seconds); +}; + +exports.sortCollection = sortCollectionBy; +exports.playerArrayContains = playerArrayContains; +exports.timeStringToDecimal = timeStringToDecimal; +exports.decimalToTimeString = decimalToTimeString; diff --git a/api/war-parser/clean.sh b/api/war-parser/clean.sh deleted file mode 100755 index 5d34276..0000000 --- a/api/war-parser/clean.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -FILE="$1/war.log" - -while IFS='' read -r line || [[ -n "$line" ]]; do - case "$line" in - *"Budget"* | *"Mission"* | *"Abschuss"* | *"Respawn"* | *"Punkte"* | *"Flagge"* | *"Revive"* | *"Transport"*) - echo $line; - echo "" - ;; - esac -done < ${FILE} - -# Add OPT Scoreboard -while IFS= read -r line; do - if [[ $line =~ [^[:space:]] ]]; then - echo "$line" - echo "" - fi -done < <(grep -A 100 Scoreboard ${FILE} ) diff --git a/api/war-parser/run.sh b/api/war-parser/run.sh deleted file mode 100755 index 999fac4..0000000 --- a/api/war-parser/run.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bash - -createScoreboard() { - NAME="$1" - FILE="$2" - WAR_ID="$3" - - KILL=0 - FF=0 - REVIVE=0 - DEATH=0 - RESPAWN=0 - FLAG=0 - FRACTION= - - #escape '[' -> somehow escapes all special chars, hah? - ESC_NAME=$(echo "$NAME" | sed -r 's/[\[]+/\\[/g') - - while IFS= read -r line; do - case "$line" in - *\(WEST\)[" "]von:[" "]${ESC_NAME}[" "]\(EAST\)* | *\(EAST\)[" "]von:[" "]${ESC_NAME}[" "]\(WEST\)*) - ((KILL++)) - ;; - *\(EAST\)[" "]von:[" "]${ESC_NAME}[" "]\(EAST\)* | *\(WEST\)[" "]von:[" "]${ESC_NAME}[" "]\(WEST\)*) - ((FF++)) - ;; - *\(EAST\)[" "]wurde[" "]von[" "]${ESC_NAME}[" "]\(EAST\)[" "]wiederbelebt* | *\(WEST\)[" "]wurde[" "]von[" "]${ESC_NAME}[" "]\(WEST\)[" "]wiederbelebt*) - ((REVIVE++)) - ;; - *${ESC_NAME}[" "]*von:*) - ((DEATH++)) - ;; - *Flagge[" "]erobert[" "]von[" "]${ESC_NAME}* | *Flagge[" "]gesichert[" "]von[" "]${ESC_NAME}*) - ((FLAG++)) - ;; - *Spieler:*${ESC_NAME}*) - ((RESPAWN++)) - ;; - esac - - if [[ -z ${FRACTION} && ( "$line" == *${ESC_NAME}[" "]\(WEST\)* || "$line" == *${ESC_NAME}[" "]\(NATO\)* ) ]]; then - FRACTION="BLUFOR" - fi - if [[ -z ${FRACTION} && ( "$line" == *${ESC_NAME}[" "]\(EAST\)* || "$line" == *${ESC_NAME}[" "]\(CSAT\)* ) ]]; then - FRACTION="OPFOR" - fi - done < <(grep -- "${ESC_NAME}" ${FILE}) - - printf "\t{\"name\":\"$NAME\", \"fraction\":\"$FRACTION\", \"kill\":${KILL}, \"friendlyFire\":${FF}, \"revive\":${REVIVE}, \"death\":${DEATH}, \"respawn\":${RESPAWN}, \"flagTouch\":${FLAG}, \"warId\":\"${WAR_ID}\"} " - if [[ -z ${4} ]]; then - printf ",\n" - else - printf "\n" - fi -} - - -FILE="$1/war.log" -WAR_ID="$2" -PLAYERS=() - -while IFS='' read -r line || [[ -n "$line" ]]; do - if [[ -n $line ]]; then - case "$line" in - *"TFAR_RadioRequestEvent"*) - RES=$(echo "$(grep -oP ':[0-9]+\s\(\K.*?(?=\)\sREMOTE)' <<< "$line")") - ;; - *"Respawn"*) - RES=$(echo "$(grep -oP '\|\|\sSpieler:\s\K.*?(?=\s-\sKosten:)' <<< "$line")") - ;; - *"Abschuss"*) - RES=$(echo "$(grep -oP '\|\|\s\K.*?(?=\s\((EAST|WEST|CIV))' <<< "$line")") - RES1=$(echo "$(grep -oP 'von:\s\K.*?(?=\s\((EAST|WEST|CIV))' <<< "$line")") - ;; - esac - - if [[ $RES != *"Error: No unit"* && $RES1 != *"Error: No unit"* ]]; then - if [[ -n $RES && " ${PLAYERS[*]} " != *" $RES "* ]]; then - PLAYERS+=("$RES") - fi - if [[ -n $RES1 && " ${PLAYERS[*]} " != *" $RES1 "* ]]; then - PLAYERS+=("$RES1") - fi - fi - fi -done < ${FILE} - -echo "[" - -for ((i=0; i<${#PLAYERS[*]}; i++)); -do - if [[ "$((i+1))" -eq "${#PLAYERS[*]}" ]]; then - last="true" - fi - createScoreboard "${PLAYERS[i]}" ${FILE} ${WAR_ID} ${last} -done -echo "]" diff --git a/package.json b/package.json index 2ef491a..945d0b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "opt-cc", - "version": "1.5.4", + "version": "1.6.0", "license": "MIT", + "author": "Florian Hartwich ", "private": true, "scripts": { "start": "npm run deploy-static-prod && npm start --prefix ./api", diff --git a/static/package.json b/static/package.json index 9e360a3..61e2a2d 100644 --- a/static/package.json +++ b/static/package.json @@ -1,6 +1,7 @@ { "name": "opt-cc-static", "license": "MIT", + "author": "Florian Hartwich ", "private": true, "angular-cli": {}, "scripts": { @@ -22,7 +23,7 @@ "@angular/platform-browser": "^4.4.4", "@angular/platform-browser-dynamic": "^4.4.4", "@angular/router": "^4.4.4", - "@swimlane/ngx-charts": "^6.0.2", + "@swimlane/ngx-charts": "^6.1.0", "@swimlane/ngx-datatable": "^10.2.3", "bootstrap": "^3.3.7", "core-js": "^2.4.1", @@ -30,7 +31,7 @@ "jquery": "^3.1.0", "jquery-ui": "^1.12.0", "jquery-ui-bundle": "^1.11.4", - "ngx-bootstrap": "^2.0.0-beta.6", + "ngx-bootstrap": "^1.9.3", "ngx-clipboard": "^8.1.0", "ngx-cookie-service": "^1.0.9", "ngx-infinite-scroll": "^0.5.2", diff --git a/static/src/app/admin/admin.component.ts b/static/src/app/admin/admin.component.ts index 87674bf..999ffeb 100644 --- a/static/src/app/admin/admin.component.ts +++ b/static/src/app/admin/admin.component.ts @@ -2,7 +2,7 @@ import {Component} from "@angular/core"; import {AppUser, Squad} from "../models/model-interfaces"; import {Observable} from "rxjs/Observable"; import {AppUserService} from "../services/app-user-service/app-user.service"; -import {SquadService} from "../services/squad-service/squad.service"; +import {SquadService} from "../services/army-management/squad.service"; @Component({ diff --git a/static/src/app/app.component.ts b/static/src/app/app.component.ts index 2b844b7..21105b0 100644 --- a/static/src/app/app.component.ts +++ b/static/src/app/app.component.ts @@ -1,8 +1,8 @@ -import {Component, HostListener} from '@angular/core'; +import {Component} from '@angular/core'; import {NavigationEnd, NavigationStart, Router} from '@angular/router'; -import {LoginService} from './services/login-service/login-service'; -import {PromotionService} from "./services/promotion-service/promotion.service"; -import {AwardingService} from "./services/awarding-service/awarding.service"; +import {LoginService} from './services/app-user-service/login-service'; +import {PromotionService} from "./services/army-management/promotion.service"; +import {AwardingService} from "./services/army-management/awarding.service"; import {RouteConfig} from "./app.config"; declare function require(url: string); @@ -30,7 +30,7 @@ export class AppComponent { } if (event instanceof NavigationEnd) { this.loading = false; - if (router.url.includes('stats') || router.url.includes('overview')) { + if (router.url.includes('overview')) { window.scrollTo({left: 0, top: 0, behavior: 'smooth'}); } } diff --git a/static/src/app/app.config.ts b/static/src/app/app.config.ts index ad97dbe..7187683 100644 --- a/static/src/app/app.config.ts +++ b/static/src/app/app.config.ts @@ -3,19 +3,20 @@ export class AppConfig { public readonly apiUrl = '/api'; public readonly apiAppUserPath = this.apiUrl + '/account/'; - public readonly apiAwardPath = this.apiUrl + '/awardings'; - public readonly apiDecorationPath = this.apiUrl + '/decorations/'; public readonly apiAuthenticationPath = this.apiUrl + '/authenticate'; - public readonly apiSignupPath = this.apiUrl + '/authenticate/signup'; - public readonly apiRankPath = this.apiUrl + '/ranks/'; - public readonly apiSquadPath = this.apiUrl + '/squads/'; - public readonly apiUserPath = this.apiUrl + '/users/'; + public readonly apiAwardPath = this.apiUrl + '/awardings'; + public readonly apiCampaignPath = this.apiUrl + '/campaigns'; + public readonly apiDecorationPath = this.apiUrl + '/decorations/'; + public readonly apiLogsPath = this.apiUrl + '/logs'; public readonly apiOverviewPath = this.apiUrl + '/overview'; public readonly apiPlayersPath = this.apiUrl + '/players'; - public readonly apiRequestAwardPath = this.apiUrl + '/request/award'; public readonly apiPromotionPath = this.apiUrl + '/request/promotion'; + public readonly apiRankPath = this.apiUrl + '/ranks/'; + public readonly apiRequestAwardPath = this.apiUrl + '/request/award'; + public readonly apiSignupPath = this.apiUrl + '/authenticate/signup'; + public readonly apiSquadPath = this.apiUrl + '/squads/'; + public readonly apiUserPath = this.apiUrl + '/users/'; public readonly apiWarPath = this.apiUrl + '/wars'; - public readonly apiCampaignPath = this.apiUrl + '/campaigns'; } diff --git a/static/src/app/app.module.ts b/static/src/app/app.module.ts index bd133ba..936c413 100644 --- a/static/src/app/app.module.ts +++ b/static/src/app/app.module.ts @@ -2,24 +2,24 @@ import {NgModule} from '@angular/core'; import {BrowserModule} from "@angular/platform-browser"; import {HttpModule} from '@angular/http'; import {AppComponent} from './app.component'; -import {LoginService} from "./services/login-service/login-service"; +import {LoginService} from "./services/app-user-service/login-service"; import {appRouting, routingComponents, routingProviders} from './app.routing'; -import {SquadService} from "./services/squad-service/squad.service"; +import {SquadService} from "./services/army-management/squad.service"; import {SquadStore} from "./services/stores/squad.store"; import {DecorationStore} from "./services/stores/decoration.store"; -import {DecorationService} from "./services/decoration-service/decoration.service"; +import {DecorationService} from "./services/army-management/decoration.service"; import {RankStore} from "./services/stores/rank.store"; -import {RankService} from "./services/rank-service/rank.service"; +import {RankService} from "./services/army-management/rank.service"; import {AppConfig} from "./app.config"; import {LoginGuardAdmin, LoginGuardHL, LoginGuardSQL} from "./login/login.guard"; -import {AwardingService} from "./services/awarding-service/awarding.service"; +import {AwardingService} from "./services/army-management/awarding.service"; import {HttpClient} from "./services/http-client"; import {ArmyService} from "./services/army-service/army.service"; import {ClipboardModule} from 'ngx-clipboard'; -import {PromotionService} from "./services/promotion-service/promotion.service"; +import {PromotionService} from "./services/army-management/promotion.service"; import {SharedModule} from "./shared.module"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; -import {UserService} from "./services/user-service/user.service"; +import {UserService} from "./services/army-management/user.service"; import {UserStore} from "./services/stores/user.store"; import {CookieService} from "ngx-cookie-service"; diff --git a/static/src/app/army/army-member.component.ts b/static/src/app/army/army-member.component.ts index 84937ed..76c938a 100644 --- a/static/src/app/army/army-member.component.ts +++ b/static/src/app/army/army-member.component.ts @@ -1,10 +1,10 @@ import {Component} from "@angular/core"; import {Award, User} from "../models/model-interfaces"; import {ActivatedRoute, Router} from "@angular/router"; -import {UserService} from "../services/user-service/user.service"; +import {UserService} from "../services/army-management/user.service"; import {Subscription} from "rxjs/Subscription"; import {RouteConfig} from "../app.config"; -import {AwardingService} from "../services/awarding-service/awarding.service"; +import {AwardingService} from "../services/army-management/awarding.service"; @Component({ diff --git a/static/src/app/decorations/decoration-list/decoration-list.component.ts b/static/src/app/decorations/decoration-list/decoration-list.component.ts index 0578845..83448fe 100644 --- a/static/src/app/decorations/decoration-list/decoration-list.component.ts +++ b/static/src/app/decorations/decoration-list/decoration-list.component.ts @@ -5,7 +5,7 @@ import {FormControl} from "@angular/forms"; import {ActivatedRoute, Router} from "@angular/router"; import {Observable} from "rxjs/Observable"; import {Decoration} from "../../models/model-interfaces"; -import {DecorationService} from "../../services/decoration-service/decoration.service"; +import {DecorationService} from "../../services/army-management/decoration.service"; @Component({ selector: 'decoration-list', diff --git a/static/src/app/decorations/decoration.module.ts b/static/src/app/decorations/decoration.module.ts index 2e5b5f4..91f2c58 100644 --- a/static/src/app/decorations/decoration.module.ts +++ b/static/src/app/decorations/decoration.module.ts @@ -2,7 +2,7 @@ import {decorationRoutesModule, decorationsRoutingComponents} from "./decoration import {SharedModule} from "../shared.module"; import {CommonModule} from "@angular/common"; import {DecorationStore} from "../services/stores/decoration.store"; -import {DecorationService} from "../services/decoration-service/decoration.service"; +import {DecorationService} from "../services/army-management/decoration.service"; import {NgModule} from "@angular/core"; import {ButtonsModule} from "ngx-bootstrap"; diff --git a/static/src/app/decorations/edit-decoration/edit-decoration.component.ts b/static/src/app/decorations/edit-decoration/edit-decoration.component.ts index a41c590..e7ed88c 100644 --- a/static/src/app/decorations/edit-decoration/edit-decoration.component.ts +++ b/static/src/app/decorations/edit-decoration/edit-decoration.component.ts @@ -2,7 +2,7 @@ import {Component, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {NgForm} from "@angular/forms"; import {Decoration} from "../../models/model-interfaces"; -import {DecorationService} from "../../services/decoration-service/decoration.service"; +import {DecorationService} from "../../services/army-management/decoration.service"; import {Subscription} from "rxjs/Subscription"; @Component({ diff --git a/static/src/app/login/login.component.ts b/static/src/app/login/login.component.ts index 3510135..f6fb1f8 100644 --- a/static/src/app/login/login.component.ts +++ b/static/src/app/login/login.component.ts @@ -1,6 +1,6 @@ import {Component, OnInit} from "@angular/core"; import {Router} from "@angular/router"; -import {LoginService} from "../services/login-service/login-service"; +import {LoginService} from "../services/app-user-service/login-service"; import {RouteConfig} from "../app.config"; diff --git a/static/src/app/login/login.guard.ts b/static/src/app/login/login.guard.ts index f4985f4..95a5b25 100644 --- a/static/src/app/login/login.guard.ts +++ b/static/src/app/login/login.guard.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; -import {LoginService} from "../services/login-service/login-service"; +import {LoginService} from "../services/app-user-service/login-service"; @Injectable() export class LoginGuardSQL implements CanActivate { diff --git a/static/src/app/login/signup.component.ts b/static/src/app/login/signup.component.ts index 22e9531..267c98c 100644 --- a/static/src/app/login/signup.component.ts +++ b/static/src/app/login/signup.component.ts @@ -1,6 +1,6 @@ import {Component, OnInit} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; -import {LoginService} from "../services/login-service/login-service"; +import {LoginService} from "../services/app-user-service/login-service"; import {RouteConfig} from "../app.config"; diff --git a/static/src/app/models/model-interfaces.ts b/static/src/app/models/model-interfaces.ts index 1b46f7d..4830267 100644 --- a/static/src/app/models/model-interfaces.ts +++ b/static/src/app/models/model-interfaces.ts @@ -46,10 +46,15 @@ export interface War { _id?: string; title?: string; date?: string; + endDate?: string; ptBlufor?: number; ptOpfor?: number; playersBlufor?: number; playersOpfor?: number; + budgetBlufor?: number; + budgetOpfor?: number; + endBudgetBlufor?: number; + endBudgetOpfor?: number; players?: Player[]; campaign?: string; } diff --git a/static/src/app/ranks/edit-rank/edit-rank.component.ts b/static/src/app/ranks/edit-rank/edit-rank.component.ts index 2aebc98..67fd866 100644 --- a/static/src/app/ranks/edit-rank/edit-rank.component.ts +++ b/static/src/app/ranks/edit-rank/edit-rank.component.ts @@ -2,7 +2,7 @@ import {Component, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {NgForm} from "@angular/forms"; import {Rank} from "../../models/model-interfaces"; -import {RankService} from "../../services/rank-service/rank.service"; +import {RankService} from "../../services/army-management/rank.service"; import {Subscription} from "rxjs/Subscription"; diff --git a/static/src/app/ranks/rank-list/rank-list.component.ts b/static/src/app/ranks/rank-list/rank-list.component.ts index 8d2a299..807720f 100644 --- a/static/src/app/ranks/rank-list/rank-list.component.ts +++ b/static/src/app/ranks/rank-list/rank-list.component.ts @@ -5,7 +5,7 @@ import {FormControl} from "@angular/forms"; import {ActivatedRoute, Router} from "@angular/router"; import {Observable} from "rxjs/Observable"; import {Rank} from "../../models/model-interfaces"; -import {RankService} from "../../services/rank-service/rank.service"; +import {RankService} from "../../services/army-management/rank.service"; @Component({ selector: 'rank-list', diff --git a/static/src/app/ranks/ranks.module.ts b/static/src/app/ranks/ranks.module.ts index 5b34d04..ef23247 100644 --- a/static/src/app/ranks/ranks.module.ts +++ b/static/src/app/ranks/ranks.module.ts @@ -2,7 +2,7 @@ import {NgModule} from "@angular/core"; import {rankRouterModule, ranksRoutingComponents} from "./ranks.routing"; import {SharedModule} from "../shared.module"; import {CommonModule} from "@angular/common"; -import {RankService} from "../services/rank-service/rank.service"; +import {RankService} from "../services/army-management/rank.service"; import {RankStore} from "../services/stores/rank.store"; import {ButtonsModule} from "ngx-bootstrap"; diff --git a/static/src/app/request/award/req-award.component.ts b/static/src/app/request/award/req-award.component.ts index befcdb0..149e165 100644 --- a/static/src/app/request/award/req-award.component.ts +++ b/static/src/app/request/award/req-award.component.ts @@ -2,10 +2,10 @@ import {Component, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {Award, Decoration, User} from "../../models/model-interfaces"; import {NgForm} from "@angular/forms"; -import {AwardingService} from "../../services/awarding-service/awarding.service"; -import {DecorationService} from "../../services/decoration-service/decoration.service"; -import {UserService} from "../../services/user-service/user.service"; -import {LoginService} from "../../services/login-service/login-service"; +import {AwardingService} from "../../services/army-management/awarding.service"; +import {DecorationService} from "../../services/army-management/decoration.service"; +import {UserService} from "../../services/army-management/user.service"; +import {LoginService} from "../../services/app-user-service/login-service"; @Component({ diff --git a/static/src/app/request/confirm-award/confirm-award.component.ts b/static/src/app/request/confirm-award/confirm-award.component.ts index 4ce6898..751f6ab 100644 --- a/static/src/app/request/confirm-award/confirm-award.component.ts +++ b/static/src/app/request/confirm-award/confirm-award.component.ts @@ -1,7 +1,7 @@ import {Component} from "@angular/core"; import {Award} from "../../models/model-interfaces"; -import {AwardingService} from "../../services/awarding-service/awarding.service"; -import {LoginService} from "../../services/login-service/login-service"; +import {AwardingService} from "../../services/army-management/awarding.service"; +import {LoginService} from "../../services/app-user-service/login-service"; @Component({ diff --git a/static/src/app/request/confirm-promotion/confirm-promotion.component.ts b/static/src/app/request/confirm-promotion/confirm-promotion.component.ts index d3c7d40..8612ced 100644 --- a/static/src/app/request/confirm-promotion/confirm-promotion.component.ts +++ b/static/src/app/request/confirm-promotion/confirm-promotion.component.ts @@ -1,8 +1,8 @@ import {Component} from "@angular/core"; import {Promotion, Rank} from "../../models/model-interfaces"; -import {RankService} from "../../services/rank-service/rank.service"; -import {PromotionService} from "../../services/promotion-service/promotion.service"; -import {LoginService} from "../../services/login-service/login-service"; +import {RankService} from "../../services/army-management/rank.service"; +import {PromotionService} from "../../services/army-management/promotion.service"; +import {LoginService} from "../../services/app-user-service/login-service"; @Component({ diff --git a/static/src/app/request/promotion/req-promotion.component.ts b/static/src/app/request/promotion/req-promotion.component.ts index 4d6451e..bdd4a06 100644 --- a/static/src/app/request/promotion/req-promotion.component.ts +++ b/static/src/app/request/promotion/req-promotion.component.ts @@ -2,10 +2,10 @@ import {Component, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {Rank, User} from "../../models/model-interfaces"; import {NgForm} from "@angular/forms"; -import {UserService} from "../../services/user-service/user.service"; -import {RankService} from "../../services/rank-service/rank.service"; -import {PromotionService} from "../../services/promotion-service/promotion.service"; -import {LoginService} from "../../services/login-service/login-service"; +import {UserService} from "../../services/army-management/user.service"; +import {RankService} from "../../services/army-management/rank.service"; +import {PromotionService} from "../../services/army-management/promotion.service"; +import {LoginService} from "../../services/app-user-service/login-service"; @Component({ diff --git a/static/src/app/services/login-service/login-service.ts b/static/src/app/services/app-user-service/login-service.ts similarity index 92% rename from static/src/app/services/login-service/login-service.ts rename to static/src/app/services/app-user-service/login-service.ts index 1af6a68..54ad27f 100644 --- a/static/src/app/services/login-service/login-service.ts +++ b/static/src/app/services/app-user-service/login-service.ts @@ -3,8 +3,8 @@ import {Http, Response} from "@angular/http"; import "rxjs/add/operator/map"; import {AppConfig} from "../../app.config"; -import {AwardingService} from "../awarding-service/awarding.service"; -import {PromotionService} from "../promotion-service/promotion.service"; +import {AwardingService} from "../army-management/awarding.service"; +import {PromotionService} from "../army-management/promotion.service"; import {CookieService} from "ngx-cookie-service"; @Injectable() diff --git a/static/src/app/services/awarding-service/awarding.service.ts b/static/src/app/services/army-management/awarding.service.ts similarity index 100% rename from static/src/app/services/awarding-service/awarding.service.ts rename to static/src/app/services/army-management/awarding.service.ts diff --git a/static/src/app/services/decoration-service/decoration.service.ts b/static/src/app/services/army-management/decoration.service.ts similarity index 100% rename from static/src/app/services/decoration-service/decoration.service.ts rename to static/src/app/services/army-management/decoration.service.ts diff --git a/static/src/app/services/promotion-service/promotion.service.ts b/static/src/app/services/army-management/promotion.service.ts similarity index 100% rename from static/src/app/services/promotion-service/promotion.service.ts rename to static/src/app/services/army-management/promotion.service.ts diff --git a/static/src/app/services/rank-service/rank.service.ts b/static/src/app/services/army-management/rank.service.ts similarity index 100% rename from static/src/app/services/rank-service/rank.service.ts rename to static/src/app/services/army-management/rank.service.ts diff --git a/static/src/app/services/squad-service/squad.service.ts b/static/src/app/services/army-management/squad.service.ts similarity index 100% rename from static/src/app/services/squad-service/squad.service.ts rename to static/src/app/services/army-management/squad.service.ts diff --git a/static/src/app/services/user-service/user.service.ts b/static/src/app/services/army-management/user.service.ts similarity index 100% rename from static/src/app/services/user-service/user.service.ts rename to static/src/app/services/army-management/user.service.ts diff --git a/static/src/app/services/campaign-service/campaign.service.ts b/static/src/app/services/logs/campaign.service.ts similarity index 100% rename from static/src/app/services/campaign-service/campaign.service.ts rename to static/src/app/services/logs/campaign.service.ts diff --git a/static/src/app/services/logs/logs.service.ts b/static/src/app/services/logs/logs.service.ts new file mode 100644 index 0000000..f956b81 --- /dev/null +++ b/static/src/app/services/logs/logs.service.ts @@ -0,0 +1,80 @@ +import {Injectable} from "@angular/core"; +import {AppConfig} from "../../app.config"; +import {HttpClient} from "../http-client"; +import {URLSearchParams} from "@angular/http"; + +@Injectable() +export class LogsService { + + constructor(private http: HttpClient, + private config: AppConfig) { + } + + getFullLog(warId: string) { + return this.http.get(this.config.apiLogsPath + '/' + warId) + .map(res => res.json()) + } + + getBudgetLogs(warId: string, fraction = '') { + const params = new URLSearchParams(); + params.append('fraction', fraction); + return this.http.get(this.config.apiLogsPath + '/' + warId + '/budget', params) + .map(res => res.json()) + } + + getRespawnLogs(warId: string, playerName = '') { + const params = new URLSearchParams(); + params.append('player', playerName); + return this.http.get(this.config.apiLogsPath + '/' + warId + '/respawn', params) + .map(res => res.json()) + } + + getPointsLogs(warId: string, fraction = '') { + const params = new URLSearchParams(); + params.append('fraction', fraction); + return this.http.get(this.config.apiLogsPath + '/' + warId + '/points', params) + .map(res => res.json()) + } + + getReviveLogs(warId: string, medicName = '', patientName = '', fraction = '', stabilizedOnly = false, reviveOnly = false) { + const params = new URLSearchParams(); + params.append('medic', medicName); + params.append('patient', patientName); + params.append('fraction', fraction); + params.append('stabilized', stabilizedOnly ? 'true' : ''); + params.append('revive', reviveOnly ? 'true' : ''); + return this.http.get(this.config.apiLogsPath + '/' + warId + '/revive', params) + .map(res => res.json()) + } + + getKillLogs(warId: string, shooterName = '', targetName = '', fraction = '', friendlyFireOnly = false, notFriendlyFireOnly = false) { + const params = new URLSearchParams(); + params.append('shooter', shooterName); + params.append('target', targetName); + params.append('fraction', fraction); + params.append('friendlyFire', friendlyFireOnly ? 'true' : ''); + params.append('noFriendlyFire', notFriendlyFireOnly ? 'true' : ''); + return this.http.get(this.config.apiLogsPath + '/' + warId + '/kills', params) + .map(res => res.json()) + } + + getTransportLogs(warId: string, driverName = '', passengerName = '', fraction = '') { + const params = new URLSearchParams(); + params.append('driver', driverName); + params.append('passenger', passengerName); + params.append('fraction', fraction); + return this.http.get(this.config.apiLogsPath + '/' + warId + '/transport', params) + .map(res => res.json()) + } + + getFlagLogs(warId: string, playerName = '', fraction = '', captureOnly = false, defendOnly = false) { + const params = new URLSearchParams(); + params.append('player', playerName); + params.append('fraction', fraction); + params.append('capture', captureOnly ? 'true' : ''); + params.append('defend', defendOnly ? 'true' : ''); + return this.http.get(this.config.apiLogsPath + '/' + warId + '/flag', params) + .map(res => res.json()) + } + +} diff --git a/static/src/app/services/player-service/player.service.ts b/static/src/app/services/logs/player.service.ts similarity index 100% rename from static/src/app/services/player-service/player.service.ts rename to static/src/app/services/logs/player.service.ts diff --git a/static/src/app/services/war-service/war.service.ts b/static/src/app/services/logs/war.service.ts similarity index 100% rename from static/src/app/services/war-service/war.service.ts rename to static/src/app/services/logs/war.service.ts diff --git a/static/src/app/squads/edit-squad/edit-squad.component.ts b/static/src/app/squads/edit-squad/edit-squad.component.ts index d4b46b7..6ebef35 100644 --- a/static/src/app/squads/edit-squad/edit-squad.component.ts +++ b/static/src/app/squads/edit-squad/edit-squad.component.ts @@ -2,7 +2,7 @@ import {Component, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {NgForm} from "@angular/forms"; import {Squad} from "../../models/model-interfaces"; -import {SquadService} from "../../services/squad-service/squad.service"; +import {SquadService} from "../../services/army-management/squad.service"; import {Subscription} from "rxjs/Subscription"; diff --git a/static/src/app/squads/squad-list/squad-list.component.ts b/static/src/app/squads/squad-list/squad-list.component.ts index 12fd6ec..fcb5582 100644 --- a/static/src/app/squads/squad-list/squad-list.component.ts +++ b/static/src/app/squads/squad-list/squad-list.component.ts @@ -5,7 +5,7 @@ import {FormControl} from "@angular/forms"; import {ActivatedRoute, Router} from "@angular/router"; import {Observable} from "rxjs/Observable"; import {Squad} from "../../models/model-interfaces"; -import {SquadService} from "../../services/squad-service/squad.service"; +import {SquadService} from "../../services/army-management/squad.service"; @Component({ selector: 'squad-list', diff --git a/static/src/app/squads/squads.module.ts b/static/src/app/squads/squads.module.ts index 41c63b7..919b3a9 100644 --- a/static/src/app/squads/squads.module.ts +++ b/static/src/app/squads/squads.module.ts @@ -3,7 +3,7 @@ import {CommonModule} from "@angular/common"; import {SharedModule} from "../shared.module"; import {squadRouterModule, squadsRoutingComponents} from "./squads.routing"; import {SquadStore} from "../services/stores/squad.store"; -import {SquadService} from "../services/squad-service/squad.service"; +import {SquadService} from "../services/army-management/squad.service"; import {ButtonsModule} from "ngx-bootstrap"; @NgModule({ diff --git a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts index 7f7b556..3b63160 100644 --- a/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts +++ b/static/src/app/statistic/campaign-player-detail/campaign-player-detail.component.ts @@ -1,7 +1,7 @@ import {Component} from "@angular/core"; import {ActivatedRoute} from "@angular/router"; import {CampaignPlayer} from "../../models/model-interfaces"; -import {PlayerService} from "../../services/player-service/player.service"; +import {PlayerService} from "../../services/logs/player.service"; import {ChartUtils} from "../../utils/chart-utils"; import {Location} from '@angular/common'; @@ -91,6 +91,10 @@ export class CampaignPlayerDetailComponent { this.reviveData = this.assignData(this.yAxisRevive, "revive"); this.captureData = this.assignData(this.yAxisCapture, "flagTouch"); + if (this.totalDeath === 0) { + // avoid infinite or NaN with no death + this.totalDeath = 1; + } this.kdRatio = parseFloat((this.totalKills / this.totalDeath).toFixed(2)); if (this.kdRatio > 1) this.maxKd = this.kdRatio * 1.7; diff --git a/static/src/app/statistic/campaign-submit/campaign-submit.component.ts b/static/src/app/statistic/campaign-submit/campaign-submit.component.ts index 8182d8e..59c67cf 100644 --- a/static/src/app/statistic/campaign-submit/campaign-submit.component.ts +++ b/static/src/app/statistic/campaign-submit/campaign-submit.component.ts @@ -2,7 +2,7 @@ import {Component, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {NgForm} from "@angular/forms"; import {Campaign} from "../../models/model-interfaces"; -import {CampaignService} from "../../services/campaign-service/campaign.service"; +import {CampaignService} from "../../services/logs/campaign.service"; @Component({ diff --git a/static/src/app/statistic/overview/stats-overview.component.ts b/static/src/app/statistic/overview/stats-overview.component.ts index 919a966..a5395a1 100644 --- a/static/src/app/statistic/overview/stats-overview.component.ts +++ b/static/src/app/statistic/overview/stats-overview.component.ts @@ -1,7 +1,7 @@ import {Component} from "@angular/core"; import {ActivatedRoute} from "@angular/router"; import {CarouselConfig} from "ngx-bootstrap"; -import {CampaignService} from "../../services/campaign-service/campaign.service"; +import {CampaignService} from "../../services/logs/campaign.service"; import {ChartUtils} from "../../utils/chart-utils"; diff --git a/static/src/app/statistic/stats.module.ts b/static/src/app/statistic/stats.module.ts index e2edbc4..8311f4e 100644 --- a/static/src/app/statistic/stats.module.ts +++ b/static/src/app/statistic/stats.module.ts @@ -2,18 +2,19 @@ import {NgModule} from '@angular/core'; import {CommonModule} from "@angular/common"; import {SharedModule} from "../shared.module"; import {statsRouterModule, statsRoutingComponents} from "./stats.routing"; -import {WarService} from "../services/war-service/war.service"; +import {WarService} from "../services/logs/war.service"; import {NgxChartsModule} from "@swimlane/ngx-charts"; -import {AccordionModule, CarouselModule} from "ngx-bootstrap"; -import {CampaignService} from "../services/campaign-service/campaign.service"; +import {AccordionModule, TabsModule} from "ngx-bootstrap"; +import {CampaignService} from "../services/logs/campaign.service"; import {NgxDatatableModule} from "@swimlane/ngx-datatable"; -import {PlayerService} from "../services/player-service/player.service"; +import {PlayerService} from "../services/logs/player.service"; +import {LogsService} from "../services/logs/logs.service"; @NgModule({ declarations: statsRoutingComponents, imports: [CommonModule, SharedModule, statsRouterModule, NgxChartsModule, - AccordionModule.forRoot(), CarouselModule.forRoot(), NgxDatatableModule], - providers: [WarService, CampaignService, PlayerService] + AccordionModule.forRoot(), TabsModule.forRoot(), NgxDatatableModule], + providers: [WarService, CampaignService, PlayerService, LogsService] }) export class StatsModule { static routes = statsRouterModule; diff --git a/static/src/app/statistic/war-detail/war-detail.component.css b/static/src/app/statistic/war-detail/war-detail.component.css index 83ff550..00eaf09 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.css +++ b/static/src/app/statistic/war-detail/war-detail.component.css @@ -1,9 +1,15 @@ .vertical-spacer { - height: 100vh; + height: 205px; float: left; width: 4%; } +.head-field { + font-size: 24px; + margin-top: 10px; + margin-bottom: 10px; +} + @media screen and (min-width: 1500px) { .vertical-spacer { width: 15%; @@ -36,6 +42,36 @@ color: blue; } +.chart-container { + width: 90%; + margin: 2%; + min-width: 900px; + height: 400px; + padding: 15px; + float: left; +} + +/* ########### TABS ########### */ + +:host /deep/ .nav-tabs { + padding-left: 35%!important; +} + +:host /deep/ .nav-link { + background: #4b4b4b; + color: white; +} + +:host /deep/ .nav-link:hover { + background: #286090; + color: #000; +} + +:host /deep/ .nav-tabs>li.active>a{ + background: #222222; + color: white; +} + /* ########### DATATABLE ########### */ :host /deep/ .datatable-header { diff --git a/static/src/app/statistic/war-detail/war-detail.component.html b/static/src/app/statistic/war-detail/war-detail.component.html index 525e7eb..64ab7dd 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.html +++ b/static/src/app/statistic/war-detail/war-detail.component.html @@ -1,21 +1,20 @@ -
-
-
-
-
+
+
+
+

{{war.title}} - vom {{war.date | date: 'dd.MM.yyyy'}}

-

+

Endpunktestand:

NATO {{war.ptBlufor}} | {{war.ptOpfor}} CSAT -

+
-

-

Teilnehmer:

+
+

Teilnehmer:

- +
-
+
Logfile anzeigen
@@ -51,37 +50,194 @@
+
- - - + + + + Scoreboard + +
+ + + {{value}} - - - - - {{value === 'BLUFOR' ? 'NATO' : 'CSAT'}} - - - - - - - - - -
+ + + + + {{value === 'BLUFOR' ? 'NATO' : 'CSAT'}} + + + + + + + + + + + + + + Fraktionen + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
diff --git a/static/src/app/statistic/war-detail/war-detail.component.ts b/static/src/app/statistic/war-detail/war-detail.component.ts index 2952cc4..232dc44 100644 --- a/static/src/app/statistic/war-detail/war-detail.component.ts +++ b/static/src/app/statistic/war-detail/war-detail.component.ts @@ -1,7 +1,10 @@ import {Component, ElementRef, ViewChild} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; -import {WarService} from "../../services/war-service/war.service"; +import {WarService} from "../../services/logs/war.service"; import {War} from "../../models/model-interfaces"; +import {LogsService} from "../../services/logs/logs.service"; +import {TabsetComponent} from "ngx-bootstrap"; +import * as d3 from "d3"; @Component({ @@ -11,14 +14,16 @@ import {War} from "../../models/model-interfaces"; }) export class WarDetailComponent { + @ViewChild('overview') private overviewContainer: ElementRef; + + @ViewChild('staticTabs') staticTabs: TabsetComponent; + war: War = {players: []}; fractionRadioSelect: string; playerChart: any[] = []; - @ViewChild('overview') private overviewContainer: ElementRef; - cellHeight = 40; rows = []; @@ -30,10 +35,55 @@ export class WarDetailComponent { sortDescending: 'glyphicon glyphicon-triangle-bottom', }; + pointData: any[] = []; + budgetData: any[] = []; + killData: any[] = []; + friendlyFireData: any[] = []; + transportData: any[] = []; + reviveData: any[] = []; + stabilizedData: any[] = []; + flagData: any[] = []; + + tmpPointData; + tmpBudgetData; + tmpKillData; + tmpFrienlyFireData; + tmpTransportData; + tmpReviveData; + tmpStabilizeData; + tmpFlagCaptureData; + + colorScheme = { + domain: ['#0000FF', '#B22222'] + }; + + yAxisLabelPoints = 'Punkte'; + yAxisLabelBudget = 'Budget'; + yAxisLabelKill = 'Kills'; + yAxisLabelFriendlyFire = 'FriendlyFire'; + yAxisLabelTransport = 'Lufttransport'; + yAxisLabelRevive = 'Revive'; + yAxisLabelStabilize = 'Stabilisiert'; + yAxisLabelFlag = 'Flaggenbesitz'; + + monotoneYCurve = d3.curveMonotoneY; + stepCurve = d3.curveStepAfter; + gradient = false; + yAxis = true; + xAxis = true; + legend = false; + legendTitle = false; + showXAxisLabel = false; + showYAxisLabel = true; + autoscale = true; + timeline = false; + roundDomains = true; + fractionChartsInitialized: boolean = false; + constructor(private route: ActivatedRoute, private router: Router, - private warService: WarService) { - Object.assign(this, this.playerChart) + private warService: WarService, + private logsService: LogsService) { } ngOnInit() { @@ -54,6 +104,29 @@ export class WarDetailComponent { "value": war.playersBlufor } ]; + this.tmpPointData = [ + { + "name": "NATO", + "series": [] + }, + { + "name": "CSAT", + "series": [] + } + ]; + this.tmpBudgetData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpKillData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpFrienlyFireData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpTransportData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpReviveData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpStabilizeData = JSON.parse(JSON.stringify(this.tmpPointData)); + this.tmpFlagCaptureData = JSON.parse(JSON.stringify(this.tmpPointData)); + + Object.assign(this, [this.playerChart, this.pointData, this.budgetData, this.killData, + this.friendlyFireData, this.transportData, this.reviveData, this.stabilizedData, this.flagData]); + + this.fractionChartsInitialized = false; + this.staticTabs.tabs[0].active = true; this.scrollOverviewTop(); }); } @@ -82,4 +155,212 @@ export class WarDetailComponent { } } + loadFractionData() { + if (!this.fractionChartsInitialized) { + const startDateObj = new Date(this.war.date); + startDateObj.setHours(0); + startDateObj.setMinutes(1); + + this.logsService.getFullLog(this.war._id).subscribe((data) => { + // POINTS + data.points.forEach(pointEntry => { + const dateObj = new Date(this.war.date); + const time = pointEntry.time.split(':'); + dateObj.setHours(time[0]); + dateObj.setMinutes(time[1]); + this.tmpPointData[0].series.push({ + "name": new Date(pointEntry.time), + "value": pointEntry.ptBlufor + }); + this.tmpPointData[1].series.push({ + "name": new Date(pointEntry.time), + "value": pointEntry.ptOpfor + }); + }); + + // BUDGET + this.tmpBudgetData[0].series.push({ + "name": startDateObj, + "value": this.war.budgetBlufor + }); + this.tmpBudgetData[1].series.push({ + "name": startDateObj, + "value": this.war.budgetOpfor + }); + data.budget.forEach(budgetEntry => { + this.tmpBudgetData[budgetEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(budgetEntry.time), + "value": budgetEntry.newBudget + }); + }); + + // KILLS + let killCountBlufor = 0; + let killCountOpfor = 0; + let ffKillCountBlufor = 0; + let ffKillCountOpfor = 0; + this.tmpKillData[0].series.push({ + "name": startDateObj, + "value": killCountBlufor + }); + this.tmpKillData[1].series.push({ + "name": startDateObj, + "value": killCountOpfor + }); + this.tmpFrienlyFireData[0].series.push({ + "name": startDateObj, + "value": ffKillCountBlufor + }); + this.tmpFrienlyFireData[1].series.push({ + "name": startDateObj, + "value": ffKillCountOpfor + }); + + data.kill.forEach(killEntry => { + if (killEntry.fraction === 'BLUFOR') { + if (killEntry.friendlyFire === false) { + killCountBlufor++; + } else { + ffKillCountBlufor++; + } + } else { + if (killEntry.friendlyFire === false) { + killCountOpfor++; + } else { + ffKillCountOpfor++; + } + } + this.tmpKillData[killEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(killEntry.time), + "value": killEntry.fraction === 'BLUFOR' ? killCountBlufor : killCountOpfor + }); + this.tmpFrienlyFireData[killEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(killEntry.time), + "value": killEntry.fraction === 'BLUFOR' ? ffKillCountBlufor : ffKillCountOpfor + }); + }); + + // TRANSPORT + let transportCountBlufor = 0; + let transportCountOpfor = 0; + this.tmpTransportData[0].series.push({ + "name": startDateObj, + "value": transportCountBlufor + }); + this.tmpTransportData[1].series.push({ + "name": startDateObj, + "value": transportCountOpfor + }); + + data.transport.forEach(transportEntry => { + if (transportEntry.fraction === 'BLUFOR') { + transportCountBlufor++; + } else { + transportCountOpfor++; + } + this.tmpTransportData[transportEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(transportEntry.time), + "value": transportEntry.fraction === 'BLUFOR' ? transportCountBlufor : transportCountOpfor + }); + }); + + // REVIVE & STABILIZE + let reviveCountBlufor = 0; + let reviveCountOpfor = 0; + let stabilizeCountBlufor = 0; + let stabilizeCountOpfor = 0; + this.tmpReviveData[0].series.push({ + "name": startDateObj, + "value": reviveCountBlufor + }); + this.tmpReviveData[1].series.push({ + "name": startDateObj, + "value": reviveCountOpfor + }); + this.tmpStabilizeData[0].series.push({ + "name": startDateObj, + "value": stabilizeCountBlufor + }); + this.tmpStabilizeData[1].series.push({ + "name": startDateObj, + "value": stabilizeCountOpfor + }); + data.revive.forEach(reviveEntry => { + if (reviveEntry.fraction === 'BLUFOR') { + if (reviveEntry.stabilized === false) { + reviveCountBlufor++; + } else { + reviveCountOpfor++; + } + } else { + if (reviveEntry.stabilized === false) { + stabilizeCountBlufor++; + } else { + stabilizeCountOpfor++; + } + } + this.tmpReviveData[reviveEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(reviveEntry.time), + "value": reviveEntry.fraction === 'BLUFOR' ? reviveCountBlufor : reviveCountOpfor + }); + this.tmpStabilizeData[reviveEntry.fraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(reviveEntry.time), + "value": reviveEntry.fraction === 'BLUFOR' ? stabilizeCountBlufor : stabilizeCountOpfor + }); + }); + + // FLAG + let flagStatusBlufor = true; + let flagStatusOpfor = true; + this.tmpFlagCaptureData[0].series.push({ + "name": startDateObj, + "value": flagStatusBlufor + }); + this.tmpFlagCaptureData[1].series.push({ + "name": startDateObj, + "value": flagStatusOpfor + }); + + data.flag.forEach(flagEntry => { + if (flagEntry.flagFraction === 'BLUFOR') { + flagStatusBlufor = !flagEntry.capture + } else { + flagStatusOpfor = !flagEntry.capture; + } + this.tmpFlagCaptureData[flagEntry.flagFraction === 'BLUFOR' ? 0 : 1].series.push({ + "name": new Date(flagEntry.time), + "value": flagEntry.flagFraction === 'BLUFOR' ? flagStatusBlufor : flagStatusOpfor + }); + }); + + this.addFinalTimeDataEntriesAndPopulate(new Date(this.war.endDate)); + + this.fractionChartsInitialized = true; + }); + } + } + + addFinalTimeDataEntriesAndPopulate(endDate) { + [this.tmpPointData, this.tmpBudgetData, this.tmpTransportData, this.tmpReviveData, this.tmpStabilizeData, + this.tmpKillData, this.tmpFrienlyFireData, this.tmpFlagCaptureData].forEach(tmp => { + for (let j in [0, 1]) { + if (tmp[j].series[tmp[j].series.length - 1].name < endDate) { + tmp[j].series.push({ + 'name': endDate, + 'value': tmp[j].series[tmp[j].series.length - 1].value + } + ) + } + } + }); + this.pointData = this.tmpPointData; + this.budgetData = this.tmpBudgetData; + this.transportData = this.tmpTransportData; + this.reviveData = this.tmpReviveData; + this.stabilizedData = this.tmpStabilizeData; + this.killData = this.tmpKillData; + this.friendlyFireData = this.tmpFrienlyFireData; + this.flagData = this.tmpFlagCaptureData; + } + } diff --git a/static/src/app/statistic/war-list/war-item.component.ts b/static/src/app/statistic/war-list/war-item.component.ts index fcbff1c..cbb63b4 100644 --- a/static/src/app/statistic/war-list/war-item.component.ts +++ b/static/src/app/statistic/war-list/war-item.component.ts @@ -1,6 +1,6 @@ import {ChangeDetectionStrategy, Component, EventEmitter} from "@angular/core"; import {War} from "../../models/model-interfaces"; -import {LoginService} from "../../services/login-service/login-service"; +import {LoginService} from "../../services/app-user-service/login-service"; @Component({ selector: 'pjm-war-item', diff --git a/static/src/app/statistic/war-list/war-list.component.ts b/static/src/app/statistic/war-list/war-list.component.ts index e3f515c..f109160 100644 --- a/static/src/app/statistic/war-list/war-list.component.ts +++ b/static/src/app/statistic/war-list/war-list.component.ts @@ -1,9 +1,9 @@ import {Component, OnInit} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; import {Campaign, War} from "../../models/model-interfaces"; -import {WarService} from "../../services/war-service/war.service"; -import {LoginService} from "../../services/login-service/login-service"; -import {CampaignService} from "../../services/campaign-service/campaign.service"; +import {WarService} from "../../services/logs/war.service"; +import {LoginService} from "../../services/app-user-service/login-service"; +import {CampaignService} from "../../services/logs/campaign.service"; import {RouteConfig} from "../../app.config"; @Component({ diff --git a/static/src/app/statistic/war-submit/war-submit.component.html b/static/src/app/statistic/war-submit/war-submit.component.html index cf7b4fd..cca4dff 100644 --- a/static/src/app/statistic/war-submit/war-submit.component.html +++ b/static/src/app/statistic/war-submit/war-submit.component.html @@ -12,36 +12,6 @@
-
- - - -
- -
- - - -
- -
- - - -
-