/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2017 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" oRunner::RaceIdFormatter oRunner::raceIdFormatter; const wstring &oRunner::RaceIdFormatter::formatData(const oBase *ob) const { return itow(dynamic_cast(*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(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; CardNo=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; CardNo=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;iRunners.size(); i++) if (tInTeam->Runners[i] && tInTeam->Runners[i]==this) tInTeam->Runners[i]=0; tInTeam=0; } for (size_t k=0;ktParentRunner=0; } if (tParentRunner) { for (size_t k=0;kmultiRunner.size(); k++) if (tParentRunner->multiRunner[k] == this) tParentRunner->multiRunner[k]=0; } 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", CardNo); 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;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->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")){ CardNo=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; } 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 age = getBirthAge(); int fee = Class->getEntryFee(date, age); 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):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) { if (tParentRunner) tParentRunner->setClassId(id, isManualUpdate); else { pClass pc = Class; pClass nPc = id>0 ? oe->getClass(id):0; 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()); 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;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) Class->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 (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)==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 = max(FinishTime-tStartTime, 0); 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() const { return oEvent::formatStatus(tStatus); } const wstring &oAbstractRunner::getTotalStatusS() const { return oEvent::formatStatus(getTotalStatus()); } /* - 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 &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 (CardNo==0) CardNo=card->cardNo; //315422 assert(card->tOwner==0 || card->tOwner==this); } // Auto-select shortening pCourse mainCourse = getCourse(false); if (mainCourse && Card) { pCourse shortVersion = mainCourse->getShorterVersion(); 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; Card->getSICard(sic); int level = 0; while (mainCourse->distance(sic) < 0 && abs(numCtrl-numCtrlShort) < abs(numCtrl-numCtrlLong)) { level++; if (shortVersion->distance(sic) >= 0) { setNumShortening(level); // We passed at some level break; } mainCourse = shortVersion; shortVersion = mainCourse->getShorterVersion(); numCtrlLong = numCtrlShort; if (!shortVersion) { break; } numCtrlShort = shortVersion->getNumControls(); } } } 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) { if (Class->hasMultiCourse()) { if (tInTeam) { if (size_t(tLeg) >= tInTeam->Runners.size() || tInTeam->Runners[tLeg] != this) { tInTeam->quickApply(); } } 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); /*if (legMultiCourse.size()) { vector &courses=Class->MultiCourse[leg]; if (courses.size()>0) { int index=StartNo; if (index>0) index = (index-1) % courses.size(); tCrs = courses[index]; } }*/ } else { if (unsigned(tDuplicateLeg)MultiCourse.size()) { vector &courses=Class->MultiCourse[tDuplicateLeg]; if (courses.size()>0) { int index=StartNo % courses.size(); tCrs = courses[index]; } } } } else tCrs = Class->Course; } if (tCrs && useAdaptedCourse) { // Find shortened version of course int ns = getNumShortening(); pCourse shortCrs = tCrs; while (ns > 0 && shortCrs) { shortCrs = shortCrs->getShorterVersion(); 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); tCrs = tCrs->getAdapetedCourse(*Card, *tAdaptedCourse); 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, bool sync) { if (unsigned(status) >= 100u) status = StatusUnknown; //Reset bad input 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->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(sync, 0, false); //Post apply. Update start times. if (storeTimes() && Class && sync) { set cls; cls.insert(Class->getId()); oe->reEvaluateAll(cls, sync); } normalizedSplitTimes.clear(); if (oldTimes.size() > 0 && Class) Class->clearSplitAnalysis(); return false; } //Try to match class?! if (!Class) return false; if (Class->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(); return false; } int startPunchCode = course->getStartPunchType(); int finishPunchCode = course->getFinishPunchType(); bool hasRogaining = course->hasRogaining(); // Pairs: intkeymap< pair > 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; jControls[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;knControls;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() && Class && sync) { set cls; cls.insert(Class->getId()); oe->reEvaluateAll(cls, sync); } normalizedSplitTimes.clear(); if (oldTimes.size() > 0 && Class) Class->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->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::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.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 (knControls) { 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 visitedControls; for (p_it=Card->punches.begin(); p_it != Card->punches.end(); ++p_it) { pair pt; if (rogaining.lookup(p_it->Type, pt)) { 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 = course->Controls[pt.first]->getId(); 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 && 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 (Class && FinishTime>0) { int mt = Class->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 (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); } } } 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; } // Clear split analysis data if necessary bool clear = splitTimes.size() != oldTimes.size() || clearSplitAnalysis; for (size_t k = 0; !clear && kclearSplitAnalysis(); } if (doApply) storeTimes(); if (Class && sync) { bool update = false; if (tInTeam) { int t1 = Class->getTotalLegLeaderTime(tLeg, false); int t2 = tInTeam->getLegRunningTime(tLeg, false); if (t2<=t1 && t2>0) update = true; int t3 = Class->getTotalLegLeaderTime(tLeg, true); int t4 = tInTeam->getLegRunningTime(tLeg, true); if (t4<=t3 && t4>0) update = true; } if (!update) { int t1 = Class->getBestLegTime(tLeg); int t2 = getRunningTime(); if (t2<=t1 && t2>0) update = true; } if (update) { set cls; cls.insert(Class->getId()); oe->reEvaluateAll(cls, sync); } } return true; } void oRunner::clearOnChangedRunningTime() { if (tCachedRunningTime != FinishTime - tStartTime) { tCachedRunningTime = FinishTime - tStartTime; normalizedSplitTimes.clear(); if (Class) Class->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); return updated; } bool oRunner::storeTimesAux(pClass targetClass) { 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(); 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; legtLeaderTime[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; legtLeaderTime[leg].totalLeaderTimeInput; if (rt > 0 && (bt == 0 || rt < bt)) { bt=rt; updated = true; } } } } } else { if (targetClass && unsigned(tDuplicateLeg)tLeaderTime.size()) { if (tStatus==StatusOK) { int &bt=targetClass->tLeaderTime[tDuplicateLeg].bestTimeOnLeg; int rt=getRunningTime(); if (rt > 0 && (bt==0 || rttLeaderTime[tDuplicateLeg].totalLeaderTime; int rt=getRaceRunningTime(tDuplicateLeg); if (rt>0 && (bt==0 || rttLeaderTime[tDuplicateLeg].totalLeaderTimeInput = rt; } } } // Best input time if (targetClass && unsigned(tLeg)tLeaderTime.size()) { int &it = targetClass->tLeaderTime[tLeg].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::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(); int bcid=b.getClassId(); if (acid!=bcid) return acidgetClassStatus() != 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 (Class->Id != c.Class->Id) return Class->tSortIndex < c.Class->tSortIndex; 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 (Class != c.Class) return Class->tSortIndex < c.Class->tSortIndex; 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 tCurrentSortOrder == ClassCourseResult) { if (Class != c.Class) return Class->tSortIndex < c.Class->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 tCurrentSortOrder==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 (Class != c.Class) return Class->tSortIndex < c.Class->tSortIndex; if (tStatus != c.tStatus) return RunnerStatusOrderMap[tStatus] < RunnerStatusOrderMap[c.tStatus]; else{ int ft = getFinishTimeAdjusted(); int cft = c.getFinishTimeAdjusted(); if (tStatus==StatusOK && ft != cft) return ftCurrentSortOrder==SortByStartTime){ if (tStartTime < c.tStartTime) return true; else if (tStartTime > c.tStartTime) return false; } 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 (Class != c.Class) return Class->tSortIndex < c.Class->tSortIndex; 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 (Class != c.Class) return Class->tSortIndex < c.Class->tSortIndex; 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 tCurrentSortOrder==ClassStartTimeClub) { if (Class != c.Class) return Class->tSortIndex < c.Class->tSortIndex; 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 (Class->Id != c.Class->Id) return Class->tSortIndex < c.Class->tSortIndex; 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; } } 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 Class->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); for (size_t k=0;kClub!=Club) { multiRunner[k]->Club = Club; multiRunner[k]->updateChanged(); } } } pClub oRunner::setClubId(int clubId) { if (tParentRunner) tParentRunner->setClubId(clubId); else { oAbstractRunner::setClubId(clubId); for (size_t k=0;kClub!=Club) { multiRunner[k]->Club = Club; multiRunner[k]->updateChanged(); } } return Club; } 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 (tParentRunner) tParentRunner->setStartNo(no, tmpOnly); else { oAbstractRunner::setStartNo(no, tmpOnly); for (size_t k=0;koAbstractRunner::setStartNo(no, tmpOnly); } } int oRunner::getPlace() const { return tPlace; } 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!=CardNo){ int oldNo = CardNo; CardNo=cno; oFreePunch::rehashPunches(*oe, oldNo, 0); oFreePunch::rehashPunches(*oe, CardNo, 0); if (matchCard && !Card) { pCard c=oe->getCardByNumber(cno); if (c && !c->tOwner) { vector mp; addPunches(c, mp); } } if (!updateFromDatabase) updateChanged(); } } 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; CardNo=c->cardNo; } Card=c; vector 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;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 &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; tStatus=st; if (Class) { 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 &r, bool sort) { if (sort) { synchronizeList(oLRunnerId); sortRunners(SortByName); } r.clear(); if (Classes.size() > 0) r.reserve((Runners.size()*min(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() == classId) r.push_back(&*it); } } void oEvent::getRunnersByCard(int cardNo, vector &r) { synchronizeList(oLRunnerId); sortRunners(SortByName); r.clear(); for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { if (it->getCardNo() == cardNo) 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->CardNo == CardNo && !tr->Card && !tr->statusOK()) return tr; } } if (!Card || Card->cardNo!=CardNo || Card->isConstructedFromPunches()) //-1 means card constructed from punches return pRunner(this); for (size_t k=0;kCard || multiRunner[k]->Card->cardNo!=CardNo)) return multiRunner[k]; } return 0; } void oEvent::setupCardHash(bool clear) { if (clear) { cardHash.clear(); } else { assert(cardHash.empty()); for (oRunnerList::iterator it = Runners.begin(); it != Runners.end(); ++it) { if (it->isRemoved()) continue; if (it->CardNo != 0) cardHash.insert(make_pair(it->CardNo, &*it)); } } } typedef unordered_multimap::const_iterator hashConstIter; pRunner oEvent::getRunnerByCardNo(int cardNo, int time, bool onlyWithNoCard, bool ignoreRunnersWithNoStart) const { oRunnerList::const_iterator it; vector cand; bool forceRet = false; if (!onlyWithNoCard && !cardHash.empty()) { forceRet = true; pair range = cardHash.equal_range(cardNo); if (range.first != range.second) { hashConstIter t = range.first; ++t; if (t == range.second) { pRunner r = range.first->second; assert(r->getCardNo() == cardNo); if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) return 0; if (r->getStatus() == StatusNotCompetiting) return 0; return r; // Only one runner with this card } } for (hashConstIter it = range.first; it != range.second; ++it) { pRunner r = it->second; assert(r->getCardNo() == cardNo); if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) continue; if (r->getStatus() == StatusNotCompetiting) continue; if (!r->isRemoved()) cand.push_back(r); } } else { if (time <= 0) { //No time specified. Card readout search //First try runners with no card read or a different card read. for (it=Runners.begin(); it != Runners.end(); ++it) { if (it->skip()) continue; if (ignoreRunnersWithNoStart && (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL)) continue; if (it->getStatus() == StatusNotCompetiting) continue; pRunner ret; if (it->CardNo==cardNo && (ret = it->nextNeedReadout()) != 0) return ret; } } else { for (it=Runners.begin(); it != Runners.end(); ++it) { pRunner r = pRunner(&*it); if (r->CardNo != cardNo || r->isRemoved()) continue; if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) continue; if (r->getStatus() == StatusNotCompetiting) continue; cand.push_back(r); } } } 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 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) 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 (!onlyWithNoCard) { //Then try all runners. for (it=Runners.begin(); it != Runners.end(); ++it){ if (ignoreRunnersWithNoStart && (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL)) continue; if (it->getStatus() == StatusNotCompetiting) continue; if (!it->isRemoved() && it->CardNo==cardNo) { pRunner r = it->nextNeedReadout(); return r ? r : pRunner(&*it); } } } return 0; } void oEvent::getRunnersByCardNo(int cardNo, bool ignoreRunnersWithNoStart, bool skipDuplicates, vector &out) const { out.clear(); if (!cardHash.empty()) { pair range = cardHash.equal_range(cardNo); for (hashConstIter it = range.first; it != range.second; ++it) { pRunner r = it->second; assert(r->getCardNo() == cardNo); if (ignoreRunnersWithNoStart && (r->getStatus() == StatusDNS || r->getStatus() == StatusCANCEL)) continue; if (skipDuplicates && r->getRaceNo() != 0) continue; if (r->getStatus() == StatusNotCompetiting) continue; if (!r->isRemoved()) out.push_back(r); } } else { for (oRunnerList::const_iterator it=Runners.begin(); it != Runners.end(); ++it) { if (it->CardNo != cardNo) continue; if (ignoreRunnersWithNoStart && (it->getStatus() == StatusDNS || it->getStatus() == StatusCANCEL)) continue; if (it->getStatus() == StatusNotCompetiting) continue; if (skipDuplicates && it->getRaceNo() != 0) continue; if (!it->isRemoved()) out.push_back(pRunner(&*it)); } } } 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::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 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(*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(*pa); if (t.getStartNo()==sno || stringMatch(t.getBib(), bib)) { if (!findWithoutCardNo) { for (int leg=0; leggetCardNo() > 0 && t.Runners[leg]->getStatus()==StatusUnknown) return t.Runners[leg]; } } else { for (int leg=0; leggetCardNo() == 0 && t.Runners[leg]->needNoCard() == false) return t.Runners[leg]; } } } } } } return 0; } pRunner oEvent::getRunnerByName(const wstring &pname, const wstring &pclub) const { oRunnerList::const_iterator it; vector 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 > d; oe->fillRunners(d, longName, filter, unordered_set()); gdi.addItem(id, d); } const vector< pair > &oEvent::fillRunners(vector< pair > &out, bool longName, int filter, const unordered_set &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(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) { swprintf_s(bf, L"%s, %s (%s)", it->getNameAndRace(true).c_str(), it->getClub().c_str(), it->getClass().c_str()); } else { swprintf_s(bf, L"%s\t%s\t%s", it->getNameAndRace(true).c_str(), it->getClass().c_str(), it->getClub().c_str()); } out.push_back(make_pair(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().c_str()); out.push_back(make_pair(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 &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. if (multiRunnerId.size()>0) { multiRunner.resize(multiRunnerId.size() - 1); for (size_t k=0;kgetRunner(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; multiRunner[k]->CardNo=0; if (multiRunner[k]->Id != multiRunnerId[k]) markForCorrection(); } else if (multiRunnerId[k]>0) markForCorrection(); 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 toRemove; for (size_t k = ndup-1; kgetStatus()==StatusUnknown) { toRemove.push_back(multiRunner[k]->getId()); multiRunner[k]->tParentRunner = 0; if (multiRunner[k]->tInTeam && size_t(multiRunner[k]->tLeg)tInTeam->Runners.size()) { if (multiRunner[k]->tInTeam->Runners[multiRunner[k]->tLeg]==multiRunner[k]) multiRunner[k]->tInTeam->Runners[multiRunner[k]->tLeg] = 0; } } } multiRunner.resize(ndup-1); for(int k=1;kaddRunner(sName, getClubId(), getClassId(), 0, 0, false); multiRunner[k-1]->tDuplicateLeg=k; multiRunner[k-1]->tParentRunner=this; 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) { createMultiRunner(false, sync); 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(oLRunnerId); oRunnerList::iterator it; table.reserve(Runners.size()); for (it=Runners.begin(); it != Runners.end(); ++it){ if (!it->skip()){ 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); table.set(row++, it, TID_RUNNER, getUIName(), true); table.set(row++, it, TID_CLASSNAME, getClass(), true, cellSelection); table.set(row++, it, TID_COURSE, getCourseName(), true, cellSelection); table.set(row++, it, TID_CLUB, getClub(), true, 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(), 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 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(), getCardNo(), getBirthYear()); 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(), getCardNo(), getBirthYear()); setClub(pc ? pc->getName() : L""); synchronize(true); output = getClub(); } break; case TID_CLASSNAME: setClassId(inputId, true); synchronize(true); output = getClass(); 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(); } 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 > &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(); } 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 { const vector &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)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)=unsigned(crs->nControls) || unsigned(controlNumber)>=splitTimes.size()) return -1; pControl ctrl=crs->Controls[controlNumber]; if (!ctrl || !ctrl->hasName()) return -1; int k=controlNumber-1; //Measure from previous named control while(k>=0) { pControl c=crs->Controls[k]; if (c && c->hasName()) { if (splitTimes[controlNumber].time>0 && splitTimes[k].time>0) return max(splitTimes[controlNumber].time - splitTimes[k].time, -1); else return -1; } k--; } //Measure from start time if (splitTimes[controlNumber].time>0) return max(splitTimes[controlNumber].time - tStartTime, -1); return -1; } 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 { const vector &st = getSplitTimes(normalized); if ( unsigned(controlNumber)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::needNoCard() const { const_cast(this)->apply(false, 0, false); return tNeedNoCard; } void oRunner::getSplitTime(int courseControlId, RunnerStatus &stat, int &rt) const { rt = 0; stat = StatusUnknown; int cardno = tParentRunner ? tParentRunner->CardNo : CardNo; 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(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::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 &inputFilter, unordered_set &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::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->CardNo, 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->CardNo, 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->CardNo, 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;ksynchronize(); } 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 (tParentRunner) 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(); } for (size_t k=0;kgetDI().setString("Bib", bib); } } } } void oEvent::analyseDNS(vector &unknown_dns, vector &known_dns, vector &known, vector &unknown, bool &hasSetDNS) { autoSynchronizeLists(true); vector stUnknown; vector 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 punchHash; map cardCount; for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) { if (!it->isRemoved() && it->getCardNo() > 0) ++cardCount[it->getCardNo()]; } typedef multimap::const_iterator TPunchIter; for (oFreePunchList::iterator it = punches.begin(); it != punches.end(); ++it) { punchHash.insert(make_pair(it->getCardNo(), &*it)); } set 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;kCardNo; if (card == 0) unknown.push_back(stUnknown[k]); else { bool hitCard = knownCards.count(card)==1 && cardCount[card] == 1; if (!hitCard) { pair 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;kCardNo; if (card == 0) unknown_dns.push_back(stDNS[k]); else { bool hitCard = knownCards.count(card)==1 && cardCount[card] == 1; if (!hitCard) { pair 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 &ctrl, int startIndex, int id, int &offset, bool supportRogaining) { vector::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; if (Class && Class->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()); gdi.addStringUT(normal, getClub()); gdi.dropLine(0.5); gdi.addStringUT(normal, lang.tl("Start: ") + getStartTimeS() + lang.tl(", Mål: ") + getFinishTimeS()); wstring statInfo = lang.tl("Status: ") + getStatusS() + 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 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 headerPos; set 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 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 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 (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(oEvent::RTClassResult); if (hasInputData()) oe->calculateResults(oEvent::RTTotalResult); if (tInTeam) oe->calculateTeamResults(tLeg, true); 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 > lines; oe->getExtraLines("SPExtra", lines); for (size_t k = 0; k < lines.size(); k++) { gdi.addStringUT(lines[k].second, 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()); 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 > lines; oe->getExtraLines("EntryExtra", lines); for (size_t k = 0; k < lines.size(); k++) { gdi.addStringUT(lines[k].second, lines[k].first); } if (lines.size()>0) gdi.dropLine(0.5); gdi.addStringUT(fontSmall, L"Av MeOS " + getMeosCompectVersion() + L" / www.melin.nu/meos"); } vector oRunner::getRunnersOrdered() const { if (tParentRunner) return tParentRunner->getRunnersOrdered(); vector r(multiRunner.size()+1); r[0] = (pRunner)this; for (size_t k=0;k &r = tParentRunner->multiRunner; for (size_t k=0;ktParentRunner = 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(), it->getCardNo(), it->getBirthYear()); } } bool oRunner::updateFromDB(const wstring &name, int clubId, int classId, int cardNo, int birthYear) { if (!oe->useRunnerDb()) return false; 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 myNames, inNames; split(tRealName, L" ", myNames); split(pname, L" ", inNames); 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(myNames.size(), 2); } bool oRunner::autoAssignBib() { if (Class == 0 || !getBib().empty()) return !getBib().empty(); int maxbib = 0; wchar_t pattern[32]; int noBib = 0; int withBib = 0; for(oRunnerList::iterator it = oe->Runners.begin(); it !=oe->Runners.end();++it) { if (it->Class == Class) { const wstring &bib = it->getBib(); 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); setBib(bib, maxbib+1, true, false); return true; } return false; } void oRunner::getSplitAnalysis(vector &deltaTimes) const { deltaTimes.clear(); vector mp; if (splitTimes.empty() || !Class) return; if (Class->tSplitRevision == tSplitRevision) deltaTimes = tMissedTime; pCourse pc = getCourse(true); if (!pc) return; vector 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 (Class->tSplitAnalysisData.count(id) == 0) Class->calculateSplits(); const vector &baseLine = Class->tSplitAnalysisData[id]; const unsigned nc = pc->getNumControls(); if (baseLine.size() != nc+1) return; vector 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>=15) deltaTimes[k] = deltaAbs; } } } void oRunner::getLegPlaces(vector &places) const { places.clear(); pCourse pc = getCourse(true); if (!pc || !Class || splitTimes.empty()) return; if (Class->tSplitRevision == tSplitRevision) places = tPlaceLeg; int id = pc->getId(); if (Class->tSplitAnalysisData.count(id) == 0) Class->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 (kgetControl(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] = Class->getLegPlace(from, to, time); else places[k] = 0; } } void oRunner::getLegTimeAfter(vector ×) const { times.clear(); if (splitTimes.empty() || !Class) return; if (Class->tSplitRevision == tSplitRevision) { times = tAfterLeg; return; } pCourse pc = getCourse(false); if (!pc) return; int id = pc->getId(); if (Class->tCourseLegLeaderTime.count(id) == 0) Class->calculateSplits(); const unsigned nc = pc->getNumControls(); const vector leaders = Class->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 &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { vector orderedTimes(times.size()); for (size_t k = 0; k < times.size(); k++) { orderedTimes[k] = times[reorder[k]]; } times.swap(orderedTimes); } } void oRunner::getLegTimeAfterAcc(vector ×) const { times.clear(); if (splitTimes.empty() || !Class || tStartTime<=0) return; if (Class->tSplitRevision == tSplitRevision) times = tAfterLegAcc; pCourse pc = getCourse(false); //XXX Does not work for loop courses if (!pc) return; int id = pc->getId(); if (Class->tCourseAccLegLeaderTime.count(id) == 0) Class->calculateSplits(); const unsigned nc = pc->getNumControls(); const vector leaders = Class->tCourseAccLegLeaderTime[id]; const vector &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 &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { vector orderedTimes(times.size()); for (size_t k = 0; k < times.size(); k++) { orderedTimes[k] = times[reorder[k]]; } times.swap(orderedTimes); } } void oRunner::getLegPlacesAcc(vector &places) const { places.clear(); pCourse pc = getCourse(false); if (!pc || !Class) return; if (splitTimes.empty() || tStartTime<=0) return; if (Class->tSplitRevision == tSplitRevision) { places = tPlaceLegAcc; return; } int id = pc->getId(); const unsigned nc = pc->getNumControls(); const vector &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] = Class->getAccLegPlace(id, k, time); else places[k] = 0; } } // Normalized order const vector &reorder = getCourse(true)->getMapToOriginalOrder(); if (!reorder.empty()) { vector orderedPlaces(places.size()); for (size_t k = 0; k < places.size(); k++) { orderedPlaces[k] = places[reorder[k]]; } places.swap(orderedPlaces); } } void oRunner::setupRunnerStatistics() const { if (!Class) return; if (Class->tSplitRevision == tSplitRevision) return; getSplitAnalysis(tMissedTime); getLegPlaces(tPlaceLeg); getLegTimeAfter(tAfterLeg); getLegPlacesAcc(tPlaceLegAcc); getLegPlacesAcc(tAfterLegAcc); tSplitRevision = Class->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; k0) 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 { setupRunnerStatistics(); if (unsigned(ctrlNo) < tPlaceLegAcc.size()) return tPlaceLegAcc[ctrlNo]; else return 0; } int oRunner::getLegTimeAfterAcc(int ctrlNo) const { 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); if (Class->getLegType(tLeg) != LTExtra) return pRunner(this); const vector &multiV = tParentRunner ? tParentRunner->multiRunner : multiRunner; vector 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; kCard || multiOrdered[k]->getStatus() != StatusUnknown) continue; if (Class->getLegType(multiOrdered[k]->tLeg) != LTExtra) return pRunner(this); vector 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 (dDistance) { 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 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); } 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 &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) { setTemporary(); dbr.getName(sName); getRealName(sName, tRealName); CardNo = 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 &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 &oRunner::getSplitTimes(bool normalized) const { if (!normalized) return splitTimes; else { pCourse pc = getCourse(true); if (pc && pc->isAdapted() && splitTimes.size() == pc->nControls) { if (!normalizedSplitTimes.empty()) return normalizedSplitTimes; const vector &mapToOriginal = pc->getMapToOriginalOrder(); normalizedSplitTimes.resize(splitTimes.size()); // nControls vector 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); 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()); else if (inputStatus == StatusUnknown) return oEvent::formatStatus(StatusUnknown); else return oEvent::formatStatus(max(inputStatus, getStatus())); } 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) return MaxRankingConstant; else return rank; } void oAbstractRunner::hasManuallyUpdatedTimeStatus() { if (Class && Class->hasClassGlobalDependance()) { set cls; oe->reEvaluateAll(cls, false); } } bool oRunner::canShareCard(const pRunner other, int newCardNo) const { if (!other || other->CardNo != newCardNo || newCardNo == 0) return true; if (getCard() && getCard()->getCardNo() == newCardNo) return true; if (other->skip() || other->getCard() || other == this || other->getMultiRunner(0) == getMultiRunner(0)) return true; if (!getTeam() || other->getTeam() != getTeam()) return false; pClass tCls = getTeam()->getClassRef(); 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; }