Merge branch 'release/v1.7.0' of hardi/opt-cc into master

pull/35/head v1.7.0
hardi 2018-03-04 20:05:44 +01:00 committed by Gogs
commit 08aeda0de3
92 changed files with 2573 additions and 2307 deletions

View File

@ -12,8 +12,16 @@ let check = (requiredPermission, actualPermission, res, next) => {
}; };
module.exports = { module.exports = {
checkSql: (req, res, next) => { check(1, req.user.permission, res, next) }, checkSql: (req, res, next) => {
checkHl: (req, res, next) => { check(2, req.user.permission, res, next) }, check(1, req.user.permission, res, next)
checkMT: (req, res, next) => { check(3, req.user.permission, res, next) }, },
checkAdmin: (req, res, next) => { check(4, req.user.permission, res, next) } checkHl: (req, res, next) => {
check(2, req.user.permission, res, next)
},
checkMT: (req, res, next) => {
check(3, req.user.permission, res, next)
},
checkAdmin: (req, res, next) => {
check(4, req.user.permission, res, next)
}
}; };

View File

@ -36,6 +36,6 @@ const LogTransportSchema = new Schema({
collection: 'logTransport' collection: 'logTransport'
}); });
// optional more indices // optional more indices
LogTransportSchema.index({war: 1}); LogTransportSchema.index({war: 1, driver: 1});
module.exports = mongoose.model('LogTransport', LogTransportSchema); module.exports = mongoose.model('LogTransport', LogTransportSchema);

View File

@ -0,0 +1,34 @@
"use strict";
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const LogVehicleKillSchema = 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
},
fraction: {
type: String,
enum: ['BLUFOR', 'OPFOR', 'NONE'],
required: true
}
}, {
collection: 'logVehicle'
});
// optional more indices
LogVehicleKillSchema.index({war: 1, shooter: 1, target: 1});
module.exports = mongoose.model('LogVehicle', LogVehicleKillSchema);

View File

@ -24,6 +24,12 @@ const PlayerSchema = new Schema({
set: v => Math.round(v), set: v => Math.round(v),
required: true required: true
}, },
vehicle: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
required: true
},
death: { death: {
type: Number, type: Number,
get: v => Math.round(v), get: v => Math.round(v),
@ -58,12 +64,17 @@ const PlayerSchema = new Schema({
type: Number, type: Number,
get: v => Math.round(v), get: v => Math.round(v),
set: v => Math.round(v) set: v => Math.round(v)
},
steamUUID: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v)
} }
}, { }, {
collection: 'player', collection: 'player',
timestamps: {createdAt: 'timestamp'} timestamps: {createdAt: 'timestamp'}
}); });
// optional more indices // optional more indices
PlayerSchema.index({timestamp: 1}); PlayerSchema.index({warId: 1});
module.exports = mongoose.model('Player', PlayerSchema); module.exports = mongoose.model('Player', PlayerSchema);

View File

@ -45,7 +45,8 @@ account.route('/:id')
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
// reset attributes that are missing.
AppUserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}).populate('squad').exec((err, item) => { AppUserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}).populate('squad').exec((err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
@ -70,7 +71,8 @@ account.route('/:id')
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true; res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter next(err); // this works because err is in normal case undefined and that is the same as no parameter
}); });

View File

@ -44,8 +44,8 @@ awarding.route('/')
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
// with return before (or after) the next(err) we prevent that the code continues here after next(err) has finished. // with return before (or after) the next(err) we prevent that the code continues here after next(err)
// this saves an extra else {..} // has finished. this saves an extra else {..}
} }
// if the collection is empty we do not send empty arrays back. // if the collection is empty we do not send empty arrays back.
if (items && items.length > 0) { if (items && items.length > 0) {
@ -60,8 +60,8 @@ awarding.route('/')
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
// with return before (or after) the next(err) we prevent that the code continues here after next(err) has finished. // with return before (or after) the next(err) we prevent that the code continues here after next(err)
// this saves an extra else {..} // has finished. this saves an extra else {..}
} }
let results = []; let results = [];
if (req.query.fractFilter) { if (req.query.fractFilter) {
@ -118,7 +118,8 @@ awarding.route('/:id')
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
// reset attributes that are missing.
AwardingModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { AwardingModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
@ -143,7 +144,8 @@ awarding.route('/:id')
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true; res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter next(err); // this works because err is in normal case undefined and that is the same as no parameter
}); });

View File

@ -34,7 +34,14 @@ decoration.route('/')
if (req.query.q) { if (req.query.q) {
filter.name = {$regex: req.query.q, $options: 'i'} filter.name = {$regex: req.query.q, $options: 'i'}
} }
DecorationModel.find(filter, {}, {sort: {fraction: 'asc', isMedal: 'asc', sortingNumber: 'asc', name: 'asc'}}, (err, items) => { DecorationModel.find(filter, {}, {
sort: {
fraction: 'asc',
isMedal: 'asc',
sortingNumber: 'asc',
name: 'asc'
}
}, (err, items) => {
if (err) { if (err) {
err.status = codes.servererror; err.status = codes.servererror;
return next(err); return next(err);
@ -112,7 +119,8 @@ decoration.route('/:id')
}); });
} }
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need
// to reset attributes that are missing.
DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
@ -147,7 +155,8 @@ decoration.route('/:id')
if (err) next(err); if (err) next(err);
}); });
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true; res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter next(err); // this works because err is in normal case undefined and that is the same as no parameter
}); });

View File

