/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2023 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 .
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
#include "localizer.h"
#include "SportIdent.h"
#include
#include "intkeymapimpl.hpp"
#include "runnerdb.h"
#include "meosexception.h"
#include
#include "socket.h"
#include "MeOSFeatures.h"
#include "oListInfo.h"
#include "qualification_final.h"
#include "metalist.h"
#include "generalresult.h"
char RunnerStatusOrderMap[100];
bool oAbstractRunner::DynamicValue::isOld(const oEvent &oe) const {
return oe.dataRevision != dataRevision;
}
oAbstractRunner::DynamicValue &oAbstractRunner::DynamicValue::update(const oEvent &oe, int v, bool preferStd) {
if (preferStd)
valueStd = v; // A temporary result for "default" when computing with result modules (internal calculation)
else {
value = v;
dataRevision = oe.dataRevision;
}
return *this;
}
int oAbstractRunner::DynamicValue::get(bool preferStd) const {
if (preferStd && valueStd >= 0)
return valueStd;
return value;
}
void oAbstractRunner::DynamicValue::reset() {
value = -1;
valueStd = -1;
dataRevision = -1;
}
const wstring &oAbstractRunner::encodeStatus(RunnerStatus st, bool allowError) {
wstring &res = StringCache::getInstance().wget();
switch (st) {
case StatusOK:
res = L"OK";
break;
case StatusUnknown:
res = L"UN";
break;
case StatusDNS:
res = L"NS";
break;
case StatusCANCEL:
res = L"CC";
break;
case StatusOutOfCompetition:
res = L"OC";
break;
case StatusNoTiming:
res = L"NT";
break;
case StatusMP:
res = L"MP";
break;
case StatusDNF:
res = L"NF";
break;
case StatusDQ:
res = L"DQ";
break;
case StatusMAX:
res = L"MX";
break;
case StatusNotCompetiting:
res = L"NC";
break;
default:
if (allowError)
res = L"ERROR";
else
throw std::exception("Unknown status");
}
return res;
}
RunnerStatus oAbstractRunner::decodeStatus(const wstring &stat) {
wstring ustat = stat;
for (wchar_t &t : ustat) {
t = toupper(t);
}
for (RunnerStatus st : getAllRunnerStatus())
if (encodeStatus(st) == stat)
return st;
return StatusUnknown;
}
const wstring &oRunner::RaceIdFormatter::formatData(const oBase *ob) const {
return itow(dynamic_cast(*ob).getRaceIdentifier());
}
pair oRunner::RaceIdFormatter::setData(oBase *ob, const wstring &input, wstring &output, int inputId) const {
int rid = _wtoi(input.c_str());
if (input == L"0")
ob->getDI().setInt("RaceId", 0);
else if (rid>0 && rid != dynamic_cast(ob)->getRaceIdentifier())
ob->getDI().setInt("RaceId", rid);
output = formatData(ob);
return make_pair(0, false);
}
int oRunner::RaceIdFormatter::addTableColumn(Table *table, const string &description, int minWidth) const {
return table->addColumn(description, max(minWidth, 90), true, true);
}
const wstring &oRunner::RunnerReference::formatData(const oBase *obj) const {
int id = obj->getDCI().getInt("Reference");
if (id > 0) {
pRunner r = obj->getEvent()->getRunner(id, 0);
if (r)
return r->getUIName();
else {
return lang.tl("Okänd");
}
}
return _EmptyWString;
}
pair oRunner::RunnerReference::setData(oBase *obj, const wstring &input, wstring &output, int inputId) const {
int oldRef = obj->getDCI().getInt("Reference");
obj->getDI().setInt("Reference", inputId);
bool clearAll = false;
if (inputId != oldRef) {
if (oldRef != 0) {
pRunner oldRefR = obj->getEvent()->getRunner(oldRef, 0);
if (oldRefR) {
oldRefR->setReference(0);
clearAll = true;
}
}
if (inputId != 0) {
pRunner newRefR = obj->getEvent()->getRunner(inputId, 0);
if (newRefR)
newRefR->setReference(obj->getId());
}
}
output = formatData(obj);
return make_pair(inputId, clearAll);
}
void oRunner::RunnerReference::fillInput(const oBase *obj, vector> &out, size_t &selected) const {
const oRunner *r = static_cast(obj);
int cls = r->getClassId(true);
vector runners;
r->oe->getRunners(cls, 0, runners, true);
int id = obj->getDCI().getInt("Reference");
selected = id;
out.reserve(runners.size() + 2);
out.emplace_back(lang.tl("Ingen"), 0);
for (auto rr : runners) {
if (rr->Id == id)
id = 0;
if (rr->Id == r->Id)
continue; // No self reference
out.emplace_back(rr->getUIName(), rr->Id);
}
if (id != 0) {
pRunner rr = obj->getEvent()->getRunner(id, 0);
if (rr)
out.emplace_back(rr->getUIName(), id);
else
out.emplace_back(lang.tl("Okänd"), id);
}
}
int oRunner::RunnerReference::addTableColumn(Table *table, const string &description, int minWidth) const {
return table->addColumn(description, max(minWidth, 200), true, true);
}
CellType oRunner::RunnerReference::getCellType() const {
return CellType::cellSelection;
}
//////////////////////////////////////////////////////////////////////
// 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, ChangeType::Update);
}
oRunner::oRunner(oEvent *poe) :oAbstractRunner(poe, false)
{
isTemporaryObject = false;
Id = oe->getFreeRunnerId();
Course = nullptr;
StartNo = 0;
cardNumber = 0;
tInTeam = nullptr;
tLeg = 0;
tLegEquClass = 0;
tNeedNoCard = false;
tUseStartPunch = true;
getDI().initData();
correctionNeeded = false;
tDuplicateLeg = 0;
tParentRunner = nullptr;
Card = nullptr;
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 = nullptr;
StartNo = 0;
cardNumber = 0;
tInTeam = nullptr;
tLeg = 0;
tLegEquClass = 0;
tNeedNoCard = false;
tUseStartPunch = true;
getDI().initData();
correctionNeeded = false;
tDuplicateLeg = 0;
tParentRunner = nullptr;
Card = nullptr;
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;iRunners.size(); i++)
if (tInTeam->Runners[i] && tInTeam->Runners[i]->getId() == Id)
tInTeam->Runners[i] = nullptr;
tInTeam=0;
}
for (size_t k=0;ktParentRunner == this)
multiRunner[k]->tParentRunner = nullptr;
}
if (tParentRunner) {
for (size_t k=0;kmultiRunner.size(); k++)
if (tParentRunner->multiRunner[k] == this)
tParentRunner->multiRunner[k] = nullptr;
}
delete tAdaptedCourse;
tAdaptedCourse = nullptr;
}
bool oRunner::Write(xmlparser &xml)
{
if (Removed) return true;
xml.startTag("Runner");
xml.write("Id", Id);
xml.write("Updated", getStamp());
xml.write("Name", sName);
xml.writeTime("Start", startTime);
xml.writeTime("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.writeTime("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;kWrite(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->getWStr();
getRealName(sName, tRealName);
}
else if (it->is("Start")) {
tStartTime = startTime = it->getRelativeTime();
}
else if (it->is("Finish")) {
FinishTime = it->getRelativeTime();
}
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->getRawStr());
else if (it->is("MultiR"))
decodeMultiR(it->getRawStr());
else if (it->is("InputTime")) {
inputTime = it->getRelativeTime();
}
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(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;kgetId());
r+=bf;
}
return r;
}
void oRunner::decodeMultiR(const string &r)
{
vector sv;
split(r, ":", sv);
multiRunnerId.clear();
for (size_t k=0;k0)
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) : nullptr;
if (Class!=pc) {
apply(ChangeType::Update, 0);
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;
oe->classIdToRunnerHash.reset();
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(ChangeType::Quiet, nullptr);
}
}
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; ktParentRunner == this);
multiRunner[k]->tParentRunner = 0;
vector 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, ChangeType::Update);
t->setRunner(0, this, true);
}
}
apply(ChangeType::Quiet, nullptr); //We may get old class back from team.
for (size_t k=0;kClass) {
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, ChangeType changeType, bool recalculate) {
int tOST=tStartTime;
if (t>0)
tStartTime=t;
else tStartTime=0;
if (updateSource) {
int OST=startTime;
startTime = tStartTime;
if (OST!=startTime) {
updateChanged(changeType);
}
}
if (tOST != tStartTime) {
changedObject();
if (Class) {
Class->clearCache(false);
}
}
if (tOSTreCalculateLeaderTimes(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, false)==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(bool adjusted, SubSecond mode) const
{
if (FinishTime > 0) {
if (adjusted)
return oe->getAbsTime(FinishTime, mode);
else
return oe->getAbsTime(FinishTime - getBuiltinAdjustment(), mode);
}
else return makeDash(L"-");
}
int oAbstractRunner::getRunningTime(bool computedTime) const {
if (!computedTime || tComputedTime == 0) {
int rt = FinishTime - tStartTime;
if (rt > 0)
return getTimeAdjustment(false) + rt;
else
return 0;
}
else
return tComputedTime;
}
const wstring &oAbstractRunner::getRunningTimeS(bool computedTime, SubSecond mode) const
{
return formatTime(getRunningTime(computedTime), mode);
}
const wstring &oAbstractRunner::getTotalRunningTimeS(SubSecond mode) const
{
return formatTime(getTotalRunningTime(), mode);
}
int oAbstractRunner::getTotalRunningTime() const {
int t = getRunningTime(true);
if (t > 0 && inputTime>=0)
return t + inputTime;
else
return 0;
}
int oRunner::getTotalRunningTime() const {
return getTotalRunningTime(getFinishTime(), true, true);
}
const wstring &oAbstractRunner::getStatusS(bool formatForPrint, bool computedStatus) const
{
if (computedStatus)
return oEvent::formatStatus(getStatusComputed(true), formatForPrint);
else
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, bool hasTime) {
switch(s) {
case StatusNoTiming:
if (!hasTime)
break;
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";
case StatusOutOfCompetition:
if (!hasTime)
break;
case StatusNotCompetiting:
return L"NotCompeting";
}
return L"Inactive";
}
wstring oAbstractRunner::getIOFStatusS() const
{
return formatIOFStatus(getStatusComputed(true), getFinishTime()> 0);
}
wstring oAbstractRunner::getIOFTotalStatusS() const
{
return formatIOFStatus(getTotalStatus(), getFinishTime()> 0);
}
void oRunner::addPunches(pCard card, vector &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, ChangeType::Update);
synchronizeAll(true);
if (Card != card) {
Card = card;
updateChanged();
evaluateCard(true, missingPunches, 0, ChangeType::Update);
synchronizeAll(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 > 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 &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 &missingPunches,
int addpunch, ChangeType changeType) {
if (unsigned(status) >= 100u)
status = StatusUnknown; //Reset bad input
pClass clz = getClassRef(true);
missingPunches.clear();
const 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;
apply(changeType, nullptr);
}
else {
// tmp initialized from outside. Do not change tStatus, tStartTime. Work with tmpStore instead!
oldStartTime = tStartTime;
oldStatus = tStatus;
refStartTime = &tStartTime;
refStatus = &tStatus;
createMultiRunner(false, changeType == ChangeType::Update);
}
// 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 oldTimes;
swap(splitTimes, oldTimes);
if (!Card) {
if ((inTeam || !tUseStartPunch) && doApply)
apply(changeType, nullptr); //Post apply. Update start times.
if (storeTimes() && clz && changeType == ChangeType::Update) {
oe->reEvaluateAll({ clz->getId() }, true);
}
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->getTimeInt();
else if (p_it->isFinish()) {
setFinishTime(p_it->getTimeInt());
}
}
if ((inTeam || !tUseStartPunch) && doApply)
apply(changeType, nullptr); //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(false) > 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:
intkeymap> rogaining(course->getNumControls());
unordered_set requiredRG;
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; jControls[k]->nNumbers; j++) {
rogaining.insert(course->Controls[k]->Numbers[j], make_pair(k, pt));
}
if (course->Controls[k]->getStatus() == oControl::ControlStatus::StatusRogainingRequired)
requiredRG.insert(k);
}
}
if (addpunch && Card->punches.empty()) {
Card->addPunch(addpunch, -1, course->Controls[0] ? course->Controls[0]->getId():0, 0);
}
if (Card->punches.empty()) {
for(int k=0;knControls;k++) {
if (course->Controls[k]) {
course->Controls[k]->startCheckControl();
course->Controls[k]->addUncheckedPunches(missingPunches, hasRogaining);
}
}
if ((inTeam || !tUseStartPunch) && doApply)
apply(changeType, nullptr); //Post apply. Update start times.
if (storeTimes() && clz && changeType == ChangeType::Update) {
oe->reEvaluateAll({ clz->getId() }, true);
}
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; knControls; k++) {
pControl ctrl=course->Controls[k];
if (ctrl && !ctrl->isRogaining(hasRogaining)) {
for (int j = 0; jnNumbers; 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::SplitStatus::Missing));
int k=0;
for (k=0;knControls;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.punchTime=-1;
op.isUsed=true;
op.tIndex = k;
op.tMatchControlId=ctrl->getId();
Card->punches.insert(tp_it, op);
Card->updateChanged();
}
}
if (ctrl->getStatus() == oControl::ControlStatus::StatusBad ||
ctrl->getStatus() == oControl::ControlStatus::StatusOptional ||
ctrl->getStatus() == oControl::ControlStatus::StatusBadNoTiming) {
// 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::ControlStatus::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.punchTime=-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(), 0);
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].getTime(false) == 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 (knControls) {
if (course->Controls[k]) {
pControl ctrl = course->Controls[k];
ctrl->startCheckControl();
if (ctrl->hasNumberUnchecked(addpunch)) {
Card->addPunch(addpunch, -1, ctrl->getId(), 0);
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) {
unordered_set visitedControls;
for (p_it=Card->punches.begin(); p_it != Card->punches.end(); ++p_it) {
pair pt;
if (rogaining.lookup(p_it->type, pt)) {
p_it->anyRogainingMatchControlId = course->Controls[pt.first]->getId();
if (visitedControls.insert(pt.first).second) {
requiredRG.erase(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;
}
}
}
for (int mp : requiredRG) {
missingPunches.push_back(course->Controls[mp]->getFirstNumber());
}
OK = missingPunches.empty();
// Manual point adjustment
tRogainingPoints = max(0, tRogainingPoints + getPointAdjustment());
int point_limit = course->getMinimumRogainingPoints();
if (point_limit>0 && tRogainingPointsgetMaximumRogainingTime();
for (int k = 0; knControls; 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(false) > mt)
maxTimeStatus = 1;
else
maxTimeStatus = 2;
}
else
maxTimeStatus = 2;
}
if ( (*refStatus == StatusMAX && maxTimeStatus == 2) ||
*refStatus == StatusOutOfCompetition ||
*refStatus == StatusNoTiming)
*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->getTimeInt();
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".";
}
if (*refStatus == StatusOK) {
if (hasFlag(TransferFlags::FlagOutsideCompetition))
*refStatus = StatusOutOfCompetition;
else if (hasFlag(TransferFlags::FlagNoTiming))
*refStatus = StatusNoTiming;
else if (clz && clz->getNoTiming())
*refStatus = StatusNoTiming;
}
// Adjust times on course, including finish time
doAdjustTimes(course);
tRogainingPointsGross = tRogainingPoints;
if (oldStatus!=*refStatus || oldFT!=FinishTime) {
clearSplitAnalysis = true;
}
if (oldFT != FinishTime)
updateChanged(changeType);
if ((inTeam || !tUseStartPunch) && doApply)
apply(changeType, nullptr); //Post apply. Update start times.
if (tCachedRunningTime != FinishTime - *refStartTime) {
tCachedRunningTime = FinishTime - *refStartTime;
clearSplitAnalysis = true;
}
if (time_limit > 0) {
int rt = getRunningTime(false);
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 && kclearSplitAnalysis();
}
if (doApply)
storeTimes();
if (clz && changeType == ChangeType::Update) {
bool update = false;
if (tInTeam) {
int t1 = clz->getTotalLegLeaderTime(oClass::AllowRecompute::No, tLeg, false, false);
int t2 = tInTeam->getLegRunningTime(tLeg, false, false);
if (t2<=t1 && t2>0)
update = true;
int t3 = clz->getTotalLegLeaderTime(oClass::AllowRecompute::No, tLeg, false, true);
int t4 = tInTeam->getLegRunningTime(tLeg, false, true);
if (t4<=t3 && t4>0)
update = true;
}
if (!update) {
int t1 = clz->getBestLegTime(oClass::AllowRecompute::No, tLeg, false);
int t2 = getRunningTime(false);
if (t2<=t1 && t2>0)
update = true;
}
if (update) {
oe->reEvaluateAll({ clz->getId() }, true);
}
}
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;
pControl ctrlPrev = n > 0 ? course->Controls[n - 1] : nullptr;
while (it != Card->punches.end() && !it->isUsed) {
it->setTimeAdjust(adjustment);
++it;
}
int minTime = ctrl->getMinTime();
int pN = n -1;
while (pN >= 0 && (course->Controls[pN]->getStatus() == oControl::ControlStatus::StatusBad ||
course->Controls[pN]->getStatus() == oControl::ControlStatus::StatusBadNoTiming)) {
pN--; // Skip bad controls
}
if (ctrl->getStatus() == oControl::ControlStatus::StatusNoTiming || (ctrlPrev && ctrlPrev->getStatus() == oControl::ControlStatus::StatusBadNoTiming)) {
int t = 0;
if (n>0 && pN>=0 && splitTimes[n].time>0 && splitTimes[pN].time>0) {
t = splitTimes[n].time + adjustment - splitTimes[pN].time;
}
else if (pN < 0 && splitTimes[n].time>0) {
t = splitTimes[n].time - tStartTime;
}
adjustment -= t;
}
else if (minTime > 0) {
int t = 0;
if (n > 0 && pN >= 0 && splitTimes[n].time > 0 && splitTimes[pN].time > 0) {
t = splitTimes[n].time + adjustment - splitTimes[pN].time;
}
else if (pN < 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;
splitTimes[n].setAdjustment(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)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(lastLeglegInfo[lastLeg].isOptional())
lastLeg++;
for (int leg = firstLeg; legtLeaderTime[leg].bestTimeOnLeg;
int rt=getRunningTime(false);
if (targetClass->tLeaderTime[leg].update(rt, oClass::LeaderInfo::Type::Leg))
updated = true;
/*if (rt > 0 && (bt == 0 || rt < bt)) {
bt=rt;
updated = true;
}*/
}
if (getStatusComputed(false) == StatusOK) {
int rt = getRunningTime(true);
if (targetClass->tLeaderTime[leg].updateComputed(rt, oClass::LeaderInfo::Type::Leg))
updated = true;
}
}
bool updateTotal = true;
bool updateTotalInput = true;
bool updateTotalC = true;
bool updateTotalInputC = 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, false)==StatusOK;
updateTotalInput = updateTotalInput && tInTeam->getLegStatus(ix, false, true)==StatusOK;
updateTotalC = updateTotalC && tInTeam->getLegStatus(ix, true, false) == StatusOK;
updateTotalInputC = updateTotalInputC && tInTeam->getLegStatus(ix, true, 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, false));
ix++;
}
for (int leg = firstLeg; legtLeaderTime[leg].totalLeaderTime;
if (rt > 0 && (bt == 0 || rt < bt)) {
bt=rt;
updated = true;
}*/
if (targetClass->tLeaderTime[leg].update(rt, oClass::LeaderInfo::Type::Total))
updated = true;
}
}
if (updateTotalC) {
int rt = 0;
int ix = basePLeg;
while (ix < nleg && (ix == basePLeg || targetClass->legInfo[ix].isParallel())) {
rt = max(rt, tInTeam->getLegRunningTime(ix, true, false));
ix++;
}
for (int leg = firstLeg; legtLeaderTime[leg].updateComputed(rt, oClass::LeaderInfo::Type::Total))
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, false, true));
ix++;
}
for (int leg = firstLeg; legtLeaderTime[leg].totalLeaderTimeInput;
if (rt > 0 && (bt <= 0 || rt < bt)) {
bt=rt;
updated = true;
}*/
if (targetClass->tLeaderTime[leg].update(rt, oClass::LeaderInfo::Type::TotalInput))
updated = true;
}
}
if (updateTotalInputC) {
int rt = 0;
int ix = basePLeg;
while (ix < nleg && (ix == basePLeg || targetClass->legInfo[ix].isParallel())) {
rt = max(rt, tInTeam->getLegRunningTime(ix, true, true));
ix++;
}
for (int leg = firstLeg; legtLeaderTime[leg].updateComputed(rt, oClass::LeaderInfo::Type::TotalInput))
updated = true;
}
}
}
}
else {
size_t dupLeg = targetClass->mapLeg(tDuplicateLeg);
if (targetClass && dupLeg < targetClass->tLeaderTime.size()) {
if (tStatus == StatusOK) {
int rt = getRunningTime(false);
if (targetClass->tLeaderTime[dupLeg].update(rt, oClass::LeaderInfo::Type::Leg))
updated = true;
}
if (getStatusComputed(false) == StatusOK) {
int rt = getRunningTime(true);
if (targetClass->tLeaderTime[dupLeg].updateComputed(rt, oClass::LeaderInfo::Type::Leg))
updated = true;
}
int rt = getRaceRunningTime(false, dupLeg, false);
if (targetClass->tLeaderTime[dupLeg].update(rt, oClass::LeaderInfo::Type::Total))
updated = true;
rt = getRaceRunningTime(true, dupLeg, false);
if (targetClass->tLeaderTime[dupLeg].updateComputed(rt, oClass::LeaderInfo::Type::Total))
updated = true;
if (getTotalStatus(false) == StatusOK) {
rt = getTotalRunningTime(getFinishTime(), false, true);
if (targetClass->tLeaderTime[dupLeg].update(rt, oClass::LeaderInfo::Type::TotalInput))
updated = true;
rt = getTotalRunningTime(getFinishTime(), true, true);
if (targetClass->tLeaderTime[dupLeg].updateComputed(rt, oClass::LeaderInfo::Type::TotalInput))
updated = true;
}
/*int &bt = targetClass->tLeaderTime[dupLeg].totalLeaderTime;
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 (mappedLegtLeaderTime.size()) {
if (inputStatus == StatusOK) {
if (targetClass->tLeaderTime[mappedLeg].update(inputTime, oClass::LeaderInfo::Type::Input)) {
updated = true;
}
}
}
if (targetClass && tStatus==StatusOK) {
int rt = getRunningTime(true);
pCourse pCrs = getCourse(false);
if (pCrs && rt > 0) {
map::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(bool computedTime, int leg, bool allowUpdate) const {
if (tParentRunner)
return tParentRunner->getRaceRunningTime(computedTime, leg, allowUpdate);
if (leg == -1)
leg = multiRunner.size() - 1;
if (leg == 0) { /// XXX This code is buggy
if (getTotalStatus(allowUpdate) == StatusOK)
return getRunningTime(computedTime) + 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(computedTime, allowUpdate)) {
int dt=leg>0 ? r->getRaceRunningTime(computedTime, leg, allowUpdate)+r->getRunningTime(computedTime):0;
return max(r->getFinishTime()-tStartTime, dt); // ### Luckor, jaktstart???
}
else return 0;
break;
case LTSum:
if (r->statusOK(computedTime, allowUpdate))
return r->getRunningTime(computedTime)+getRaceRunningTime(computedTime, leg, allowUpdate);
else return 0;
default:
return 0;
}
}
else
return getRunningTime(computedTime);
}
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 acidCurrentSortOrder == ClubClassStartTime) {
pClub cl = getClubRef();
pClub ocl = c.getClubRef();
if (cl != ocl) {
int cres = compareClubs(cl, ocl);
if (cres != 2)
return cres != 0;
}
}
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::ClassStatus::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 || oe->CurrentSortOrder == ClubClassStartTime) {
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);
}
}
}
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 == ClassDefaultResult) {
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(false);
if (t <= 0)
t = timeConstHour * 1000;
int ct = c.getRunningTime(false);
if (ct <= 0)
ct = timeConstHour * 1000;
if (t != ct)
return t < ct;
}
}
}
else if (oe->CurrentSortOrder == ClassResult) {
RunnerStatus stat = getStatusComputed(false);
RunnerStatus cstat = c.getStatusComputed(false);
stat = stat == StatusUnknown ? StatusOK : stat;
cstat = cstat == StatusUnknown ? StatusOK : cstat;
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(true);
if (t <= 0)
t = timeConstHour * 1000;
int ct = c.getRunningTime(true);
if (ct <= 0)
ct = timeConstHour * 1000;
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);
RunnerStatus stat = getStatusComputed(false);
RunnerStatus cstat = c.getStatusComputed(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 (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(true);
int ct = c.getRunningTime(true);
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) {
RunnerStatus stat = getStatusComputed(false);
RunnerStatus cstat = c.getStatusComputed(false);
if (stat != cstat)
return RunnerStatusOrderMap[stat] < RunnerStatusOrderMap[cstat];
else {
int ft = getFinishTimeAdjusted(true);
int cft = c.getFinishTimeAdjusted(true);
if (stat == StatusOK && ft != cft)
return ft < cft;
}
}
else if (oe->CurrentSortOrder == SortByFinishTimeReverse) {
int ft = getFinishTimeAdjusted(true);
int cft = c.getFinishTimeAdjusted(true);
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);
RunnerStatus stat = getStatusComputed(false);
RunnerStatus cstat = c.getStatusComputed(false);
if (stat != cstat)
return RunnerStatusOrderMap[stat] < RunnerStatusOrderMap[cstat];
else {
int ft = getFinishTimeAdjusted(true);
int cft = c.getFinishTimeAdjusted(true);
if (stat == 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) {
int myP = getRogainingPoints(true, false);
int otherP = c.getRogainingPoints(true, false);
if (myP != otherP)
return myP > otherP;
int t = getRunningTime(true);
int ct = c.getRunningTime(true);
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, true);
int ct = c.getTotalRunningTime(c.FinishTime, true, true);
if (t != ct)
return t < ct;
}
}
}
else if (oe->CurrentSortOrder == CourseResult) {
const pCourse crs1 = getCourse(false);
const pCourse crs2 = c.getCourse(false);
RunnerStatus stat = getStatusComputed(false);
RunnerStatus cstat = c.getStatusComputed(false);
if (crs1 != crs2) {
int id1 = crs1 ? crs1->getId() : 0;
int id2 = crs2 ? crs2->getId() : 0;
return id1 < id2;
}
else if (stat != cstat)
return RunnerStatusOrderMap[stat] < RunnerStatusOrderMap[cstat];
else {
if (stat == StatusOK) {
int s = getNumShortening();
int cs = c.getNumShortening();
if (s != cs)
return s < cs;
int t = getRunningTime(true);
int ct = c.getRunningTime(true);
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) {
int cres = compareClubs(Club, c.Club);
if (cres != 2)
return cres != 0;
}
}
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, ChangeType changeType) {
if (no!=StartNo) {
if (oe)
oe->bibStartNoToRunnerTeam.clear();
StartNo=no;
updateChanged(changeType);
}
}
void oRunner::setStartNo(int no, ChangeType changeType) {
if (tInTeam) {
if (tInTeam->getStartNo() == 0)
tInTeam->setStartNo(no, changeType);
else {
// Do not allow different from team
no = tInTeam->getStartNo();
}
}
if (tParentRunner)
tParentRunner->setStartNo(no, changeType);
else {
oAbstractRunner::setStartNo(no, changeType);
for (size_t k=0;koAbstractRunner::setStartNo(no, changeType);
}
}
void oRunner::updateStartNo(int no) {
if (tInTeam) {
tInTeam->synchronize(false);
for (pRunner r : tInTeam->Runners) {
if (r) {
r->synchronize(false);
}
}
tInTeam->setStartNo(no, ChangeType::Update);
for (pRunner r : tInTeam->Runners) {
if (r) {
r->setStartNo(no, ChangeType::Update);
}
}
tInTeam->synchronize(true);
for (pRunner r : tInTeam->Runners) {
if (r)
r->synchronize(true);
}
}
else {
setStartNo(no, ChangeType::Update);
synchronize(true);
}
}
int oRunner::getPlace(bool allowUpdate) const {
if (allowUpdate && tPlace.isOld(*oe)) {
if (Class) {
oEvent::ResultType rt = oEvent::ResultType::ClassResult;
oe->calculateResults({ getClassId(true) }, rt, false);
}
}
return tPlace.get(!allowUpdate);
}
RunnerStatus oRunner::getStatusComputed(bool allowUpdate) const {
if (allowUpdate && tPlace.isOld(*oe)) {
if (Class) {
oEvent::ResultType rt = oEvent::ResultType::ClassResult;
oe->calculateResults({ getClassId(true) }, rt, false);
}
}
return tComputedStatus != StatusUnknown ? tComputedStatus : tStatus;
}
int oRunner::getCoursePlace(bool perClass) const {
if (perClass) {
if (tCourseClassPlace.isOld(*oe) && Class) {
oEvent::ResultType rt = oEvent::ResultType::ClassCourseResult;
oe->calculateResults({ getClassId(true) }, rt, false);
}
return tCourseClassPlace.get(false);
}
else {
if (tCoursePlace.isOld(*oe) && Class) {
oEvent::ResultType rt = oEvent::ResultType::CourseResult;
oe->calculateResults({ getClassId(true) }, rt, false);
}
return tCoursePlace.get(false);
}
}
int oRunner::getTotalPlace(bool allowUpdate) const {
if (tInTeam)
return tInTeam->getLegPlace(getParResultLeg(), true, allowUpdate);
else {
if (allowUpdate && tTotalPlace.isOld(*oe) && Class) {
oEvent::ResultType rt = oEvent::ResultType::TotalResult;
oe->calculateResults({ getClassId(true) }, rt, false);
}
return tTotalPlace.get(!allowUpdate);
}
}
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 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, ChangeType::Update);
otherR->synchronize(true);
}
c->tOwner = this;
setCardNo(c->cardNo, false, true);
}
Card = c;
vector mp;
evaluateCard(true, mp, 0, ChangeType::Update);
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);
bool wasSpace = false;
int kx = 0;
for (size_t k = 0; k < n.length(); k++) {
if (iswspace(n[k])) {
if (!wasSpace) {
n[kx++] = ' ';
wasSpace = true;
}
}
else {
n[kx++] = n[k];
wasSpace = false;
}
}
if (wasSpace)
kx = kx - 1;
n.resize(kx);
if (n.empty())
throw std::exception("Tomt namn är inte tillåtet.");
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;ksName) {
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& getNameLastFirst(const wstring &sName) {
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(sName) + L", " + getGivenName(sName);
return res;
}
const wstring &oRunner::getNameLastFirst() const {
return ::getNameLastFirst(sName);
}
void oRunner::getRealName(const wstring &input, wstring &output) const {
bool wasSpace = false;
wstring n = input;
int kx = 0;
for (size_t k = 0; k < n.length(); k++) {
if (iswspace(n[k])) {
if (!wasSpace) {
n[kx++] = ' ';
wasSpace = true;
}
}
else {
if (n[k] == ',' && wasSpace)
kx--; // Ignore space before comma
n[kx++] = n[k];
wasSpace = false;
}
}
if (wasSpace)
kx = kx - 1;
n.resize(kx);
size_t comma = n.find_first_of(',');
if (oe->getNameMode() != oEvent::NameMode::LastFirst) {
if (comma == string::npos)
output = n;
else
output = trim(n.substr(comma + 1) + L" " + trim(n.substr(0, comma)));
}
else {
if (comma != string::npos)
output = n;
else
output = ::getNameLastFirst(n);
}
}
bool oAbstractRunner::isResultStatus(RunnerStatus st) {
switch (st) {
case StatusDNS:
case StatusCANCEL:
case StatusOutOfCompetition:
case StatusNotCompetiting:
case StatusUnknown:
case StatusNoTiming:
return false;
default:
return true;
}
}
bool oAbstractRunner::setStatus(RunnerStatus st, bool updateSource, ChangeType changeType, bool recalculate) {
assert(!(updateSource && changeType == ChangeType::Quiet));
bool ch = false;
if (tStatus != st) {
ch = true;
bool someOK = (st == StatusOK) || (tStatus == StatusOK);
tStatus = st;
if (Class && someOK) {
Class->clearCache(recalculate);
}
if (st == StatusUnknown)
tComputedStatus = StatusUnknown;
}
if (st != status) {
status = st;
if (updateSource) {
updateChanged(changeType);
if (st == StatusOutOfCompetition)
setFlag(TransferFlags::FlagOutsideCompetition, true);
else {
setFlag(TransferFlags::FlagOutsideCompetition, false);
}
if (st == StatusNoTiming)
setFlag(TransferFlags::FlagNoTiming, true);
else {
setFlag(TransferFlags::FlagNoTiming, false);
}
}
else
changedObject();
}
return ch;
}
int oAbstractRunner::getPrelRunningTime() const
{
if (FinishTime>0 && tStatus!=StatusDNS && tStatus != StatusCANCEL && tStatus!=StatusDNF && tStatus!=StatusNotCompetiting)
return getRunningTime(true);
else if (tStatus==StatusUnknown)
return oe->getComputerTime()-tStartTime;
else return 0;
}
oDataContainer &oRunner::getDataBuffers(pvoid &data, pvoid &olddata, pvectorstr &strData) const {
data = (pvoid)oData;
olddata = (pvoid)oDataOld;
strData = const_cast(&dynamicData);
return *oe->oRunnerData;
}
void oEvent::getRunners(int classId, int courseId, vector &r, bool sort) {
if (sort) {
synchronizeList(oListId::oLRunnerId);
if (classId > 0 && classIdToRunnerHash) {
sortRunners(SortByName, (*classIdToRunnerHash)[classId]);
}
else
sortRunners(SortByName);
}
r.clear();
if (classId > 0 && classIdToRunnerHash) {
auto &rh = (*classIdToRunnerHash)[classId];
r.reserve(rh.size());
for (pRunner rr : rh) {
if (!rr->isRemoved() && rr->getClassId(true) == classId) {
bool skip = false;
if (courseId > 0) {
pCourse pc = rr->getCourse(false);
if (pc == 0 || pc->getId() != courseId)
skip = true;
}
if (!skip)
r.push_back(rr);
}
}
return;
}
if (classId <= 0)
r.reserve(Runners.size());
else if (Classes.size() > 0)
r.reserve((Runners.size()*min(Classes.size(), 4)) / Classes.size());
bool hash = false;
if (!classIdToRunnerHash) {
classIdToRunnerHash = make_shared