6048 lines
167 KiB
C++
6048 lines
167 KiB
C++
/************************************************************************
|
|
MeOS - Orienteering Software
|
|
Copyright (C) 2009-2019 Melin Software HB
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Melin Software HB - software@melin.nu - www.melin.nu
|
|
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
|
|
|
|
************************************************************************/
|
|
|
|
// oRunner.cpp: implementation of the oRunner class.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "stdafx.h"
|
|
#include "oRunner.h"
|
|
|
|
#include "oEvent.h"
|
|
#include "gdioutput.h"
|
|
#include "gdifonts.h"
|
|
#include "table.h"
|
|
#include "meos_util.h"
|
|
#include "oFreeImport.h"
|
|
#include <cassert>
|
|
#include "localizer.h"
|
|
#include "SportIdent.h"
|
|
#include <cmath>
|
|
#include "intkeymapimpl.hpp"
|
|
#include "runnerdb.h"
|
|
#include "meosexception.h"
|
|
#include <algorithm>
|
|
#include "socket.h"
|
|
#include "MeOSFeatures.h"
|
|
#include "oListInfo.h"
|
|
#include "qualification_final.h"
|
|
#include "metalist.h"
|
|
|
|
oRunner::RaceIdFormatter oRunner::raceIdFormatter;
|
|
|
|
bool oAbstractRunner::DynamicValue::isOld(const oEvent &oe) const {
|
|
return oe.dataRevision != dataRevision;
|
|
}
|
|
|
|
void oAbstractRunner::DynamicValue::update(const oEvent &oe, int v) {
|
|
value = v;
|
|
dataRevision = oe.dataRevision;
|
|
}
|
|
|
|
|
|
const wstring &oRunner::RaceIdFormatter::formatData(const oBase *ob) const {
|
|
return itow(dynamic_cast<const oRunner &>(*ob).getRaceIdentifier());
|
|
}
|
|
|
|
wstring oRunner::RaceIdFormatter::setData(oBase *ob, const wstring &input) const {
|
|
int rid = _wtoi(input.c_str());
|
|
if (input == L"0")
|
|
ob->getDI().setInt("RaceId", 0);
|
|
else if (rid>0 && rid != dynamic_cast<oRunner *>(ob)->getRaceIdentifier())
|
|
ob->getDI().setInt("RaceId", rid);
|
|
return formatData(ob);
|
|
}
|
|
|
|
int oRunner::RaceIdFormatter::addTableColumn(Table *table, const string &description, int minWidth) const {
|
|
return table->addColumn(description, max(minWidth, 90), true, true);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Construction/Destruction
|
|
//////////////////////////////////////////////////////////////////////
|
|
oAbstractRunner::oAbstractRunner(oEvent *poe, bool loading):oBase(poe)
|
|
{
|
|
Class=0;
|
|
Club=0;
|
|
startTime = 0;
|
|
tStartTime = 0;
|
|
|
|
FinishTime = 0;
|
|
tStatus = status = StatusUnknown;
|
|
|
|
inputPoints = 0;
|
|
if (loading || !oe->hasPrevStage())
|
|
inputStatus = StatusOK;
|
|
else
|
|
inputStatus = StatusNotCompetiting;
|
|
|
|
inputTime = 0;
|
|
inputPlace = 0;
|
|
|
|
tTimeAdjustment = 0;
|
|
tPointAdjustment = 0;
|
|
tAdjustDataRevision = -1;
|
|
}
|
|
|
|
wstring oAbstractRunner::getInfo() const
|
|
{
|
|
return getName();
|
|
}
|
|
|
|
void oAbstractRunner::setFinishTimeS(const wstring &t)
|
|
{
|
|
setFinishTime(oe->getRelativeTime(t));
|
|
}
|
|
|
|
void oAbstractRunner::setStartTimeS(const wstring &t)
|
|
{
|
|
setStartTime(oe->getRelativeTime(t), true, false);
|
|
}
|
|
|
|
oRunner::oRunner(oEvent *poe) :oAbstractRunner(poe, false)
|
|
{
|
|
isTemporaryObject = false;
|
|
Id = oe->getFreeRunnerId();
|
|
Course = 0;
|
|
StartNo = 0;
|
|
cardNumber = 0;
|
|
|
|
tInTeam = 0;
|
|
tLeg = 0;
|
|
tLegEquClass = 0;
|
|
tNeedNoCard = false;
|
|
tUseStartPunch = true;
|
|
getDI().initData();
|
|
correctionNeeded = false;
|
|
|
|
tDuplicateLeg = 0;
|
|
tParentRunner = 0;
|
|
|
|
Card = 0;
|
|
cPriority = 0;
|
|
|
|
tCachedRunningTime = 0;
|
|
tSplitRevision = -1;
|
|
|
|
tRogainingPoints = 0;
|
|
tRogainingOvertime = 0;
|
|
tReduction = 0;
|
|
tRogainingPointsGross = 0;
|
|
tAdaptedCourse = 0;
|
|
tAdaptedCourseRevision = -1;
|
|
|
|
tShortenDataRevision = -1;
|
|
tNumShortening = 0;
|
|
}
|
|
|
|
oRunner::oRunner(oEvent *poe, int id) :oAbstractRunner(poe, true)
|
|
{
|
|
isTemporaryObject = false;
|
|
Id = id;
|
|
oe->qFreeRunnerId = max(id, oe->qFreeRunnerId);
|
|
Course = 0;
|
|
StartNo = 0;
|
|
cardNumber = 0;
|
|
|
|
tInTeam = 0;
|
|
tLeg = 0;
|
|
tLegEquClass = 0;
|
|
tNeedNoCard = false;
|
|
tUseStartPunch = true;
|
|
getDI().initData();
|
|
correctionNeeded = false;
|
|
|
|
tDuplicateLeg = 0;
|
|
tParentRunner = 0;
|
|
|
|
Card = 0;
|
|
cPriority = 0;
|
|
tCachedRunningTime = 0;
|
|
tSplitRevision = -1;
|
|
|
|
tRogainingPoints = 0;
|
|
tRogainingOvertime = 0;
|
|
tReduction = 0;
|
|
tRogainingPointsGross = 0;
|
|
tAdaptedCourse = 0;
|
|
tAdaptedCourseRevision = -1;
|
|
}
|
|
|
|
oRunner::~oRunner()
|
|
{
|
|
if (tInTeam){
|
|
for(unsigned i=0;i<tInTeam->Runners.size(); i++)
|
|
if (tInTeam->Runners[i] && tInTeam->Runners[i]->getId() == Id)
|
|
tInTeam->Runners[i] = nullptr;
|
|
|
|
tInTeam=0;
|
|
}
|
|
|
|
for (size_t k=0;k<multiRunner.size(); k++) {
|
|
if (multiRunner[k] && multiRunner[k]->tParentRunner == this)
|
|
multiRunner[k]->tParentRunner = nullptr;
|
|
}
|
|
|
|
if (tParentRunner) {
|
|
for (size_t k=0;k<tParentRunner->multiRunner.size(); k++)
|
|
if (tParentRunner->multiRunner[k] == this)
|
|
tParentRunner->multiRunner[k] = nullptr;
|
|
}
|
|
|
|
delete tAdaptedCourse;
|
|
tAdaptedCourse = 0;
|
|
}
|
|
|
|
bool oRunner::Write(xmlparser &xml)
|
|
{
|
|
if (Removed) return true;
|
|
|
|
xml.startTag("Runner");
|
|
xml.write("Id", Id);
|
|
xml.write("Updated", Modified.getStamp());
|
|
xml.write("Name", sName);
|
|
xml.write("Start", startTime);
|
|
xml.write("Finish", FinishTime);
|
|
xml.write("Status", status);
|
|
xml.write("CardNo", cardNumber);
|
|
xml.write("StartNo", StartNo);
|
|
|
|
xml.write("InputPoint", inputPoints);
|
|
if (inputStatus != StatusOK)
|
|
xml.write("InputStatus", itos(inputStatus)); //Force write of 0
|
|
xml.write("InputTime", inputTime);
|
|
xml.write("InputPlace", inputPlace);
|
|
|
|
if (Club) xml.write("Club", Club->Id);
|
|
if (Class) xml.write("Class", Class->Id);
|
|
if (Course) xml.write("Course", Course->Id);
|
|
|
|
if (multiRunner.size()>0)
|
|
xml.write("MultiR", codeMultiR());
|
|
|
|
if (Card) {
|
|
assert(Card->tOwner==this);
|
|
Card->Write(xml);
|
|
}
|
|
getDI().write(xml);
|
|
|
|
xml.endTag();
|
|
|
|
for (size_t k=0;k<multiRunner.size();k++)
|
|
if (multiRunner[k])
|
|
multiRunner[k]->Write(xml);
|
|
|
|
return true;
|
|
}
|
|
|
|
void oRunner::Set(const xmlobject &xo)
|
|
{
|
|
xmlList xl;
|
|
xo.getObjects(xl);
|
|
xmlList::const_iterator it;
|
|
|
|
for (it = xl.begin(); it != xl.end(); ++it) {
|
|
if (it->is("Id")) {
|
|
Id = it->getInt();
|
|
}
|
|
else if (it->is("Name")) {
|
|
sName = it->getw();
|
|
getRealName(sName, tRealName);
|
|
}
|
|
else if (it->is("Start")) {
|
|
tStartTime = startTime = it->getInt();
|
|
}
|
|
else if (it->is("Finish")) {
|
|
FinishTime = it->getInt();
|
|
}
|
|
else if (it->is("Status")) {
|
|
unsigned rawStat = it->getInt();
|
|
tStatus = status = RunnerStatus(rawStat < 100u ? rawStat : 0);
|
|
}
|
|
else if (it->is("CardNo")) {
|
|
cardNumber = it->getInt();
|
|
}
|
|
else if (it->is("StartNo") || it->is("OrderId"))
|
|
StartNo = it->getInt();
|
|
else if (it->is("Club"))
|
|
Club = oe->getClub(it->getInt());
|
|
else if (it->is("Class"))
|
|
Class = oe->getClass(it->getInt());
|
|
else if (it->is("Course"))
|
|
Course = oe->getCourse(it->getInt());
|
|
else if (it->is("Card")) {
|
|
Card = oe->allocateCard(this);
|
|
Card->Set(*it);
|
|
assert(Card->getId() != 0);
|
|
}
|
|
else if (it->is("oData"))
|
|
getDI().set(*it);
|
|
else if (it->is("Updated"))
|
|
Modified.setStamp(it->getRaw());
|
|
else if (it->is("MultiR"))
|
|
decodeMultiR(it->getRaw());
|
|
else if (it->is("InputTime")) {
|
|
inputTime = it->getInt();
|
|
}
|
|
else if (it->is("InputStatus")) {
|
|
unsigned rawStat = it->getInt();
|
|
inputStatus = RunnerStatus(rawStat < 100u ? rawStat : 0);
|
|
}
|
|
else if (it->is("InputPoint")) {
|
|
inputPoints = it->getInt();
|
|
}
|
|
else if (it->is("InputPlace")) {
|
|
inputPlace = it->getInt();
|
|
}
|
|
}
|
|
}
|
|
|
|
int oAbstractRunner::getBirthAge() const {
|
|
return 0;
|
|
}
|
|
|
|
int oRunner::getBirthAge() const {
|
|
int y = getBirthYear();
|
|
if (y > 0)
|
|
return getThisYear() - y;
|
|
return 0;
|
|
}
|
|
|
|
int oAbstractRunner::getDefaultFee() const {
|
|
int age = getBirthAge();
|
|
wstring date = getEntryDate();
|
|
if (Class) {
|
|
int fee = Class->getEntryFee(date, age);
|
|
return fee;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int oAbstractRunner::getEntryFee() const {
|
|
return getDCI().getInt("Fee");
|
|
}
|
|
|
|
void oAbstractRunner::addClassDefaultFee(bool resetFees) {
|
|
if (Class) {
|
|
oDataInterface di = getDI();
|
|
|
|
if (isVacant()) {
|
|
di.setInt("Fee", 0);
|
|
di.setInt("EntryDate", 0);
|
|
di.setInt("EntryTime", 0);
|
|
di.setInt("Paid", 0);
|
|
if (typeid(*this)==typeid(oRunner))
|
|
di.setInt("CardFee", 0);
|
|
return;
|
|
}
|
|
wstring date = getEntryDate();
|
|
int currentFee = di.getInt("Fee");
|
|
|
|
pTeam t = getTeam();
|
|
if (t && t != this) {
|
|
// Thus us a runner in a team
|
|
// Check if the team has a fee.
|
|
// Don't assign personal fee if so.
|
|
if (t->getDCI().getInt("Fee") > 0)
|
|
return;
|
|
}
|
|
|
|
if ((currentFee == 0 && !hasFlag(FlagFeeSpecified)) || resetFees) {
|
|
int fee = getDefaultFee();
|
|
di.setInt("Fee", fee);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get entry date of runner (or its team)
|
|
wstring oRunner::getEntryDate(bool useTeamEntryDate) const {
|
|
if (useTeamEntryDate && tInTeam) {
|
|
wstring date = tInTeam->getEntryDate(false);
|
|
if (!date.empty())
|
|
return date;
|
|
}
|
|
oDataConstInterface dci = getDCI();
|
|
int date = dci.getInt("EntryDate");
|
|
if (date == 0) {
|
|
auto di = (const_cast<oRunner *>(this)->getDI());
|
|
di.setDate("EntryDate", getLocalDate());
|
|
di.setInt("EntryTime", convertAbsoluteTimeHMS(getLocalTimeOnly(), -1));
|
|
|
|
}
|
|
return dci.getDate("EntryDate");
|
|
}
|
|
|
|
string oRunner::codeMultiR() const
|
|
{
|
|
char bf[32];
|
|
string r;
|
|
|
|
for (size_t k=0;k<multiRunner.size() && multiRunner[k];k++) {
|
|
if (!r.empty())
|
|
r+=":";
|
|
sprintf_s(bf, "%d", multiRunner[k]->getId());
|
|
r+=bf;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void oRunner::decodeMultiR(const string &r)
|
|
{
|
|
vector<string> sv;
|
|
split(r, ":", sv);
|
|
multiRunnerId.clear();
|
|
|
|
for (size_t k=0;k<sv.size();k++) {
|
|
int d = atoi(sv[k].c_str());
|
|
if (d>0)
|
|
multiRunnerId.push_back(d);
|
|
}
|
|
multiRunnerId.push_back(0); // Mark as containing something
|
|
}
|
|
|
|
void oAbstractRunner::setClassId(int id, bool isManualUpdate) {
|
|
pClass pc=Class;
|
|
Class=id ? oe->getClass(id):0;
|
|
|
|
if (Class!=pc) {
|
|
apply(false, 0, false);
|
|
if (Class) {
|
|
Class->clearCache(true);
|
|
}
|
|
if (pc) {
|
|
pc->clearCache(true);
|
|
if (isManualUpdate) {
|
|
setFlag(FlagUpdateClass, true);
|
|
// Update heat data
|
|
int heat = pc->getDCI().getInt("Heat");
|
|
if (heat != 0)
|
|
getDI().setInt("Heat", heat);
|
|
}
|
|
}
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
// Update all classes (for multirunner)
|
|
void oRunner::setClassId(int id, bool isManualUpdate) {
|
|
pClass nPc = id>0 ? oe->getClass(id) : 0;
|
|
if (Class == nPc)
|
|
return;
|
|
if (Class && Class->getQualificationFinal() && isManualUpdate && nPc && nPc->parentClass == Class) {
|
|
int heat = Class->getQualificationFinal()->getHeatFromClass(id, Class->getId());
|
|
if (heat >= 0) {
|
|
int oldHeat = getDI().getInt("Heat");
|
|
|
|
if (heat != oldHeat) {
|
|
pClass oldHeatClass = getClassRef(true);
|
|
getDI().setInt("Heat", heat);
|
|
pClass newHeatClass = getClassRef(true);
|
|
oldHeatClass->clearCache(true);
|
|
newHeatClass->clearCache(true);
|
|
tSplitRevision = 0;
|
|
apply(false, 0, false);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (tParentRunner) {
|
|
assert(!isManualUpdate); // Do not support! This may be destroyed if calling tParentRunner->setClass
|
|
return;
|
|
}
|
|
else {
|
|
pClass pc = Class;
|
|
|
|
if (pc && pc->isSingleRunnerMultiStage() && nPc!=pc && tInTeam) {
|
|
if (!isTemporaryObject) {
|
|
oe->autoRemoveTeam(this);
|
|
|
|
if (nPc) {
|
|
int newNR = max(nPc->getNumMultiRunners(0), 1);
|
|
for (size_t k = newNR - 1; k<multiRunner.size(); k++) {
|
|
if (multiRunner[k]) {
|
|
assert(multiRunner[k]->tParentRunner == this);
|
|
multiRunner[k]->tParentRunner = 0;
|
|
vector<int> toRemove;
|
|
toRemove.push_back(multiRunner[k]->Id);
|
|
oe->removeRunner(toRemove);
|
|
}
|
|
}
|
|
multiRunner.resize(newNR-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
Class = nPc;
|
|
|
|
if (Class != 0 && Class != pc && tInTeam==0 &&
|
|
Class->isSingleRunnerMultiStage()) {
|
|
if (!isTemporaryObject) {
|
|
pTeam t = oe->addTeam(getName(), getClubId(), getClassId(false));
|
|
t->setStartNo(StartNo, false);
|
|
t->setRunner(0, this, true);
|
|
}
|
|
}
|
|
|
|
apply(false, 0, false); //We may get old class back from team.
|
|
|
|
for (size_t k=0;k<multiRunner.size();k++) {
|
|
if (multiRunner[k] && Class!=multiRunner[k]->Class) {
|
|
multiRunner[k]->Class=Class;
|
|
multiRunner[k]->updateChanged();
|
|
}
|
|
}
|
|
|
|
if (Class!=pc && !isTemporaryObject) {
|
|
if (Class) {
|
|
Class->clearCache(true);
|
|
}
|
|
if (pc) {
|
|
pc->clearCache(true);
|
|
}
|
|
tSplitRevision = 0;
|
|
updateChanged();
|
|
if (isManualUpdate && pc) {
|
|
setFlag(FlagUpdateClass, true);
|
|
// Update heat data
|
|
int heat = pc->getDCI().getInt("Heat");
|
|
if (heat != 0)
|
|
getDI().setInt("Heat", heat);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void oRunner::setCourseId(int id)
|
|
{
|
|
pCourse pc=Course;
|
|
|
|
if (id>0)
|
|
Course=oe->getCourse(id);
|
|
else
|
|
Course=0;
|
|
|
|
if (Course!=pc) {
|
|
updateChanged();
|
|
if (Class)
|
|
getClassRef(true)->clearSplitAnalysis();
|
|
tSplitRevision = 0;
|
|
}
|
|
}
|
|
|
|
bool oAbstractRunner::setStartTime(int t, bool updateSource, bool tmpOnly, bool recalculate) {
|
|
assert(!(updateSource && tmpOnly));
|
|
if (tmpOnly) {
|
|
tmpStore.startTime = t;
|
|
return false;
|
|
}
|
|
|
|
int tOST=tStartTime;
|
|
if (t>0)
|
|
tStartTime=t;
|
|
else tStartTime=0;
|
|
|
|
if (updateSource) {
|
|
int OST=startTime;
|
|
startTime = tStartTime;
|
|
|
|
if (OST!=startTime) {
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
if (tOST != tStartTime) {
|
|
changedObject();
|
|
if (Class) {
|
|
Class->clearCache(false);
|
|
}
|
|
}
|
|
|
|
if (tOST<tStartTime && Class && recalculate)
|
|
oe->reCalculateLeaderTimes(Class->getId());
|
|
|
|
return tOST != tStartTime;
|
|
}
|
|
|
|
void oAbstractRunner::setFinishTime(int t)
|
|
{
|
|
int OFT=FinishTime;
|
|
|
|
if (t>tStartTime)
|
|
FinishTime=t;
|
|
else //Beeb
|
|
FinishTime=0;
|
|
|
|
if (OFT != FinishTime) {
|
|
updateChanged();
|
|
if (Class) {
|
|
Class->clearCache(false);
|
|
}
|
|
}
|
|
|
|
if (OFT>FinishTime && Class)
|
|
oe->reCalculateLeaderTimes(Class->getId());
|
|
}
|
|
|
|
void oRunner::setFinishTime(int t)
|
|
{
|
|
bool update=false;
|
|
if (Class && (getTimeAfter(tDuplicateLeg)==0 || getTimeAfter()==0))
|
|
update=true;
|
|
|
|
oAbstractRunner::setFinishTime(t);
|
|
|
|
tSplitRevision = 0;
|
|
|
|
if (update && t!=FinishTime)
|
|
oe->reCalculateLeaderTimes(Class->getId());
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getStartTimeS() const {
|
|
if (tStartTime>0)
|
|
return oe->getAbsTime(tStartTime);
|
|
else if (Class && Class->hasFreeStart())
|
|
return _EmptyWString;
|
|
else
|
|
return makeDash(L"-");
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getStartTimeCompact() const {
|
|
if (tStartTime>0) {
|
|
if (oe->useStartSeconds())
|
|
return oe->getAbsTime(tStartTime);
|
|
else
|
|
return oe->getAbsTimeHM(tStartTime);
|
|
}
|
|
else if (Class && Class->hasFreeStart())
|
|
return _EmptyWString;
|
|
else
|
|
return makeDash(L"-");
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getFinishTimeS() const
|
|
{
|
|
if (FinishTime>0)
|
|
return oe->getAbsTime(FinishTime);
|
|
else return makeDash(L"-");
|
|
}
|
|
|
|
int oAbstractRunner::getRunningTime() const {
|
|
int rt = FinishTime-tStartTime;
|
|
if (rt > 0)
|
|
return getTimeAdjustment() + rt;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getRunningTimeS() const
|
|
{
|
|
return formatTime(getRunningTime());
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getTotalRunningTimeS() const
|
|
{
|
|
return formatTime(getTotalRunningTime());
|
|
}
|
|
|
|
int oAbstractRunner::getTotalRunningTime() const {
|
|
int t = getRunningTime();
|
|
if (t > 0 && inputTime>=0)
|
|
return t + inputTime;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int oRunner::getTotalRunningTime() const {
|
|
return getTotalRunningTime(getFinishTime(), true);
|
|
}
|
|
|
|
|
|
const wstring &oAbstractRunner::getStatusS(bool formatForPrint) const
|
|
{
|
|
return oEvent::formatStatus(tStatus, formatForPrint);
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getTotalStatusS(bool formatForPrint) const
|
|
{
|
|
auto ts = getTotalStatus();
|
|
return oEvent::formatStatus(ts, formatForPrint);
|
|
}
|
|
|
|
/*
|
|
- Inactive : Has not yet started
|
|
- DidNotStart : Did Not Start (in this race)
|
|
- Active : Currently on course
|
|
- Finished : Finished but not validated
|
|
- OK : Finished and validated
|
|
- MisPunch : Missing Punch
|
|
- DidNotFinish : Did Not Finish
|
|
- Disqualified : Disqualified
|
|
- NotCompeting : Not Competing (running outside the competition)
|
|
- SportWithdr : Sporting Withdrawal (e.g. helping injured)
|
|
- OverTime : Overtime, i.e. did not finish within max time
|
|
- Moved : Moved to another class
|
|
- MovedUp : Moved to a "better" class, in case of entry
|
|
restrictions
|
|
- Cancelled
|
|
*/
|
|
const wchar_t *formatIOFStatus(RunnerStatus s) {
|
|
switch(s) {
|
|
case StatusOK:
|
|
return L"OK";
|
|
case StatusDNS:
|
|
return L"DidNotStart";
|
|
case StatusCANCEL:
|
|
return L"Cancelled";
|
|
case StatusMP:
|
|
return L"MisPunch";
|
|
case StatusDNF:
|
|
return L"DidNotFinish";
|
|
case StatusDQ:
|
|
return L"Disqualified";
|
|
case StatusMAX:
|
|
return L"OverTime";
|
|
}
|
|
return L"Inactive";
|
|
}
|
|
|
|
wstring oAbstractRunner::getIOFStatusS() const
|
|
{
|
|
return formatIOFStatus(tStatus);
|
|
}
|
|
|
|
wstring oAbstractRunner::getIOFTotalStatusS() const
|
|
{
|
|
return formatIOFStatus(getTotalStatus());
|
|
}
|
|
|
|
void oRunner::addPunches(pCard card, vector<int> &missingPunches) {
|
|
RunnerStatus oldStatus = getStatus();
|
|
int oldFinishTime = getFinishTime();
|
|
pCard oldCard = Card;
|
|
|
|
if (Card && card != Card) {
|
|
Card->tOwner = 0;
|
|
}
|
|
|
|
Card = card;
|
|
card->adaptTimes(getStartTime());
|
|
updateChanged();
|
|
|
|
if (card) {
|
|
if (card->cardNo > 0)
|
|
setCardNo(card->cardNo, false, true);
|
|
//315422
|
|
assert(card->tOwner==0 || card->tOwner==this);
|
|
}
|
|
// Auto-select shortening
|
|
pCourse mainCourse = getCourse(false);
|
|
int shortenLevel = 0;
|
|
|
|
if (mainCourse && Card) {
|
|
pCourse shortVersion = mainCourse->getShorterVersion().second;
|
|
if (shortVersion) {
|
|
//int s = mainCourse->getStartPunchType();
|
|
//int f = mainCourse->getFinishPunchType();
|
|
const int numCtrl = Card->getNumControlPunches(-1,-1);
|
|
int numCtrlLong = mainCourse->getNumControls();
|
|
int numCtrlShort = shortVersion->getNumControls();
|
|
|
|
SICard sic(ConvertedTimeStatus::Unknown);
|
|
Card->getSICard(sic);
|
|
while (mainCourse->distance(sic) < 0 && abs(numCtrl-numCtrlShort) < abs(numCtrl-numCtrlLong)) {
|
|
shortenLevel++;
|
|
if (shortVersion->distance(sic) >= 0) {
|
|
setNumShortening(shortenLevel); // We passed at some level
|
|
break;
|
|
}
|
|
mainCourse = shortVersion;
|
|
shortVersion = mainCourse->getShorterVersion().second;
|
|
numCtrlLong = numCtrlShort;
|
|
if (!shortVersion) {
|
|
break;
|
|
}
|
|
numCtrlShort = shortVersion->getNumControls();
|
|
}
|
|
}
|
|
}
|
|
if (mainCourse && mainCourse->getCommonControl() != 0 && mainCourse->getShorterVersion().first) {
|
|
oCourse tmpCourse(oe);
|
|
int numShorten;
|
|
mainCourse->getAdapetedCourse(*Card, tmpCourse, numShorten);
|
|
setNumShortening(shortenLevel + numShorten);
|
|
}
|
|
|
|
|
|
if (Card)
|
|
Card->tOwner=this;
|
|
|
|
evaluateCard(true, missingPunches, 0, true);
|
|
|
|
if (oe->isClient() && oe->getPropertyInt("UseDirectSocket", true)!=0) {
|
|
if (oldStatus != getStatus() || oldFinishTime != getFinishTime()) {
|
|
SocketPunchInfo pi;
|
|
pi.runnerId = getId();
|
|
pi.time = getFinishTime();
|
|
pi.status = getStatus();
|
|
pi.iHashType = oPunch::PunchFinish;
|
|
oe->getDirectSocket().sendPunch(pi);
|
|
}
|
|
}
|
|
|
|
oe->pushDirectChange();
|
|
if (oldCard && Card && oldCard != Card && oldCard->isConstructedFromPunches())
|
|
oldCard->remove(); // Remove card constructed from punches
|
|
}
|
|
|
|
pCourse oRunner::getCourse(bool useAdaptedCourse) const {
|
|
pCourse tCrs = 0;
|
|
if (Course)
|
|
tCrs = Course;
|
|
else if (Class) {
|
|
const oClass *cls = getClassRef(true);
|
|
|
|
if (cls->hasMultiCourse()) {
|
|
if (tInTeam) {
|
|
if (size_t(tLeg) >= tInTeam->Runners.size() || tInTeam->Runners[tLeg] != this) {
|
|
tInTeam->quickApply();
|
|
}
|
|
}
|
|
|
|
if (Class == cls) {
|
|
if (tInTeam && Class->hasUnorderedLegs()) {
|
|
vector< pair<int, pCourse> > group;
|
|
Class->getParallelCourseGroup(tLeg, StartNo, group);
|
|
|
|
if (group.size() == 1) {
|
|
tCrs = group[0].second;
|
|
}
|
|
else {
|
|
// Remove used courses
|
|
int myStart = 0;
|
|
|
|
for (size_t k = 0; k < group.size(); k++) {
|
|
if (group[k].first == tLeg)
|
|
myStart = k;
|
|
|
|
pRunner tr = tInTeam->getRunner(group[k].first);
|
|
if (tr && tr->Course) {
|
|
// The course is assigned. Remove from group
|
|
for (size_t j = 0; j < group.size(); j++) {
|
|
if (group[j].second == tr->Course) {
|
|
group[j].second = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear out already preliminary assigned courses
|
|
for (int k = 0; k < myStart; k++) {
|
|
pRunner r = tInTeam->getRunner(group[k].first);
|
|
if (r && !r->Course) {
|
|
size_t j = k;
|
|
while (j < group.size()) {
|
|
if (group[j].second) {
|
|
group[j].second = 0;
|
|
break;
|
|
}
|
|
else j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t j = 0; j < group.size(); j++) {
|
|
int ix = (j + myStart) % group.size();
|
|
pCourse gcrs = group[ix].second;
|
|
if (gcrs) {
|
|
tCrs = gcrs;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (tInTeam) {
|
|
unsigned leg = legToRun();
|
|
tCrs = Class->getCourse(leg, StartNo);
|
|
}
|
|
else {
|
|
if (unsigned(tDuplicateLeg) < Class->MultiCourse.size()) {
|
|
vector<pCourse> &courses = Class->MultiCourse[tDuplicateLeg];
|
|
if (courses.size() > 0) {
|
|
int index = StartNo % courses.size();
|
|
tCrs = courses[index];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Final / qualification classes
|
|
tCrs = cls->getCourse(0, StartNo);
|
|
}
|
|
}
|
|
else
|
|
tCrs = cls->Course;
|
|
}
|
|
|
|
if (tCrs && useAdaptedCourse) {
|
|
// Find shortened version of course
|
|
int ns = getNumShortening();
|
|
pCourse shortCrs = tCrs;
|
|
while (ns > 0 && shortCrs) {
|
|
shortCrs = shortCrs->getShorterVersion().second;
|
|
if (shortCrs)
|
|
tCrs = shortCrs;
|
|
ns--;
|
|
}
|
|
}
|
|
|
|
if (tCrs && useAdaptedCourse && Card && tCrs->getCommonControl() != 0) {
|
|
if (tAdaptedCourse && tAdaptedCourseRevision == oe->dataRevision) {
|
|
return tAdaptedCourse;
|
|
}
|
|
if (!tAdaptedCourse)
|
|
tAdaptedCourse = new oCourse(oe, -1);
|
|
|
|
int numShorten;
|
|
tCrs = tCrs->getAdapetedCourse(*Card, *tAdaptedCourse, numShorten);
|
|
tAdaptedCourseRevision = oe->dataRevision;
|
|
return tCrs;
|
|
}
|
|
|
|
return tCrs;
|
|
}
|
|
|
|
const wstring &oRunner::getCourseName() const
|
|
{
|
|
pCourse oc=getCourse(false);
|
|
if (oc) return oc->getName();
|
|
return makeDash(L"-");
|
|
}
|
|
|
|
#define NOTATIME 0xF0000000
|
|
void oAbstractRunner::resetTmpStore() {
|
|
tmpStore.startTime = startTime;
|
|
tmpStore.status = status;
|
|
tmpStore.startNo = StartNo;
|
|
tmpStore.bib = getBib();
|
|
}
|
|
|
|
bool oAbstractRunner::setTmpStore() {
|
|
bool res = false;
|
|
setStartNo(tmpStore.startNo, false);
|
|
res |= setStartTime(tmpStore.startTime, false, false, false);
|
|
res |= setStatus(tmpStore.status, false, false, false);
|
|
setBib(tmpStore.bib, 0, false, false);
|
|
return res;
|
|
}
|
|
|
|
bool oRunner::evaluateCard(bool doApply, vector<int> & MissingPunches,
|
|
int addpunch, bool sync) {
|
|
if (unsigned(status) >= 100u)
|
|
status = StatusUnknown; //Reset bad input
|
|
pClass clz = getClassRef(true);
|
|
MissingPunches.clear();
|
|
int oldFT = FinishTime;
|
|
int oldStartTime;
|
|
RunnerStatus oldStatus;
|
|
int *refStartTime;
|
|
RunnerStatus *refStatus;
|
|
|
|
if (doApply) {
|
|
oldStartTime = tStartTime;
|
|
tStartTime = startTime;
|
|
oldStatus = tStatus;
|
|
tStatus = status;
|
|
refStartTime = &tStartTime;
|
|
refStatus = &tStatus;
|
|
|
|
resetTmpStore();
|
|
apply(sync, 0, true);
|
|
}
|
|
else {
|
|
// tmp initialized from outside. Do not change tStatus, tStartTime. Work with tmpStore instead!
|
|
oldStartTime = tStartTime;
|
|
oldStatus = tStatus;
|
|
refStartTime = &tmpStore.startTime;
|
|
refStatus = &tmpStore.status;
|
|
|
|
createMultiRunner(false, sync);
|
|
}
|
|
|
|
// Reset card data
|
|
oPunchList::iterator p_it;
|
|
if (Card) {
|
|
for (p_it=Card->punches.begin(); p_it!=Card->punches.end(); ++p_it) {
|
|
p_it->tRogainingIndex = -1;
|
|
p_it->anyRogainingMatchControlId = -1;
|
|
p_it->tRogainingPoints = 0;
|
|
p_it->isUsed = false;
|
|
p_it->tIndex = -1;
|
|
p_it->tMatchControlId = -1;
|
|
p_it->tTimeAdjust = 0;
|
|
}
|
|
}
|
|
|
|
bool inTeam = tInTeam != 0;
|
|
tProblemDescription.clear();
|
|
tReduction = 0;
|
|
tRogainingPointsGross = 0;
|
|
tRogainingOvertime = 0;
|
|
|
|
vector<SplitData> oldTimes;
|
|
swap(splitTimes, oldTimes);
|
|
|
|
if (!Card) {
|
|
if ((inTeam || !tUseStartPunch) && doApply)
|
|
apply(sync, 0, false); //Post apply. Update start times.
|
|
|
|
if (storeTimes() && clz && sync) {
|
|
oe->reEvaluateAll({ clz->getId() }, sync);
|
|
}
|
|
normalizedSplitTimes.clear();
|
|
if (oldTimes.size() > 0 && Class)
|
|
clz->clearSplitAnalysis();
|
|
return false;
|
|
}
|
|
//Try to match class?!
|
|
if (!clz)
|
|
return false;
|
|
|
|
if (clz->ignoreStartPunch())
|
|
tUseStartPunch = false;
|
|
|
|
const pCourse course = getCourse(true);
|
|
|
|
if (!course) {
|
|
// Reset rogaining. Store start/finish
|
|
for (p_it = Card->punches.begin(); p_it != Card->punches.end(); ++p_it) {
|
|
if (p_it->isStart() && tUseStartPunch)
|
|
*refStartTime = p_it->Time;
|
|
else if (p_it->isFinish())
|
|
setFinishTime(p_it->Time);
|
|
}
|
|
if ((inTeam || !tUseStartPunch) && doApply)
|
|
apply(sync, 0, false); //Post apply. Update start times.
|
|
|
|
storeTimes();
|
|
// No course mode
|
|
int maxTimeStatus = 0;
|
|
if (getFinishTime() <= 0)
|
|
*refStatus = StatusDNF;
|
|
else {
|
|
if (clz) {
|
|
int mt = clz->getMaximumRunnerTime();
|
|
if (mt>0) {
|
|
if (getRunningTime() > mt)
|
|
maxTimeStatus = 1;
|
|
else
|
|
maxTimeStatus = 2;
|
|
}
|
|
else
|
|
maxTimeStatus = 2;
|
|
}
|
|
|
|
if (*refStatus == StatusMAX && maxTimeStatus == 2)
|
|
*refStatus = StatusUnknown;
|
|
}
|
|
if (*refStatus == StatusUnknown || *refStatus == StatusCANCEL || *refStatus == StatusDNS || *refStatus == StatusMAX) {
|
|
if (maxTimeStatus == 1)
|
|
*refStatus = StatusMAX;
|
|
else
|
|
*refStatus = StatusOK;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int startPunchCode = course->getStartPunchType();
|
|
int finishPunchCode = course->getFinishPunchType();
|
|
|
|
bool hasRogaining = course->hasRogaining();
|
|
|
|
// Pairs: <control index, point>
|
|
intkeymap< pair<int, int> > rogaining(course->getNumControls());
|
|
for (int k = 0; k< course->nControls; k++) {
|
|
if (course->Controls[k] && course->Controls[k]->isRogaining(hasRogaining)) {
|
|
int pt = course->Controls[k]->getRogainingPoints();
|
|
for (int j = 0; j<course->Controls[k]->nNumbers; j++) {
|
|
rogaining.insert(course->Controls[k]->Numbers[j], make_pair(k, pt));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (addpunch && Card->punches.empty()) {
|
|
Card->addPunch(addpunch, -1, course->Controls[0] ? course->Controls[0]->getId():0);
|
|
}
|
|
|
|
if (Card->punches.empty()) {
|
|
for(int k=0;k<course->nControls;k++) {
|
|
if (course->Controls[k]) {
|
|
course->Controls[k]->startCheckControl();
|
|
course->Controls[k]->addUncheckedPunches(MissingPunches, hasRogaining);
|
|
}
|
|
}
|
|
if ((inTeam || !tUseStartPunch) && doApply)
|
|
apply(sync, 0, false); //Post apply. Update start times.
|
|
|
|
if (storeTimes() && clz && sync) {
|
|
oe->reEvaluateAll({ clz->getId() }, sync);
|
|
}
|
|
|
|
normalizedSplitTimes.clear();
|
|
if (oldTimes.size() > 0 && clz)
|
|
clz->clearSplitAnalysis();
|
|
tRogainingPoints = max(0, getPointAdjustment());
|
|
return false;
|
|
}
|
|
|
|
// Reset rogaining
|
|
for (p_it=Card->punches.begin(); p_it!=Card->punches.end(); ++p_it) {
|
|
p_it->tRogainingIndex = -1;
|
|
p_it->anyRogainingMatchControlId = -1;
|
|
p_it->tRogainingPoints = 0;
|
|
}
|
|
|
|
bool clearSplitAnalysis = false;
|
|
|
|
|
|
//Search for start and update start time.
|
|
p_it=Card->punches.begin();
|
|
while ( p_it!=Card->punches.end()) {
|
|
if (p_it->Type == startPunchCode) {
|
|
if (tUseStartPunch && p_it->getAdjustedTime() != *refStartTime) {
|
|
p_it->setTimeAdjust(0);
|
|
*refStartTime = p_it->getAdjustedTime();
|
|
if (*refStartTime != oldStartTime)
|
|
clearSplitAnalysis = true;
|
|
//updateChanged();
|
|
}
|
|
break;
|
|
}
|
|
++p_it;
|
|
}
|
|
|
|
inthashmap expectedPunchCount(course->nControls);
|
|
inthashmap punchCount(Card->punches.size());
|
|
for (int k=0; k<course->nControls; k++) {
|
|
pControl ctrl=course->Controls[k];
|
|
if (ctrl && !ctrl->isRogaining(hasRogaining)) {
|
|
for (int j = 0; j<ctrl->nNumbers; j++)
|
|
++expectedPunchCount[ctrl->Numbers[j]];
|
|
}
|
|
}
|
|
|
|
for (p_it = Card->punches.begin(); p_it != Card->punches.end(); ++p_it) {
|
|
if (p_it->Type>=10 && p_it->Type<=1024)
|
|
++punchCount[p_it->Type];
|
|
}
|
|
|
|
p_it = Card->punches.begin();
|
|
splitTimes.resize(course->nControls, SplitData(NOTATIME, SplitData::Missing));
|
|
int k=0;
|
|
|
|
|
|
for (k=0;k<course->nControls;k++) {
|
|
//Skip start finish check
|
|
while(p_it!=Card->punches.end() &&
|
|
(p_it->isCheck() || p_it->isFinish() || p_it->isStart())) {
|
|
p_it->setTimeAdjust(0);
|
|
++p_it;
|
|
}
|
|
|
|
if (p_it==Card->punches.end())
|
|
break;
|
|
|
|
oPunchList::iterator tp_it=p_it;
|
|
pControl ctrl=course->Controls[k];
|
|
int skippedPunches = 0;
|
|
|
|
if (ctrl) {
|
|
int timeAdjust=ctrl->getTimeAdjust();
|
|
ctrl->startCheckControl();
|
|
|
|
// Add rogaining punches
|
|
if (addpunch && ctrl->isRogaining(hasRogaining) && ctrl->getFirstNumber() == addpunch) {
|
|
if ( Card->getPunchByType(addpunch) == 0) {
|
|
oPunch op(oe);
|
|
op.Type=addpunch;
|
|
op.Time=-1;
|
|
op.isUsed=true;
|
|
op.tIndex = k;
|
|
op.tMatchControlId=ctrl->getId();
|
|
Card->punches.insert(tp_it, op);
|
|
Card->updateChanged();
|
|
}
|
|
}
|
|
|
|
if (ctrl->getStatus() == oControl::StatusBad || ctrl->getStatus() == oControl::StatusOptional) {
|
|
// The control is marked "bad" but we found it anyway in the card. Mark it as used.
|
|
if (tp_it!=Card->punches.end() && ctrl->hasNumberUnchecked(tp_it->Type)) {
|
|
tp_it->isUsed=true; //Show that this is used when splittimes are calculated.
|
|
// Adjust if the time of this control was incorrectly set.
|
|
tp_it->setTimeAdjust(timeAdjust);
|
|
tp_it->tMatchControlId=ctrl->getId();
|
|
tp_it->tIndex = k;
|
|
splitTimes[k].setPunchTime(tp_it->getAdjustedTime());
|
|
++tp_it;
|
|
p_it=tp_it;
|
|
}
|
|
}
|
|
else {
|
|
while(!ctrl->controlCompleted(hasRogaining) && tp_it!=Card->punches.end()) {
|
|
if (ctrl->hasNumberUnchecked(tp_it->Type)) {
|
|
|
|
if (skippedPunches>0) {
|
|
if (ctrl->Status == oControl::StatusOK) {
|
|
int code = tp_it->Type;
|
|
if (expectedPunchCount[code]>1 && punchCount[code] < expectedPunchCount[code]) {
|
|
tp_it==Card->punches.end();
|
|
ctrl->uncheckNumber(code);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tp_it->isUsed=true; //Show that this is used when splittimes are calculated.
|
|
// Adjust if the time of this control was incorrectly set.
|
|
tp_it->setTimeAdjust(timeAdjust);
|
|
tp_it->tMatchControlId=ctrl->getId();
|
|
tp_it->tIndex = k;
|
|
if (ctrl->controlCompleted(hasRogaining))
|
|
splitTimes[k].setPunchTime(tp_it->getAdjustedTime());
|
|
++tp_it;
|
|
p_it=tp_it;
|
|
}
|
|
else {
|
|
if (ctrl->hasNumberUnchecked(addpunch)){
|
|
//Add this punch.
|
|
oPunch op(oe);
|
|
op.Type=addpunch;
|
|
op.Time=-1;
|
|
op.isUsed=true;
|
|
|
|
op.tMatchControlId=ctrl->getId();
|
|
op.tIndex = k;
|
|
Card->punches.insert(tp_it, op);
|
|
Card->updateChanged();
|
|
if (ctrl->controlCompleted(hasRogaining))
|
|
splitTimes[k].setPunched();
|
|
}
|
|
else {
|
|
skippedPunches++;
|
|
tp_it->isUsed=false;
|
|
++tp_it;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tp_it==Card->punches.end() && !ctrl->controlCompleted(hasRogaining)
|
|
&& ctrl->hasNumberUnchecked(addpunch) ) {
|
|
Card->addPunch(addpunch, -1, ctrl->getId());
|
|
if (ctrl->controlCompleted(hasRogaining))
|
|
splitTimes[k].setPunched();
|
|
Card->punches.back().isUsed=true;
|
|
Card->punches.back().tMatchControlId=ctrl->getId();
|
|
Card->punches.back().tIndex = k;
|
|
}
|
|
|
|
if (ctrl->controlCompleted(hasRogaining) && splitTimes[k].time == NOTATIME)
|
|
splitTimes[k].setPunched();
|
|
}
|
|
else //if (ctrl && ctrl->Status==oControl::StatusBad){
|
|
splitTimes[k].setNotPunched();
|
|
|
|
//Add missing punches
|
|
if (ctrl && !ctrl->controlCompleted(hasRogaining))
|
|
ctrl->addUncheckedPunches(MissingPunches, hasRogaining);
|
|
}
|
|
|
|
//Add missing punches for remaining controls
|
|
while (k<course->nControls) {
|
|
if (course->Controls[k]) {
|
|
pControl ctrl = course->Controls[k];
|
|
ctrl->startCheckControl();
|
|
|
|
if (ctrl->hasNumberUnchecked(addpunch)) {
|
|
Card->addPunch(addpunch, -1, ctrl->getId());
|
|
Card->updateChanged();
|
|
if (ctrl->controlCompleted(hasRogaining))
|
|
splitTimes[k].setNotPunched();
|
|
}
|
|
ctrl->addUncheckedPunches(MissingPunches, hasRogaining);
|
|
}
|
|
k++;
|
|
}
|
|
|
|
//Set the rest (if exist -- probably not) to "not used"
|
|
while(p_it!=Card->punches.end()){
|
|
p_it->isUsed=false;
|
|
p_it->tIndex = -1;
|
|
p_it->setTimeAdjust(0);
|
|
++p_it;
|
|
}
|
|
|
|
int OK = MissingPunches.empty();
|
|
|
|
tRogaining.clear();
|
|
tRogainingPoints = 0;
|
|
int time_limit = 0;
|
|
|
|
// Rogaining logic
|
|
if (rogaining.size() > 0) {
|
|
set<int> visitedControls;
|
|
for (p_it=Card->punches.begin(); p_it != Card->punches.end(); ++p_it) {
|
|
pair<int, int> pt;
|
|
if (rogaining.lookup(p_it->Type, pt)) {
|
|
p_it->anyRogainingMatchControlId = course->Controls[pt.first]->getId();
|
|
if (visitedControls.count(pt.first) == 0) {
|
|
visitedControls.insert(pt.first); // May noy be revisited
|
|
p_it->isUsed = true;
|
|
p_it->tRogainingIndex = pt.first;
|
|
p_it->tMatchControlId = p_it->anyRogainingMatchControlId;
|
|
p_it->tRogainingPoints = pt.second;
|
|
tRogaining.push_back(make_pair(course->Controls[pt.first], p_it->getAdjustedTime()));
|
|
splitTimes[pt.first].setPunchTime(p_it->getAdjustedTime());
|
|
tRogainingPoints += pt.second;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Manual point adjustment
|
|
tRogainingPoints = max(0, tRogainingPoints + getPointAdjustment());
|
|
|
|
int point_limit = course->getMinimumRogainingPoints();
|
|
if (point_limit>0 && tRogainingPoints<point_limit) {
|
|
tProblemDescription = L"X poäng fattas.#" + itow(point_limit-tRogainingPoints);
|
|
OK = false;
|
|
}
|
|
|
|
// Check this later
|
|
time_limit = course->getMaximumRogainingTime();
|
|
|
|
for (int k = 0; k<course->nControls; k++) {
|
|
if (course->Controls[k] && course->Controls[k]->isRogaining(hasRogaining)) {
|
|
if (!visitedControls.count(k))
|
|
splitTimes[k].setNotPunched();// = splitTimes[k-1];
|
|
}
|
|
}
|
|
}
|
|
|
|
int maxTimeStatus = 0;
|
|
if (clz && FinishTime>0) {
|
|
int mt = clz->getMaximumRunnerTime();
|
|
if (mt>0) {
|
|
if (getRunningTime() > mt)
|
|
maxTimeStatus = 1;
|
|
else
|
|
maxTimeStatus = 2;
|
|
}
|
|
else
|
|
maxTimeStatus = 2;
|
|
}
|
|
|
|
if (*refStatus == StatusMAX && maxTimeStatus == 2)
|
|
*refStatus = StatusUnknown;
|
|
|
|
if (OK && (*refStatus==0 || *refStatus==StatusDNS || *refStatus == StatusCANCEL || *refStatus==StatusMP || *refStatus==StatusOK || *refStatus==StatusDNF))
|
|
*refStatus = StatusOK;
|
|
else *refStatus = RunnerStatus(max(int(StatusMP), int(*refStatus)));
|
|
|
|
oPunchList::reverse_iterator backIter = Card->punches.rbegin();
|
|
|
|
if (finishPunchCode != oPunch::PunchFinish) {
|
|
while (backIter != Card->punches.rend()) {
|
|
if (backIter->Type == finishPunchCode)
|
|
break;
|
|
++backIter;
|
|
}
|
|
}
|
|
|
|
if (backIter != Card->punches.rend() && backIter->Type == finishPunchCode) {
|
|
FinishTime = backIter->Time;
|
|
if (finishPunchCode == oPunch::PunchFinish)
|
|
backIter->tMatchControlId=oPunch::PunchFinish;
|
|
}
|
|
else if (FinishTime<=0) {
|
|
*refStatus=RunnerStatus(max(int(StatusDNF), int(tStatus)));
|
|
tProblemDescription = L"Måltid saknas.";
|
|
FinishTime=0;
|
|
}
|
|
|
|
if (*refStatus == StatusOK && maxTimeStatus == 1)
|
|
*refStatus = StatusMAX; //Maxtime
|
|
|
|
if (!MissingPunches.empty()) {
|
|
tProblemDescription = L"Stämplingar saknas: X#" + itow(MissingPunches[0]);
|
|
for (unsigned j = 1; j<3; j++) {
|
|
if (MissingPunches.size()>j)
|
|
tProblemDescription += L", " + itow(MissingPunches[j]);
|
|
}
|
|
if (MissingPunches.size()>3)
|
|
tProblemDescription += L"...";
|
|
else
|
|
tProblemDescription += L".";
|
|
}
|
|
|
|
// Adjust times on course, including finish time
|
|
doAdjustTimes(course);
|
|
|
|
tRogainingPointsGross = tRogainingPoints;
|
|
|
|
if (oldStatus!=*refStatus || oldFT!=FinishTime) {
|
|
clearSplitAnalysis = true;
|
|
}
|
|
|
|
if ((inTeam || !tUseStartPunch) && doApply)
|
|
apply(sync, 0, false); //Post apply. Update start times.
|
|
|
|
if (tCachedRunningTime != FinishTime - *refStartTime) {
|
|
tCachedRunningTime = FinishTime - *refStartTime;
|
|
clearSplitAnalysis = true;
|
|
}
|
|
|
|
if (time_limit > 0) {
|
|
int rt = getRunningTime();
|
|
if (rt > 0) {
|
|
int overTime = rt - time_limit;
|
|
if (overTime > 0) {
|
|
tRogainingOvertime = overTime;
|
|
tReduction = course->calculateReduction(overTime);
|
|
tProblemDescription = L"Tidsavdrag: X poäng.#" + itow(tReduction);
|
|
tRogainingPoints = max(0, tRogainingPoints - tReduction);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Clear split analysis data if necessary
|
|
bool clear = splitTimes.size() != oldTimes.size() || clearSplitAnalysis;
|
|
for (size_t k = 0; !clear && k<oldTimes.size(); k++) {
|
|
if (splitTimes[k].time != oldTimes[k].time)
|
|
clear = true;
|
|
}
|
|
|
|
if (clear) {
|
|
normalizedSplitTimes.clear();
|
|
if (clz)
|
|
clz->clearSplitAnalysis();
|
|
}
|
|
|
|
if (doApply)
|
|
storeTimes();
|
|
if (clz && sync) {
|
|
bool update = false;
|
|
if (tInTeam) {
|
|
int t1 = clz->getTotalLegLeaderTime(tLeg, false);
|
|
int t2 = tInTeam->getLegRunningTime(tLeg, false);
|
|
if (t2<=t1 && t2>0)
|
|
update = true;
|
|
|
|
int t3 = clz->getTotalLegLeaderTime(tLeg, true);
|
|
int t4 = tInTeam->getLegRunningTime(tLeg, true);
|
|
if (t4<=t3 && t4>0)
|
|
update = true;
|
|
}
|
|
|
|
if (!update) {
|
|
int t1 = clz->getBestLegTime(tLeg);
|
|
int t2 = getRunningTime();
|
|
if (t2<=t1 && t2>0)
|
|
update = true;
|
|
}
|
|
if (update) {
|
|
oe->reEvaluateAll({ clz->getId() }, sync);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void oRunner::clearOnChangedRunningTime() {
|
|
if (tCachedRunningTime != FinishTime - tStartTime) {
|
|
tCachedRunningTime = FinishTime - tStartTime;
|
|
normalizedSplitTimes.clear();
|
|
if (Class)
|
|
getClassRef(true)->clearSplitAnalysis();
|
|
}
|
|
}
|
|
|
|
void oRunner::doAdjustTimes(pCourse course) {
|
|
if (!Card)
|
|
return;
|
|
|
|
assert(course->nControls == splitTimes.size());
|
|
int adjustment = 0;
|
|
oPunchList::iterator it = Card->punches.begin();
|
|
|
|
adjustTimes.resize(splitTimes.size());
|
|
for (int n = 0; n < course->nControls; n++) {
|
|
pControl ctrl = course->Controls[n];
|
|
if (!ctrl)
|
|
continue;
|
|
|
|
while (it != Card->punches.end() && !it->isUsed) {
|
|
it->setTimeAdjust(adjustment);
|
|
++it;
|
|
}
|
|
|
|
int minTime = ctrl->getMinTime();
|
|
if (ctrl->getStatus() == oControl::StatusNoTiming) {
|
|
int t = 0;
|
|
if (n>0 && splitTimes[n].time>0 && splitTimes[n-1].time>0) {
|
|
t = splitTimes[n].time + adjustment - splitTimes[n-1].time;
|
|
}
|
|
else if (n == 0 && splitTimes[n].time>0) {
|
|
t = splitTimes[n].time - tStartTime;
|
|
}
|
|
adjustment -= t;
|
|
}
|
|
else if (minTime>0) {
|
|
int t = 0;
|
|
if (n>0 && splitTimes[n].time>0 && splitTimes[n-1].time>0) {
|
|
t = splitTimes[n].time + adjustment - splitTimes[n-1].time;
|
|
}
|
|
else if (n == 0 && splitTimes[n].time>0) {
|
|
t = splitTimes[n].time - tStartTime;
|
|
}
|
|
int maxadjust = max(minTime-t, 0);
|
|
adjustment += maxadjust;
|
|
}
|
|
|
|
if (it != Card->punches.end() && it->tMatchControlId == ctrl->getId()) {
|
|
it->adjustTimeAdjust(adjustment);
|
|
++it;
|
|
}
|
|
|
|
adjustTimes[n] = adjustment;
|
|
if (splitTimes[n].time>0)
|
|
splitTimes[n].time += adjustment;
|
|
}
|
|
|
|
// Adjust remaining
|
|
while (it != Card->punches.end()) {
|
|
it->setTimeAdjust(adjustment);
|
|
++it;
|
|
}
|
|
|
|
FinishTime += adjustment;
|
|
}
|
|
|
|
bool oRunner::storeTimes() {
|
|
bool updated = storeTimesAux(Class);
|
|
if (tInTeam && tInTeam->Class && tInTeam->Class != Class)
|
|
updated |= storeTimesAux(tInTeam->Class);
|
|
else if (Class && Class->getQualificationFinal()) {
|
|
updated |= storeTimesAux(getClassRef(true));
|
|
}
|
|
return updated;
|
|
}
|
|
|
|
bool oRunner::storeTimesAux(pClass targetClass) {
|
|
if (!targetClass)
|
|
return false;
|
|
if (tInTeam) {
|
|
if (tInTeam->getNumShortening(tLeg) > 0)
|
|
return false;
|
|
}
|
|
else {
|
|
if (getNumShortening() > 0)
|
|
return false;
|
|
}
|
|
bool updated = false;
|
|
//Store best time in class
|
|
if (tInTeam && tInTeam->Class == targetClass) {
|
|
if (targetClass && unsigned(tLeg)<targetClass->tLeaderTime.size()) {
|
|
// Update for extra/optional legs
|
|
int firstLeg = tLeg;
|
|
int lastLeg = tLeg + 1;
|
|
while(firstLeg>0 && targetClass->legInfo[firstLeg].isOptional())
|
|
firstLeg--;
|
|
int nleg = targetClass->legInfo.size();
|
|
while(lastLeg<nleg && targetClass->legInfo[lastLeg].isOptional())
|
|
lastLeg++;
|
|
|
|
for (int leg = firstLeg; leg<lastLeg; leg++) {
|
|
if (tStatus==StatusOK) {
|
|
int &bt=targetClass->tLeaderTime[leg].bestTimeOnLeg;
|
|
int rt=getRunningTime();
|
|
if (rt > 0 && (bt == 0 || rt < bt)) {
|
|
bt=rt;
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool updateTotal = true;
|
|
bool updateTotalInput = true;
|
|
|
|
int basePLeg = firstLeg;
|
|
while (basePLeg > 0 && targetClass->legInfo[basePLeg].isParallel())
|
|
basePLeg--;
|
|
|
|
int ix = basePLeg;
|
|
while (ix < nleg && (ix == basePLeg || targetClass->legInfo[ix].isParallel()) ) {
|
|
updateTotal = updateTotal && tInTeam->getLegStatus(ix, false)==StatusOK;
|
|
updateTotalInput = updateTotalInput && tInTeam->getLegStatus(ix, true)==StatusOK;
|
|
ix++;
|
|
}
|
|
|
|
if (updateTotal) {
|
|
int rt = 0;
|
|
int ix = basePLeg;
|
|
while (ix < nleg && (ix == basePLeg || targetClass->legInfo[ix].isParallel()) ) {
|
|
rt = max(rt, tInTeam->getLegRunningTime(ix, false));
|
|
ix++;
|
|
}
|
|
|
|
for (int leg = firstLeg; leg<lastLeg; leg++) {
|
|
int &bt=targetClass->tLeaderTime[leg].totalLeaderTime;
|
|
if (rt > 0 && (bt == 0 || rt < bt)) {
|
|
bt=rt;
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
if (updateTotalInput) {
|
|
//int rt=tInTeam->getLegRunningTime(tLeg, true);
|
|
int rt = 0;
|
|
int ix = basePLeg;
|
|
while (ix < nleg && (ix == basePLeg || targetClass->legInfo[ix].isParallel()) ) {
|
|
rt = max(rt, tInTeam->getLegRunningTime(ix, true));
|
|
ix++;
|
|
}
|
|
for (int leg = firstLeg; leg<lastLeg; leg++) {
|
|
int &bt=targetClass->tLeaderTime[leg].totalLeaderTimeInput;
|
|
if (rt > 0 && (bt == 0 || rt < bt)) {
|
|
bt=rt;
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
size_t dupLeg = targetClass->mapLeg(tDuplicateLeg);
|
|
if (targetClass && dupLeg < targetClass->tLeaderTime.size()) {
|
|
if (tStatus == StatusOK) {
|
|
int &bt = targetClass->tLeaderTime[dupLeg].bestTimeOnLeg;
|
|
int rt = getRunningTime();
|
|
if (rt > 0 && (bt == 0 || rt < bt)) {
|
|
bt = rt;
|
|
updated = true;
|
|
}
|
|
}
|
|
int &bt = targetClass->tLeaderTime[dupLeg].totalLeaderTime;
|
|
int rt = getRaceRunningTime(dupLeg);
|
|
if (rt > 0 && (bt == 0 || rt < bt)) {
|
|
bt = rt;
|
|
updated = true;
|
|
targetClass->tLeaderTime[dupLeg].totalLeaderTimeInput = rt;
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t mappedLeg = targetClass->mapLeg(tLeg);
|
|
// Best input time
|
|
if (mappedLeg<targetClass->tLeaderTime.size()) {
|
|
int &it = targetClass->tLeaderTime[mappedLeg].inputTime;
|
|
if (inputTime > 0 && inputStatus == StatusOK && (it == 0 || inputTime < it) ) {
|
|
it = inputTime;
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
if (targetClass && tStatus==StatusOK) {
|
|
int rt = getRunningTime();
|
|
pCourse pCrs = getCourse(false);
|
|
if (pCrs && rt > 0) {
|
|
map<int, int>::iterator res = targetClass->tBestTimePerCourse.find(pCrs->getId());
|
|
if (res == targetClass->tBestTimePerCourse.end()) {
|
|
targetClass->tBestTimePerCourse[pCrs->getId()] = rt;
|
|
updated = true;
|
|
}
|
|
else if (rt < res->second) {
|
|
res->second = rt;
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
return updated;
|
|
}
|
|
|
|
int oRunner::getRaceRunningTime(int leg) const
|
|
{
|
|
if (tParentRunner)
|
|
return tParentRunner->getRaceRunningTime(leg);
|
|
|
|
if (leg==-1)
|
|
leg=multiRunner.size()-1;
|
|
|
|
if (leg==0) {
|
|
if (getTotalStatus() == StatusOK)
|
|
return getRunningTime() + inputTime;
|
|
else return 0;
|
|
}
|
|
leg--;
|
|
|
|
if (unsigned(leg) < multiRunner.size() && multiRunner[leg]) {
|
|
if (Class) {
|
|
pClass pc=Class;
|
|
LegTypes lt=pc->getLegType(leg);
|
|
pRunner r=multiRunner[leg];
|
|
|
|
switch(lt) {
|
|
case LTNormal:
|
|
if (r->statusOK()) {
|
|
int dt=leg>0 ? r->getRaceRunningTime(leg)+r->getRunningTime():0;
|
|
return max(r->getFinishTime()-tStartTime, dt); // ### Luckor, jaktstart???
|
|
}
|
|
else return 0;
|
|
break;
|
|
|
|
case LTSum:
|
|
if (r->statusOK())
|
|
return r->getRunningTime()+getRaceRunningTime(leg);
|
|
else return 0;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
else return getRunningTime();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool oRunner::sortSplit(const oRunner &a, const oRunner &b)
|
|
{
|
|
int acid=a.getClassId(true);
|
|
int bcid=b.getClassId(true);
|
|
if (acid!=bcid)
|
|
return acid<bcid;
|
|
else if (a.tempStatus != b.tempStatus)
|
|
return a.tempStatus<b.tempStatus;
|
|
else {
|
|
if (a.tempStatus==StatusOK) {
|
|
if (a.tempRT!=b.tempRT)
|
|
return a.tempRT<b.tempRT;
|
|
}
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
a.tRealName.c_str(), a.tRealName.length(),
|
|
b.tRealName.c_str(), b.tRealName.length()) == CSTR_LESS_THAN;
|
|
}
|
|
}
|
|
|
|
bool oRunner::operator<(const oRunner &c) const {
|
|
const oClass * myClass = getClassRef(true);
|
|
const oClass * cClass = c.getClassRef(true);
|
|
if (!myClass || !cClass)
|
|
return size_t(myClass) < size_t(cClass);
|
|
else if (Class == cClass && Class->getClassStatus() != oClass::Normal)
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
tRealName.c_str(), tRealName.length(),
|
|
c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN;
|
|
|
|
if (oe->CurrentSortOrder == ClassStartTime) {
|
|
if (myClass->Id != cClass->Id) {
|
|
if (myClass->tSortIndex != cClass->tSortIndex)
|
|
return myClass->tSortIndex < cClass->tSortIndex;
|
|
else
|
|
return myClass->Id < cClass->Id;
|
|
}
|
|
else if (tStartTime != c.tStartTime) {
|
|
if (tStartTime <= 0 && c.tStartTime > 0)
|
|
return false;
|
|
else if (c.tStartTime <= 0 && tStartTime > 0)
|
|
return true;
|
|
else return tStartTime < c.tStartTime;
|
|
}
|
|
else {
|
|
//if (StartNo != c.StartNo && !(getBib().empty() && c.getBib().empty()))
|
|
// return StartNo < c.StartNo;
|
|
const wstring &b1 = getBib();
|
|
const wstring &b2 = c.getBib();
|
|
if (b1 != b2) {
|
|
return compareBib(b1, b2);
|
|
}
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassResult) {
|
|
RunnerStatus stat = tStatus == StatusUnknown ? StatusOK : tStatus;
|
|
RunnerStatus cstat = c.tStatus == StatusUnknown ? StatusOK : c.tStatus;
|
|
|
|
if (myClass != cClass)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
else if (tLegEquClass != c.tLegEquClass)
|
|
return tLegEquClass < c.tLegEquClass;
|
|
else if (tDuplicateLeg != c.tDuplicateLeg)
|
|
return tDuplicateLeg < c.tDuplicateLeg;
|
|
else if (stat != cstat)
|
|
return RunnerStatusOrderMap[stat] < RunnerStatusOrderMap[cstat];
|
|
else {
|
|
if (stat == StatusOK) {
|
|
if (Class->getNoTiming()) {
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
tRealName.c_str(), tRealName.length(),
|
|
c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN;
|
|
}
|
|
int s = getNumShortening();
|
|
int cs = c.getNumShortening();
|
|
if (s != cs)
|
|
return s < cs;
|
|
|
|
int t = getRunningTime();
|
|
if (t <= 0)
|
|
t = 3600 * 100;
|
|
int ct = c.getRunningTime();
|
|
if (ct <= 0)
|
|
ct = 3600 * 100;
|
|
|
|
if (t != ct)
|
|
return t < ct;
|
|
}
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassCourseResult) {
|
|
if (myClass != cClass)
|
|
return myClass->tSortIndex < cClass->tSortIndex;
|
|
|
|
const pCourse crs1 = getCourse(false);
|
|
const pCourse crs2 = c.getCourse(false);
|
|
if (crs1 != crs2) {
|
|
int id1 = crs1 ? crs1->getId() : 0;
|
|
int id2 = crs2 ? crs2->getId() : 0;
|
|
return id1 < id2;
|
|
}
|
|
else if (tDuplicateLeg != c.tDuplicateLeg)
|
|
return tDuplicateLeg < c.tDuplicateLeg;
|
|
else if (tStatus != c.tStatus)
|
|
return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus];
|
|
else {
|
|
if (tStatus == StatusOK) {
|
|
if (Class->getNoTiming()) {
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
tRealName.c_str(), tRealName.length(),
|
|
c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN;
|
|
}
|
|
int s = getNumShortening();
|
|
int cs = c.getNumShortening();
|
|
if (s != cs)
|
|
return s < cs;
|
|
|
|
int t = getRunningTime();
|
|
int ct = c.getRunningTime();
|
|
if (t != ct)
|
|
return t < ct;
|
|
}
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == SortByName) {
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
tRealName.c_str(), tRealName.length(),
|
|
c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN;
|
|
}
|
|
else if (oe->CurrentSortOrder == SortByLastName) {
|
|
wstring a = getFamilyName();
|
|
wstring b = c.getFamilyName();
|
|
if (a.empty() && !b.empty())
|
|
return false;
|
|
else if (b.empty() && !a.empty())
|
|
return true;
|
|
else if (a != b) {
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
a.c_str(), a.length(),
|
|
b.c_str(), b.length()) == CSTR_LESS_THAN;
|
|
}
|
|
a = getGivenName();
|
|
b = c.getGivenName();
|
|
if (a != b) {
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
a.c_str(), a.length(),
|
|
b.c_str(), b.length()) == CSTR_LESS_THAN;
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == SortByFinishTime) {
|
|
if (tStatus != c.tStatus)
|
|
return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus];
|
|
else {
|
|
int ft = getFinishTimeAdjusted();
|
|
int cft = c.getFinishTimeAdjusted();
|
|
if (tStatus == StatusOK && ft != cft)
|
|
return ft < cft;
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == SortByFinishTimeReverse) {
|
|
int ft = getFinishTimeAdjusted();
|
|
int cft = c.getFinishTimeAdjusted();
|
|
if (ft != cft)
|
|
return ft > cft;
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassFinishTime) {
|
|
if (myClass != cClass)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
if (tStatus != c.tStatus)
|
|
return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus];
|
|
else {
|
|
int ft = getFinishTimeAdjusted();
|
|
int cft = c.getFinishTimeAdjusted();
|
|
if (tStatus == StatusOK && ft != cft)
|
|
return ft < cft;
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == SortByStartTime) {
|
|
if (tStartTime < c.tStartTime)
|
|
return true;
|
|
else if (tStartTime > c.tStartTime)
|
|
return false;
|
|
|
|
const wstring &b1 = getBib();
|
|
const wstring &b2 = c.getBib();
|
|
if (b1 != b2) {
|
|
return compareBib(b1, b2);
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == SortByStartTimeClass) {
|
|
if (tStartTime < c.tStartTime)
|
|
return true;
|
|
else if (tStartTime > c.tStartTime)
|
|
return false;
|
|
|
|
if (myClass != cClass)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
}
|
|
else if (oe->CurrentSortOrder == SortByEntryTime) {
|
|
auto dci = getDCI(), cdci = c.getDCI();
|
|
int ed = dci.getInt("EntryDate");
|
|
int ced = cdci.getInt("EntryDate");
|
|
if (ed != ced)
|
|
return ed > ced;
|
|
int et = dci.getInt("EntryTime");
|
|
int cet = cdci.getInt("EntryTime");
|
|
if (et != cet)
|
|
return et > cet;
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassPoints) {
|
|
if (myClass != cClass)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
else if (tDuplicateLeg != c.tDuplicateLeg)
|
|
return tDuplicateLeg < c.tDuplicateLeg;
|
|
else if (tStatus != c.tStatus)
|
|
return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus];
|
|
else {
|
|
if (tStatus == StatusOK) {
|
|
if (tRogainingPoints != c.tRogainingPoints)
|
|
return tRogainingPoints > c.tRogainingPoints;
|
|
int t = getRunningTime();
|
|
int ct = c.getRunningTime();
|
|
if (t != ct)
|
|
return t < ct;
|
|
}
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassTotalResult) {
|
|
if (myClass != cClass)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
else if (tDuplicateLeg != c.tDuplicateLeg)
|
|
return tDuplicateLeg < c.tDuplicateLeg;
|
|
else {
|
|
RunnerStatus s1, s2;
|
|
s1 = getTotalStatus();
|
|
s2 = c.getTotalStatus();
|
|
if (s1 != s2)
|
|
return s1 < s2;
|
|
else if (s1 == StatusOK) {
|
|
if (Class->getNoTiming()) {
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
tRealName.c_str(), tRealName.length(),
|
|
c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN;
|
|
}
|
|
int t = getTotalRunningTime(FinishTime, true);
|
|
int ct = c.getTotalRunningTime(c.FinishTime, true);
|
|
if (t != ct)
|
|
return t < ct;
|
|
}
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == CourseResult) {
|
|
const pCourse crs1 = getCourse(false);
|
|
const pCourse crs2 = c.getCourse(false);
|
|
if (crs1 != crs2) {
|
|
int id1 = crs1 ? crs1->getId() : 0;
|
|
int id2 = crs2 ? crs2->getId() : 0;
|
|
return id1 < id2;
|
|
}
|
|
else if (tStatus != c.tStatus)
|
|
return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus];
|
|
else {
|
|
if (tStatus == StatusOK) {
|
|
|
|
int s = getNumShortening();
|
|
int cs = c.getNumShortening();
|
|
if (s != cs)
|
|
return s < cs;
|
|
|
|
int t = getRunningTime();
|
|
int ct = c.getRunningTime();
|
|
if (t != ct) {
|
|
return t < ct;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == CourseStartTime) {
|
|
const pCourse crs1 = getCourse(false);
|
|
const pCourse crs2 = c.getCourse(false);
|
|
if (crs1 != crs2) {
|
|
int id1 = crs1 ? crs1->getId() : 0;
|
|
int id2 = crs2 ? crs2->getId() : 0;
|
|
return id1 < id2;
|
|
}
|
|
else if (tStartTime != c.tStartTime)
|
|
return tStartTime < c.tStartTime;
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassStartTimeClub) {
|
|
if (myClass != cClass)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
else if (tStartTime != c.tStartTime) {
|
|
if (tStartTime <= 0 && c.tStartTime > 0)
|
|
return false;
|
|
else if (c.tStartTime <= 0 && tStartTime > 0)
|
|
return true;
|
|
else return tStartTime < c.tStartTime;
|
|
}
|
|
else if (Club != c.Club) {
|
|
return getClub() < c.getClub();
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassTeamLeg) {
|
|
if (myClass->Id != cClass->Id)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
else if (tInTeam != c.tInTeam) {
|
|
if (tInTeam == 0)
|
|
return true;
|
|
else if (c.tInTeam == 0)
|
|
return false;
|
|
if (tInTeam->StartNo != c.tInTeam->StartNo)
|
|
return tInTeam->StartNo < c.tInTeam->StartNo;
|
|
else
|
|
return tInTeam->sName < c.tInTeam->sName;
|
|
}
|
|
else if (tInTeam && tLeg != c.tLeg)
|
|
return tLeg < c.tLeg;
|
|
else if (tStartTime != c.tStartTime) {
|
|
if (tStartTime <= 0 && c.tStartTime > 0)
|
|
return false;
|
|
else if (c.tStartTime <= 0 && tStartTime > 0)
|
|
return true;
|
|
else return tStartTime < c.tStartTime;
|
|
}
|
|
else {
|
|
const wstring &b1 = getBib();
|
|
const wstring &b2 = c.getBib();
|
|
if (StartNo != c.StartNo && b1 != b2)
|
|
return StartNo < c.StartNo;
|
|
}
|
|
}
|
|
else if (oe->CurrentSortOrder == ClassLiveResult) {
|
|
if (myClass->Id != cClass->Id)
|
|
return myClass->tSortIndex < cClass->tSortIndex || (myClass->tSortIndex == cClass->tSortIndex && myClass->Id < cClass->Id);
|
|
|
|
if (currentControlTime != c.currentControlTime)
|
|
return currentControlTime < c.currentControlTime;
|
|
}
|
|
return CompareString(LOCALE_USER_DEFAULT, 0,
|
|
tRealName.c_str(), tRealName.length(),
|
|
c.tRealName.c_str(), c.tRealName.length()) == CSTR_LESS_THAN;
|
|
|
|
}
|
|
|
|
void oAbstractRunner::setClub(const wstring &clubName)
|
|
{
|
|
pClub pc=Club;
|
|
Club = clubName.empty() ? 0 : oe->getClubCreate(0, clubName);
|
|
if (pc != Club) {
|
|
updateChanged();
|
|
if (Class) {
|
|
// Vacant clubs have special logic
|
|
getClassRef(true)->tResultInfo.clear();
|
|
}
|
|
if (Club && Club->isVacant()) { // Clear entry date/time for vacant
|
|
getDI().setInt("EntryDate", 0);
|
|
getDI().setInt("EntryTime", 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
pClub oAbstractRunner::setClubId(int clubId)
|
|
{
|
|
pClub pc=Club;
|
|
Club = oe->getClub(clubId);
|
|
if (pc != Club) {
|
|
updateChanged();
|
|
if (Class) {
|
|
// Vacant clubs have special logic
|
|
Class->tResultInfo.clear();
|
|
}
|
|
if (Club && Club->isVacant()) { // Clear entry date/time for vacant
|
|
getDI().setInt("EntryDate", 0);
|
|
getDI().setInt("EntryTime", 0);
|
|
}
|
|
}
|
|
return Club;
|
|
}
|
|
|
|
void oRunner::setClub(const wstring &clubName)
|
|
{
|
|
if (tParentRunner)
|
|
tParentRunner->setClub(clubName);
|
|
else {
|
|
oAbstractRunner::setClub(clubName);
|
|
propagateClub();
|
|
}
|
|
}
|
|
|
|
pClub oRunner::setClubId(int clubId) {
|
|
if (tParentRunner)
|
|
tParentRunner->setClubId(clubId);
|
|
else {
|
|
oAbstractRunner::setClubId(clubId);
|
|
|
|
propagateClub();
|
|
}
|
|
return Club;
|
|
}
|
|
|
|
void oRunner::propagateClub() {
|
|
for (size_t k = 0; k < multiRunner.size(); k++) {
|
|
if (multiRunner[k] && multiRunner[k]->Club != Club) {
|
|
multiRunner[k]->Club = Club;
|
|
multiRunner[k]->updateChanged();
|
|
}
|
|
}
|
|
if (tInTeam && tInTeam->getClubRef() != Club && ((Class && Class->getNumDistinctRunners() == 1) || tInTeam->getNumAssignedRunners() <= 1)) {
|
|
tInTeam->Club = Club;
|
|
tInTeam->updateChanged();
|
|
}
|
|
}
|
|
|
|
void oAbstractRunner::setStartNo(int no, bool tmpOnly) {
|
|
if (tmpOnly) {
|
|
tmpStore.startNo = no;
|
|
return;
|
|
}
|
|
|
|
if (no!=StartNo) {
|
|
if (oe)
|
|
oe->bibStartNoToRunnerTeam.clear();
|
|
StartNo=no;
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
void oRunner::setStartNo(int no, bool tmpOnly)
|
|
{
|
|
if (tInTeam) {
|
|
if (tInTeam->getStartNo() == 0)
|
|
tInTeam->setStartNo(no, tmpOnly);
|
|
else {
|
|
// Do not allow different from team
|
|
no = tInTeam->getStartNo();
|
|
}
|
|
}
|
|
if (tParentRunner)
|
|
tParentRunner->setStartNo(no, tmpOnly);
|
|
else {
|
|
oAbstractRunner::setStartNo(no, tmpOnly);
|
|
|
|
for (size_t k=0;k<multiRunner.size();k++)
|
|
if (multiRunner[k])
|
|
multiRunner[k]->oAbstractRunner::setStartNo(no, tmpOnly);
|
|
}
|
|
}
|
|
|
|
void oRunner::updateStartNo(int no) {
|
|
if (tInTeam) {
|
|
tInTeam->synchronize(false);
|
|
for (pRunner r : tInTeam->Runners) {
|
|
if (r) {
|
|
r->synchronize(false);
|
|
}
|
|
}
|
|
|
|
tInTeam->setStartNo(no, false);
|
|
for (pRunner r : tInTeam->Runners) {
|
|
if (r) {
|
|
r->setStartNo(no, false);
|
|
}
|
|
}
|
|
|
|
tInTeam->synchronize(true);
|
|
for (pRunner r : tInTeam->Runners) {
|
|
if (r)
|
|
r->synchronize(true);
|
|
}
|
|
}
|
|
else {
|
|
setStartNo(no, false);
|
|
synchronize(true);
|
|
}
|
|
}
|
|
|
|
|
|
int oRunner::getPlace() const {
|
|
if (tPlace.isOld(*oe)) {
|
|
if (Class) {
|
|
oEvent::ResultType rt = oEvent::ResultType::ClassResult;
|
|
if (Class->isRogaining())
|
|
oe->calculateRogainingResults({ getClassId(true) });
|
|
else
|
|
oe->calculateResults({ getClassId(true) }, rt, false);
|
|
}
|
|
}
|
|
return tPlace.value;
|
|
}
|
|
|
|
int oRunner::getCoursePlace() const {
|
|
return tCoursePlace;
|
|
}
|
|
|
|
int oRunner::getTotalPlace() const
|
|
{
|
|
if (tInTeam)
|
|
return tInTeam->getLegPlace(tLeg, true);
|
|
else
|
|
return tTotalPlace;
|
|
}
|
|
|
|
wstring oAbstractRunner::getPlaceS() const
|
|
{
|
|
wchar_t bf[16];
|
|
int p=getPlace();
|
|
if (p>0 && p<10000){
|
|
_itow_s(p, bf, 16, 10);
|
|
return bf;
|
|
}
|
|
else return _EmptyWString;
|
|
}
|
|
|
|
wstring oAbstractRunner::getPrintPlaceS(bool withDot) const
|
|
{
|
|
wchar_t bf[16];
|
|
int p=getPlace();
|
|
if (p>0 && p<10000){
|
|
if (withDot) {
|
|
_itow_s(p, bf, 16, 10);
|
|
return wstring(bf)+L".";
|
|
}
|
|
else
|
|
return itow(p);
|
|
}
|
|
else return _EmptyWString;
|
|
}
|
|
|
|
wstring oAbstractRunner::getTotalPlaceS() const
|
|
{
|
|
wchar_t bf[16];
|
|
int p=getTotalPlace();
|
|
if (p>0 && p<10000){
|
|
_itow_s(p, bf, 16, 10);
|
|
return bf;
|
|
}
|
|
else return _EmptyWString;
|
|
}
|
|
|
|
wstring oAbstractRunner::getPrintTotalPlaceS(bool withDot) const
|
|
{
|
|
wchar_t bf[16];
|
|
int p=getTotalPlace();
|
|
if (p>0 && p<10000){
|
|
if (withDot) {
|
|
_itow_s(p, bf, 16, 10);
|
|
return wstring(bf)+L".";
|
|
}
|
|
else
|
|
return itow(p);
|
|
}
|
|
else return _EmptyWString;
|
|
}
|
|
wstring oRunner::getGivenName() const
|
|
{
|
|
return ::getGivenName(sName);
|
|
}
|
|
|
|
wstring oRunner::getFamilyName() const
|
|
{
|
|
return ::getFamilyName(sName);
|
|
}
|
|
|
|
void oRunner::setCardNo(int cno, bool matchCard, bool updateFromDatabase)
|
|
{
|
|
if (cno != getCardNo()) {
|
|
int oldNo = getCardNo();
|
|
cardNumber = cno;
|
|
|
|
if (oe->cardToRunnerHash && cno != 0 && isAddedToEvent() && !isTemporaryObject) {
|
|
oe->cardToRunnerHash->emplace(cno, this);
|
|
}
|
|
|
|
if (isAddedToEvent()) {
|
|
oFreePunch::rehashPunches(*oe, oldNo, 0);
|
|
oFreePunch::rehashPunches(*oe, cardNumber, 0);
|
|
}
|
|
|
|
if (matchCard && !Card) {
|
|
pCard c = oe->getCardByNumber(cno);
|
|
|
|
if (c && !c->tOwner) {
|
|
vector<int> mp;
|
|
addPunches(c, mp);
|
|
}
|
|
}
|
|
|
|
if (!updateFromDatabase)
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
bool oRunner::isHiredCard() const {
|
|
if (getDCI().getInt("CardFee") != 0)
|
|
return true;
|
|
if (tParentRunner && tParentRunner != this)
|
|
return tParentRunner->isHiredCard(getCardNo());
|
|
|
|
return isHiredCard(cardNumber);
|
|
}
|
|
|
|
bool oRunner::isHiredCard(int cno) const {
|
|
if (cno == getCardNo())
|
|
return getDCI().getInt("CardFee") != 0;
|
|
|
|
for (pRunner r : multiRunner) {
|
|
if (r && r->getCardNo() == cno && r->getDCI().getInt("CardFee") != 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int oRunner::setCard(int cardId)
|
|
{
|
|
pCard c = cardId ? oe->getCard(cardId) : 0;
|
|
int oldId = 0;
|
|
|
|
if (Card != c) {
|
|
if (Card) {
|
|
oldId = Card->getId();
|
|
Card->tOwner = 0;
|
|
}
|
|
if (c) {
|
|
if (c->tOwner) {
|
|
pRunner otherR = c->tOwner;
|
|
assert(otherR != this);
|
|
otherR->Card = 0;
|
|
otherR->updateChanged();
|
|
otherR->setStatus(StatusUnknown, true, false);
|
|
otherR->synchronize(true);
|
|
}
|
|
c->tOwner = this;
|
|
setCardNo(c->cardNo, false, true);
|
|
}
|
|
Card = c;
|
|
vector<int> mp;
|
|
evaluateCard(true, mp);
|
|
updateChanged();
|
|
synchronize(true);
|
|
}
|
|
return oldId;
|
|
}
|
|
|
|
void oAbstractRunner::setName(const wstring &n, bool manualUpdate)
|
|
{
|
|
wstring tn = trim(n);
|
|
if (tn.empty())
|
|
throw std::exception("Tomt namn är inte tillåtet.");
|
|
if (tn != sName){
|
|
sName.swap(tn);
|
|
if (manualUpdate)
|
|
setFlag(FlagUpdateName, true);
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
void oRunner::setName(const wstring &in, bool manualUpdate)
|
|
{
|
|
wstring n = trim(in);
|
|
|
|
if (n.empty())
|
|
throw std::exception("Tomt namn är inte tillåtet.");
|
|
for (size_t k = 0; k < n.length(); k++) {
|
|
if (iswspace(n[k]))
|
|
n[k] = ' ';
|
|
}
|
|
if (n.length() <= 4 || n == lang.tl("N.N."))
|
|
manualUpdate = false; // Never consider default names manual
|
|
|
|
if (tParentRunner)
|
|
tParentRunner->setName(n, manualUpdate);
|
|
else {
|
|
wstring oldName = sName;
|
|
wstring oldRealName = tRealName;
|
|
wstring newRealName;
|
|
getRealName(n, newRealName);
|
|
if (newRealName != tRealName || n != sName) {
|
|
sName = n;
|
|
tRealName = newRealName;
|
|
|
|
if (manualUpdate)
|
|
setFlag(FlagUpdateName, true);
|
|
|
|
updateChanged();
|
|
}
|
|
|
|
for (size_t k=0;k<multiRunner.size();k++) {
|
|
if (multiRunner[k] && n!=multiRunner[k]->sName) {
|
|
multiRunner[k]->sName = n;
|
|
multiRunner[k]->tRealName = tRealName;
|
|
multiRunner[k]->updateChanged();
|
|
}
|
|
}
|
|
if (tInTeam && Class && Class->isSingleRunnerMultiStage()) {
|
|
if (tInTeam->sName == oldName || tInTeam->sName == oldRealName)
|
|
tInTeam->setName(tRealName, manualUpdate);
|
|
}
|
|
}
|
|
}
|
|
|
|
const wstring &oRunner::getName() const {
|
|
return tRealName;
|
|
}
|
|
|
|
const wstring &oRunner::getNameLastFirst() const {
|
|
if (sName.find_first_of(',') != sName.npos)
|
|
return sName; // Already "Fiske, Eric"
|
|
if (sName.find_first_of(' ') == sName.npos)
|
|
return sName; // No space "Vacant", "Eric"
|
|
|
|
wstring &res = StringCache::getInstance().wget();
|
|
res = getFamilyName() + L", " + getGivenName();
|
|
return res;
|
|
}
|
|
|
|
void oRunner::getRealName(const wstring &input, wstring &output) {
|
|
size_t comma = input.find_first_of(',');
|
|
if (comma == string::npos)
|
|
output = input;
|
|
else
|
|
output = trim(input.substr(comma+1) + L" " + input.substr(0, comma));
|
|
}
|
|
|
|
bool oAbstractRunner::setStatus(RunnerStatus st, bool updateSource, bool tmpOnly, bool recalculate) {
|
|
assert(!(updateSource && tmpOnly));
|
|
if (tmpOnly) {
|
|
tmpStore.status = st;
|
|
return false;
|
|
}
|
|
bool ch = false;
|
|
if (tStatus!=st) {
|
|
ch = true;
|
|
bool someOK = (st == StatusOK) || (tStatus == StatusOK);
|
|
tStatus=st;
|
|
|
|
if (Class && someOK) {
|
|
Class->clearCache(recalculate);
|
|
}
|
|
}
|
|
|
|
if (st != status) {
|
|
status = st;
|
|
if (updateSource)
|
|
updateChanged();
|
|
else
|
|
changedObject();
|
|
}
|
|
|
|
return ch;
|
|
}
|
|
|
|
int oAbstractRunner::getPrelRunningTime() const
|
|
{
|
|
if (FinishTime>0 && tStatus!=StatusDNS && tStatus != StatusCANCEL && tStatus!=StatusDNF && tStatus!=StatusNotCompetiting)
|
|
return getRunningTime();
|
|
else if (tStatus==StatusUnknown)
|
|
return oe->getComputerTime()-tStartTime;
|
|
else return 0;
|
|
}
|
|
|
|
wstring oAbstractRunner::getPrelRunningTimeS() const
|
|
{
|
|
int rt=getPrelRunningTime();
|
|
return formatTime(rt);
|
|
}
|
|
|
|
oDataContainer &oRunner::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const {
|
|
data = (pvoid)oData;
|
|
olddata = (pvoid)oDataOld;
|
|
strData = 0;
|
|
return *oe->oRunnerData;
|
|
}
|
|
|
|
void oEvent::getRunners(int classId, int courseId, vector<pRunner> &r, bool sort) {
|
|
if (sort) {
|
|
synchronizeList(oListId::oLRunnerId);
|
|
sortRunners(SortByName);
|
|
}
|
|
r.clear();
|
|
if (Classes.size() > 0)
|
|
r.reserve((Runners.size()*min<size_t>(Classes.size(), 4)) / Classes.size());
|
|
|
|
for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
if (courseId > 0) {
|
|
pCourse pc = it->getCourse(false);
|
|
if (pc == 0 || pc->getId() != courseId)
|
|
continue;
|
|
}
|
|
|
|
if (classId <= 0 || it->getClassId(true) == classId)
|
|
r.push_back(&*it);
|
|
}
|
|
}
|
|
|
|
pRunner oEvent::getRunner(int Id, int stage) const
|
|
{
|
|
pRunner value;
|
|
|
|
if (runnerById.lookup(Id, value) && value) {
|
|
if (value->isRemoved())
|
|
return 0;
|
|
assert(value->Id == Id);
|
|
if (stage==0)
|
|
return value;
|
|
else if (unsigned(stage)<=value->multiRunner.size())
|
|
return value->multiRunner[stage-1];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
pRunner oRunner::nextNeedReadout() const {
|
|
if (tInTeam) {
|
|
// For a runner in a team, first the team for the card
|
|
for (size_t k = 0; k < tInTeam->Runners.size(); k++) {
|
|
pRunner tr = tInTeam->Runners[k];
|
|
if (tr && tr->getCardNo() == getCardNo() && !tr->Card && !tr->statusOK())
|
|
return tr;
|
|
}
|
|
}
|
|
|
|
if (!Card || Card->cardNo!=getCardNo() || Card->isConstructedFromPunches()) //-1 means card constructed from punches
|
|
return pRunner(this);
|
|
|
|
for (size_t k=0;k<multiRunner.size();k++) {
|
|
if (multiRunner[k] && (!multiRunner[k]->Card ||
|
|
multiRunner[k]->Card->cardNo!=getCardNo()))
|
|
return multiRunner[k];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
vector<pRunner> oEvent::getCardToRunner(int cardNo) const {
|
|
if (!cardToRunnerHash || cardToRunnerHash->size() > Runners.size() * 2) {
|
|
cardToRunnerHash = make_shared<unordered_multimap<int, pRunner>>();
|
|
for (auto &rc : Runners) {
|
|
pRunner r = const_cast<pRunner>(&rc);
|
|
int cno = r->getCardNo();
|
|
if (cno == 0 || r->isRemoved())
|
|
continue;
|
|
|
|
cardToRunnerHash->emplace(cno, r); // The cache is "to large" -> filter is needed when looking into it.
|
|
}
|
|
}
|
|
vector<pRunner> res;
|
|
set<int> ids;
|
|
auto rng = cardToRunnerHash->equal_range(cardNo);
|
|
for (auto it = rng.first; it != rng.second; ++it) {
|
|
pRunner r = it->second;
|
|
if (!r->isRemoved() && r->getCardNo() == cardNo) {
|
|
if (ids.insert(r->getId()).second)
|
|
res.push_back(r);
|
|
|
|
for (pRunner r2 : r->multiRunner) {
|
|
if (r2 && r2->getCardNo() == cardNo) {
|
|
if (ids.insert(r2->getId()).second)
|
|
res.push_back(r2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
pRunner oEvent::getRunnerByCardNo(int cardNo, int time, CardLookupProperty prop) const {
|
|
auto range = getCardToRunner(cardNo);
|
|
bool skipDNS = (prop == CardLookupProperty::SkipNoStart || prop == CardLookupProperty::CardInUse);
|
|
|
|
if (range.size() == 1) {
|
|
// Single hit
|
|
pRunner r = range[0];
|
|
if (r->isRemoved() || r->getCardNo() != cardNo)
|
|
return nullptr;
|
|
if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL))
|
|
return nullptr;
|
|
if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting)
|
|
return nullptr;
|
|
if (prop == CardLookupProperty::ForReadout || prop == CardLookupProperty::CardInUse)
|
|
return r->nextNeedReadout();
|
|
|
|
return r; // Only one runner with this card
|
|
}
|
|
vector<pRunner> cand;
|
|
bool forceRet = false;
|
|
|
|
for (auto r : range) {
|
|
if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL))
|
|
continue;
|
|
|
|
if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting)
|
|
continue;
|
|
|
|
if (prop == CardLookupProperty::OnlyMainInstance && r->skip())
|
|
continue;
|
|
|
|
cand.push_back(r);
|
|
}
|
|
|
|
if (time <= 0) { //No time specified. Card readout search
|
|
pRunner secondTry = nullptr;
|
|
pRunner dnsR = nullptr;
|
|
for (pRunner r : cand) {
|
|
pRunner ret = r->nextNeedReadout();
|
|
if (ret) {
|
|
if (ret->getStatus() == StatusDNS || ret->getStatus() == StatusCANCEL || ret->getStatus() == StatusDNF)
|
|
dnsR = ret; //Return a DNS runner if there is no better match.
|
|
else if (!r->skip())
|
|
return ret;
|
|
else if (secondTry == 0 || secondTry->tLeg > ret->tLeg)
|
|
secondTry = ret;
|
|
}
|
|
}
|
|
if (secondTry)
|
|
return secondTry;
|
|
if (dnsR)
|
|
return dnsR;
|
|
}
|
|
else {
|
|
pRunner bestR = 0;
|
|
const int K = 3600 * 24;
|
|
int dist = 10 * K;
|
|
for (size_t k = 0; k < cand.size(); k++) {
|
|
pRunner r = cand[k];
|
|
if (time <= 0)
|
|
return r; // No time specified.
|
|
//int start = r->getStartTime();
|
|
//int finish = r->getFinishTime();
|
|
int start = r->getStartTime();
|
|
int finish = r->getFinishTime();
|
|
if (r->getCard()) {
|
|
pair<int, int> cc = r->getCard()->getTimeRange();
|
|
if (cc.first > 0)
|
|
start = min(start, cc.first);
|
|
if (cc.second > 0)
|
|
finish = max(finish, cc.second);
|
|
}
|
|
start = max(0, start - 3 * 60); // Allow some extra time before start
|
|
|
|
if (start > 0 && finish > 0 && time >= start && time <= finish)
|
|
return r;
|
|
int d = 3 * K;
|
|
if (start > 0 && finish > 0 && start < finish) {
|
|
if (time < start)
|
|
d += K + (start - time);
|
|
else if (time > finish)
|
|
d += K + (time - finish);
|
|
}
|
|
else {
|
|
if (start > 0) {
|
|
if (time < start)
|
|
d = K + start - time;
|
|
else
|
|
d = time - start;
|
|
}
|
|
if (finish > 0) {
|
|
if (time > finish)
|
|
d += K + time - finish;
|
|
}
|
|
}
|
|
if (d < dist) {
|
|
bestR = r;
|
|
dist = d;
|
|
}
|
|
}
|
|
|
|
if (bestR != 0 || forceRet)
|
|
return bestR;
|
|
}
|
|
|
|
if (prop != CardLookupProperty::ForReadout && !skipDNS) {
|
|
for (pRunner r : cand) {
|
|
pRunner rx = r->nextNeedReadout();
|
|
return rx ? rx : r;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void oEvent::getRunnersByCardNo(int cardNo, bool sortUpdate, CardLookupProperty prop, vector<pRunner> &out) const {
|
|
out.clear();
|
|
bool skipDNS = (prop == CardLookupProperty::SkipNoStart || prop == CardLookupProperty::CardInUse);
|
|
|
|
if (sortUpdate)
|
|
const_cast<oEvent *>(this)->synchronizeList(oListId::oLRunnerId);
|
|
|
|
if (cardNo != 0) {
|
|
auto range = getCardToRunner(cardNo);
|
|
for (auto r : range) {
|
|
if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL))
|
|
continue;
|
|
if (prop == CardLookupProperty::OnlyMainInstance && r->getRaceNo() != 0)
|
|
continue;
|
|
if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting)
|
|
continue;
|
|
if (prop == CardLookupProperty::ForReadout && r->getCard() && !r->getCard()->isConstructedFromPunches())
|
|
continue;
|
|
|
|
out.push_back(r);
|
|
}
|
|
}
|
|
else {
|
|
for (auto it=Runners.begin(); it != Runners.end(); ++it) {
|
|
pRunner r = const_cast<pRunner>(&*it);
|
|
if (r->isRemoved() || r->getCardNo() != cardNo)
|
|
continue;
|
|
if (skipDNS && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL))
|
|
continue;
|
|
if (prop == CardLookupProperty::OnlyMainInstance && r->getRaceNo() != 0)
|
|
continue;
|
|
if (prop != CardLookupProperty::IncludeNotCompeting && r->getStatus() == StatusNotCompetiting)
|
|
continue;
|
|
if (prop == CardLookupProperty::ForReadout && r->getCard() && !r->getCard()->isConstructedFromPunches())
|
|
continue;
|
|
|
|
out.push_back(r);
|
|
}
|
|
}
|
|
|
|
if (sortUpdate) {
|
|
const_cast<oEvent *>(this)->CurrentSortOrder = SortByName;
|
|
sort(out.begin(), out.end(), [](const pRunner &a, const pRunner &b) {return *a < *b; });
|
|
}
|
|
}
|
|
|
|
int oRunner::getRaceIdentifier() const {
|
|
if (tParentRunner)
|
|
return tParentRunner->getRaceIdentifier();// A unique person has a unique race identifier, even if the race is "split" into several
|
|
|
|
int stored = getDCI().getInt("RaceId");
|
|
if (stored != 0)
|
|
return stored;
|
|
|
|
if (!tInTeam)
|
|
return 1000000 + (Id&0xFFFFFFF) * 2;//Even
|
|
else
|
|
return 1000000 * (tLeg+1) + (tInTeam->Id & 0xFFFFFFF) * 2 + 1;//Odd
|
|
}
|
|
|
|
static int getEncodedBib(const wstring &bib) {
|
|
int enc = 0;
|
|
for (size_t j = 0; j < bib.length(); j++) { //WCS
|
|
int x = toupper(bib[j])-32;
|
|
if (x<0)
|
|
return 0; // Not a valid bib
|
|
enc = enc * 97 - x;
|
|
}
|
|
return enc;
|
|
}
|
|
|
|
int oAbstractRunner::getEncodedBib() const {
|
|
return ::getEncodedBib(getBib());
|
|
}
|
|
|
|
|
|
typedef multimap<int, oAbstractRunner*>::iterator BSRTIterator;
|
|
|
|
pRunner oEvent::getRunnerByBibOrStartNo(const wstring &bib, bool findWithoutCardNo) const {
|
|
if (bib.empty() || bib == L"0")
|
|
return 0;
|
|
|
|
if (bibStartNoToRunnerTeam.empty()) {
|
|
for (oTeamList::const_iterator tit = Teams.begin(); tit != Teams.end(); ++tit) {
|
|
const oTeam &t=*tit;
|
|
if (t.skip())
|
|
continue;
|
|
|
|
int sno = t.getStartNo();
|
|
if (sno != 0)
|
|
bibStartNoToRunnerTeam.insert(make_pair(sno, (oAbstractRunner *)&t));
|
|
int enc = t.getEncodedBib();
|
|
if (enc != 0)
|
|
bibStartNoToRunnerTeam.insert(make_pair(enc, (oAbstractRunner *)&t));
|
|
}
|
|
|
|
for (oRunnerList::const_iterator it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip())
|
|
continue;
|
|
const oRunner &t=*it;
|
|
|
|
int sno = t.getStartNo();
|
|
if (sno != 0)
|
|
bibStartNoToRunnerTeam.insert(make_pair(sno, (oAbstractRunner *)&t));
|
|
int enc = t.getEncodedBib();
|
|
if (enc != 0)
|
|
bibStartNoToRunnerTeam.insert(make_pair(enc, (oAbstractRunner *)&t));
|
|
}
|
|
}
|
|
|
|
int sno = _wtoi(bib.c_str());
|
|
|
|
pair<BSRTIterator, BSRTIterator> res;
|
|
if (sno > 0) {
|
|
// Require that a bib starts with numbers
|
|
int bibenc = getEncodedBib(bib);
|
|
res = bibStartNoToRunnerTeam.equal_range(bibenc);
|
|
if (res.first == res.second)
|
|
res = bibStartNoToRunnerTeam.equal_range(sno); // Try startno instead
|
|
|
|
for(BSRTIterator it = res.first; it != res.second; ++it) {
|
|
oAbstractRunner *pa = it->second;
|
|
if (pa->isRemoved())
|
|
continue;
|
|
|
|
if (typeid(*pa)==typeid(oRunner)) {
|
|
oRunner &r = dynamic_cast<oRunner &>(*pa);
|
|
if (r.getStartNo()==sno || stringMatch(r.getBib(), bib)) {
|
|
if (findWithoutCardNo) {
|
|
if (r.getCardNo() == 0 && r.needNoCard() == false)
|
|
return &r;
|
|
}
|
|
else {
|
|
if (r.getNumMulti()==0 || r.tStatus == StatusUnknown)
|
|
return &r;
|
|
else {
|
|
for(int race = 0; race < r.getNumMulti(); race++) {
|
|
pRunner r2 = r.getMultiRunner(race);
|
|
if (r2 && r2->tStatus == StatusUnknown)
|
|
return r2;
|
|
}
|
|
return &r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
oTeam &t = dynamic_cast<oTeam &>(*pa);
|
|
if (t.getStartNo()==sno || stringMatch(t.getBib(), bib)) {
|
|
if (!findWithoutCardNo) {
|
|
for (int leg=0; leg<t.getNumRunners(); leg++) {
|
|
pRunner r = t.Runners[leg];
|
|
if (r && r->getCardNo() > 0 && r->getStatus()==StatusUnknown)
|
|
return r;
|
|
}
|
|
}
|
|
else {
|
|
for (int leg=0; leg<t.getNumRunners(); leg++) {
|
|
pRunner r = t.Runners[leg];
|
|
if (r && r->getCardNo() == 0 && r->needNoCard() == false)
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
pRunner oEvent::getRunnerByName(const wstring &pname, const wstring &pclub) const
|
|
{
|
|
oRunnerList::const_iterator it;
|
|
vector<pRunner> cnd;
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!it->skip() && it->matchName(pname)) {
|
|
if (pclub.empty() || pclub==it->getClub())
|
|
cnd.push_back(pRunner(&*it));
|
|
}
|
|
}
|
|
|
|
if (cnd.size() == 1)
|
|
return cnd[0]; // Only return if uniquely defined.
|
|
|
|
return 0;
|
|
}
|
|
|
|
void oEvent::fillRunners(gdioutput &gdi, const string &id, bool longName, int filter)
|
|
{
|
|
vector< pair<wstring, size_t> > d;
|
|
oe->fillRunners(d, longName, filter, unordered_set<int>());
|
|
gdi.addItem(id, d);
|
|
}
|
|
|
|
const vector< pair<wstring, size_t> > &oEvent::fillRunners(vector< pair<wstring, size_t> > &out,
|
|
bool longName, int filter,
|
|
const unordered_set<int> &personFilter)
|
|
{
|
|
const bool showAll = (filter & RunnerFilterShowAll) == RunnerFilterShowAll;
|
|
const bool noResult = (filter & RunnerFilterOnlyNoResult) == RunnerFilterOnlyNoResult;
|
|
const bool withResult = (filter & RunnerFilterWithResult) == RunnerFilterWithResult;
|
|
const bool compact = (filter & RunnerCompactMode) == RunnerCompactMode;
|
|
|
|
synchronizeList(oListId::oLRunnerId);
|
|
oRunnerList::iterator it;
|
|
int lVacId = getVacantClubIfExist(false);
|
|
if (getNameMode() == LastFirst)
|
|
CurrentSortOrder = SortByLastName;
|
|
else
|
|
CurrentSortOrder = SortByName;
|
|
Runners.sort();
|
|
out.clear();
|
|
if (personFilter.empty())
|
|
out.reserve(Runners.size());
|
|
else
|
|
out.reserve(personFilter.size());
|
|
|
|
wchar_t bf[512];
|
|
const bool usePersonFilter = !personFilter.empty();
|
|
|
|
if (longName) {
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (noResult && (it->Card || it->FinishTime>0))
|
|
continue;
|
|
if (withResult && !it->Card && it->FinishTime == 0)
|
|
continue;
|
|
if (usePersonFilter && personFilter.count(it->Id) == 0)
|
|
continue;
|
|
if (!it->skip() || (showAll && !it->isRemoved())) {
|
|
if (compact) {
|
|
const wstring &club = it->getClub();
|
|
if (!club.empty()) {
|
|
swprintf_s(bf, L"%s, %s (%s)", it->getNameAndRace(true).c_str(),
|
|
club.c_str(),
|
|
it->getClass(true).c_str());
|
|
}
|
|
else {
|
|
swprintf_s(bf, L"%s (%s)", it->getNameAndRace(true).c_str(),
|
|
it->getClass(true).c_str());
|
|
}
|
|
|
|
} else {
|
|
swprintf_s(bf, L"%s\t%s\t%s", it->getNameAndRace(true).c_str(),
|
|
it->getClass(true).c_str(),
|
|
it->getClub().c_str());
|
|
}
|
|
out.emplace_back(bf, it->Id);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (noResult && (it->Card || it->FinishTime>0))
|
|
continue;
|
|
if (withResult && !it->Card && it->FinishTime == 0)
|
|
continue;
|
|
if (usePersonFilter && personFilter.count(it->Id) == 0)
|
|
continue;
|
|
|
|
if (!it->skip() || (showAll && !it->isRemoved())) {
|
|
if ( it->getClubId() != lVacId || lVacId == 0)
|
|
out.push_back(make_pair(it->getUIName(), it->Id));
|
|
else {
|
|
swprintf_s(bf, L"%s (%s)", it->getUIName().c_str(), it->getClass(true).c_str());
|
|
out.emplace_back(bf, it->Id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void oRunner::resetPersonalData()
|
|
{
|
|
oDataInterface di = getDI();
|
|
di.setInt("BirthYear", 0);
|
|
di.setString("Nationality", L"");
|
|
di.setString("Country", L"");
|
|
di.setInt64("ExtId", 0);
|
|
}
|
|
|
|
wstring oRunner::getNameAndRace(bool userInterface) const
|
|
{
|
|
if (tDuplicateLeg>0 || multiRunner.size()>0) {
|
|
wchar_t bf[16];
|
|
swprintf_s(bf, L" (%d)", getRaceNo()+1);
|
|
if (userInterface)
|
|
return getUIName() + bf;
|
|
return getName()+bf;
|
|
}
|
|
else if (userInterface)
|
|
return getUIName();
|
|
else return getName();
|
|
}
|
|
|
|
pRunner oRunner::getMultiRunner(int race) const
|
|
{
|
|
if (race==0) {
|
|
if (!tParentRunner)
|
|
return pRunner(this);
|
|
else return tParentRunner;
|
|
}
|
|
|
|
const vector<pRunner> &mr = tParentRunner ? tParentRunner->multiRunner : multiRunner;
|
|
|
|
if (unsigned(race-1)>=mr.size()) {
|
|
assert(tParentRunner);
|
|
return 0;
|
|
}
|
|
|
|
return mr[race-1];
|
|
}
|
|
|
|
void oRunner::createMultiRunner(bool createMaster, bool sync)
|
|
{
|
|
if (tDuplicateLeg)
|
|
return; //Never allow chains.
|
|
bool allowCreate = true;
|
|
if (multiRunnerId.size()>0) {
|
|
multiRunner.resize(multiRunnerId.size() - 1);
|
|
for (size_t k=0;k<multiRunner.size();k++) {
|
|
multiRunner[k]=oe->getRunner(multiRunnerId[k], 0);
|
|
if (multiRunner[k]) {
|
|
if (multiRunner[k]->multiRunnerId.size() > 1 || !multiRunner[k]->multiRunner.empty())
|
|
multiRunner[k]->markForCorrection();
|
|
|
|
multiRunner[k]->multiRunner.clear(); //Do not allow chains
|
|
multiRunner[k]->multiRunnerId.clear();
|
|
multiRunner[k]->tDuplicateLeg = k+1;
|
|
multiRunner[k]->tParentRunner = this;
|
|
|
|
if (multiRunner[k]->Id != multiRunnerId[k])
|
|
markForCorrection();
|
|
}
|
|
else if (multiRunnerId[k] > 0) {
|
|
markForCorrection();
|
|
allowCreate = false;
|
|
}
|
|
|
|
assert(multiRunner[k]);
|
|
}
|
|
multiRunnerId.clear();
|
|
}
|
|
|
|
if (!Class || !createMaster)
|
|
return;
|
|
|
|
int ndup=0;
|
|
|
|
if (!tInTeam)
|
|
ndup=Class->getNumMultiRunners(0);
|
|
else
|
|
ndup=Class->getNumMultiRunners(tLeg);
|
|
|
|
bool update = false;
|
|
|
|
vector<int> toRemove;
|
|
|
|
for (size_t k = ndup-1; k<multiRunner.size();k++) {
|
|
if (multiRunner[k] && multiRunner[k]->getStatus()==StatusUnknown) {
|
|
toRemove.push_back(multiRunner[k]->getId());
|
|
multiRunner[k]->tParentRunner = 0;
|
|
if (multiRunner[k]->tInTeam && size_t(multiRunner[k]->tLeg)<multiRunner[k]->tInTeam->Runners.size()) {
|
|
if (multiRunner[k]->tInTeam->Runners[multiRunner[k]->tLeg] == multiRunner[k])
|
|
multiRunner[k]->tInTeam->Runners[multiRunner[k]->tLeg] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
multiRunner.resize(ndup-1);
|
|
for (int k = 1; k < ndup; k++) {
|
|
if (!multiRunner[k - 1] && allowCreate) {
|
|
update = true;
|
|
multiRunner[k - 1] = oe->addRunner(sName, getClubId(),
|
|
getClassId(false), 0, 0, false);
|
|
multiRunner[k - 1]->tDuplicateLeg = k;
|
|
multiRunner[k - 1]->tParentRunner = this;
|
|
multiRunner[k - 1]->cardNumber = 0;
|
|
|
|
if (sync)
|
|
multiRunner[k - 1]->synchronize();
|
|
}
|
|
}
|
|
if (update)
|
|
updateChanged();
|
|
|
|
if (sync) {
|
|
synchronize(true);
|
|
oe->removeRunner(toRemove);
|
|
}
|
|
}
|
|
|
|
pRunner oRunner::getPredecessor() const
|
|
{
|
|
if (!tParentRunner || unsigned(tDuplicateLeg-1)>=16)
|
|
return 0;
|
|
|
|
if (tDuplicateLeg==1)
|
|
return tParentRunner;
|
|
else
|
|
return tParentRunner->multiRunner[tDuplicateLeg-2];
|
|
}
|
|
|
|
bool oRunner::apply(bool sync, pRunner src, bool setTmpOnly) {
|
|
for (size_t k = 0; k < multiRunner.size(); k++) {
|
|
if (multiRunner[k] && multiRunner[k]->isRemoved()) {
|
|
multiRunner[k]->tParentRunner = nullptr;
|
|
multiRunner[k] = nullptr;
|
|
}
|
|
}
|
|
|
|
createMultiRunner(false, sync);
|
|
if (sync) {
|
|
for (size_t k = 0; k < multiRunner.size(); k++) {
|
|
if (multiRunner[k])
|
|
multiRunner[k]->synchronize(true);
|
|
}
|
|
}
|
|
tLeg = -1;
|
|
tLegEquClass = 0;
|
|
tUseStartPunch=true;
|
|
if (tInTeam)
|
|
tInTeam->apply(sync, this, setTmpOnly);
|
|
else {
|
|
if (Class && Class->hasMultiCourse()) {
|
|
pClass pc=Class;
|
|
StartTypes st=pc->getStartType(tDuplicateLeg);
|
|
if (st==STTime) {
|
|
pCourse crs = getCourse(false);
|
|
int startType = crs ? crs->getStartPunchType() : oPunch::PunchStart;
|
|
if (!Card || Card->getPunchByType(startType) == 0 || !pc->hasFreeStart()) {
|
|
setStartTime(pc->getStartData(tDuplicateLeg), false, setTmpOnly);
|
|
tUseStartPunch = false;
|
|
}
|
|
}
|
|
else if (st==STChange) {
|
|
pRunner r=getPredecessor();
|
|
int lastStart=0;
|
|
if (r && r->FinishTime>0)
|
|
lastStart = r->FinishTime;
|
|
|
|
int restart=pc->getRestartTime(tDuplicateLeg);
|
|
int rope=pc->getRopeTime(tDuplicateLeg);
|
|
|
|
if (restart && rope && (lastStart>rope || lastStart==0))
|
|
lastStart=restart; //Runner in restart
|
|
|
|
setStartTime(lastStart, false, setTmpOnly);
|
|
tUseStartPunch=false;
|
|
}
|
|
else if (st==STHunting) {
|
|
pRunner r=getPredecessor();
|
|
int lastStart=0;
|
|
|
|
if (r && r->FinishTime>0 && r->statusOK()) {
|
|
int rt=r->getRaceRunningTime(tDuplicateLeg-1);
|
|
int timeAfter=rt-pc->getTotalLegLeaderTime(r->tDuplicateLeg, true);
|
|
if (rt>0 && timeAfter>=0)
|
|
lastStart=pc->getStartData(tDuplicateLeg)+timeAfter;
|
|
}
|
|
int restart=pc->getRestartTime(tDuplicateLeg);
|
|
int rope=pc->getRopeTime(tDuplicateLeg);
|
|
|
|
if (restart && rope && (lastStart>rope || lastStart==0))
|
|
lastStart=restart; //Runner in restart
|
|
|
|
setStartTime(lastStart, false, setTmpOnly);
|
|
tUseStartPunch=false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tLeg==-1) {
|
|
tLeg=0;
|
|
tInTeam=0;
|
|
return false;
|
|
}
|
|
else return true;
|
|
}
|
|
|
|
void oRunner::cloneStartTime(const pRunner r) {
|
|
if (tParentRunner)
|
|
tParentRunner->cloneStartTime(r);
|
|
else {
|
|
setStartTime(r->getStartTime(), true, false);
|
|
|
|
for (size_t k=0; k < min(multiRunner.size(), r->multiRunner.size()); k++) {
|
|
if (multiRunner[k]!=0 && r->multiRunner[k]!=0)
|
|
multiRunner[k]->setStartTime(r->multiRunner[k]->getStartTime(), true, false);
|
|
}
|
|
apply(false, 0, false);
|
|
}
|
|
}
|
|
|
|
void oRunner::cloneData(const pRunner r) {
|
|
if (tParentRunner)
|
|
tParentRunner->cloneData(r);
|
|
else {
|
|
size_t t = sizeof(oData);
|
|
memcpy(oData, r->oData, t);
|
|
}
|
|
}
|
|
|
|
|
|
Table *oEvent::getRunnersTB()//Table mode
|
|
{
|
|
if (tables.count("runner") == 0) {
|
|
Table *table=new Table(this, 20, L"Deltagare", "runners");
|
|
|
|
table->addColumn("Id", 70, true, true);
|
|
table->addColumn("Ändrad", 70, false);
|
|
|
|
table->addColumn("Namn", 200, false);
|
|
table->addColumn("Klass", 120, false);
|
|
table->addColumn("Bana", 120, false);
|
|
|
|
table->addColumn("Klubb", 120, false);
|
|
table->addColumn("Lag", 120, false);
|
|
table->addColumn("Sträcka", 70, true);
|
|
|
|
table->addColumn("SI", 90, true, false);
|
|
|
|
table->addColumn("Start", 70, false, true);
|
|
table->addColumn("Mål", 70, false, true);
|
|
table->addColumn("Status", 70, false);
|
|
table->addColumn("Tid", 70, false, true);
|
|
table->addColumn("Plac.", 70, true, true);
|
|
table->addColumn("Start nr.", 70, true, false);
|
|
|
|
oe->oRunnerData->buildTableCol(table);
|
|
|
|
table->addColumn("Tid in", 70, false, true);
|
|
table->addColumn("Status in", 70, false, true);
|
|
table->addColumn("Poäng in", 70, true);
|
|
table->addColumn("Placering in", 70, true);
|
|
|
|
tables["runner"] = table;
|
|
table->addOwnership();
|
|
}
|
|
tables["runner"]->update();
|
|
return tables["runner"];
|
|
}
|
|
|
|
void oEvent::generateRunnerTableData(Table &table, oRunner *addRunner)
|
|
{
|
|
if (addRunner) {
|
|
addRunner->addTableRow(table);
|
|
return;
|
|
}
|
|
|
|
synchronizeList(oListId::oLRunnerId);
|
|
oRunnerList::iterator it;
|
|
table.reserve(Runners.size());
|
|
for (it=Runners.begin(); it != Runners.end(); ++it){
|
|
if (!it->isRemoved()){
|
|
it->addTableRow(table);
|
|
}
|
|
}
|
|
}
|
|
|
|
pRunner oRunner::getReference() const
|
|
{
|
|
int rid = getDCI().getInt("Reference");
|
|
if (rid != 0)
|
|
return oe->getRunner(rid, 0);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void oRunner::setReference(int runnerId)
|
|
{
|
|
getDI().setInt("Reference", runnerId);
|
|
}
|
|
|
|
const wstring &oRunner::getUIName() const {
|
|
oEvent::NameMode nameMode = oe->getNameMode();
|
|
|
|
switch (nameMode) {
|
|
case oEvent::Raw:
|
|
return getNameRaw();
|
|
case oEvent::LastFirst:
|
|
return getNameLastFirst();
|
|
default:
|
|
return getName();
|
|
}
|
|
}
|
|
|
|
|
|
void oRunner::addTableRow(Table &table) const
|
|
{
|
|
oRunner &it = *pRunner(this);
|
|
table.addRow(getId(), &it);
|
|
|
|
int row = 0;
|
|
table.set(row++, it, TID_ID, itow(getId()), false);
|
|
table.set(row++, it, TID_MODIFIED, getTimeStamp(), false);
|
|
|
|
if (tParentRunner == 0)
|
|
table.set(row++, it, TID_RUNNER, getUIName(), true);
|
|
else
|
|
table.set(row++, it, TID_RUNNER, getUIName() + L" (" + itow(tDuplicateLeg+1) + L")", false);
|
|
table.set(row++, it, TID_CLASSNAME, getClass(true), true, cellSelection);
|
|
table.set(row++, it, TID_COURSE, getCourseName(), true, cellSelection);
|
|
table.set(row++, it, TID_CLUB, getClub(), tParentRunner == 0, cellCombo);
|
|
|
|
table.set(row++, it, TID_TEAM, tInTeam ? tInTeam->getName() : L"", false);
|
|
table.set(row++, it, TID_LEG, tInTeam ? itow(tLeg+1) : L"" , false);
|
|
|
|
int cno = getCardNo();
|
|
table.set(row++, it, TID_CARD, cno>0 ? itow(cno) : L"", true);
|
|
|
|
table.set(row++, it, TID_START, getStartTimeS(), true);
|
|
table.set(row++, it, TID_FINISH, getFinishTimeS(), true);
|
|
table.set(row++, it, TID_STATUS, getStatusS(false), true, cellSelection);
|
|
table.set(row++, it, TID_RUNNINGTIME, getRunningTimeS(), false);
|
|
|
|
table.set(row++, it, TID_PLACE, getPlaceS(), false);
|
|
table.set(row++, it, TID_STARTNO, itow(getStartNo()), true);
|
|
|
|
row = oe->oRunnerData->fillTableCol(it, table, true);
|
|
|
|
table.set(row++, it, TID_INPUTTIME, getInputTimeS(), true);
|
|
table.set(row++, it, TID_INPUTSTATUS, getInputStatusS(), true, cellSelection);
|
|
table.set(row++, it, TID_INPUTPOINTS, itow(inputPoints), true);
|
|
table.set(row++, it, TID_INPUTPLACE, itow(inputPlace), true);
|
|
}
|
|
|
|
bool oRunner::inputData(int id, const wstring &input,
|
|
int inputId, wstring &output, bool noUpdate)
|
|
{
|
|
int t,s;
|
|
vector<int> mp;
|
|
synchronize(false);
|
|
|
|
if (id>1000) {
|
|
return oe->oRunnerData->inputData(this, id, input,
|
|
inputId, output, noUpdate);
|
|
}
|
|
switch(id) {
|
|
case TID_CARD:
|
|
setCardNo(_wtoi(input.c_str()), true);
|
|
synchronizeAll();
|
|
output = itow(getCardNo());
|
|
return true;
|
|
case TID_RUNNER:
|
|
if (trim(input).empty())
|
|
throw std::exception("Tomt namn inte tillåtet.");
|
|
|
|
if (sName != input && tRealName != input) {
|
|
updateFromDB(input, getClubId(), getClassId(false), getCardNo(), getBirthYear(), false);
|
|
setName(input, true);
|
|
synchronizeAll();
|
|
}
|
|
output = getName();
|
|
return true;
|
|
break;
|
|
|
|
case TID_START:
|
|
setStartTimeS(input);
|
|
t=getStartTime();
|
|
evaluateCard(true, mp);
|
|
s=getStartTime();
|
|
if (s!=t)
|
|
throw std::exception("Starttiden är definerad genom klassen eller löparens startstämpling.");
|
|
synchronize(true);
|
|
output = getStartTimeS();
|
|
return true;
|
|
break;
|
|
|
|
case TID_FINISH:
|
|
setFinishTimeS(input);
|
|
t=getFinishTime();
|
|
evaluateCard(true, mp);
|
|
s=getFinishTime();
|
|
if (s!=t)
|
|
throw std::exception("För att ändra måltiden måste löparens målstämplingstid ändras.");
|
|
synchronize(true);
|
|
output = getStartTimeS();
|
|
return true;
|
|
break;
|
|
|
|
case TID_COURSE:
|
|
setCourseId(inputId);
|
|
synchronize(true);
|
|
output = getCourseName();
|
|
break;
|
|
|
|
case TID_CLUB:
|
|
{
|
|
pClub pc = 0;
|
|
if (inputId > 0)
|
|
pc = oe->getClub(inputId);
|
|
else
|
|
pc = oe->getClubCreate(0, input);
|
|
|
|
updateFromDB(getName(), pc ? pc->getId():0, getClassId(false), getCardNo(), getBirthYear(), false);
|
|
|
|
setClub(pc ? pc->getName() : L"");
|
|
synchronize(true);
|
|
output = getClub();
|
|
}
|
|
break;
|
|
|
|
case TID_CLASSNAME:
|
|
setClassId(inputId, true);
|
|
synchronize(true);
|
|
output = getClass(true);
|
|
break;
|
|
|
|
case TID_STATUS: {
|
|
setStatus(RunnerStatus(inputId), true, false);
|
|
int s = getStatus();
|
|
evaluateCard(true, mp);
|
|
if (s!=getStatus())
|
|
throw std::exception("Status matchar inte data i löparbrickan.");
|
|
synchronize(true);
|
|
output = getStatusS(false);
|
|
}
|
|
break;
|
|
|
|
case TID_STARTNO:
|
|
setStartNo(_wtoi(input.c_str()), false);
|
|
synchronize(true);
|
|
output = itow(getStartNo());
|
|
break;
|
|
|
|
case TID_INPUTSTATUS:
|
|
setInputStatus(RunnerStatus(inputId));
|
|
synchronize(true);
|
|
output = getInputStatusS();
|
|
break;
|
|
|
|
case TID_INPUTTIME:
|
|
setInputTime(input);
|
|
synchronize(true);
|
|
output = getInputTimeS();
|
|
break;
|
|
|
|
case TID_INPUTPOINTS:
|
|
setInputPoints(_wtoi(input.c_str()));
|
|
synchronize(true);
|
|
output = itow(getInputPoints());
|
|
break;
|
|
|
|
case TID_INPUTPLACE:
|
|
setInputPlace(_wtoi(input.c_str()));
|
|
synchronize(true);
|
|
output = itow(getInputPlace());
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void oRunner::fillInput(int id, vector< pair<wstring, size_t> > &out, size_t &selected)
|
|
{
|
|
if (id>1000) {
|
|
oe->oRunnerData->fillInput(oData, id, 0, out, selected);
|
|
return;
|
|
}
|
|
|
|
if (id==TID_COURSE) {
|
|
oe->fillCourses(out, true);
|
|
out.push_back(make_pair(lang.tl(L"Klassens bana"), 0));
|
|
selected = getCourseId();
|
|
}
|
|
else if (id==TID_CLASSNAME) {
|
|
oe->fillClasses(out, oEvent::extraNone, oEvent::filterNone);
|
|
out.push_back(make_pair(lang.tl(L"Ingen klass"), 0));
|
|
selected = getClassId(true);
|
|
}
|
|
else if (id==TID_CLUB) {
|
|
oe->fillClubs(out);
|
|
out.push_back(make_pair(lang.tl(L"Klubblös"), 0));
|
|
selected = getClubId();
|
|
}
|
|
else if (id==TID_STATUS) {
|
|
oe->fillStatus(out);
|
|
selected = getStatus();
|
|
}
|
|
else if (id==TID_INPUTSTATUS) {
|
|
oe->fillStatus(out);
|
|
selected = inputStatus;
|
|
}
|
|
}
|
|
|
|
int oRunner::getSplitTime(int controlNumber, bool normalized) const
|
|
{
|
|
if (!Card) {
|
|
if (controlNumber == 0)
|
|
return getPunchTime(0, false);
|
|
else {
|
|
int ct = getPunchTime(controlNumber, false);
|
|
if (ct > 0) {
|
|
int dt = getPunchTime(controlNumber - 1, false);
|
|
if (dt > 0 && ct > dt)
|
|
return ct - dt;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
const vector<SplitData> &st = getSplitTimes(normalized);
|
|
if (controlNumber>0 && controlNumber == st.size() && FinishTime>0) {
|
|
int t = st.back().time;
|
|
if (t >0)
|
|
return max(FinishTime - t, -1);
|
|
}
|
|
else if ( unsigned(controlNumber)<st.size() ) {
|
|
if (controlNumber==0)
|
|
return (tStartTime>0 && st[0].time>0) ? max(st[0].time-tStartTime, -1) : -1;
|
|
else if (st[controlNumber].time>0 && st[controlNumber-1].time>0)
|
|
return max(st[controlNumber].time - st[controlNumber-1].time, -1);
|
|
else return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int oRunner::getTimeAdjust(int controlNumber) const
|
|
{
|
|
if ( unsigned(controlNumber)<adjustTimes.size() ) {
|
|
return adjustTimes[controlNumber];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int oRunner::getNamedSplit(int controlNumber) const {
|
|
pCourse crs=getCourse(true);
|
|
if (!crs || unsigned(controlNumber)>=unsigned(crs->nControls))
|
|
return -1;
|
|
|
|
pControl ctrl=crs->Controls[controlNumber];
|
|
if (!ctrl || !ctrl->hasName())
|
|
return -1;
|
|
|
|
int k=controlNumber-1;
|
|
int ct = getPunchTime(controlNumber, false);
|
|
if (ct <= 0)
|
|
return -1;
|
|
|
|
//Measure from previous named control
|
|
while (k >= 0) {
|
|
pControl c = crs->Controls[k];
|
|
|
|
if (c && c->hasName()) {
|
|
int dt = getPunchTime(k, false);
|
|
if (dt > 0 && ct > dt)
|
|
return max(ct - dt, -1);
|
|
else return -1;
|
|
}
|
|
k--;
|
|
}
|
|
|
|
//Measure from start time
|
|
return ct;
|
|
}
|
|
|
|
wstring oRunner::getSplitTimeS(int controlNumber, bool normalized) const
|
|
{
|
|
return formatTime(getSplitTime(controlNumber, normalized));
|
|
}
|
|
|
|
wstring oRunner::getNamedSplitS(int controlNumber) const
|
|
{
|
|
return formatTime(getNamedSplit(controlNumber));
|
|
}
|
|
|
|
int oRunner::getPunchTime(int controlNumber, bool normalized) const
|
|
{
|
|
if (!Card) {
|
|
pCourse pc = getCourse(false);
|
|
if (!pc || controlNumber > pc->getNumControls())
|
|
return -1;
|
|
|
|
if (controlNumber == pc->getNumControls())
|
|
return getFinishTime() - tStartTime;
|
|
|
|
int ccId = pc->getCourseControlId(controlNumber);
|
|
pFreePunch fp = oe->getPunch(Id, ccId, getCardNo());
|
|
if (fp)
|
|
return fp->Time - tStartTime;
|
|
return -1;
|
|
}
|
|
const vector<SplitData> &st = getSplitTimes(normalized);
|
|
|
|
if ( unsigned(controlNumber)<st.size() ) {
|
|
if (st[controlNumber].time>0)
|
|
return st[controlNumber].time-tStartTime;
|
|
else return -1;
|
|
}
|
|
else if ( unsigned(controlNumber)==st.size() )
|
|
return FinishTime-tStartTime;
|
|
|
|
return -1;
|
|
}
|
|
|
|
wstring oRunner::getPunchTimeS(int controlNumber, bool normalized) const
|
|
{
|
|
return formatTime(getPunchTime(controlNumber, normalized));
|
|
}
|
|
|
|
bool oAbstractRunner::isVacant() const
|
|
{
|
|
int vacClub = oe->getVacantClubIfExist(false);
|
|
return vacClub > 0 && getClubId()==vacClub;
|
|
}
|
|
|
|
bool oRunner::isAnnonumousTeamMember() const {
|
|
wstring anon = lang.tl("N.N.");
|
|
if (getNameRaw() == anon && getExtIdentifier() == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool oRunner::needNoCard() const {
|
|
const_cast<oRunner*>(this)->apply(false, 0, false);
|
|
return tNeedNoCard;
|
|
}
|
|
|
|
void oRunner::getSplitTime(int courseControlId, RunnerStatus &stat, int &rt) const
|
|
{
|
|
rt = 0;
|
|
stat = StatusUnknown;
|
|
int cardno = getCardNo();
|
|
|
|
if (courseControlId==oPunch::PunchFinish &&
|
|
FinishTime>0 && tStatus!=StatusUnknown) {
|
|
stat = tStatus;
|
|
rt = getFinishTimeAdjusted();
|
|
}
|
|
else if (Card) {
|
|
oPunch *p=Card->getPunchById(courseControlId);
|
|
if (p && p->Time>0) {
|
|
rt=p->getAdjustedTime();
|
|
stat = StatusOK;
|
|
}
|
|
else if (p && p->Time == -1 && statusOK()) {
|
|
rt = getFinishTimeAdjusted();
|
|
if (rt > 0)
|
|
stat = StatusOK;
|
|
else
|
|
stat = StatusMP;
|
|
}
|
|
else
|
|
stat = courseControlId==oPunch::PunchFinish ? StatusDNF: StatusMP;
|
|
}
|
|
else if (cardno) {
|
|
oFreePunch *fp=oe->getPunch(getId(), courseControlId, cardno);
|
|
|
|
if (fp) {
|
|
rt=fp->getAdjustedTime();
|
|
stat=StatusOK;
|
|
}
|
|
if (courseControlId==oPunch::PunchFinish && tStatus!=StatusUnknown)
|
|
stat = tStatus;
|
|
}
|
|
rt-=tStartTime;
|
|
|
|
if (rt<0)
|
|
rt=0;
|
|
}
|
|
|
|
void oRunner::fillSpeakerObject(int leg, int courseControlId, int previousControlCourseId,
|
|
bool totalResult, oSpeakerObject &spk) const {
|
|
spk.status=StatusUnknown;
|
|
spk.owner=const_cast<oRunner *>(this);
|
|
|
|
getSplitTime(courseControlId, spk.status, spk.runningTime.time);
|
|
|
|
if (courseControlId == oPunch::PunchFinish)
|
|
spk.timeSinceChange = oe->getComputerTime() - FinishTime;
|
|
else
|
|
spk.timeSinceChange = oe->getComputerTime() - (spk.runningTime.time + tStartTime);
|
|
|
|
spk.bib = getBib();
|
|
spk.names.push_back(getName());
|
|
|
|
spk.club = getClub();
|
|
spk.finishStatus=totalResult ? getTotalStatus() : getStatus();
|
|
|
|
spk.startTimeS=getStartTimeCompact();
|
|
spk.missingStartTime = tStartTime<=0;
|
|
|
|
spk.isRendered=false;
|
|
|
|
map<int, int>::const_iterator mapit = priority.find(courseControlId);
|
|
if (mapit!=priority.end())
|
|
spk.priority=mapit->second;
|
|
else
|
|
spk.priority=0;
|
|
|
|
spk.runningTime.preliminary = getPrelRunningTime();
|
|
|
|
if (spk.status==StatusOK) {
|
|
spk.runningTimeLeg=spk.runningTime;
|
|
spk.runningTime.preliminary = spk.runningTime.time;
|
|
spk.runningTimeLeg.preliminary = spk.runningTime.time;
|
|
}
|
|
else {
|
|
spk.runningTimeLeg.time = spk.runningTime.preliminary;
|
|
spk.runningTimeLeg.preliminary = spk.runningTime.preliminary;
|
|
}
|
|
|
|
if (totalResult) {
|
|
if (spk.runningTime.preliminary > 0)
|
|
spk.runningTime.preliminary += inputTime;
|
|
if (spk.runningTime.time > 0)
|
|
spk.runningTime.time += inputTime;
|
|
|
|
if (inputStatus != StatusOK)
|
|
spk.status = spk.finishStatus;
|
|
}
|
|
}
|
|
|
|
pRunner oEvent::findRunner(const wstring &s, int lastId, const unordered_set<int> &inputFilter,
|
|
unordered_set<int> &matchFilter) const
|
|
{
|
|
matchFilter.clear();
|
|
wstring trm = trim(s);
|
|
int len = trm.length();
|
|
int sn = _wtoi(trm.c_str());
|
|
wchar_t s_lc[1024];
|
|
wcscpy_s(s_lc, s.c_str());
|
|
CharLowerBuff(s_lc, len);
|
|
|
|
pRunner res = 0;
|
|
|
|
if (!inputFilter.empty() && inputFilter.size() < Runners.size() / 2) {
|
|
for (unordered_set<int>::const_iterator it = inputFilter.begin(); it!= inputFilter.end(); ++it) {
|
|
int id = *it;
|
|
pRunner r = getRunner(id, 0);
|
|
if (!r)
|
|
continue;
|
|
|
|
if (sn>0) {
|
|
if (matchNumber(r->StartNo, s_lc) || matchNumber(r->getCardNo(), s_lc)) {
|
|
matchFilter.insert(id);
|
|
if (res == 0)
|
|
res = r;
|
|
}
|
|
}
|
|
else {
|
|
if (filterMatchString(r->tRealName, s_lc)) {
|
|
matchFilter.insert(id);
|
|
if (res == 0)
|
|
res = r;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
oRunnerList::const_iterator itstart = Runners.begin();
|
|
|
|
if (lastId) {
|
|
for (; itstart != Runners.end(); ++itstart) {
|
|
if (itstart->Id==lastId) {
|
|
++itstart;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
oRunnerList::const_iterator it;
|
|
for (it=itstart; it != Runners.end(); ++it) {
|
|
pRunner r = pRunner(&(*it));
|
|
if (r->skip())
|
|
continue;
|
|
|
|
if (sn>0) {
|
|
if (matchNumber(r->StartNo, s_lc) || matchNumber(r->getCardNo(), s_lc)) {
|
|
matchFilter.insert(r->Id);
|
|
if (res == 0)
|
|
res = r;
|
|
}
|
|
}
|
|
else {
|
|
if (filterMatchString(r->tRealName, s_lc)) {
|
|
matchFilter.insert(r->Id);
|
|
if (res == 0)
|
|
res = r;
|
|
}
|
|
}
|
|
}
|
|
for (it=Runners.begin(); it != itstart; ++it) {
|
|
pRunner r = pRunner(&(*it));
|
|
if (r->skip())
|
|
continue;
|
|
|
|
if (sn>0) {
|
|
if (matchNumber(r->StartNo, s_lc) || matchNumber(r->getCardNo(), s_lc)) {
|
|
matchFilter.insert(r->Id);
|
|
if (res == 0)
|
|
res = r;
|
|
}
|
|
}
|
|
else {
|
|
if (filterMatchString(r->tRealName, s_lc)) {
|
|
matchFilter.insert(r->Id);
|
|
if (res == 0)
|
|
res = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int oRunner::getTimeAfter(int leg) const
|
|
{
|
|
if (leg==-1)
|
|
leg=tDuplicateLeg;
|
|
|
|
if (!Class || Class->tLeaderTime.size()<=unsigned(leg))
|
|
return -1;
|
|
|
|
int t=getRaceRunningTime(leg);
|
|
|
|
if (t<=0)
|
|
return -1;
|
|
|
|
return t-Class->getTotalLegLeaderTime(leg, true);
|
|
}
|
|
|
|
int oRunner::getTimeAfter() const
|
|
{
|
|
int leg=0;
|
|
if (tInTeam)
|
|
leg=tLeg;
|
|
else
|
|
leg=tDuplicateLeg;
|
|
|
|
if (!Class || Class->tLeaderTime.size()<=unsigned(leg))
|
|
return -1;
|
|
|
|
int t=getRunningTime();
|
|
|
|
if (t<=0)
|
|
return -1;
|
|
|
|
return t-Class->getBestLegTime(leg);
|
|
}
|
|
|
|
int oRunner::getTimeAfterCourse() const
|
|
{
|
|
if (!Class)
|
|
return -1;
|
|
|
|
const pCourse crs = getCourse(false);
|
|
if (!crs)
|
|
return -1;
|
|
|
|
int t = getRunningTime();
|
|
|
|
if (t<=0)
|
|
return -1;
|
|
|
|
int bt = Class->getBestTimeCourse(crs->getId());
|
|
|
|
if (bt <= 0)
|
|
return -1;
|
|
|
|
return t - bt;
|
|
}
|
|
|
|
bool oRunner::synchronizeAll()
|
|
{
|
|
if (tParentRunner)
|
|
tParentRunner->synchronizeAll();
|
|
else {
|
|
synchronize();
|
|
for (size_t k=0;k<multiRunner.size();k++) {
|
|
if (multiRunner[k])
|
|
multiRunner[k]->synchronize();
|
|
}
|
|
if (Class && Class->isSingleRunnerMultiStage())
|
|
if (tInTeam)
|
|
tInTeam->synchronize(false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getBib() const
|
|
{
|
|
return getDCI().getString("Bib");
|
|
}
|
|
|
|
void oRunner::setBib(const wstring &bib, int bibNumerical, bool updateStartNo, bool tmpOnly) {
|
|
if (!tmpOnly && getBib() == bib)
|
|
return;
|
|
|
|
const bool freeBib = !Class || Class->getBibMode() == BibMode::BibFree;
|
|
|
|
if (tParentRunner && !freeBib)
|
|
tParentRunner->setBib(bib, bibNumerical, updateStartNo, tmpOnly);
|
|
else {
|
|
if (updateStartNo)
|
|
setStartNo(bibNumerical, tmpOnly); // Updates multi too.
|
|
|
|
if (tmpOnly) {
|
|
tmpStore.bib = bib;
|
|
return;
|
|
}
|
|
if (getDI().setString("Bib", bib)) {
|
|
if (oe)
|
|
oe->bibStartNoToRunnerTeam.clear();
|
|
}
|
|
if (!freeBib) {
|
|
for (size_t k = 0; k < multiRunner.size(); k++) {
|
|
if (multiRunner[k]) {
|
|
multiRunner[k]->getDI().setString("Bib", bib);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void oEvent::analyseDNS(vector<pRunner> &unknown_dns, vector<pRunner> &known_dns,
|
|
vector<pRunner> &known, vector<pRunner> &unknown, bool &hasSetDNS)
|
|
{
|
|
autoSynchronizeLists(true);
|
|
|
|
vector<pRunner> stUnknown;
|
|
vector<pRunner> stDNS;
|
|
|
|
for (oRunnerList::iterator it = Runners.begin(); it!=Runners.end();++it) {
|
|
if (!it->isRemoved() && !it->needNoCard()) {
|
|
if (it->getStatus() == StatusUnknown)
|
|
stUnknown.push_back(&*it);
|
|
else if (it->getStatus() == StatusDNS) {
|
|
stDNS.push_back(&*it);
|
|
if (it->hasFlag(oAbstractRunner::FlagAutoDNS))
|
|
hasSetDNS = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Map cardNo -> punch
|
|
multimap<int, pFreePunch> punchHash;
|
|
map<int, int> cardCount;
|
|
|
|
for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!it->isRemoved() && it->getCardNo() > 0)
|
|
++cardCount[it->getCardNo()];
|
|
}
|
|
|
|
typedef multimap<int, pFreePunch>::const_iterator TPunchIter;
|
|
for (oFreePunchList::iterator it = punches.begin(); it != punches.end(); ++it) {
|
|
if (!it->isRemoved() && !it->isHiredCard())
|
|
punchHash.insert(make_pair(it->getCardNo(), &*it));
|
|
}
|
|
|
|
set<int> knownCards;
|
|
for (oCardList::iterator it = Cards.begin(); it!=Cards.end(); ++it) {
|
|
if (it->tOwner == 0)
|
|
knownCards.insert(it->cardNo);
|
|
}
|
|
|
|
unknown.clear();
|
|
known.clear();
|
|
|
|
for (size_t k=0;k<stUnknown.size();k++) {
|
|
int card = stUnknown[k]->getCardNo();
|
|
if (card == 0)
|
|
unknown.push_back(stUnknown[k]);
|
|
else {
|
|
bool hitCard = knownCards.count(card)==1 && cardCount[card] == 1;
|
|
if (!hitCard) {
|
|
pair<TPunchIter, TPunchIter> res = punchHash.equal_range(card);
|
|
while (res.first != res.second) {
|
|
if (cardCount[card] == 1 || res.first->second->tRunnerId == stUnknown[k]->getId()) {
|
|
hitCard = true;
|
|
break;
|
|
}
|
|
++res.first;
|
|
}
|
|
}
|
|
if (hitCard)
|
|
known.push_back(stUnknown[k]);
|
|
else
|
|
unknown.push_back(stUnknown[k]); //These can be given "dns"
|
|
}
|
|
}
|
|
|
|
unknown_dns.clear();
|
|
known_dns.clear();
|
|
|
|
for (size_t k=0;k<stDNS.size(); k++) {
|
|
int card = stDNS[k]->getCardNo();
|
|
if (card == 0)
|
|
unknown_dns.push_back(stDNS[k]);
|
|
else {
|
|
bool hitCard = knownCards.count(card)==1 && cardCount[card] == 1;
|
|
if (!hitCard) {
|
|
pair<TPunchIter, TPunchIter> res = punchHash.equal_range(card);
|
|
while (res.first != res.second) {
|
|
if (cardCount[card] == 1 || res.first->second->tRunnerId == stDNS[k]->getId()) {
|
|
hitCard = true;
|
|
break;
|
|
}
|
|
++res.first;
|
|
}
|
|
}
|
|
if (hitCard)
|
|
known_dns.push_back(stDNS[k]);
|
|
else
|
|
unknown_dns.push_back(stDNS[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int findNextControl(const vector<pControl> &ctrl, int startIndex, int id, int &offset, bool supportRogaining)
|
|
{
|
|
vector<pControl>::const_iterator it=ctrl.begin();
|
|
int index=0;
|
|
offset = 1;
|
|
while(startIndex>0 && it!=ctrl.end()) {
|
|
int multi = (*it)->getNumMulti();
|
|
offset += multi-1;
|
|
++it, --startIndex, ++index;
|
|
if (it!=ctrl.end() && (*it)->isRogaining(supportRogaining))
|
|
index--;
|
|
}
|
|
|
|
while(it!=ctrl.end() && (*it) && (*it)->getId()!=id) {
|
|
int multi = (*it)->getNumMulti();
|
|
offset += multi-1;
|
|
++it, ++index;
|
|
if (it!=ctrl.end() && (*it)->isRogaining(supportRogaining))
|
|
index--;
|
|
}
|
|
|
|
if (it==ctrl.end())
|
|
return -1;
|
|
else
|
|
return index;
|
|
}
|
|
|
|
static void gotoNextLine(gdioutput &gdi, int &xcol, int &cx, int &cy, int colDeltaX, int numCol, int baseCX) {
|
|
if (++xcol < numCol) {
|
|
cx += colDeltaX;
|
|
}
|
|
else {
|
|
xcol = 0;
|
|
cy += int(gdi.getLineHeight()*1.1);
|
|
cx = baseCX;
|
|
}
|
|
}
|
|
|
|
static void addMissingControl(bool wideFormat, gdioutput &gdi,
|
|
int &xcol, int &cx, int &cy,
|
|
int colDeltaX, int numCol, int baseCX) {
|
|
int xx = cx;
|
|
wstring str = makeDash(L"-");
|
|
int posy = wideFormat ? cy : cy-int(gdi.getLineHeight()*0.4);
|
|
const int endx = cx + colDeltaX - 27;
|
|
|
|
while (xx < endx) {
|
|
gdi.addStringUT(posy, xx, fontSmall, str);
|
|
xx += 20;
|
|
}
|
|
|
|
// Make a thin line for list format, otherwise, take a full place
|
|
if (wideFormat) {
|
|
gotoNextLine(gdi, xcol, cx, cy, colDeltaX, numCol, baseCX);
|
|
}
|
|
else
|
|
cy+=int(gdi.getLineHeight()*0.3);
|
|
}
|
|
|
|
void oRunner::printSplits(gdioutput &gdi) const {
|
|
bool withAnalysis = (oe->getDI().getInt("Analysis") & 1) == 0;
|
|
bool withSpeed = (oe->getDI().getInt("Analysis") & 2) == 0;
|
|
bool withResult = (oe->getDI().getInt("Analysis") & 4) == 0;
|
|
const bool wideFormat = oe->getPropertyInt("WideSplitFormat", 0) == 1;
|
|
const int numCol = 4;
|
|
pClass cls = getClassRef(true);
|
|
if (cls && cls->getNoTiming()) {
|
|
withResult = false;
|
|
withAnalysis = false;
|
|
}
|
|
|
|
gdiFonts head = boldText;
|
|
gdiFonts normal = fontSmall;
|
|
gdiFonts bnormal = boldSmall;
|
|
if (wideFormat) {
|
|
head = boldLarge;
|
|
normal = normalText;
|
|
bnormal = boldText;
|
|
}
|
|
else {
|
|
gdi.setCX(10);
|
|
}
|
|
gdi.fillDown();
|
|
gdi.addStringUT(head, oe->getName());
|
|
gdi.addStringUT(normal, oe->getDate());
|
|
gdi.dropLine(0.5);
|
|
pCourse pc = getCourse(true);
|
|
|
|
gdi.addStringUT(bnormal, getName() + L", " + getClass(true));
|
|
gdi.addStringUT(normal, getClub());
|
|
gdi.dropLine(0.5);
|
|
gdi.addStringUT(normal, lang.tl("Start: ") + getStartTimeS() + lang.tl(", Mål: ") + getFinishTimeS());
|
|
if (cls && cls->isRogaining()) {
|
|
gdi.addStringUT(normal, lang.tl("Poäng: ") +
|
|
itow(getRogainingPoints(false)) +
|
|
+L" (" + lang.tl("Avdrag: ") + itow(getRogainingReduction()) + L")");
|
|
}
|
|
|
|
wstring statInfo = lang.tl("Status: ") + getStatusS(true) + lang.tl(", Tid: ") + getRunningTimeS();
|
|
if (withSpeed && pc && pc->getLength() > 0) {
|
|
int kmt = (getRunningTime() * 1000) / pc->getLength();
|
|
statInfo += L" (" + formatTime(kmt) + lang.tl(" min/km") + L")";
|
|
}
|
|
if (pc && withSpeed) {
|
|
if (pc->legLengths.empty() || *max_element(pc->legLengths.begin(), pc->legLengths.end()) <= 0)
|
|
withSpeed = false; // No leg lenghts available
|
|
}
|
|
gdi.addStringUT(normal, statInfo);
|
|
|
|
int cy = gdi.getCY()+4;
|
|
int cx = gdi.getCX();
|
|
|
|
int spMax = 0;
|
|
int totMax = 0;
|
|
if (pc) {
|
|
for (int n = 0; n < pc->nControls; n++) {
|
|
spMax = max(spMax, getSplitTime(n, false));
|
|
totMax = max(totMax, getPunchTime(n, false));
|
|
}
|
|
}
|
|
bool moreThanHour = max(totMax, getRunningTime()) >= 3600;
|
|
bool moreThanHourSplit = spMax >= 3600;
|
|
|
|
const int c1=35;
|
|
const int c2=95 + (moreThanHourSplit ? 65 : 55);
|
|
const int c3 = c2 + 10;
|
|
const int c4 = moreThanHour ? c3+153 : c3+133;
|
|
const int c5 = withSpeed ? c4 + 80 : c4;
|
|
const int baseCX = cx;
|
|
const int colDeltaX = c5 + 32;
|
|
|
|
char bf[256];
|
|
int lastIndex = -1;
|
|
int adjust = 0;
|
|
int offset = 1;
|
|
|
|
vector<pControl> ctrl;
|
|
|
|
int finishType = -1;
|
|
int startType = -1, startOffset = 0;
|
|
if (pc) {
|
|
pc->getControls(ctrl);
|
|
finishType = pc->getFinishPunchType();
|
|
|
|
if (pc->useFirstAsStart()) {
|
|
startType = pc->getStartPunchType();
|
|
startOffset = -1;
|
|
}
|
|
}
|
|
|
|
set<int> headerPos;
|
|
set<int> checkedIndex;
|
|
|
|
if (Card && pc) {
|
|
bool hasRogaining = pc->hasRogaining();
|
|
|
|
const int cyHead = cy;
|
|
cy += int(gdi.getLineHeight()*0.9);
|
|
int xcol = 0;
|
|
int baseY = cy;
|
|
|
|
oPunchList &p=Card->punches;
|
|
for (oPunchList::iterator it=p.begin();it!=p.end();++it) {
|
|
if (headerPos.count(cx) == 0) {
|
|
headerPos.insert(cx);
|
|
gdi.addString("", cyHead, cx, italicSmall, "Kontroll");
|
|
gdi.addString("", cyHead, cx+c2-55, italicSmall, "Tid");
|
|
if (withSpeed)
|
|
gdi.addString("", cyHead, cx+c5, italicSmall|textRight, "min/km");
|
|
}
|
|
|
|
bool any = false;
|
|
if (it->tRogainingIndex>=0) {
|
|
const pControl c = pc->getControl(it->tRogainingIndex);
|
|
string point = c ? itos(c->getRogainingPoints()) + "p." : "";
|
|
|
|
gdi.addStringUT(cy, cx + c1 + 10, fontSmall, point);
|
|
any = true;
|
|
|
|
sprintf_s(bf, "%d", it->Type);
|
|
gdi.addStringUT(cy, cx, fontSmall, bf);
|
|
int st = Card->getSplitTime(getStartTime(), &*it);
|
|
|
|
if (st>0)
|
|
gdi.addStringUT(cy, cx + c2, fontSmall|textRight, formatTime(st));
|
|
|
|
gdi.addStringUT(cy, cx+c3, fontSmall, it->getTime());
|
|
|
|
int pt = it->getAdjustedTime();
|
|
st = getStartTime();
|
|
if (st>0 && pt>0 && pt>st) {
|
|
wstring punchTime = formatTime(pt-st);
|
|
gdi.addStringUT(cy, cx+c4, fontSmall|textRight, punchTime);
|
|
}
|
|
|
|
cy+=int(gdi.getLineHeight()*0.9);
|
|
continue;
|
|
}
|
|
|
|
int cid = it->tMatchControlId;
|
|
wstring punchTime;
|
|
int sp;
|
|
int controlLegIndex = -1;
|
|
if (it->isFinish(finishType)) {
|
|
// Check if the last normal control was missing, and indicate this
|
|
for (int j = pc->getNumControls() - 1; j >= 0; j--) {
|
|
pControl ctrl = pc->getControl(j);
|
|
if (ctrl && ctrl->isSingleStatusOK()) {
|
|
if (checkedIndex.count(j) == 0) {
|
|
addMissingControl(wideFormat, gdi, xcol, cx, cy, colDeltaX, numCol, baseCX);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
gdi.addString("", cy, cx, fontSmall, "Mål");
|
|
sp = getSplitTime(splitTimes.size(), false);
|
|
if (sp>0) {
|
|
gdi.addStringUT(cy, cx+c2, fontSmall|textRight, formatTime(sp));
|
|
punchTime = formatTime(getRunningTime());
|
|
}
|
|
gdi.addStringUT(cy, cx+c3, fontSmall, oe->getAbsTime(it->Time + adjust));
|
|
any = true;
|
|
if (!punchTime.empty()) {
|
|
gdi.addStringUT(cy, cx+c4, fontSmall|textRight, punchTime);
|
|
}
|
|
controlLegIndex = pc->getNumControls();
|
|
}
|
|
else if (it->Type>10) { //Filter away check and start
|
|
int index = -1;
|
|
if (cid>0)
|
|
index = findNextControl(ctrl, lastIndex+1, cid, offset, hasRogaining);
|
|
if (index>=0) {
|
|
if (index > lastIndex + 1) {
|
|
addMissingControl(wideFormat, gdi, xcol, cx, cy, colDeltaX, numCol, baseCX);
|
|
|
|
/*int xx = cx;
|
|
string str = MakeDash("-");
|
|
int posy = wideFormat ? cy : cy-int(gdi.getLineHeight()*0.4);
|
|
const int endx = cx+c5 + 5;
|
|
|
|
while (xx < endx) {
|
|
gdi.addStringUT(posy, xx, fontSmall, str);
|
|
xx += 20;
|
|
}
|
|
|
|
// Make a thin line for list format, otherwise, take a full place
|
|
if (wideFormat) {
|
|
gotoNextLine(gdi, xcol, cx, cy, colDeltaX, numCol, baseCX);
|
|
}
|
|
else
|
|
cy+=int(gdi.getLineHeight()*0.3);*/
|
|
}
|
|
lastIndex = index;
|
|
|
|
if (it->Type == startType && (index+offset) == 1)
|
|
continue; // Skip start control
|
|
|
|
sprintf_s(bf, "%d.", index+offset+startOffset);
|
|
gdi.addStringUT(cy, cx, fontSmall, bf);
|
|
sprintf_s(bf, "(%d)", it->Type);
|
|
gdi.addStringUT(cy, cx+c1, fontSmall, bf);
|
|
|
|
controlLegIndex = it->tIndex;
|
|
checkedIndex.insert(controlLegIndex);
|
|
adjust = getTimeAdjust(controlLegIndex);
|
|
sp = getSplitTime(controlLegIndex, false);
|
|
if (sp>0) {
|
|
punchTime = getPunchTimeS(controlLegIndex, false);
|
|
gdi.addStringUT(cy, cx+c2, fontSmall|textRight, formatTime(sp));
|
|
}
|
|
}
|
|
else {
|
|
if (!it->isUsed) {
|
|
gdi.addStringUT(cy, cx, fontSmall, makeDash(L"-"));
|
|
}
|
|
sprintf_s(bf, "(%d)", it->Type);
|
|
gdi.addStringUT(cy, cx+c1, fontSmall, bf);
|
|
}
|
|
if (it->Time > 0)
|
|
gdi.addStringUT(cy, cx+c3, fontSmall, oe->getAbsTime(it->Time + adjust));
|
|
else {
|
|
wstring str = makeDash(L"-");
|
|
gdi.addStringUT(cy, cx+c3, fontSmall, str);
|
|
}
|
|
|
|
if (!punchTime.empty()) {
|
|
gdi.addStringUT(cy, cx+c4, fontSmall|textRight, punchTime);
|
|
}
|
|
any = true;
|
|
}
|
|
|
|
if (withSpeed && controlLegIndex>=0 && size_t(controlLegIndex) < pc->legLengths.size()) {
|
|
int length = pc->legLengths[controlLegIndex];
|
|
if (length > 0) {
|
|
int tempo=(sp*1000)/length;
|
|
gdi.addStringUT(cy, cx+c5, fontSmall|textRight, formatTime(tempo));
|
|
}
|
|
}
|
|
|
|
if (any) {
|
|
if (!wideFormat) {
|
|
cy+=int(gdi.getLineHeight()*0.9);
|
|
}
|
|
else {
|
|
gotoNextLine(gdi, xcol, cx, cy, colDeltaX, numCol, baseCX);
|
|
}
|
|
}
|
|
}
|
|
gdi.dropLine();
|
|
if (wideFormat) {
|
|
for (int i = 0; i < numCol-1; i++) {
|
|
RECT rc;
|
|
rc.top = baseY;
|
|
rc.bottom = cy;
|
|
rc.left = baseCX + colDeltaX*(i+1)-10;
|
|
rc.right = rc.left + 1;
|
|
gdi.addRectangle(rc, colorBlack);
|
|
}
|
|
}
|
|
|
|
if (withAnalysis) {
|
|
vector<wstring> misses;
|
|
int last = ctrl.size();
|
|
if (pc->useLastAsFinish())
|
|
last--;
|
|
|
|
for (int k = pc->useFirstAsStart() ? 1 : 0; k < last; k++) {
|
|
int missed = getMissedTime(k);
|
|
if (missed>0) {
|
|
misses.push_back(pc->getControlOrdinal(k) + L"/" + formatTime(missed));
|
|
}
|
|
}
|
|
if (misses.size()==0) {
|
|
vector<pRunner> rOut;
|
|
oe->getRunners(0, pc->getId(), rOut, false);
|
|
int count = 0;
|
|
for (size_t k = 0; k < rOut.size(); k++) {
|
|
if (rOut[k]->getCard())
|
|
count++;
|
|
}
|
|
|
|
if (count < 3)
|
|
gdi.addString("", normal, "Underlag saknas för bomanalys.");
|
|
else
|
|
gdi.addString("", normal, "Inga bommar registrerade.");
|
|
}
|
|
else {
|
|
wstring out = lang.tl("Tidsförluster (kontroll-tid): ");
|
|
for (size_t k = 0; k<misses.size(); k++) {
|
|
if (out.length() > (wideFormat ? 80u : (withSpeed ? 40u : 35u))) {
|
|
gdi.addStringUT(normal, out);
|
|
out.clear();
|
|
}
|
|
out += misses[k];
|
|
if (k < misses.size()-1)
|
|
out += L", ";
|
|
else
|
|
out += L".";
|
|
}
|
|
gdi.addStringUT(fontSmall, out);
|
|
}
|
|
}
|
|
|
|
if (withResult && statusOK()) {
|
|
gdi.dropLine(0.5);
|
|
oe->calculateResults({ getClassId(true) }, oEvent::ResultType::ClassResult);
|
|
if (hasInputData())
|
|
oe->calculateResults({ getClassId(true) }, oEvent::ResultType::TotalResult);
|
|
if (tInTeam)
|
|
oe->calculateTeamResults(tLeg, true);
|
|
if (cls && cls->isRogaining())
|
|
oe->calculateRogainingResults({cls->getId()});
|
|
wstring place = oe->formatListString(lRunnerGeneralPlace, pRunner(this), L"%s");
|
|
wstring timestatus;
|
|
if (tInTeam || hasInputData()) {
|
|
timestatus = oe->formatListString(lRunnerGeneralTimeStatus, pRunner(this));
|
|
if (!place.empty() && !timestatus.empty())
|
|
timestatus = L", " + timestatus;
|
|
}
|
|
|
|
wstring after = oe->formatListString(lRunnerGeneralTimeAfter, pRunner(this));
|
|
if (!after.empty() && !(place.empty() && timestatus.empty()))
|
|
after = L", " + after;
|
|
|
|
gdi.fillRight();
|
|
gdi.pushX();
|
|
if (!place.empty())
|
|
gdi.addString("", bnormal, "Placering:");
|
|
else
|
|
gdi.addString("", bnormal, "Resultat:");
|
|
gdi.fillDown();
|
|
gdi.addString("", normal, place + timestatus + after);
|
|
gdi.popX();
|
|
}
|
|
}
|
|
gdi.dropLine();
|
|
|
|
vector< pair<wstring, int> > lines;
|
|
oe->getExtraLines("SPExtra", lines);
|
|
|
|
for (size_t k = 0; k < lines.size(); k++) {
|
|
/*wstring ws = lines[k].first, wsOut;
|
|
size_t parS = ws.find_first_of('[');
|
|
while (parS != wstring::npos) {
|
|
size_t parE = ws.find_first_of(']');
|
|
if (parE != wstring::npos && parE > parS) {
|
|
wsOut += ws.substr(0, parS);
|
|
wstring cmd = ws.substr(parS + 1, parE-parS-1);
|
|
ws = ws.substr(parE + 1);
|
|
parS = ws.find_first_of('[');
|
|
|
|
auto type = MetaList::getTypeFromSymbol(cmd);
|
|
if (type == EPostType::lNone)
|
|
wsOut += L"{Error: " + cmd + L"}";
|
|
else {
|
|
wsOut += oe->formatListString(type, pRunner(this));
|
|
}
|
|
}
|
|
else
|
|
break; // Error
|
|
}
|
|
wsOut += ws;*/
|
|
gdi.addStringUT(lines[k].second, formatExtraLine(pRunner(this), lines[k].first));
|
|
}
|
|
if (lines.size()>0)
|
|
gdi.dropLine(0.5);
|
|
|
|
gdi.addString("", fontSmall, "Av MeOS: www.melin.nu/meos");
|
|
}
|
|
|
|
|
|
void oRunner::printStartInfo(gdioutput &gdi) const {
|
|
gdi.setCX(10);
|
|
gdi.fillDown();
|
|
gdi.addString("", boldText, L"Startbevis X#" + oe->getName());
|
|
gdi.addStringUT(fontSmall, oe->getDate());
|
|
gdi.dropLine(0.5);
|
|
|
|
wstring bib = getBib();
|
|
if (!bib.empty())
|
|
bib = bib + L": ";
|
|
|
|
gdi.addStringUT(boldSmall, bib + getName() + L", " + getClass(true));
|
|
gdi.addStringUT(fontSmall, getClub());
|
|
gdi.dropLine(0.5);
|
|
|
|
wstring startName;
|
|
if (getCourse(false)) {
|
|
startName = trim(getCourse(false)->getStart());
|
|
if (!startName.empty())
|
|
startName = L" (" + startName + L")";
|
|
}
|
|
if (getStartTime() > 0)
|
|
gdi.addStringUT(fontSmall, lang.tl(L"Start: ") + getStartTimeS() + startName);
|
|
else
|
|
gdi.addStringUT(fontSmall, lang.tl(L"Fri starttid") + startName);
|
|
|
|
wstring borrowed = getDCI().getInt("CardFee") != 0 ? L" (" + lang.tl(L"Hyrd") + L")" : L"";
|
|
|
|
gdi.addStringUT(fontSmall, lang.tl(L"Bricka: ") + itow(getCardNo()) + borrowed);
|
|
|
|
int cardFee = getDCI().getInt("CardFee");
|
|
if (cardFee < 0)
|
|
cardFee = 0;
|
|
|
|
int fee = oe->getMeOSFeatures().hasFeature(MeOSFeatures::Economy) ? getDCI().getInt("Fee") + cardFee : 0;
|
|
|
|
if (fee > 0) {
|
|
wstring info;
|
|
if (getDCI().getInt("Paid") == fee)
|
|
info = lang.tl("Betalat");
|
|
else
|
|
info = lang.tl("Faktureras");
|
|
|
|
gdi.addStringUT(fontSmall, lang.tl("Anmälningsavgift: ") + itow(fee) + L" (" + info + L")");
|
|
}
|
|
|
|
gdi.dropLine(1);
|
|
vector< pair<wstring, int> > lines;
|
|
oe->getExtraLines("EntryExtra", lines);
|
|
|
|
for (size_t k = 0; k < lines.size(); k++) {
|
|
gdi.addStringUT(lines[k].second, formatExtraLine(pRunner(this), lines[k].first));
|
|
}
|
|
if (lines.size()>0)
|
|
gdi.dropLine(0.5);
|
|
|
|
gdi.addStringUT(fontSmall, L"Av MeOS " + getMeosCompectVersion() + L" / www.melin.nu/meos");
|
|
}
|
|
|
|
vector<pRunner> oRunner::getRunnersOrdered() const {
|
|
if (tParentRunner)
|
|
return tParentRunner->getRunnersOrdered();
|
|
|
|
vector<pRunner> r(multiRunner.size()+1);
|
|
r[0] = (pRunner)this;
|
|
for (size_t k=0;k<multiRunner.size();k++)
|
|
r[k+1] = (pRunner)multiRunner[k];
|
|
|
|
return r;
|
|
}
|
|
|
|
int oRunner::getMultiIndex() {
|
|
if (!tParentRunner)
|
|
return 0;
|
|
|
|
const vector<pRunner> &r = tParentRunner->multiRunner;
|
|
|
|
for (size_t k=0;k<r.size(); k++)
|
|
if (r[k]==this)
|
|
return k+1;
|
|
|
|
// Error
|
|
tParentRunner = 0;
|
|
markForCorrection();
|
|
return -1;
|
|
}
|
|
|
|
void oRunner::correctRemove(pRunner r) {
|
|
for(unsigned i=0;i<multiRunner.size(); i++)
|
|
if (r!=0 && multiRunner[i]==r) {
|
|
multiRunner[i] = 0;
|
|
r->tParentRunner = 0;
|
|
r->tLeg = 0;
|
|
r->tLegEquClass = 0;
|
|
if (i+1==multiRunner.size())
|
|
multiRunner.pop_back();
|
|
|
|
correctionNeeded = true;
|
|
r->correctionNeeded = true;
|
|
}
|
|
}
|
|
|
|
void oEvent::updateRunnersFromDB()
|
|
{
|
|
oRunnerList::iterator it;
|
|
if (!oe->useRunnerDb())
|
|
return;
|
|
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (!it->isVacant() && !it->isRemoved())
|
|
it->updateFromDB(it->sName, it->getClubId(), it->getClassId(false), it->getCardNo(), it->getBirthYear(), true);
|
|
}
|
|
}
|
|
|
|
bool oRunner::updateFromDB(const wstring &name, int clubId, int classId,
|
|
int cardNo, int birthYear, bool forceUpdate) {
|
|
if (!oe->useRunnerDb())
|
|
return false;
|
|
uint64_t oldId = getExtIdentifier();
|
|
if (oldId && !forceUpdate && oe->getRunnerDatabase().getRunnerById(oldId) == 0)
|
|
return false; // Keep data if database is not installed
|
|
|
|
pRunner db_r = 0;
|
|
if (cardNo>0) {
|
|
db_r = oe->dbLookUpByCard(cardNo);
|
|
|
|
if (db_r && db_r->matchName(name)) {
|
|
//setName(db_r->getName());
|
|
//setClub(db_r->getClub()); Don't...
|
|
setExtIdentifier(db_r->getExtIdentifier());
|
|
setBirthYear(db_r->getBirthYear());
|
|
setSex(db_r->getSex());
|
|
setNationality(db_r->getNationality());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
db_r = oe->dbLookUpByName(name, clubId, classId, birthYear);
|
|
|
|
if (db_r) {
|
|
setExtIdentifier(db_r->getExtIdentifier());
|
|
setBirthYear(db_r->getBirthYear());
|
|
setSex(db_r->getSex());
|
|
setNationality(db_r->getNationality());
|
|
return true;
|
|
}
|
|
else if (getExtIdentifier()>0) {
|
|
db_r = oe->dbLookUpById(getExtIdentifier());
|
|
if (db_r && db_r->matchName(name)) {
|
|
setBirthYear(db_r->getBirthYear());
|
|
setSex(db_r->getSex());
|
|
setNationality(db_r->getNationality());
|
|
return true;
|
|
}
|
|
// Reset external identifier
|
|
setExtIdentifier(0);
|
|
setBirthYear(0);
|
|
// Do not reset nationality and sex,
|
|
// since they are likely correct.
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void oRunner::setSex(PersonSex sex)
|
|
{
|
|
getDI().setString("Sex", encodeSex(sex));
|
|
}
|
|
|
|
PersonSex oRunner::getSex() const
|
|
{
|
|
return interpretSex(getDCI().getString("Sex"));
|
|
}
|
|
|
|
void oRunner::setBirthYear(int year)
|
|
{
|
|
getDI().setInt("BirthYear", year);
|
|
}
|
|
|
|
int oRunner::getBirthYear() const
|
|
{
|
|
return getDCI().getInt("BirthYear");
|
|
}
|
|
|
|
void oAbstractRunner::setSpeakerPriority(int year)
|
|
{
|
|
if (Class) {
|
|
oe->classChanged(Class, false);
|
|
}
|
|
getDI().setInt("Priority", year);
|
|
}
|
|
|
|
int oAbstractRunner::getSpeakerPriority() const
|
|
{
|
|
return getDCI().getInt("Priority");
|
|
}
|
|
|
|
int oRunner::getSpeakerPriority() const {
|
|
int p = oAbstractRunner::getSpeakerPriority();
|
|
|
|
if (tParentRunner)
|
|
p = max(p, tParentRunner->getSpeakerPriority());
|
|
else if (tInTeam) {
|
|
p = max(p, tInTeam->getSpeakerPriority());
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
void oRunner::setNationality(const wstring &nat)
|
|
{
|
|
getDI().setString("Nationality", nat);
|
|
}
|
|
|
|
wstring oRunner::getNationality() const
|
|
{
|
|
return getDCI().getString("Nationality");
|
|
}
|
|
|
|
bool oRunner::matchName(const wstring &pname) const
|
|
{
|
|
if (pname == sName || pname == tRealName)
|
|
return true;
|
|
|
|
vector<wstring> myNames, inNames;
|
|
|
|
split(tRealName, L" ", myNames);
|
|
split(pname, L" ", inNames);
|
|
int numInNames = inNames.size();
|
|
|
|
for (size_t k = 0; k < myNames.size(); k++)
|
|
myNames[k] = canonizeName(myNames[k].c_str());
|
|
|
|
int nMatched = 0;
|
|
for (size_t j = 0; j < inNames.size(); j++) {
|
|
wstring inName = canonizeName(inNames[j].c_str());
|
|
for (size_t k = 0; k < myNames.size(); k++) {
|
|
if (myNames[k] == inName) {
|
|
nMatched++;
|
|
|
|
// Suppert changed last name in the most common case
|
|
/*if (j == 0 && k == 0 && inNames.size() == 2 && myNames.size() == 2) {
|
|
return true;
|
|
}*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nMatched >= min<int>(max<int>(numInNames, myNames.size()), 2);
|
|
}
|
|
|
|
oRunner::BibAssignResult oRunner::autoAssignBib() {
|
|
if (Class == 0 || !getBib().empty())
|
|
return BibAssignResult::NoBib;
|
|
|
|
int maxbib = 0;
|
|
wchar_t pattern[32];
|
|
int noBib = 0;
|
|
int withBib = 0;
|
|
unordered_set<wstring> allBibs;
|
|
allBibs.reserve(oe->Runners.size());
|
|
|
|
for(oRunnerList::iterator it = oe->Runners.begin(); it !=oe->Runners.end();++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
|
|
const wstring &bib = it->getBib();
|
|
allBibs.insert(bib);
|
|
|
|
if (it->Class == Class) {
|
|
if (!bib.empty()) {
|
|
withBib++;
|
|
int ibib = oClass::extractBibPattern(bib, pattern);
|
|
maxbib = max(ibib, maxbib);
|
|
}
|
|
else
|
|
noBib++;
|
|
}
|
|
}
|
|
|
|
if (maxbib>0 && withBib>noBib) {
|
|
wchar_t bib[32];
|
|
swprintf_s(bib, pattern, maxbib+1);
|
|
wstring nBib = bib;
|
|
if (allBibs.count(nBib))
|
|
return BibAssignResult::Failed; // Bib already use. Do not allow duplicates.
|
|
setBib(nBib, maxbib+1, true, false);
|
|
return BibAssignResult::Assigned;
|
|
}
|
|
return BibAssignResult::NoBib;
|
|
}
|
|
|
|
void oRunner::getSplitAnalysis(vector<int> &deltaTimes) const {
|
|
deltaTimes.clear();
|
|
vector<int> mp;
|
|
|
|
if (splitTimes.empty() || !Class)
|
|
return;
|
|
pClass cls = getClassRef(true);
|
|
|
|
if (cls->tSplitRevision == tSplitRevision) {
|
|
deltaTimes = tMissedTime;
|
|
return;
|
|
}
|
|
|
|
pCourse pc = getCourse(true);
|
|
if (!pc)
|
|
return;
|
|
vector<int> reorder;
|
|
if (pc->isAdapted())
|
|
reorder = pc->getMapToOriginalOrder();
|
|
else {
|
|
reorder.reserve(pc->nControls+1);
|
|
for (int k = 0; k <= pc->nControls; k++)
|
|
reorder.push_back(k);
|
|
}
|
|
|
|
int id = pc->getId();
|
|
if (cls->tSplitAnalysisData.count(id) == 0)
|
|
cls->calculateSplits();
|
|
|
|
const vector<int> &baseLine = cls->tSplitAnalysisData[id];
|
|
const unsigned nc = pc->getNumControls();
|
|
|
|
if (baseLine.size() != nc+1)
|
|
return;
|
|
|
|
vector<double> res(nc+1);
|
|
|
|
double resSum = 0;
|
|
double baseSum = 0;
|
|
double bestTime = 0;
|
|
for (size_t k = 0; k <= nc; k++) {
|
|
res[k] = getSplitTime(k, false);
|
|
if (res[k] > 0) {
|
|
resSum += res[k];
|
|
baseSum += baseLine[reorder[k]];
|
|
}
|
|
bestTime += baseLine[reorder[k]];
|
|
}
|
|
|
|
deltaTimes.resize(nc+1);
|
|
|
|
// Adjust expected time by removing mistakes
|
|
for (size_t k = 0; k <= nc; k++) {
|
|
if (res[k] > 0) {
|
|
double part = res[k]*baseSum/(resSum * bestTime);
|
|
double delta = part - baseLine[reorder[k]] / bestTime;
|
|
int deltaAbs = int(floor(delta * resSum + 0.5));
|
|
if (res[k]-deltaAbs < baseLine[reorder[k]])
|
|
deltaAbs = int(res[k] - baseLine[reorder[k]]);
|
|
|
|
if (deltaAbs>0)
|
|
resSum -= deltaAbs;
|
|
}
|
|
}
|
|
|
|
for (size_t k = 0; k <= nc; k++) {
|
|
if (res[k] > 0) {
|
|
double part = res[k]*baseSum/(resSum * bestTime);
|
|
double delta = part - baseLine[reorder[k]] / bestTime;
|
|
|
|
int deltaAbs = int(floor(delta * resSum + 0.5));
|
|
|
|
if (deltaAbs > 0) {
|
|
if ( fabs(delta) > 1.0/100 && (20.0*deltaAbs)>res[k] && deltaAbs>=15)
|
|
deltaTimes[k] = deltaAbs;
|
|
|
|
res[k] -= deltaAbs;
|
|
if (res[k] < baseLine[reorder[k]])
|
|
res[k] = baseLine[reorder[k]];
|
|
}
|
|
}
|
|
}
|
|
|
|
resSum = 0;
|
|
for (size_t k = 0; k <= nc; k++) {
|
|
if (res[k] > 0) {
|
|
resSum += res[k];
|
|
}
|
|
}
|
|
|
|
for (size_t k = 0; k <= nc; k++) {
|
|
if (res[k] > 0) {
|
|
double part = res[k]*baseSum/(resSum * bestTime);
|
|
double delta = part - baseLine[reorder[k]] / bestTime;
|
|
int deltaAbs = int(floor(delta * resSum + 0.5));
|
|
|
|
if (deltaTimes[k]==0 && fabs(delta) > 1.0/100 && deltaAbs>=8)
|
|
deltaTimes[k] = deltaAbs;
|
|
}
|
|
}
|
|
}
|
|
|
|
void oRunner::getLegPlaces(vector<int> &places) const {
|
|
places.clear();
|
|
pCourse pc = getCourse(true);
|
|
if (!pc || !Class || splitTimes.empty())
|
|
return;
|
|
pClass cls = getClassRef(true);
|
|
|
|
if (cls->tSplitRevision == tSplitRevision) {
|
|
places = tPlaceLeg;
|
|
return;
|
|
}
|
|
|
|
int id = pc->getId();
|
|
|
|
if (cls->tSplitAnalysisData.count(id) == 0)
|
|
cls->calculateSplits();
|
|
|
|
const unsigned nc = pc->getNumControls();
|
|
|
|
places.resize(nc+1);
|
|
int cc = pc->getCommonControl();
|
|
for (unsigned k = 0; k<=nc; k++) {
|
|
int to = cc;
|
|
if (k<nc)
|
|
to = pc->getControl(k) ? pc->getControl(k)->getId() : 0;
|
|
int from = cc;
|
|
if (k>0)
|
|
from = pc->getControl(k-1) ? pc->getControl(k-1)->getId() : 0;
|
|
|
|
int time = getSplitTime(k, false);
|
|
|
|
if (time>0)
|
|
places[k] = cls->getLegPlace(from, to, time);
|
|
else
|
|
places[k] = 0;
|
|
}
|
|
}
|
|
|
|
void oRunner::getLegTimeAfter(vector<int> ×) const
|
|
{
|
|
times.clear();
|
|
if (splitTimes.empty() || !Class)
|
|
return;
|
|
pClass cls = getClassRef(true);
|
|
if (cls->tSplitRevision == tSplitRevision) {
|
|
times = tAfterLeg;
|
|
return;
|
|
}
|
|
|
|
pCourse pc = getCourse(false);
|
|
if (!pc)
|
|
return;
|
|
|
|
int id = pc->getId();
|
|
|
|
if (cls->tCourseLegLeaderTime.count(id) == 0)
|
|
cls->calculateSplits();
|
|
|
|
const unsigned nc = pc->getNumControls();
|
|
|
|
const vector<int> leaders = cls->tCourseLegLeaderTime[id];
|
|
|
|
if (leaders.size() != nc + 1)
|
|
return;
|
|
|
|
times.resize(nc+1);
|
|
|
|
for (unsigned k = 0; k<=nc; k++) {
|
|
int s = getSplitTime(k, true);
|
|
|
|
if (s>0) {
|
|
times[k] = s - leaders[k];
|
|
if (times[k]<0)
|
|
times[k] = -1;
|
|
}
|
|
else
|
|
times[k] = -1;
|
|
}
|
|
// Normalized order
|
|
const vector<int> &reorder = getCourse(true)->getMapToOriginalOrder();
|
|
if (!reorder.empty()) {
|
|
vector<int> orderedTimes(times.size());
|
|
for (size_t k = 0; k < min(reorder.size(), times.size()); k++) {
|
|
orderedTimes[k] = times[reorder[k]];
|
|
}
|
|
times.swap(orderedTimes);
|
|
}
|
|
}
|
|
|
|
void oRunner::getLegTimeAfterAcc(vector<int> ×) const
|
|
{
|
|
times.clear();
|
|
if (splitTimes.empty() || !Class || tStartTime<=0)
|
|
return;
|
|
pClass cls = getClassRef(true);
|
|
if (cls->tSplitRevision == tSplitRevision) {
|
|
times = tAfterLegAcc;
|
|
return;
|
|
}
|
|
pCourse pc = getCourse(false); //XXX Does not work for loop courses
|
|
if (!pc)
|
|
return;
|
|
|
|
int id = pc->getId();
|
|
|
|
if (cls->tCourseAccLegLeaderTime.count(id) == 0)
|
|
cls->calculateSplits();
|
|
|
|
const unsigned nc = pc->getNumControls();
|
|
|
|
const vector<int> leaders = cls->tCourseAccLegLeaderTime[id];
|
|
const vector<SplitData> &sp = getSplitTimes(true);
|
|
if (leaders.size() != nc + 1)
|
|
return;
|
|
//xxx reorder output
|
|
times.resize(nc+1);
|
|
|
|
for (unsigned k = 0; k<=nc; k++) {
|
|
int s = 0;
|
|
if (k < sp.size())
|
|
s = sp[k].time;
|
|
else if (k==nc)
|
|
s = FinishTime;
|
|
|
|
if (s>0) {
|
|
times[k] = s - tStartTime - leaders[k];
|
|
if (times[k]<0)
|
|
times[k] = -1;
|
|
}
|
|
else
|
|
times[k] = -1;
|
|
}
|
|
|
|
// Normalized order
|
|
const vector<int> &reorder = getCourse(true)->getMapToOriginalOrder();
|
|
if (!reorder.empty()) {
|
|
vector<int> orderedTimes(times.size());
|
|
for (size_t k = 0; k < min(reorder.size(), times.size()); k++) {
|
|
orderedTimes[k] = times[reorder[k]];
|
|
}
|
|
times.swap(orderedTimes);
|
|
}
|
|
}
|
|
|
|
void oRunner::getLegPlacesAcc(vector<int> &places) const
|
|
{
|
|
places.clear();
|
|
pCourse pc = getCourse(false);
|
|
if (!pc || !Class)
|
|
return;
|
|
if (splitTimes.empty() || tStartTime<=0)
|
|
return;
|
|
pClass cls = getClassRef(true);
|
|
if (cls->tSplitRevision == tSplitRevision) {
|
|
places = tPlaceLegAcc;
|
|
return;
|
|
}
|
|
|
|
int id = pc->getId();
|
|
const unsigned nc = pc->getNumControls();
|
|
const vector<SplitData> &sp = getSplitTimes(true);
|
|
places.resize(nc+1);
|
|
for (unsigned k = 0; k<=nc; k++) {
|
|
int s = 0;
|
|
if (k < sp.size())
|
|
s = sp[k].time;
|
|
else if (k==nc)
|
|
s = FinishTime;
|
|
|
|
if (s>0) {
|
|
int time = s - tStartTime;
|
|
|
|
if (time>0)
|
|
places[k] = cls->getAccLegPlace(id, k, time);
|
|
else
|
|
places[k] = 0;
|
|
}
|
|
}
|
|
|
|
// Normalized order
|
|
const vector<int> &reorder = getCourse(true)->getMapToOriginalOrder();
|
|
if (!reorder.empty()) {
|
|
vector<int> orderedPlaces(reorder.size());
|
|
for (size_t k = 0; k < reorder.size(); k++) {
|
|
orderedPlaces[k] = places[reorder[k]];
|
|
}
|
|
places.swap(orderedPlaces);
|
|
}
|
|
}
|
|
|
|
void oRunner::setupRunnerStatistics() const
|
|
{
|
|
if (!Class)
|
|
return;
|
|
pClass cls = getClassRef(true);
|
|
|
|
if (cls->tSplitRevision == tSplitRevision)
|
|
return;
|
|
if (Card)
|
|
tOnCourseResults.clear();
|
|
|
|
getSplitAnalysis(tMissedTime);
|
|
getLegPlaces(tPlaceLeg);
|
|
getLegTimeAfter(tAfterLeg);
|
|
getLegPlacesAcc(tPlaceLegAcc);
|
|
getLegTimeAfterAcc(tAfterLegAcc);
|
|
tSplitRevision = cls->tSplitRevision;
|
|
}
|
|
|
|
int oRunner::getMissedTime(int ctrlNo) const {
|
|
setupRunnerStatistics();
|
|
if (unsigned(ctrlNo) < tMissedTime.size())
|
|
return tMissedTime[ctrlNo];
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
wstring oRunner::getMissedTimeS() const
|
|
{
|
|
setupRunnerStatistics();
|
|
int t = 0;
|
|
for (size_t k = 0; k<tMissedTime.size(); k++)
|
|
if (tMissedTime[k]>0)
|
|
t += tMissedTime[k];
|
|
|
|
return getTimeMS(t);
|
|
}
|
|
|
|
wstring oRunner::getMissedTimeS(int ctrlNo) const
|
|
{
|
|
int t = getMissedTime(ctrlNo);
|
|
if (t>0)
|
|
return getTimeMS(t);
|
|
else
|
|
return L"";
|
|
}
|
|
|
|
int oRunner::getLegPlace(int ctrlNo) const {
|
|
setupRunnerStatistics();
|
|
if (unsigned(ctrlNo) < tPlaceLeg.size())
|
|
return tPlaceLeg[ctrlNo];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int oRunner::getLegTimeAfter(int ctrlNo) const {
|
|
setupRunnerStatistics();
|
|
if (unsigned(ctrlNo) < tAfterLeg.size())
|
|
return tAfterLeg[ctrlNo];
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int oRunner::getLegPlaceAcc(int ctrlNo) const {
|
|
for (auto &res : tOnCourseResults.res) {
|
|
if (res.controlIx == ctrlNo)
|
|
return res.place;
|
|
}
|
|
if (!Card) {
|
|
return 0;
|
|
}
|
|
setupRunnerStatistics();
|
|
if (unsigned(ctrlNo) < tPlaceLegAcc.size())
|
|
return tPlaceLegAcc[ctrlNo];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int oRunner::getLegTimeAfterAcc(int ctrlNo) const {
|
|
for (auto &res : tOnCourseResults.res) {
|
|
if (res.controlIx == ctrlNo)
|
|
return res.after;
|
|
}
|
|
if (!Card)
|
|
return -1;
|
|
setupRunnerStatistics();
|
|
if (unsigned(ctrlNo) < tAfterLegAcc.size())
|
|
return tAfterLegAcc[ctrlNo];
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int oRunner::getTimeWhenPlaceFixed() const {
|
|
if (!Class || !statusOK())
|
|
return -1;
|
|
|
|
#ifndef MEOSDB
|
|
if (unsigned(tLeg) >= Class->tResultInfo.size()) {
|
|
oe->analyzeClassResultStatus();
|
|
if (unsigned(tLeg) >= Class->tResultInfo.size())
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int lst = Class->tResultInfo[tLeg].lastStartTime;
|
|
return lst > 0 ? lst + getRunningTime() : lst;
|
|
}
|
|
|
|
|
|
pRunner oRunner::getMatchedRunner(const SICard &sic) const {
|
|
if (multiRunner.size() == 0 && tParentRunner == 0)
|
|
return pRunner(this);
|
|
if (!Class)
|
|
return pRunner(this);
|
|
|
|
const vector<pRunner> &multiV = tParentRunner ? tParentRunner->multiRunner : multiRunner;
|
|
|
|
vector<pRunner> multiOrdered;
|
|
multiOrdered.push_back( tParentRunner ? tParentRunner : pRunner(this));
|
|
multiOrdered.insert(multiOrdered.end(), multiV.begin(), multiV.end());
|
|
|
|
int Distance=-1000;
|
|
pRunner r = 0; //Best runner
|
|
|
|
for (size_t k = 0; k<multiOrdered.size(); k++) {
|
|
if (!multiOrdered[k] || multiOrdered[k]->Card || multiOrdered[k]->getStatus() != StatusUnknown)
|
|
continue;
|
|
|
|
LegTypes lt = Class->getLegType(multiOrdered[k]->tLeg);
|
|
StartTypes st = Class->getStartType(multiOrdered[k]->tLeg);
|
|
|
|
if (lt == LTNormal || lt == LTParallel || st==STChange || st == STHunting)
|
|
return pRunner(this);
|
|
|
|
vector<pCourse> crs;
|
|
if (Class->hasCoursePool()) {
|
|
Class->getCourses(multiOrdered[k]->tLeg, crs);
|
|
}
|
|
else {
|
|
pCourse pc = multiOrdered[k]->getCourse(false);
|
|
crs.push_back(pc);
|
|
}
|
|
|
|
for (size_t j = 0; j < crs.size(); j++) {
|
|
pCourse pc = crs[j];
|
|
if (!pc)
|
|
continue;
|
|
|
|
int d = pc->distance(sic);
|
|
|
|
if (d>=0) {
|
|
if (Distance<0) Distance=1000;
|
|
if (d<Distance) {
|
|
Distance=d;
|
|
r = multiOrdered[k];
|
|
}
|
|
}
|
|
else {
|
|
if (Distance<0 && d>Distance) {
|
|
Distance=d;
|
|
r = multiOrdered[k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (r)
|
|
return r;
|
|
else
|
|
return pRunner(this);
|
|
}
|
|
|
|
int oRunner::getTotalRunningTime(int time, bool includeInput) const {
|
|
if (tStartTime < 0)
|
|
return 0;
|
|
if (tInTeam == 0 || tLeg == 0) {
|
|
if (time == FinishTime)
|
|
return getRunningTime() + (includeInput ? inputTime : 0);
|
|
else
|
|
return time-tStartTime + (includeInput ? inputTime : 0);
|
|
}
|
|
else {
|
|
if (Class == 0 || unsigned(tLeg) >= Class->legInfo.size())
|
|
return 0;
|
|
|
|
if (time == FinishTime) {
|
|
return tInTeam->getLegRunningTime(tLeg, includeInput); // Use the official running time in this case (which works with parallel legs)
|
|
}
|
|
|
|
int baseleg = tLeg;
|
|
while (baseleg>0 && (Class->legInfo[baseleg].isParallel() ||
|
|
Class->legInfo[baseleg].isOptional())) {
|
|
baseleg--;
|
|
}
|
|
|
|
int leg = baseleg-1;
|
|
while (leg>0 && (Class->legInfo[leg].legMethod == LTExtra || Class->legInfo[leg].legMethod == LTIgnore)) {
|
|
leg--;
|
|
}
|
|
|
|
int pt = leg>=0 ? tInTeam->getLegRunningTime(leg, includeInput) : 0;
|
|
if (pt>0)
|
|
return pt + time - tStartTime;
|
|
else if (tInTeam->tStartTime > 0)
|
|
return (time - tInTeam->tStartTime) + (includeInput ? tInTeam->inputTime : 0);
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Get the complete name, including team and club.
|
|
wstring oRunner::getCompleteIdentification(bool includeExtra) const {
|
|
if (tInTeam == 0 || !Class || tInTeam->getName() == sName) {
|
|
if (Club)
|
|
return getName() + L" (" + Club->name + L")";
|
|
else
|
|
return getName();
|
|
}
|
|
else {
|
|
wstring names;
|
|
pClass clsToUse = tInTeam->Class != 0 ? tInTeam->Class : Class;
|
|
// Get many names for paralell legs
|
|
int firstLeg = tLeg;
|
|
LegTypes lt=clsToUse->getLegType(firstLeg--);
|
|
while(firstLeg>=0 && (lt==LTIgnore || lt==LTParallel || lt==LTParallelOptional || (lt==LTExtra && includeExtra)) )
|
|
lt=clsToUse->getLegType(firstLeg--);
|
|
|
|
for (size_t k = firstLeg+1; k < clsToUse->legInfo.size(); k++) {
|
|
pRunner r = tInTeam->getRunner(k);
|
|
if (r) {
|
|
if (names.empty())
|
|
names = r->tRealName;
|
|
else
|
|
names += L"/" + r->tRealName;
|
|
}
|
|
lt = clsToUse->getLegType(k + 1);
|
|
if ( !(lt==LTIgnore || lt==LTParallel || lt == LTParallelOptional || (lt==LTExtra && includeExtra)))
|
|
break;
|
|
}
|
|
|
|
if (clsToUse->legInfo.size() <= 2)
|
|
return names + L" (" + tInTeam->sName + L")";
|
|
else
|
|
return tInTeam->sName + L" (" + names + L")";
|
|
}
|
|
}
|
|
|
|
RunnerStatus oAbstractRunner::getTotalStatus() const {
|
|
if (tStatus == StatusUnknown && inputStatus != StatusNotCompetiting)
|
|
return StatusUnknown;
|
|
else if (inputStatus == StatusUnknown)
|
|
return StatusDNS;
|
|
|
|
return max(tStatus, inputStatus);
|
|
}
|
|
|
|
RunnerStatus oRunner::getTotalStatus() const {
|
|
if (tStatus == StatusUnknown && inputStatus != StatusNotCompetiting)
|
|
return StatusUnknown;
|
|
else if (inputStatus == StatusUnknown)
|
|
return StatusDNS;
|
|
|
|
if (tInTeam == 0 || tLeg == 0)
|
|
return max(tStatus, inputStatus);
|
|
else {
|
|
RunnerStatus st = tInTeam->getLegStatus(tLeg-1, true);
|
|
|
|
if (tLeg + 1 == tInTeam->getNumRunners())
|
|
st = max(st, tInTeam->getStatus());
|
|
|
|
if (st == StatusOK || st == StatusUnknown)
|
|
return tStatus;
|
|
else
|
|
return max(max(tStatus, st), inputStatus);
|
|
}
|
|
}
|
|
|
|
void oRunner::remove()
|
|
{
|
|
if (oe) {
|
|
vector<int> me;
|
|
me.push_back(Id);
|
|
oe->removeRunner(me);
|
|
}
|
|
}
|
|
|
|
bool oRunner::canRemove() const
|
|
{
|
|
return !oe->isRunnerUsed(Id);
|
|
}
|
|
|
|
void oAbstractRunner::setInputTime(const wstring &time) {
|
|
int t = convertAbsoluteTimeMS(time);
|
|
if (t != inputTime) {
|
|
inputTime = t;
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
wstring oAbstractRunner::getInputTimeS() const {
|
|
if (inputTime > 0)
|
|
return formatTime(inputTime);
|
|
else
|
|
return makeDash(L"-");
|
|
}
|
|
|
|
void oAbstractRunner::setInputStatus(RunnerStatus s) {
|
|
if (inputStatus != s) {
|
|
inputStatus = s;
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
wstring oAbstractRunner::getInputStatusS() const {
|
|
return oe->formatStatus(inputStatus, true);
|
|
}
|
|
|
|
void oAbstractRunner::setInputPoints(int p)
|
|
{
|
|
if (p != inputPoints) {
|
|
inputPoints = p;
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
void oAbstractRunner::setInputPlace(int p)
|
|
{
|
|
if (p != inputPlace) {
|
|
inputPlace = p;
|
|
updateChanged();
|
|
}
|
|
}
|
|
|
|
void oRunner::setInputData(const oRunner &r) {
|
|
if (!r.multiRunner.empty() && r.multiRunner.back() && r.multiRunner.back() != &r)
|
|
setInputData(*r.multiRunner.back());
|
|
else {
|
|
oDataInterface dest = getDI();
|
|
oDataConstInterface src = r.getDCI();
|
|
|
|
if (r.tStatus != StatusNotCompetiting) {
|
|
inputTime = r.getTotalRunningTime(r.FinishTime, true);
|
|
inputStatus = r.getTotalStatus();
|
|
if (r.tInTeam) { // If a team has not status ok, transfer this status to all team members.
|
|
if (r.tInTeam->getTotalStatus() > StatusOK)
|
|
inputStatus = r.tInTeam->getTotalStatus();
|
|
}
|
|
inputPoints = r.getRogainingPoints(true) + r.inputPoints;
|
|
inputPlace = r.tTotalPlace < 99000 ? r.tTotalPlace : 0;
|
|
}
|
|
else {
|
|
// Copy input
|
|
inputTime = r.inputTime;
|
|
inputStatus = r.inputStatus;
|
|
inputPoints = r.inputPoints;
|
|
inputPlace = r.inputPlace;
|
|
}
|
|
|
|
if (r.getClubRef())
|
|
setClub(r.getClub());
|
|
|
|
if (!Card && r.isTransferCardNoNextStage()) {
|
|
setCardNo(r.getCardNo(), false);
|
|
dest.setInt("CardFee", src.getInt("CardFee"));
|
|
setTransferCardNoNextStage(true);
|
|
}
|
|
// Copy flags.
|
|
// copy....
|
|
|
|
dest.setInt("TransferFlags", src.getInt("TransferFlags"));
|
|
dest.setString("Nationality", src.getString("Nationality"));
|
|
dest.setString("Country", src.getString("Country"));
|
|
|
|
dest.setInt("Fee", src.getInt("Fee"));
|
|
dest.setInt("Paid", src.getInt("Paid"));
|
|
dest.setInt("Taxable", src.getInt("Taxable"));
|
|
}
|
|
}
|
|
|
|
void oEvent::getDBRunnersInEvent(intkeymap<pClass, __int64> &runners) const {
|
|
runners.clear();
|
|
for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->isRemoved())
|
|
continue;
|
|
__int64 id = it->getExtIdentifier();
|
|
if (id != 0)
|
|
runners.insert(id, it->Class);
|
|
}
|
|
}
|
|
|
|
void oRunner::init(const RunnerWDBEntry &dbr, bool updateOnlyExt) {
|
|
if (updateOnlyExt) {
|
|
dbr.getName(sName);
|
|
getRealName(sName, tRealName);
|
|
getDI().setString("Nationality", dbr.getNationality());
|
|
getDI().setInt("BirthYear", dbr.getBirthYear());
|
|
getDI().setString("Sex", dbr.getSex());
|
|
setExtIdentifier(dbr.getExtId());
|
|
}
|
|
else {
|
|
setTemporary();
|
|
dbr.getName(sName);
|
|
getRealName(sName, tRealName);
|
|
cardNumber = dbr.dbe().cardNo;
|
|
Club = oe->getRunnerDatabase().getClub(dbr.dbe().clubNo);
|
|
getDI().setString("Nationality", dbr.getNationality());
|
|
getDI().setInt("BirthYear", dbr.getBirthYear());
|
|
getDI().setString("Sex", dbr.getSex());
|
|
setExtIdentifier(dbr.getExtId());
|
|
}
|
|
}
|
|
|
|
void oEvent::selectRunners(const wstring &classType, int lowAge,
|
|
int highAge, const wstring &firstDate,
|
|
const wstring &lastDate, bool includeWithFee,
|
|
vector<pRunner> &output) const {
|
|
oRunnerList::const_iterator it;
|
|
int cid = 0;
|
|
if (classType.length() > 2 && classType.substr(0,2) == L"::")
|
|
cid = _wtoi(classType.c_str() + 2);
|
|
|
|
output.clear();
|
|
|
|
int firstD = 0, lastD = 0;
|
|
if (!firstDate.empty()) {
|
|
firstD = convertDateYMS(firstDate, true);
|
|
if (firstD <= 0)
|
|
throw meosException(L"Felaktigt datumformat 'X' (Använd ÅÅÅÅ-MM-DD).#" + firstDate);
|
|
}
|
|
|
|
if (!lastDate.empty()) {
|
|
lastD = convertDateYMS(lastDate, true);
|
|
if (lastD <= 0)
|
|
throw meosException(L"Felaktigt datumformat 'X' (Använd ÅÅÅÅ-MM-DD).#" + lastDate);
|
|
}
|
|
|
|
|
|
bool allClass = classType == L"*";
|
|
for (it=Runners.begin(); it != Runners.end(); ++it) {
|
|
if (it->skip())
|
|
continue;
|
|
|
|
const pClass pc = it->Class;
|
|
if (cid > 0 && (pc == 0 || pc->getId() != cid))
|
|
continue;
|
|
|
|
if (cid == 0 && !allClass) {
|
|
if ((pc && pc->getType()!=classType) || (pc==0 && !classType.empty()))
|
|
continue;
|
|
}
|
|
|
|
int age = it->getBirthAge();
|
|
if (age > 0 && (lowAge > 0 || highAge > 0)) {
|
|
if (lowAge > highAge)
|
|
throw meosException("Undre åldersgränsen är högre än den övre.");
|
|
|
|
if (age < lowAge || age > highAge)
|
|
continue;
|
|
/*
|
|
bool ageOK = false;
|
|
if (lowAge > 0 && age <= lowAge)
|
|
ageOK = true;
|
|
else if (highAge > 0 && age >= highAge)
|
|
ageOK = true;
|
|
|
|
if (!ageOK)
|
|
continue;*/
|
|
}
|
|
|
|
int date = it->getDCI().getInt("EntryDate");
|
|
if (date > 0) {
|
|
if (firstD > 0 && date < firstD)
|
|
continue;
|
|
if (lastD > 0 && date > lastD)
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!includeWithFee) {
|
|
int fee = it->getDCI().getInt("Fee");
|
|
if (fee != 0)
|
|
continue;
|
|
}
|
|
// string date = di.getDate("EntryDate");
|
|
|
|
output.push_back(pRunner(&*it));
|
|
}
|
|
}
|
|
|
|
void oRunner::changeId(int newId) {
|
|
pRunner old = oe->runnerById[Id];
|
|
if (old == this)
|
|
oe->runnerById.remove(Id);
|
|
|
|
oBase::changeId(newId);
|
|
|
|
oe->runnerById[newId] = this;
|
|
}
|
|
|
|
const vector<SplitData> &oRunner::getSplitTimes(bool normalized) const {
|
|
if (!normalized)
|
|
return splitTimes;
|
|
else {
|
|
pCourse pc = getCourse(true);
|
|
if (pc && pc->isAdapted() && splitTimes.size() == pc->nControls
|
|
&& getCourse(false)->nControls == pc->nControls) {
|
|
if (!normalizedSplitTimes.empty())
|
|
return normalizedSplitTimes;
|
|
const vector<int> &mapToOriginal = pc->getMapToOriginalOrder();
|
|
normalizedSplitTimes.resize(splitTimes.size()); // nControls
|
|
vector<int> orderedSplits(splitTimes.size() + 1, -1);
|
|
|
|
for (int k = 0; k < pc->nControls; k++) {
|
|
if (splitTimes[k].hasTime()) {
|
|
int t = -1;
|
|
int j = k - 1;
|
|
while (j >= -1 && t == -1) {
|
|
if (j == -1)
|
|
t = getStartTime();
|
|
else if (splitTimes[j].hasTime())
|
|
t = splitTimes[j].time;
|
|
j--;
|
|
}
|
|
orderedSplits[mapToOriginal[k]] = splitTimes[k].time - t;
|
|
}
|
|
}
|
|
|
|
// Last to finish
|
|
{
|
|
int t = -1;
|
|
int j = pc->nControls - 1;
|
|
while (j >= -1 && t == -1) {
|
|
if (j == -1)
|
|
t = getStartTime();
|
|
else if (splitTimes[j].hasTime())
|
|
t = splitTimes[j].time;
|
|
j--;
|
|
}
|
|
orderedSplits[mapToOriginal[pc->nControls]] = FinishTime - t;
|
|
}
|
|
|
|
int accumulatedTime = getStartTime();
|
|
for (int k = 0; k < pc->nControls; k++) {
|
|
if (orderedSplits[k] > 0) {
|
|
accumulatedTime += orderedSplits[k];
|
|
normalizedSplitTimes[k].setPunchTime(accumulatedTime);
|
|
}
|
|
else
|
|
normalizedSplitTimes[k].setNotPunched();
|
|
}
|
|
|
|
return normalizedSplitTimes;
|
|
}
|
|
return splitTimes;
|
|
}
|
|
}
|
|
|
|
void oRunner::markClassChanged(int controlId) {
|
|
assert(controlId < 4096);
|
|
if (Class) {
|
|
Class->markSQLChanged(tLeg, controlId);
|
|
pClass cls2 = getClassRef(true);
|
|
if (cls2 != Class)
|
|
cls2->markSQLChanged(-1, controlId);
|
|
|
|
if (tInTeam && tInTeam->Class != Class && tInTeam->Class) {
|
|
tInTeam->Class->markSQLChanged(tLeg, controlId);
|
|
}
|
|
}
|
|
else if (oe)
|
|
oe->globalModification = true;
|
|
}
|
|
|
|
void oAbstractRunner::changedObject() {
|
|
markClassChanged(-1);
|
|
}
|
|
|
|
int oAbstractRunner::getTimeAdjustment() const {
|
|
if (oe->dataRevision != tAdjustDataRevision) {
|
|
oDataConstInterface dci = getDCI();
|
|
tTimeAdjustment = dci.getInt("TimeAdjust");
|
|
tPointAdjustment = dci.getInt("PointAdjust");
|
|
tAdjustDataRevision = oe->dataRevision;
|
|
}
|
|
return tTimeAdjustment;
|
|
}
|
|
|
|
int oAbstractRunner::getPointAdjustment() const {
|
|
if (oe->dataRevision != tAdjustDataRevision) {
|
|
getTimeAdjustment(); //Setup cache
|
|
}
|
|
return tPointAdjustment;
|
|
}
|
|
|
|
void oAbstractRunner::setTimeAdjustment(int adjust) {
|
|
tTimeAdjustment = adjust;
|
|
getDI().setInt("TimeAdjust", adjust);
|
|
}
|
|
|
|
void oAbstractRunner::setPointAdjustment(int adjust) {
|
|
tPointAdjustment = adjust;
|
|
getDI().setInt("PointAdjust", adjust);
|
|
}
|
|
|
|
int oRunner::getRogainingPoints(bool multidayTotal) const {
|
|
if (multidayTotal)
|
|
return inputPoints + tRogainingPoints;
|
|
else
|
|
return tRogainingPoints;
|
|
}
|
|
|
|
int oRunner::getRogainingReduction() const {
|
|
return tReduction;
|
|
}
|
|
|
|
int oRunner::getRogainingPointsGross() const {
|
|
return tRogainingPointsGross;
|
|
}
|
|
|
|
int oRunner::getRogainingOvertime() const {
|
|
return tRogainingOvertime;
|
|
}
|
|
|
|
void oAbstractRunner::TempResult::reset() {
|
|
runningTime = 0;
|
|
timeAfter = 0;
|
|
points = 0;
|
|
place = 0;
|
|
startTime = 0;
|
|
status = StatusUnknown;
|
|
internalScore = 0;
|
|
}
|
|
|
|
oAbstractRunner::TempResult::TempResult() {
|
|
reset();
|
|
}
|
|
|
|
oAbstractRunner::TempResult::TempResult(RunnerStatus statusIn,
|
|
int startTimeIn,
|
|
int runningTimeIn,
|
|
int pointsIn) :status(statusIn),
|
|
startTime(startTimeIn), runningTime(runningTimeIn),
|
|
timeAfter(0), points(0), place(0) {
|
|
}
|
|
|
|
const oAbstractRunner::TempResult &oAbstractRunner::getTempResult(int tempResultIndex) const {
|
|
return tmpResult; //Ignore index for now...
|
|
/*if (tempResultIndex == 0)
|
|
return tmpResult;
|
|
else
|
|
throw meosException("Not implemented");*/
|
|
}
|
|
|
|
oAbstractRunner::TempResult &oAbstractRunner::getTempResult() {
|
|
return tmpResult;
|
|
}
|
|
|
|
|
|
void oAbstractRunner::setTempResultZero(const TempResult &tr) {
|
|
tmpResult = tr;
|
|
}
|
|
|
|
const wstring &oAbstractRunner::TempResult::getStatusS(RunnerStatus inputStatus) const {
|
|
if (inputStatus == StatusOK)
|
|
return oEvent::formatStatus(getStatus(), true);
|
|
else if (inputStatus == StatusUnknown)
|
|
return formatTime(-1);
|
|
else
|
|
return oEvent::formatStatus(max(inputStatus, getStatus()), true);
|
|
}
|
|
|
|
const wstring &oAbstractRunner::TempResult::getPrintPlaceS(bool withDot) const {
|
|
int p=getPlace();
|
|
if (p>0 && p<10000){
|
|
if (withDot) {
|
|
wstring &res = StringCache::getInstance().wget();
|
|
res = itow(p);
|
|
res += L".";
|
|
return res;
|
|
}
|
|
else
|
|
return itow(p);
|
|
}
|
|
return _EmptyWString;
|
|
}
|
|
|
|
const wstring &oAbstractRunner::TempResult::getRunningTimeS(int inputTime) const {
|
|
return formatTime(getRunningTime() + inputTime);
|
|
}
|
|
|
|
const wstring &oAbstractRunner::TempResult::getFinishTimeS(const oEvent *oe) const {
|
|
return oe->getAbsTime(getFinishTime());
|
|
}
|
|
|
|
const wstring &oAbstractRunner::TempResult::getStartTimeS(const oEvent *oe) const {
|
|
int st = getStartTime();
|
|
if (st > 0)
|
|
return oe->getAbsTime(st);
|
|
else return makeDash(L"-");
|
|
}
|
|
|
|
const wstring &oAbstractRunner::TempResult::getOutputTime(int ix) const {
|
|
int t = size_t(ix) < outputTimes.size() ? outputTimes[ix] : 0;
|
|
return formatTime(t);
|
|
}
|
|
|
|
int oAbstractRunner::TempResult::getOutputNumber(int ix) const {
|
|
return size_t(ix) < outputNumbers.size() ? outputNumbers[ix] : 0;
|
|
}
|
|
|
|
void oAbstractRunner::resetInputData() {
|
|
setInputPlace(0);
|
|
if (0 != inputTime) {
|
|
inputTime = 0;
|
|
updateChanged();
|
|
}
|
|
setInputStatus(StatusNotCompetiting);
|
|
setInputPoints(0);
|
|
}
|
|
|
|
bool oRunner::isTransferCardNoNextStage() const {
|
|
return hasFlag(FlagUpdateCard);
|
|
}
|
|
|
|
void oRunner::setTransferCardNoNextStage(bool state) {
|
|
setFlag(FlagUpdateCard, state);
|
|
}
|
|
|
|
bool oAbstractRunner::hasFlag(TransferFlags flag) const {
|
|
return (getDCI().getInt("TransferFlags") & flag) != 0;
|
|
}
|
|
|
|
void oAbstractRunner::setFlag(TransferFlags flag, bool onoff) {
|
|
int cf = getDCI().getInt("TransferFlags");
|
|
cf = onoff ? (cf | flag) : (cf & (~flag));
|
|
getDI().setInt("TransferFlags", cf);
|
|
}
|
|
|
|
int oRunner::getNumShortening() const {
|
|
if (oe->dataRevision != tShortenDataRevision) {
|
|
oDataConstInterface dci = getDCI();
|
|
tNumShortening = dci.getInt("Shorten");
|
|
tShortenDataRevision = oe->dataRevision;
|
|
}
|
|
return tNumShortening;
|
|
}
|
|
|
|
void oRunner::setNumShortening(int numShorten) {
|
|
tNumShortening = numShorten;
|
|
tShortenDataRevision = oe->dataRevision;
|
|
oDataInterface di = getDI();
|
|
di.setInt("Shorten", numShorten);
|
|
}
|
|
|
|
int oAbstractRunner::getEntrySource() const {
|
|
return getDCI().getInt("EntrySource");
|
|
}
|
|
|
|
void oAbstractRunner::setEntrySource(int src) {
|
|
getDI().setInt("EntrySource", src);
|
|
}
|
|
|
|
void oAbstractRunner::flagEntryTouched(bool flag) {
|
|
tEntryTouched = flag;
|
|
}
|
|
|
|
bool oAbstractRunner::isEntryTouched() const {
|
|
return tEntryTouched;
|
|
}
|
|
|
|
int oRunner::getTotalTimeInput() const {
|
|
if (tInTeam) {
|
|
if (getLegNumber()>0) {
|
|
return tInTeam->getLegRunningTime(getLegNumber()-1, true);
|
|
}
|
|
else {
|
|
return tInTeam->getInputTime();
|
|
}
|
|
}
|
|
else {
|
|
return getInputTime();
|
|
}
|
|
}
|
|
|
|
|
|
RunnerStatus oRunner::getTotalStatusInput() const {
|
|
RunnerStatus inStatus = StatusOK;
|
|
if (tInTeam) {
|
|
const pTeam t = tInTeam;
|
|
if (getLegNumber()>0) {
|
|
inStatus = t->getLegStatus(getLegNumber()-1, true);
|
|
}
|
|
else {
|
|
inStatus = t->getInputStatus();
|
|
}
|
|
}
|
|
else {
|
|
inStatus = getInputStatus();
|
|
}
|
|
return inStatus;
|
|
}
|
|
|
|
bool oAbstractRunner::startTimeAvailable() const {
|
|
if (getFinishTime() > 0)
|
|
return true;
|
|
|
|
return getStartTime() > 0;
|
|
}
|
|
|
|
bool oRunner::startTimeAvailable() const {
|
|
if (getFinishTime() > 0)
|
|
return true;
|
|
|
|
int st = getStartTime();
|
|
bool definedTime = st > 0;
|
|
|
|
if (!definedTime)
|
|
return false;
|
|
|
|
if (!Class || !tInTeam || tLeg == 0)
|
|
return definedTime;
|
|
|
|
// Check if time is restart time
|
|
int restart = Class->getRestartTime(tLeg);
|
|
if (st == restart && Class->getStartType(tLeg) == STChange) {
|
|
int currentTime = oe->getComputerTime();
|
|
int rope = Class->getRopeTime(tLeg);
|
|
return rope != 0 && currentTime + 600 > rope;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int oRunner::getRanking() const {
|
|
int rank = getDCI().getInt("Rank");
|
|
if (rank == 0 && tParentRunner)
|
|
rank = tParentRunner->getRanking();
|
|
if (rank <= 0)
|
|
return MaxRankingConstant;
|
|
else
|
|
return rank;
|
|
}
|
|
|
|
void oAbstractRunner::hasManuallyUpdatedTimeStatus() {
|
|
if (Class && Class->hasClassGlobalDependence()) {
|
|
set<int> cls;
|
|
oe->reEvaluateAll(cls, false);
|
|
}
|
|
if (Class) {
|
|
Class->updateFinalClasses(dynamic_cast<oRunner *>(this), false);
|
|
}
|
|
}
|
|
|
|
bool oRunner::canShareCard(const pRunner other, int newCardNo) const {
|
|
if (!other || other->getCardNo() != newCardNo || newCardNo == 0)
|
|
return true;
|
|
|
|
if (getCard() && getCard()->getCardNo() == newCardNo)
|
|
return true;
|
|
|
|
|
|
if (other->getStatus() == StatusDNF || other->getStatus() == StatusCANCEL
|
|
|| other->getStatus() == StatusNotCompetiting || other->getStatus() == StatusDNS)
|
|
return true;
|
|
|
|
if (other->skip() || other->getCard() || other == this ||
|
|
other->getMultiRunner(0) == getMultiRunner(0))
|
|
return true;
|
|
|
|
if (!getTeam() || other->getTeam() != getTeam())
|
|
return false;
|
|
|
|
const oClass * tCls = getTeam()->getClassRef(false);
|
|
if (!tCls || tCls != Class)
|
|
return false;
|
|
|
|
LegTypes lt1 = tCls->getLegType(tLeg);
|
|
LegTypes lt2 = tCls->getLegType(other->tLeg);
|
|
|
|
if (lt1 == LTGroup || lt2 == LTGroup)
|
|
return false;
|
|
|
|
int ln1, ln2, ord;
|
|
tCls->splitLegNumberParallel(tLeg, ln1, ord);
|
|
tCls->splitLegNumberParallel(other->tLeg, ln2, ord);
|
|
return ln1 != ln2;
|
|
}
|
|
|
|
int oAbstractRunner::getPaymentMode() const {
|
|
return getDCI().getInt("PayMode");
|
|
}
|
|
|
|
void oAbstractRunner::setPaymentMode(int mode) {
|
|
getDI().setInt("PayMode", mode);
|
|
}
|
|
|
|
bool oAbstractRunner::hasLateEntryFee() const {
|
|
if (!Class)
|
|
return false;
|
|
int highFee = Class->getDCI().getInt("HighClassFee");
|
|
int normalFee = Class->getDCI().getInt("ClassFee");
|
|
|
|
int fee = getDCI().getInt("Fee");
|
|
if (fee == normalFee || fee == 0)
|
|
return false;
|
|
else if (fee == highFee && highFee > normalFee && normalFee > 0)
|
|
return true;
|
|
|
|
wstring date = getEntryDate(true);
|
|
oDataConstInterface odc = oe->getDCI();
|
|
wstring oentry = odc.getDate("OrdinaryEntry");
|
|
bool late = date > oentry && oentry >= L"2010-01-01";
|
|
|
|
return late;
|
|
}
|
|
|
|
int oRunner::classInstance() const {
|
|
if (classInstanceRev.first == oe->dataRevision)
|
|
return classInstanceRev.second;
|
|
classInstanceRev.second = getDCI().getInt("Heat");
|
|
if (Class)
|
|
classInstanceRev.second = min(classInstanceRev.second, Class->getNumQualificationFinalClasses());
|
|
classInstanceRev.first = oe->dataRevision;
|
|
return classInstanceRev.second;
|
|
}
|
|
|
|
const wstring &oAbstractRunner::getClass(bool virtualClass) const {
|
|
if (Class) {
|
|
if (virtualClass)
|
|
return Class->getVirtualClass(classInstance())->Name;
|
|
else
|
|
return Class->Name;
|
|
}
|
|
|
|
else return _EmptyWString;
|
|
}
|
|
|
|
wstring oRunner::formatExtraLine(pRunner r, const wstring &input) {
|
|
wstring ws = input, wsOut;
|
|
size_t parS = ws.find_first_of('[');
|
|
while (parS != wstring::npos) {
|
|
size_t parE = ws.find_first_of(']');
|
|
if (parE != wstring::npos && parE > parS) {
|
|
wsOut += ws.substr(0, parS);
|
|
wstring cmd = ws.substr(parS + 1, parE - parS - 1);
|
|
ws = ws.substr(parE + 1);
|
|
parS = ws.find_first_of('[');
|
|
|
|
auto type = MetaList::getTypeFromSymbol(cmd);
|
|
if (r) {
|
|
if (type == EPostType::lNone)
|
|
wsOut += L"{Error: " + cmd + L"}";
|
|
else {
|
|
wsOut += r->getEvent()->formatListString(type, r);
|
|
}
|
|
}
|
|
else if (type == EPostType::lNone) {
|
|
throw meosException(L"Unknown type: " + cmd);
|
|
}
|
|
}
|
|
else {
|
|
if (r == nullptr)
|
|
throw meosException(L"Syntax error: " + input);
|
|
break; // Error
|
|
}
|
|
}
|
|
wsOut += ws;
|
|
|
|
return wsOut;
|
|
}
|
|
|
|
bool oAbstractRunner::preventRestart() const {
|
|
if (tPreventRestartCache.second == oe->dataRevision)
|
|
return tPreventRestartCache.first;
|
|
|
|
tPreventRestartCache.first = getDCI().getInt("NoRestart") != 0;
|
|
tPreventRestartCache.second = oe->dataRevision;
|
|
|
|
return tPreventRestartCache.first;
|
|
}
|
|
|
|
void oAbstractRunner::preventRestart(bool state) {
|
|
getDI().setInt("NoRestart", state);
|
|
tPreventRestartCache.first = state;
|
|
tPreventRestartCache.second = oe->dataRevision;
|
|
}
|
|
|
|
int oRunner::getCheckTime() const {
|
|
oPunch *p = nullptr;
|
|
if (Card) {
|
|
p = Card->getPunchByType(oPunch::PunchCheck);
|
|
}
|
|
else {
|
|
p = oe->getPunch(Id, oPunch::PunchCheck, getCardNo());
|
|
}
|
|
if (p && p->Time > 0)
|
|
return p->Time;
|
|
|
|
return 0;
|
|
}
|