@ -13,6 +13,7 @@ const LogBudgetModel = require('../models/logs/budget');
const LogRespawnModel = require('../models/logs/respawn'); const LogRespawnModel = require('../models/logs/respawn');
const LogReviveModel = require('../models/logs/revive'); const LogReviveModel = require('../models/logs/revive');
const LogKillModel = require('../models/logs/kill'); const LogKillModel = require('../models/logs/kill');
const LogVehicleModel = require('../models/logs/vehicle');
const LogTransportModel = require('../models/logs/transport'); const LogTransportModel = require('../models/logs/transport');
const LogFlagModel = require('../models/logs/flag'); const LogFlagModel = require('../models/logs/flag');
const LogPointsModel = require('../models/logs/points'); const LogPointsModel = require('../models/logs/points');
@ -43,6 +44,7 @@ logsRouter.route('/:warId')
const respawnObjects = LogRespawnModel.find(filter, {}, sort); const respawnObjects = LogRespawnModel.find(filter, {}, sort);
const reviveObjects = LogReviveModel.find(filter, {}, sort); const reviveObjects = LogReviveModel.find(filter, {}, sort);
const killObjects = LogKillModel.find(filter, {}, sort); const killObjects = LogKillModel.find(filter, {}, sort);
const vehicleObjects = LogVehicleModel.find(filter, {}, sort);
const transportObjects = LogTransportModel.find(filter, {}, sort); const transportObjects = LogTransportModel.find(filter, {}, sort);
const flagObjects = LogFlagModel.find(filter, {}, sort); const flagObjects = LogFlagModel.find(filter, {}, sort);
const resources = { const resources = {
@ -51,6 +53,7 @@ logsRouter.route('/:warId')
respawn: respawnObjects.exec.bind(respawnObjects), respawn: respawnObjects.exec.bind(respawnObjects),
revive: reviveObjects.exec.bind(reviveObjects), revive: reviveObjects.exec.bind(reviveObjects),
kill: killObjects.exec.bind(killObjects), kill: killObjects.exec.bind(killObjects),
vehicle: killObjects.exec.bind(vehicleObjects),
transport: transportObjects.exec.bind(transportObjects), transport: transportObjects.exec.bind(transportObjects),
flag: flagObjects.exec.bind(flagObjects) flag: flagObjects.exec.bind(flagObjects)
}; };

View File

@ -14,6 +14,9 @@ const CampaignModel = require('../models/campaign');
const PlayerModel = require('../models/player'); const PlayerModel = require('../models/player');
const WarModel = require('../models/war'); const WarModel = require('../models/war');
// Util
const isSteamUUID = require('../tools/util').isSteamUUID;
const campaignPlayer = express.Router(); const campaignPlayer = express.Router();
// routes ********************** // routes **********************
@ -33,13 +36,28 @@ campaignPlayer.route('/ranking/:campaignId')
} }
const rankingItems = []; const rankingItems = [];
new Set(items.map(x => x.name)).forEach(playerName => {
const playerInstances = items.filter(p => p.name === playerName); // check only first player to have valid steamUUID - then decide if tracked by name or by ID
const resItem = {name: playerName, kill: 0, death: 0, friendlyFire: 0, revive: 0, respawn: 0, flagTouch: 0}; const usesUUID = isSteamUUID(items[0].steamUUID);
new Set(items.map(usesUUID ? x => x.steamUUID : x => x.name))
.forEach(player => {
const playerInstances = items.filter(usesUUID ? p => p.steamUUID === player : p => p.name === player);
const resItem = {
name: usesUUID ? playerInstances[playerInstances.length - 1].name : player,
kill: 0,
vehicle: 0,
death: 0,
friendlyFire: 0,
revive: 0,
respawn: 0,
flagTouch: 0
};
for (let i = 0; i < playerInstances.length; i++) { for (let i = 0; i < playerInstances.length; i++) {
resItem.kill += playerInstances[i].kill; resItem.kill += playerInstances[i].kill;
resItem.death += playerInstances[i].death; resItem.death += playerInstances[i].death;
resItem.friendlyFire += playerInstances[i].friendlyFire; resItem.friendlyFire += playerInstances[i].friendlyFire;
resItem.vehicle += playerInstances[i].vehicle;
resItem.revive += playerInstances[i].revive; resItem.revive += playerInstances[i].revive;
resItem.respawn += playerInstances[i].respawn; resItem.respawn += playerInstances[i].respawn;
resItem.flagTouch += playerInstances[i].flagTouch; resItem.flagTouch += playerInstances[i].flagTouch;
@ -62,6 +80,7 @@ campaignPlayer.route('/ranking/:campaignId')
kill: getSortedField('kill'), kill: getSortedField('kill'),
death: getSortedField('death'), death: getSortedField('death'),
friendlyFire: getSortedField('friendlyFire'), friendlyFire: getSortedField('friendlyFire'),
vehicle: getSortedField('vehicle'),
revive: getSortedField('revive'), revive: getSortedField('revive'),
respawn: getSortedField('respawn'), respawn: getSortedField('respawn'),
flagTouch: getSortedField('flagTouch') flagTouch: getSortedField('flagTouch')
@ -75,7 +94,7 @@ campaignPlayer.route('/ranking/:campaignId')
routerHandling.httpMethodNotAllowed routerHandling.httpMethodNotAllowed
); );
campaignPlayer.route('/single/:campaignId/:playerName') campaignPlayer.route('/single/:campaignId/:playerId')
.get((req, res, next) => { .get((req, res, next) => {
CampaignModel.findById(req.params.campaignId, (err, campaign) => { CampaignModel.findById(req.params.campaignId, (err, campaign) => {
if (err) return next(err); if (err) return next(err);
@ -84,17 +103,24 @@ campaignPlayer.route('/single/:campaignId/:playerName')
const warIds = wars.map((obj) => { const warIds = wars.map((obj) => {
return obj._id; return obj._id;
}); });
PlayerModel.find({name: req.params.playerName, warId: {"$in": warIds}})
// find by player name until v1.6.12, afterwards by SteamUUID
const playerId = req.params.playerId;
const filter = {};
filter[isSteamUUID(playerId) ? 'steamUUID' : 'name'] = playerId;
filter['warId'] = {"$in": warIds};
PlayerModel.find(filter)
.populate('warId') .populate('warId')
.exec((err, items) => { .exec((err, items) => {
if (err) return next(err); if (err) return next(err);
if (!items || items.length === 0) { if (!items || items.length === 0) {
const err = new Error('Unknown player name'); const err = new Error('Unknown player id');
err.status = codes.notfound; err.status = codes.notfound;
return next(err) return next(err)
} }
res.locals.items = { res.locals.items = {
name: req.params.playerName, name: items[items.length - 1].name,
campaign: campaign, campaign: campaign,
players: items players: items
}; };

View File

@ -114,7 +114,8 @@ ranks.route('/:id')
}); });
} }
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
// reset attributes that are missing.
RankModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { RankModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
@ -139,7 +140,8 @@ ranks.route('/:id')
err = new Error("item not found"); err = new Error("item not found");
err.status = codes.notfound; err.status = codes.notfound;
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true; res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter next(err); // this works because err is in normal case undefined and that is the same as no parameter
}) })

View File

@ -141,7 +141,8 @@ request.route('/promotion/:id')
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
// reset attributes that are missing.
PromotionModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { PromotionModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;

View File

@ -116,7 +116,8 @@ squads.route('/:id')
}); });
} }
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
// reset attributes that are missing.
SquadModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { SquadModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
@ -147,7 +148,8 @@ squads.route('/:id')
if (err) next(err); if (err) next(err);
}); });
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true; res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter next(err); // this works because err is in normal case undefined and that is the same as no parameter
}); });

View File

@ -118,7 +118,8 @@ users.route('/:id')
req.body.updatedAt = new Date(); req.body.updatedAt = new Date();
req.body.$inc = {__v: 1}; req.body.$inc = {__v: 1};
// PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to
// reset attributes that are missing.
UserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { UserModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => {
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
@ -156,7 +157,8 @@ users.route('/:id')
// main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema // main difference of PUT and PATCH is that PUT expects all data in request: checked by using the schema
const user = new UserModel(req.body); const user = new UserModel(req.body);
UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) { UserModel.findById(req.params.id, req.body, {new: true}, function (err, item) {
// with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the old one. // with parameter {new: true} the TweetNModel will return the new and changed object from the DB and not the
// old one.
if (err) { if (err) {
err.status = codes.wrongrequest; err.status = codes.wrongrequest;
return next(err); return next(err);
@ -225,7 +227,8 @@ users.route('/:id')
}); });
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true; res.locals.processed = true;
next(err); // this works because err is in normal case undefined and that is the same as no parameter next(err); // this works because err is in normal case undefined and that is the same as no parameter
}); });

View File

@ -27,6 +27,7 @@ const CampaignModel = require('../models/campaign');
const WarModel = require('../models/war'); const WarModel = require('../models/war');
const PlayerModel = require('../models/player'); const PlayerModel = require('../models/player');
const LogKillModel = require('../models/logs/kill'); const LogKillModel = require('../models/logs/kill');
const LogVehicleKillModel = require('../models/logs/vehicle');
const LogRespawnModel = require('../models/logs/respawn'); const LogRespawnModel = require('../models/logs/respawn');
const LogReviveModel = require('../models/logs/revive'); const LogReviveModel = require('../models/logs/revive');
const LogTransportModel = require('../models/logs/transport'); const LogTransportModel = require('../models/logs/transport');
@ -92,6 +93,8 @@ wars.route('/')
return next(err); return next(err);
} }
LogKillModel.create(statsResult.kills, function () { LogKillModel.create(statsResult.kills, function () {
LogVehicleKillModel.create(statsResult.vehicles, function () {
LogRespawnModel.create(statsResult.respawn, function () { LogRespawnModel.create(statsResult.respawn, function () {
LogReviveModel.create(statsResult.revive, function () { LogReviveModel.create(statsResult.revive, function () {
LogFlagModel.create(statsResult.flag, function () { LogFlagModel.create(statsResult.flag, function () {
@ -129,6 +132,7 @@ wars.route('/')
}) })
}) })
}) })
})
}); });
} else { } else {
@ -207,7 +211,8 @@ wars.route('/:id')
fs.rmdir(warDir, (err) => { fs.rmdir(warDir, (err) => {
}); });
} }
// we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler user.use(..) // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last handler
// user.use(..)
res.locals.processed = true; res.locals.processed = true;
next(); next();

View File

@ -5,6 +5,7 @@ const playerArrayContains = require('./util').playerArrayContains;
const WHITESPACE = ' '; const WHITESPACE = ' ';
const parseWarLog = (lineArray, war) => { const parseWarLog = (lineArray, war) => {
const NAME_TOO_LONG_ERROR = 'Error: ENAMETOOLONG: name too long, open \''; const NAME_TOO_LONG_ERROR = 'Error: ENAMETOOLONG: name too long, open \'';
const stats = { const stats = {
@ -13,6 +14,7 @@ const parseWarLog = (lineArray, war) => {
budget: [], budget: [],
points: [], points: [],
kills: [], kills: [],
vehicles: [],
respawn: [], respawn: [],
revive: [], revive: [],
flag: [], flag: [],
@ -20,10 +22,21 @@ const parseWarLog = (lineArray, war) => {
players: [] players: []
}; };
const addPlayerIfNotExists = (inputPlayer) => { const vehicleBlacklist = [
'Prowler (Unbewaffnet)', 'Prowler (Bewaffnet)', 'Hunter',
'HEMTT Transporter', 'HEMTT Transporter (abgedeckt)', 'HEMTT SanitÀtsfahrzeug',
'Remote Designator [NATO]', 'UGV Stomper',
'Qilin (Unbewaffnet)', 'Qilin (Bewaffnet)', 'Ifrit',
'Tempest-Transporter', 'Tempest-Transporter (abgedeckt)', 'Tempest SanitÀtsfahrzeug',
'Remote Designator [CSAT]', 'UBF Saif',
'Quad Bike', 'HuntIR'
];
const addPlayerIfNotExists = (inputPlayer, steamUUID) => {
const player = getPlayerAndFractionFromString(inputPlayer); const player = getPlayerAndFractionFromString(inputPlayer);
if (player && player.name && player.fraction && !playerArrayContains(stats.players, player)) { if (player && player.name && player.fraction && !playerArrayContains(stats.players, player)) {
player['warId'] = war._id; player['warId'] = war._id;
player['steamUUID'] = steamUUID;
stats.players.push(player); stats.players.push(player);
} }
}; };
@ -37,15 +50,29 @@ const parseWarLog = (lineArray, war) => {
} }
/** /**
* KILLS * KILLS & VEHICLE KILLS
*/ */
if (line.includes('(Abschuss)') && !line.includes('Fahrzeug')) { if (line.includes('(Abschuss)')) {
stats.clean.push(line); stats.clean.push(line);
const shooterString = line.substring(line.lastIndexOf(' von: ') + 6, line.lastIndexOf('."')); const shooterString = line.substring(line.lastIndexOf(' von: ') + 6, line.lastIndexOf('."'));
const shooter = getPlayerAndFractionFromString(shooterString); const shooter = getPlayerAndFractionFromString(shooterString);
if (line.includes('Fahrzeug:')) {
const targetString = line.substring(line.lastIndexOf(' --- Fahrzeug: ') + 15, line.lastIndexOf(' von:'));
const target = getVehicleAndFractionFromString(targetString);
if (target && shooter && target.fraction !== shooter.fraction) {
stats.vehicles.push({
war: war._id,
time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]),
shooter: shooter ? shooter.name : null,
target: target ? target.name : null,
fraction: shooter ? shooter.fraction : 'NONE'
})
}
} else {
const targetString = line.substring(line.lastIndexOf(' --- ') + 5, line.lastIndexOf(' von:')); const targetString = line.substring(line.lastIndexOf(' --- ') + 5, line.lastIndexOf(' von:'));
const target = getPlayerAndFractionFromString(targetString); const target = getPlayerAndFractionFromString(targetString);
stats.kills.push({ stats.kills.push({
war: war._id, war: war._id,
time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]), time: getFullTimeDate(war.date, line.split(WHITESPACE)[5]),
@ -55,6 +82,7 @@ const parseWarLog = (lineArray, war) => {
fraction: shooter ? shooter.fraction : 'NONE' fraction: shooter ? shooter.fraction : 'NONE'
}); });
} }
}
/** /**
* BUDGET * BUDGET
@ -171,7 +199,8 @@ const parseWarLog = (lineArray, war) => {
*/ */
else if (line.includes('(Fraktionsuebersicht)')) { else if (line.includes('(Fraktionsuebersicht)')) {
const playerString = line.substring(line.lastIndexOf('--- ') + 4, line.lastIndexOf(', PUID')); const playerString = line.substring(line.lastIndexOf('--- ') + 4, line.lastIndexOf(', PUID'));
addPlayerIfNotExists(playerString) const playerUUID = line.substring(line.lastIndexOf('PUID ') + 5, line.lastIndexOf('"'));
addPlayerIfNotExists(playerString, playerUUID)
} }
}); });
@ -179,6 +208,7 @@ const parseWarLog = (lineArray, war) => {
const playerName = stats.players[i].name; const playerName = stats.players[i].name;
stats.players[i]['respawn'] = stats.respawn.filter(res => res.player === playerName).length; 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]['kill'] = stats.kills.filter(kill => kill.shooter === playerName && !kill.friendlyFire).length;
stats.players[i]['vehicle'] = stats.vehicles.filter(vehicle => vehicle.shooter === playerName && vehicleBlacklist.indexOf(vehicle.target) < 0).length;
stats.players[i]['friendlyFire'] = 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]['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]['revive'] = stats.revive.filter(rev => rev.medic === playerName && !rev.stabilized).length;
@ -231,6 +261,21 @@ const getPlayerAndFractionFromString = (nameAndFractionString) => {
} }
}; };
const getVehicleAndFractionFromString = (nameAndFractionString) => {
const nameArray = nameAndFractionString.trim().split(WHITESPACE);
const fractionPart = nameArray[nameArray.length - 1];
// escape it is some parachute fraction identifier
if (fractionPart === 'OPF_F' || fractionPart === 'BLU_F') {
return;
}
const fraction = fractionPart === '(OPT_NATO)' || fractionPart === '(OPT_NATO_T)' ? 'BLUFOR' : 'OPFOR';
const name = nameAndFractionString.substring(0, nameAndFractionString.indexOf(fractionPart) - 1);
return {name: name, fraction: fraction};
};
const transformMoneyString = (budgetString) => { const transformMoneyString = (budgetString) => {
if (!budgetString.includes('e+')) { if (!budgetString.includes('e+')) {
return parseInt(budgetString); return parseInt(budgetString);

View File

@ -1,5 +1,10 @@
"use strict"; "use strict";
const isSteamUUID = (input) => {
const steamUIDPattern = new RegExp("[0-9]{17}");
return steamUIDPattern.test(input)
};
const sortCollectionBy = (collection, key) => { const sortCollectionBy = (collection, key) => {
collection.sort((a, b) => { collection.sort((a, b) => {
a = a[key].toLowerCase(); a = a[key].toLowerCase();
@ -44,6 +49,7 @@ const decimalToTimeString = (decimal) => {
(seconds < 10 ? '0' + seconds : seconds); (seconds < 10 ? '0' + seconds : seconds);
}; };
exports.isSteamUUID = isSteamUUID;
exports.sortCollection = sortCollectionBy; exports.sortCollection = sortCollectionBy;
exports.playerArrayContains = playerArrayContains; exports.playerArrayContains = playerArrayContains;
exports.timeStringToDecimal = timeStringToDecimal; exports.timeStringToDecimal = timeStringToDecimal;

View File

@ -1,6 +1,6 @@
{ {
"name": "opt-cc", "name": "opt-cc",
"version": "1.6.12", "version": "1.7.0",
"author": "Florian Hartwich <hardi@noarch.de>", "author": "Florian Hartwich <hardi@noarch.de>",
"private": true, "private": true,
"scripts": { "scripts": {

View File

@ -2,7 +2,8 @@
<div class="army-member-view-container"> <div class="army-member-view-container">
<div class="return-button"> <div class="return-button">
<span class="btn btn-default" style="position:absolute;" (click)="backToOverview()">< zurück zur Übersicht</span> <span class="btn btn-default" style="position:absolute;" (click)="backToOverview()">< zurück zur Übersicht</span>
<h3 class="text-center" style="font-weight: 600" [style.color]="user.squadId?.fraction === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR"> <h3 class="text-center" style="font-weight: 600"
[style.color]="user.squadId?.fraction === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
Auszeichnungen von {{user.username}} Auszeichnungen von {{user.username}}
</h3> </h3>
</div> </div>

View File

@ -24,6 +24,7 @@ export interface Player {
name?: string; name?: string;
warId?: War; warId?: War;
kill?: number; kill?: number;
vehicle?: number;
death?: number; death?: number;
friendlyFire?: number; friendlyFire?: number;
revive?: number; revive?: number;

View File

@ -38,7 +38,8 @@ export class UserService {
} }
}).map(res => res.json()).do((users) => { }).map(res => res.json()).do((users) => {
this.userStore.dispatch({type: action, data: users}); this.userStore.dispatch({type: action, data: users});
}).subscribe(_ => {}); }).subscribe(_ => {
});
return this.users$; return this.users$;
} }

View File

@ -81,6 +81,26 @@
</ngx-charts-line-chart> </ngx-charts-line-chart>
</div> </div>
<div class="chart-container">
<ngx-charts-line-chart
[results]="vehicleKillData"
[showRefLines]="showRefLines"
[showRefLabels]="showRefLabels"
[referenceLines]="killRefLines"
[scheme]="colorScheme"
[gradient]="gradient"
[xAxis]="xAxis"
[yAxis]="yAxis"
[legend]="legend"
[showXAxisLabel]="showXAxisLabel"
[showYAxisLabel]="showYAxisLabel"
[yAxisLabel]="yAxisVehicleKill"
[autoScale]="autoscale"
[timeline]="timeline"
[roundDomains]="roundDomains">
</ngx-charts-line-chart>
</div>
<div class="chart-container"> <div class="chart-container">
<ngx-charts-line-chart <ngx-charts-line-chart
[results]="deathData" [results]="deathData"

View File

@ -25,6 +25,7 @@ export class CampaignPlayerDetailComponent {
sumData: any[] = []; sumData: any[] = [];
killData: any[] = []; killData: any[] = [];
friendlyFireData: any[] = []; friendlyFireData: any[] = [];
vehicleKillData: any[] = [];
deathData: any[] = []; deathData: any[] = [];
respawnData: any[] = []; respawnData: any[] = [];
reviveData: any[] = []; reviveData: any[] = [];
@ -32,6 +33,7 @@ export class CampaignPlayerDetailComponent {
yAxisKill = 'Kills'; yAxisKill = 'Kills';
yAxisFriendlyFire = 'FriendlyFire'; yAxisFriendlyFire = 'FriendlyFire';
yAxisVehicleKill = 'Farzeug-Kills';
yAxisDeath = 'Tode'; yAxisDeath = 'Tode';
yAxisRespawn = 'Respawn'; yAxisRespawn = 'Respawn';
yAxisRevive = 'Revive'; yAxisRevive = 'Revive';
@ -49,6 +51,7 @@ export class CampaignPlayerDetailComponent {
showRefLines = true; showRefLines = true;
showRefLabels = true; showRefLabels = true;
killRefLines = []; killRefLines = [];
vehicleKillRefLines = [];
deathRefLines = []; deathRefLines = [];
captureRefLines = []; captureRefLines = [];
friendlyFireRefLines = []; friendlyFireRefLines = [];
@ -67,6 +70,7 @@ export class CampaignPlayerDetailComponent {
totalKills; totalKills;
totalFriendlyFire; totalFriendlyFire;
totalVehicleKills;
totalDeath; totalDeath;
totalRespawn; totalRespawn;
totalRevive; totalRevive;
@ -87,6 +91,7 @@ export class CampaignPlayerDetailComponent {
.subscribe(campaignPlayer => { .subscribe(campaignPlayer => {
this.campaignPlayer = campaignPlayer; this.campaignPlayer = campaignPlayer;
this.killData = this.assignData(this.yAxisKill, "kill"); this.killData = this.assignData(this.yAxisKill, "kill");
this.vehicleKillData = this.assignData(this.yAxisVehicleKill, "vehicle");
this.friendlyFireData = this.assignData(this.yAxisFriendlyFire, "friendlyFire"); this.friendlyFireData = this.assignData(this.yAxisFriendlyFire, "friendlyFire");
this.deathData = this.assignData(this.yAxisDeath, "death"); this.deathData = this.assignData(this.yAxisDeath, "death");
this.respawnData = this.assignData(this.yAxisRespawn, "respawn"); this.respawnData = this.assignData(this.yAxisRespawn, "respawn");
@ -107,6 +112,10 @@ export class CampaignPlayerDetailComponent {
name: this.yAxisFriendlyFire, name: this.yAxisFriendlyFire,
value: this.totalFriendlyFire value: this.totalFriendlyFire
}, },
{
name: this.yAxisVehicleKill,
value: this.totalVehicleKills
},
{ {
name: this.yAxisDeath, name: this.yAxisDeath,
value: this.totalDeath value: this.totalDeath
@ -125,7 +134,7 @@ export class CampaignPlayerDetailComponent {
} }
]; ];
Object.assign(this, [this.sumData, this.killData, this.friendlyFireData, this.deathData, this.respawnData, this.reviveData, this.captureData]); Object.assign(this, [this.sumData, this.killData, this.friendlyFireData, this.vehicleKillData, this.deathData, this.respawnData, this.reviveData, this.captureData]);
}); });
} }
@ -138,12 +147,12 @@ export class CampaignPlayerDetailComponent {
let total = 0; let total = 0;
for (let i = 0; i < playerLength; i++) { for (let i = 0; i < playerLength; i++) {
const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date); const warDateString = ChartUtils.getShortDateString(this.campaignPlayer.players[i].warId.date);
const warKills = this.campaignPlayer.players[i][field]; const value = this.campaignPlayer.players[i][field];
killObj.series.push({ killObj.series.push({
name: warDateString, name: warDateString,
value: warKills value: value
}); });
total += warKills; total += value;
} }
switch (field) { switch (field) {
case 'kill': case 'kill':
@ -154,6 +163,10 @@ export class CampaignPlayerDetailComponent {
this.friendlyFireRefLines.push({value: total / playerLength, name: this.avgLabel}); this.friendlyFireRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalFriendlyFire = total; this.totalFriendlyFire = total;
break; break;
case 'vehicle':
this.vehicleKillRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalVehicleKills = total;
break;
case 'death': case 'death':
this.deathRefLines.push({value: total / playerLength, name: this.avgLabel}); this.deathRefLines.push({value: total / playerLength, name: this.avgLabel});
this.totalDeath = total; this.totalDeath = total;

View File

@ -18,8 +18,6 @@ ngx-datatable {
float: left; float: left;
border: solid #dfdfdf 1px; border: solid #dfdfdf 1px;
border-radius: 10px 10px 2px 2px; border-radius: 10px 10px 2px 2px;
border-right-style: none;
border-top-style: none;
} }
:host /deep/ .datatable-header { :host /deep/ .datatable-header {
@ -74,5 +72,4 @@ ngx-datatable {
background: #4b4b4b; background: #4b4b4b;
-webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.5); -webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.5);
} }
/* Table Scrollbar END */ /* Table Scrollbar END */

View File

@ -36,6 +36,48 @@
<ngx-datatable-column [width]="valueColWidth" name="Kills" prop="kill"></ngx-datatable-column> <ngx-datatable-column [width]="valueColWidth" name="Kills" prop="kill"></ngx-datatable-column>
</ngx-datatable> </ngx-datatable>
<ngx-datatable
[rows]="players.friendlyFire"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable
[rows]="players.vehicle"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="Fahrzeug-Kills" prop="vehicle"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable <ngx-datatable
[rows]="players.death" [rows]="players.death"
[messages]="emptyMessage" [messages]="emptyMessage"
@ -78,27 +120,6 @@
<ngx-datatable-column [width]="valueColWidth" name="Respawn" prop="respawn"></ngx-datatable-column> <ngx-datatable-column [width]="valueColWidth" name="Respawn" prop="respawn"></ngx-datatable-column>
</ngx-datatable> </ngx-datatable>
<ngx-datatable
[rows]="players.friendlyFire"
[messages]="emptyMessage"
[headerHeight]="cellHeight"
[rowHeight]="cellHeight"
[cssClasses]='customClasses'
[columnMode]="'force'"
[scrollbarV]="true"
[selectionType]="'single'">
<ngx-datatable-column [width]="numberColWidth" name="#" prop="num"></ngx-datatable-column>
<ngx-datatable-column name="Spieler" prop="name" [width]="nameColWidth" style="padding-left:10px">
<ng-template ngx-datatable-cell-template let-row="row" let-value="value">
<span class="player-name"
[style.color]="row['fraction'] === 'BLUFOR' ? fraction.COLOR_BLUFOR : fraction.COLOR_OPFOR">
{{value}}
</span>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [width]="valueColWidth" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column>
</ngx-datatable>
<ngx-datatable <ngx-datatable
[rows]="players.revive" [rows]="players.revive"
[messages]="emptyMessage" [messages]="emptyMessage"

View File

@ -86,6 +86,7 @@ export class StatisticHighScoreComponent {
this.players = { this.players = {
kill: this.filterPlayerAttribute('kill'), kill: this.filterPlayerAttribute('kill'),
friendlyFire: this.filterPlayerAttribute('friendlyFire'), friendlyFire: this.filterPlayerAttribute('friendlyFire'),
vehicle: this.filterPlayerAttribute('vehicle'),
death: this.filterPlayerAttribute('death'), death: this.filterPlayerAttribute('death'),
respawn: this.filterPlayerAttribute('respawn'), respawn: this.filterPlayerAttribute('respawn'),
revive: this.filterPlayerAttribute('revive'), revive: this.filterPlayerAttribute('revive'),

View File

@ -28,6 +28,8 @@
btnRadio="{{labelKill}}">{{labelKill}}</label> btnRadio="{{labelKill}}">{{labelKill}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel" <label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"
btnRadio="{{labelFriendlyFire}}">{{labelFriendlyFire}}</label> btnRadio="{{labelFriendlyFire}}">{{labelFriendlyFire}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"
btnRadio="{{labelVehicle}}">{{labelVehicle}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel" <label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"
btnRadio="{{labelRevive}}">{{labelRevive}}</label> btnRadio="{{labelRevive}}">{{labelRevive}}</label>
<label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel" <label class="btn btn-default btn-dark" [(ngModel)]="chartSelectModel"

View File

@ -34,6 +34,7 @@ export class FractionStatsComponent {
tmpBudgetData; tmpBudgetData;
tmpKillData; tmpKillData;
tmpFrienlyFireData; tmpFrienlyFireData;
tmpVehicleData;
tmpTransportData; tmpTransportData;
tmpReviveData; tmpReviveData;
tmpStabilizeData; tmpStabilizeData;
@ -47,6 +48,7 @@ export class FractionStatsComponent {
labelBudget = 'Budget'; labelBudget = 'Budget';
labelKill = 'Kills'; labelKill = 'Kills';
labelFriendlyFire = 'FriendlyFire'; labelFriendlyFire = 'FriendlyFire';
labelVehicle = 'Fahrzeug-Kills';
labelTransport = 'Lufttransport'; labelTransport = 'Lufttransport';
labelRevive = 'Revive'; labelRevive = 'Revive';
labelStabilize = 'Stabilisiert'; labelStabilize = 'Stabilisiert';
@ -111,6 +113,10 @@ export class FractionStatsComponent {
this.initKillData(); this.initKillData();
this.lineChartData = this.tmpFrienlyFireData; this.lineChartData = this.tmpFrienlyFireData;
break; break;
case this.labelVehicle:
this.initVehicleData();
this.lineChartData = this.tmpVehicleData;
break;
case this.labelRevive: case this.labelRevive:
this.initRevive(); this.initRevive();
this.lineChartData = this.tmpReviveData; this.lineChartData = this.tmpReviveData;
@ -250,6 +256,30 @@ export class FractionStatsComponent {
this.initialized.revive = true; this.initialized.revive = true;
} }
initVehicleData() {
if (this.initialized.vehicle) {
return;
}
let vehicleKillCountBlufor = 0, vehicleKillCountOpfor = 0;
for (const {transportEntry: vehicleEntry, index} of this.logData.vehicle.map((transportEntry, index) => ({
transportEntry,
index
}))) {
const vehicleEntryDate = new Date(vehicleEntry.time);
if (vehicleEntry.fraction === 'BLUFOR') {
vehicleKillCountBlufor++;
} else {
vehicleKillCountOpfor++;
}
if (this.isTwoMinutesAhead(vehicleEntryDate, this.tmpVehicleData) || index === this.logData.vehicle.length - 1) {
this.tmpVehicleData[0].series.push(ChartUtils.getSeriesEntry(vehicleEntryDate, vehicleKillCountBlufor));
this.tmpVehicleData[1].series.push(ChartUtils.getSeriesEntry(vehicleEntryDate, vehicleKillCountOpfor));
}
}
this.addFinalTimeData(this.tmpVehicleData);
this.initialized.vehicle = true;
}
initTransportData() { initTransportData() {
if (this.initialized.transport) { if (this.initialized.transport) {
return; return;
@ -272,7 +302,6 @@ export class FractionStatsComponent {
} }
this.addFinalTimeData(this.tmpTransportData); this.addFinalTimeData(this.tmpTransportData);
this.initialized.transport = true; this.initialized.transport = true;
} }
initFlagHoldData() { initFlagHoldData() {
@ -308,12 +337,13 @@ export class FractionStatsComponent {
this.tmpBudgetData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpBudgetData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpKillData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpKillData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpFrienlyFireData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpFrienlyFireData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpVehicleData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpTransportData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpTransportData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpReviveData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpReviveData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpStabilizeData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpStabilizeData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
this.tmpFlagCaptureData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR); this.tmpFlagCaptureData = ChartUtils.getMultiDataArray(Fraction.BLUFOR, Fraction.OPFOR);
[this.tmpKillData, this.tmpFrienlyFireData, this.tmpReviveData, this.tmpStabilizeData, this.tmpTransportData].forEach(tmp => { [this.tmpKillData, this.tmpFrienlyFireData, this.tmpVehicleData, this.tmpReviveData, this.tmpStabilizeData, this.tmpTransportData].forEach(tmp => {
[0, 1].forEach(index => { [0, 1].forEach(index => {
tmp[index].series.push(ChartUtils.getSeriesEntry(this.startDateObj, 0)); tmp[index].series.push(ChartUtils.getSeriesEntry(this.startDateObj, 0));
}) })

View File

@ -25,18 +25,22 @@
</ngx-datatable-column> </ngx-datatable-column>
<ngx-datatable-column [width]="90" name="Kills" prop="kill"></ngx-datatable-column> <ngx-datatable-column [width]="90" name="Kills" prop="kill"></ngx-datatable-column>
<ngx-datatable-column [width]="104" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column> <ngx-datatable-column [width]="104" name="FriendlyFire" prop="friendlyFire"></ngx-datatable-column>
<ngx-datatable-column [width]="90" name="Fahrzeug" prop="vehicle"></ngx-datatable-column>
<ngx-datatable-column [width]="80" name="Revive" prop="revive"></ngx-datatable-column> <ngx-datatable-column [width]="80" name="Revive" prop="revive"></ngx-datatable-column>
<ngx-datatable-column [width]="100" name="Eroberung" prop="flagTouch"></ngx-datatable-column> <ngx-datatable-column [width]="100" name="Eroberung" prop="flagTouch"></ngx-datatable-column>
<ngx-datatable-column [width]="70" name="Tod" prop="death"></ngx-datatable-column> <ngx-datatable-column [width]="70" name="Tod" prop="death"></ngx-datatable-column>
<ngx-datatable-column [width]="90" name="Respawn" prop="respawn"></ngx-datatable-column> <ngx-datatable-column [width]="90" name="Respawn" prop="respawn"></ngx-datatable-column>
<ngx-datatable-column [width]="80" name="" prop="name"> <!--<ngx-datatable-column [width]="80" name="" prop="name">-->
<ng-template ngx-datatable-cell-template let-value="value"> <!--<ng-template ngx-datatable-cell-template let-value="value">-->
<span class="btn btn-sm btn-default in-table-btn disabled">Detail</span> <!--<span class="btn btn-sm btn-default in-table-btn disabled">Detail</span>-->
</ng-template> <!--</ng-template>-->
</ngx-datatable-column> <!--</ngx-datatable-column>-->
<ngx-datatable-column [width]="80" name="" prop="name"> <ngx-datatable-column [width]="80">
<ng-template ngx-datatable-cell-template let-value="value"> <ng-template ngx-datatable-cell-template let-row="row">
<span class="btn btn-sm btn-default in-table-btn" (click)="selectPlayerDetail(1, value)">Gesamt</span> <span class="btn btn-sm btn-default in-table-btn"
(click)="selectPlayerDetail(1, isSteamUUID(row['steamUUID']) ? row['steamUUID'] : row['name'])">
Gesamt
</span>
</ng-template> </ng-template>
</ngx-datatable-column> </ngx-datatable-column>
</ngx-datatable> </ngx-datatable>

View File

@ -1,6 +1,7 @@
import {Component, EventEmitter, SimpleChanges} from "@angular/core"; import {Component, ElementRef, EventEmitter, SimpleChanges} from "@angular/core";
import {War} from "../../../models/model-interfaces"; import {War} from "../../../models/model-interfaces";
import {Fraction} from "../../../utils/fraction.enum"; import {Fraction} from "../../../utils/fraction.enum";
import {PlayerUtils} from "../../../utils/player-utils";
@Component({ @Component({
selector: 'scoreboard', selector: 'scoreboard',
@ -17,6 +18,8 @@ export class ScoreboardComponent {
war: War; war: War;
isSteamUUID = PlayerUtils.isSteamUUID;
fractionFilterSelected: string; fractionFilterSelected: string;
cellHeight = 40; cellHeight = 40;
@ -30,19 +33,22 @@ export class ScoreboardComponent {
sortDescending: 'glyphicon glyphicon-triangle-bottom', sortDescending: 'glyphicon glyphicon-triangle-bottom',
}; };
constructor() { constructor(private elRef: ElementRef) {
} }
ngOnInit() { selectPlayerDetail(view: number, player) {
} this.playerTabSwitch.emit({
view: view,
selectPlayerDetail(view: number, playerName: string) { player: player
this.playerTabSwitch.emit({view: view, player: playerName}) })
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes.war) { if (changes.war) {
this.rows = changes.war.currentValue.players; this.rows = changes.war.currentValue.players;
this.elRef.nativeElement
.querySelector('.datatable-body')
.scrollTo(0, 0);
} }
if (changes.fractionFilterSelected) { if (changes.fractionFilterSelected) {
this.filterPlayersByFraction(this.fractionFilterSelected) this.filterPlayersByFraction(this.fractionFilterSelected)

View File

@ -19,6 +19,9 @@
} }
.nav-tabs { .nav-tabs {
width: 980px;
margin: auto;
clear: both;
border-bottom: 0; border-bottom: 0;
} }

View File

@ -50,7 +50,7 @@
</div> </div>
</div> </div>
<ul class="nav nav-tabs" style="width:980px; margin:auto"> <ul class="nav nav-tabs">
<li class="nav-item" [ngClass]="{active :tab === 0}" (click)="switchTab(0)"> <li class="nav-item" [ngClass]="{active :tab === 0}" (click)="switchTab(0)">
<a class="nav-link"><img src="../../../assets/scoreboard-btn.png"> Scoreboard</a> <a class="nav-link"><img src="../../../assets/scoreboard-btn.png"> Scoreboard</a>
</li> </li>

View File

@ -0,0 +1,7 @@
export class PlayerUtils {
public static isSteamUUID(input: string): boolean {
const steamUIDPattern = new RegExp("[0-9]{17}");
return steamUIDPattern.test(input)
}
}