/************************************************************************ MeOS - Orienteering Software Copyright (C) 2009-2018 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 ************************************************************************/ #include "stdafx.h" #include #include #include #include "oEvent.h" #include "oSpeaker.h" #include "gdioutput.h" #include "SportIdent.h" #include "mmsystem.h" #include "meos_util.h" #include "localizer.h" #include "meos.h" #include "gdifonts.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// extern gdioutput *gdi_main; oTimeLine::oTimeLine(int time_, TimeLineType type_, Priority priority_, int classId_, int ID_, oAbstractRunner *source) : time(time_), type(type_), priority(priority_), classId(classId_), ID(ID_) { if (source) { typeId.second = source->getId(); typeId.first = typeid(*source)==typeid(oTeam); } else { typeId.first = false; typeId.second = 0; } } pCourse deduceSampleCourse(pClass pc, vector &stages, int leg) { int courseLeg = leg; for (size_t k = 0; k hasMultiCourse()) return pc->getCourse(courseLeg, 0, true); else return pc->getCourse(true); } oAbstractRunner *oTimeLine::getSource(const oEvent &oe) const { //assert(typeId.second != 0); if (typeId.second > 0) { if (typeId.first) return oe.getTeam(typeId.second); else return oe.getRunner(typeId.second, 0); } return 0; } __int64 oTimeLine::getTag() const { __int64 cs = type != TLTExpected ? time : 0; cs = 997 * cs + type; cs = 997 * cs + priority; cs = 997 * cs + classId; cs = 997 * cs + ID; cs = 997 * cs + typeId.second; return cs; } //Order by preliminary times and priotity for speaker list bool CompareSpkSPList(const oSpeakerObject &a, const oSpeakerObject &b) { if (a.priority!=b.priority) return a.priority>b.priority; else if (a.status<=1 && b.status<=1){ int at=a.runningTime.preliminary; int bt=b.runningTime.preliminary; if (at==bt) { //Compare leg times instead at=a.runningTimeLeg.preliminary; bt=b.runningTimeLeg.preliminary; } if (at == bt) { if (a.runningTimeSinceLast.preliminary > 0 && b.runningTimeSinceLast.preliminary > 0) { if (a.runningTimeSinceLast.preliminary != b.runningTimeSinceLast.preliminary) return a.runningTimeSinceLast.preliminary > b.runningTimeSinceLast.preliminary; } else if (a.runningTimeSinceLast.preliminary>0) return true; else if (b.runningTimeSinceLast.preliminary>0) return false; } if (a.missingStartTime != b.missingStartTime) return a.missingStartTime < b.missingStartTime; if (at==bt) { if (a.parallelScore != b.parallelScore) return a.parallelScore > b.parallelScore; if (a.bib != b.bib) { return compareBib(a.bib, b.bib); } return a.names=0 && bt>=0) { if (a.priority == 0) return bt=0 && bt<0) return true; else if (at<0 && bt>=0) return false; else return at>bt; } else if (a.status!=b.status) return a.status b.parallelScore; if (a.bib != b.bib) { return compareBib(a.bib, b.bib); } return a.namesgetData("oEvent", *LPDWORD(&oe)); if (!oe) return false; DWORD classId=0, controlId=0, previousControlId, leg=0, totalResult=0, shortNames = 0; gdi->getData("ClassId", classId); gdi->getData("ControlId", controlId); gdi->getData("PreviousControlId", previousControlId); gdi->getData("LegNo", leg); gdi->getData("TotalResult", totalResult); gdi->getData("ShortNames", shortNames); if (classId>0 && controlId>0) { pClass pc = oe->getClass(classId); bool update = false; if (type == GUI_TIMEOUT) update = true; else if (pc) { vector stages; pc->getTrueStages(stages); int trueIndex = -1; int start = 0; for (size_t k = 0; k < stages.size(); k++) { if (leg <= DWORD(stages[k].first)) { trueIndex = k; if (k > 0) start = stages[k-1].first + 1; break; } } int cid = oControl::getIdIndexFromCourseControlId(controlId).first; if (trueIndex != -1) { /// Check all legs corresponding to the true leg. for (int k = start; k <= stages[trueIndex].first; k++) { if (pc->wasSQLChanged(k, cid)) { update = true; break; } } } else if (pc->wasSQLChanged(-1, cid)) update = true; } if (update) oe->speakerList(*gdi, classId, leg, controlId, previousControlId, totalResult != 0, shortNames != 0); } return true; } int MovePriorityCB(gdioutput *gdi, int type, void *data) { if (type==GUI_LINK){ oEvent *oe=0; gdi->getData("oEvent", *LPDWORD(&oe)); if (!oe) return false; TextInfo *ti=(TextInfo *)data; //gdi->alert(ti->id); if (ti->id.size()>1){ //int rid=atoi(ti->id.substr(1).c_str()); int rid = ti->getExtraInt(); oRunner *r= oe->getRunner(rid, 0); if (!r) return false; DWORD classId=0, controlId=0, previousControlId = 0; if (gdi->getData("ClassId", classId) && gdi->getData("ControlId", controlId) && gdi->getData("PreviousControlId", previousControlId)){ DWORD leg = 0, totalResult = 0, shortNames = 0; gdi->getData("LegNo", leg); gdi->getData("TotalResult", totalResult); gdi->getData("ShortNames", shortNames); if (ti->id[0]=='D'){ r->setPriority(controlId, -1); } else if (ti->id[0]=='U'){ r->setPriority(controlId, 1); } else if (ti->id[0]=='M'){ r->setPriority(controlId, 0); } oe->speakerList(*gdi, classId, leg, controlId, previousControlId, totalResult != 0, shortNames != 0); } } } return true; } void renderRowSpeakerList(const oSpeakerObject &r, const oSpeakerObject *next_r, int leaderTime, int type, vector &row, bool shortName) { if (r.place > 0) row.push_back(SpeakerString(textRight, itow(r.place))); else row.push_back(SpeakerString()); wstring names; for (size_t k = 0; k < r.names.size(); k++) { if (names.empty()) { if (!r.bib.empty()) names += r.bib + L": "; } else { names += L"/"; } if (!shortName) { names += r.names[k]; } else { vector splt; split(r.names[k], L" ", splt); for (size_t j = 0; j < splt.size(); j++) { if (j == 0) { if (splt.size()>1 && splt[j].length() > 1) names += splt[j].substr(0, 1) + L"."; else names += splt[j]; } else names += L" " + splt[j]; } } } if (r.runnersFinishedLeg < r.runnersTotalLeg && r.runnersFinishedLeg>0) { // Fraction of runners has finished on leg names += L" (" + itow(r.runnersFinishedLeg) + L"/" + itow(r.runnersTotalLeg) + L")"; } for (size_t k = 0; k < r.outgoingnames.size(); k++) { if (k == 0) { names += L" > "; } else names += L"/"; if (!shortName) { names += r.outgoingnames[k]; } else { vector splt; split( r.outgoingnames[k], L" ", splt); for (size_t j = 0; j < splt.size(); j++) { if (j == 0) { if (splt.size()>1 && splt[j].length() > 1) names += splt[j].substr(0, 1) + L"."; else names += splt[j]; } else names += L" " + splt[j]; } } } if (r.finishStatus<=1 || r.finishStatus==r.status) row.push_back(SpeakerString(normalText, names)); else row.push_back(SpeakerString(normalText, names + L" ("+ oEvent::formatStatus(r.finishStatus) +L")")); row.push_back(SpeakerString(normalText, r.club)); if (r.status == StatusOK) { row.push_back(SpeakerString(textRight, formatTime(r.runningTime.preliminary))); if (r.runningTime.time != r.runningTimeLeg.time) row.push_back(SpeakerString(textRight, formatTime(r.runningTimeLeg.time))); else row.push_back(SpeakerString()); if (leaderTime!=100000 && leaderTime>0){ row.push_back(SpeakerString(textRight, gdioutput::getTimerText(r.runningTime.time-leaderTime, timerCanBeNegative))); } else row.push_back(SpeakerString()); /* if (r.timeSinceChange < 10 && r.timeSinceChange>=0) { RECT rc; rc.left=x+dx[1]-4; rc.right=x+dx[7]+gdi.scaleLength(60); rc.top=y-1; rc.bottom=y+lh+1; gdi.addRectangle(rc, colorLightGreen, false); }*/ } else if (r.status == StatusUnknown) { DWORD TimeOut=NOTIMEOUT; if (r.runningTimeLeg.preliminary>0 && !r.missingStartTime) { if (next_r && next_r->status==StatusOK && next_r->runningTime.preliminary > r.runningTime.preliminary) TimeOut = next_r->runningTime.preliminary; row.push_back(SpeakerString(textRight, r.runningTime.preliminary, TimeOut)); if (r.runningTime.preliminary != r.runningTimeLeg.preliminary) row.push_back(SpeakerString(textRight, r.runningTimeLeg.preliminary)); else row.push_back(SpeakerString()); if (leaderTime != 100000) row.push_back(SpeakerString(timerCanBeNegative|textRight, r.runningTime.preliminary - leaderTime)); else row.push_back(SpeakerString()); //gdi.addTimer(y, x+dx[5], timerCanBeNegative|textRight, r.runningTime.preliminary - leaderTime); } else{ //gdi.addStringUT(y, x+dx[4], textRight, "["+r.startTimeS+"]"); row.push_back(SpeakerString(textRight, L"["+r.startTimeS+L"]")); if (!r.missingStartTime) row.push_back(SpeakerString(timerCanBeNegative|textRight, r.runningTimeLeg.preliminary)); //gdi.addTimer(y, x+dx[5], timerCanBeNegative|textRight, r.runningTimeLeg.preliminary, 0, SpeakerCB, NOTIMEOUT); else row.push_back(SpeakerString()); row.push_back(SpeakerString()); } } else{ //gdi.addStringUT(y, x+dx[4], textRight, oEvent::formatStatus(r.status)).setColor(colorDarkRed); row.push_back(SpeakerString()); row.push_back(SpeakerString(textRight, oEvent::formatStatus(r.status))); row.back().color = colorDarkRed; row.push_back(SpeakerString()); } int ownerId = r.owner ? r.owner->getId(): 0; if (type==1) { row.push_back(SpeakerString(normalText, lang.tl(L"[Bort]"))); row.back().color = colorRed; row.back().moveKey = "D" + itos(ownerId); } else if (type==2) { row.push_back(SpeakerString(normalText, lang.tl(L"[Bevaka]"))); row.back().color = colorGreen; row.back().moveKey = "U" + itos(ownerId); row.push_back(SpeakerString(normalText, lang.tl(L"[Bort]"))); row.back().color = colorRed; row.back().moveKey = "D" + itos(ownerId); } else if (type == 3) { if (r.status <= StatusOK) { if (r.priority < 0) { row.push_back(SpeakerString(normalText, lang.tl(L"[Återställ]"))); row.back().moveKey = "M" + itos(ownerId); } else{ row.push_back(SpeakerString(normalText, lang.tl(L"[Bevaka]"))); row.back().moveKey = "U" + itos(ownerId); } } } } void renderRowSpeakerList(gdioutput &gdi, int type, const oSpeakerObject &r, int x, int y, const vector &row, const vector &pos) { int lh=gdi.getLineHeight(); bool highlight = false; if (r.timeSinceChange < 20 && r.timeSinceChange>=0) { RECT rc; rc.left = x+pos[1] - 4; rc.right=x+pos.back()+gdi.scaleLength(60); rc.top=y-1; rc.bottom=y+lh+1; gdi.addRectangle(rc, colorLightGreen, false); highlight = true; } for (size_t k = 0; k < row.size(); k++) { int limit = pos[k+1]-pos[k]-3; if (limit < 0) limit = 0; if (!row[k].hasTimer) { if (!row[k].str.empty()) { if (row[k].moveKey.empty()) { TextInfo &ti = gdi.addStringUT(y, x+pos[k], row[k].format, row[k].str, limit); if (row[k].color != colorDefault) ti.setColor(row[k].color); } else { TextInfo &ti = gdi.addStringUT(y, x+pos[k], row[k].format, row[k].str, limit, MovePriorityCB); ti.id = row[k].moveKey; ti.setExtra(r.owner->getId()); if (!highlight && row[k].color != colorDefault) ti.setColor(row[k].color); } } } else { gdi.addTimer(y, x + pos[k], row[k].format, row[k].timer, limit, row[k].timeout != NOTIMEOUT ? SpeakerCB : 0, row[k].timeout); } } } void oEvent::calculateResults(list &rl) { rl.sort(CompareSOResult); list::iterator it; int cPlace=0; int vPlace=0; int cTime=0; for (it=rl.begin(); it != rl.end(); ++it) { if (it->status==StatusOK) { cPlace++; int rt = it->runningTime.time; if (rt>cTime) vPlace=cPlace; cTime=rt; it->place=vPlace; } else it->place = 0; } } void oEvent::speakerList(gdioutput &gdi, int ClassId, int leg, int ControlId, int PreviousControlId, bool totalResults, bool shortNames) { #ifdef _DEBUG OutputDebugString(L"SpeakerListUpdate\n"); #endif DWORD clsIds = 0, ctrlIds = 0, cLegs = 0, cTotal = 0, cShort = 0; gdi.getData("ClassId", clsIds); gdi.getData("ControlId", ctrlIds); gdi.getData("LegNo", cLegs); gdi.getData("TotalResult", cTotal); gdi.getData("ShortNames", cShort); bool refresh = clsIds == ClassId && ctrlIds == ControlId && leg == cLegs && (totalResults ? 1 : 0) == cTotal && (shortNames ? 1 : 0) == cShort; if (refresh) gdi.takeShownStringsSnapshot(); int storedY = gdi.GetOffsetY(); int storedHeight = gdi.getHeight(); gdi.restoreNoUpdate("SpeakerList"); gdi.setRestorePoint("SpeakerList"); gdi.pushX(); gdi.pushY(); gdi.updatePos(0,0,0, storedHeight); gdi.popX(); gdi.popY(); gdi.SetOffsetY(storedY); gdi.setData("ClassId", ClassId); gdi.setData("ControlId", ControlId); gdi.setData("PreviousControlId", PreviousControlId); gdi.setData("LegNo", leg); gdi.setData("TotalResult", totalResults ? 1 : 0); gdi.setData("ShortNames", shortNames ? 1 : 0); gdi.setData("oEvent", DWORD(this)); gdi.registerEvent("DataUpdate", SpeakerCB); gdi.setData("DataSync", 1); gdi.setData("PunchSync", 1); list speakerList; //For preliminary times updateComputerTime(); speakerList.clear(); if (classHasTeams(ClassId)) { oTeamList::iterator it=Teams.begin(); while(it!=Teams.end()){ if (it->getClassId(true)==ClassId && !it->skip()) { oSpeakerObject so; it->fillSpeakerObject(leg, ControlId, PreviousControlId, totalResults, so); if (so.owner) speakerList.push_back(so); } ++it; } } else { pClass pc = getClass(ClassId); bool qfBaseClass = pc && pc->getQualificationFinal(); bool qfFinalClass = pc && pc->getParentClass(); if (qfBaseClass || qfFinalClass) leg = 0; for(auto &it : Runners){ if (it.getClassId(true) == ClassId && !it.isRemoved()) { if (qfBaseClass && it.getLegNumber() > 0) continue; oSpeakerObject so; it.fillSpeakerObject(leg, ControlId, PreviousControlId, totalResults, so); if (so.owner) speakerList.push_back(so); } } } if (speakerList.empty()) { gdi.dropLine(); gdi.addString("", fontMediumPlus, "Inga deltagare"); gdi.refresh(); return; } list::iterator sit; for (sit=speakerList.begin(); sit != speakerList.end(); ++sit) { if (sit->status==StatusOK && sit->priority>=0) sit->priority=1; else if (sit->status > StatusOK && sit->priority<=0) sit->priority=-1; } //Calculate place... calculateResults(speakerList); //Calculate preliminary times and sort by this and prio. speakerList.sort(CompareSpkSPList); //char bf[256]; pClass pCls=oe->getClass(ClassId); if (!pCls) return; vector stages; pCls->getTrueStages(stages); pCourse crs = deduceSampleCourse(pCls, stages, leg); //char bf2[64]=""; wstring legName, cname = pCls->getName(); size_t istage = -1; for (size_t k = 0; k < stages.size(); k++) { if (stages[k].first >= leg) { istage = k; break; } } if (stages.size()>1 && istage < stages.size()) cname += lang.tl(L", Sträcka X#" + itow(stages[istage].second)); if (ControlId != oPunch::PunchFinish && crs) { cname += L", " + crs->getRadioName(ControlId); } else { cname += lang.tl(L", Mål"); } int y=gdi.getCY()+5; int x=30; gdi.addStringUT(y, x, fontMediumPlus, cname).setColor(colorGreyBlue); int lh=gdi.getLineHeight(); y+=lh*2; int LeaderTime=100000; //Calculate leader-time for(sit=speakerList.begin(); sit != speakerList.end(); ++sit) { int rt = sit->runningTime.time; if (sit->status==StatusOK && rt>0) LeaderTime=min(LeaderTime, rt); } vector< pair > > toRender(speakerList.size()); /*const int dx_c[8]={0, 40, 280, 530-40, 590-40, 650-40, 660-40, 730-40}; vector dx; for (int k=0;k<8;k++) dx.push_back(gdi.scaleLength(dx_c[k]));*/ int ix = 0; sit = speakerList.begin(); size_t maxRow = 0; while (sit != speakerList.end()) { oSpeakerObject *so = toRender[ix].first = &*sit; ++sit; oSpeakerObject *next = sit != speakerList.end() ? &*sit : 0; int type = 3; if (so->priority > 0 || (so->status==StatusOK && so->priority>=0)) type = 1; else if (so->status == StatusUnknown && so->priority==0) type = 2; renderRowSpeakerList(*toRender[ix].first, next, LeaderTime, type, toRender[ix].second, shortNames); maxRow = max(maxRow, toRender[ix].second.size()); ix++; } vector dx_new(maxRow); for (size_t k = 0; k < toRender.size(); k++) { const vector &row = toRender[k].second; for (size_t j = 0; j < row.size(); j++) { if (!row[j].str.empty()) { double len = row[j].str.length(); double factor = 5.6; if (len < 4) factor = 7; else if (len <10) factor = 5.8; dx_new[j] = max(28, max(dx_new[j], int(len * factor)+15)); } else if (row[j].hasTimer) { dx_new[j] = max(dx_new[j], 60); } } } bool rendered; int limit = gdi.scaleLength(280); vector dx(maxRow+1); for (size_t k = 1; k < dx.size(); k++) { dx[k] = dx[k-1] + min(limit, gdi.scaleLength(dx_new[k-1])); } rendered = false; for (size_t k = 0; k < toRender.size(); k++) { oSpeakerObject *so = toRender[k].first; if (so && (so->priority > 0 || (so->status==StatusOK && so->priority>=0))) { if (rendered == false) { gdi.addString("", y, x, boldSmall, "Resultat"); y+=lh+5, rendered=true; } renderRowSpeakerList(gdi, 1, *so, x, y, toRender[k].second, dx); y+=lh; toRender[k].first = 0; } } rendered = false; for (size_t k = 0; k < toRender.size(); k++) { oSpeakerObject *so = toRender[k].first; if (so && so->status == StatusUnknown && so->priority==0) { if (rendered == false) { gdi.addString("", y+4, x, boldSmall, "Inkommande"); y+=lh+5, rendered=true; } renderRowSpeakerList(gdi, 2, *so, x, y, toRender[k].second, dx); y+=lh; toRender[k].first = 0; } } rendered = false; for (size_t k = 0; k < toRender.size(); k++) { oSpeakerObject *so = toRender[k].first; if (so) { if (rendered == false) { gdi.addString("", y+4, x, boldSmall, "Övriga"); y+=lh+5, rendered=true; } renderRowSpeakerList(gdi, 3, *so, x, y, toRender[k].second, dx); y+=lh; toRender[k].first = 0; } } if (refresh) gdi.refreshSmartFromSnapshot(false); else gdi.refresh(); } void oEvent::updateComputerTime() { SYSTEMTIME st; GetLocalTime(&st); computerTime=(((24+2+st.wHour)*3600+st.wMinute*60+st.wSecond-ZeroTime)%(24*3600)-2*3600) * 1000 + st.wMilliseconds; } void oEvent::clearPrewarningSounds() { oFreePunchList::reverse_iterator it; for (it=punches.rbegin(); it!=punches.rend(); ++it) it->hasBeenPlayed=true; } void oEvent::tryPrewarningSounds(const wstring &basedir, int number) { wchar_t wave[20]; swprintf_s(wave, L"%d.wav", number); wstring file=basedir+L"\\"+wave; if (_waccess(file.c_str(), 0)==-1) gdibase.alert(L"Fel: hittar inte filen X.#" + file); PlaySound(file.c_str(), 0, SND_SYNC|SND_FILENAME ); } void oEvent::playPrewarningSounds(const wstring &basedir, set &controls) { oFreePunchList::reverse_iterator it; for (it=punches.rbegin(); it!=punches.rend() && !it->hasBeenPlayed; ++it) { if (controls.count(it->Type)==1 || controls.empty()) { pRunner r = getRunnerByCardNo(it->CardNo, it->getAdjustedTime()); if (r){ wchar_t wave[20]; swprintf_s(wave, L"%d.wav", r->getStartNo()); wstring file=basedir+L"\\"+ r->getDI().getString("Nationality") +L"\\"+wave; if (_waccess(file.c_str(), 0)==-1) file=basedir+L"\\"+wave; PlaySound(file.c_str(), 0, SND_SYNC|SND_FILENAME ); it->hasBeenPlayed=true; } } } } static bool compareFinishTime(pRunner a, pRunner b) { return a->getFinishTime() < b->getFinishTime(); } struct TimeRunner { TimeRunner(int t, pRunner r) : time(t), runner(r) {} int time; pRunner runner; bool operator<(const TimeRunner &b) const {return time 0) { for (int k = 0; k < nt-1; k++) { if (t == times[k] && times[k+1] < maxtime) return times[k+1]; else if (t < times[k] && times[k] < maxtime) return times[k]; // Will not happen with current implementation of besttime } } return t; } }; struct PlaceRunner { PlaceRunner(int p, pRunner r) : place(p), runner(r) {} int place; pRunner runner; }; // ClassId -> (Time -> Place, Runner) typedef multimap TempResultMap; void insertResult(TempResultMap &rm, oRunner &r, int time, int &place, bool &sharedPlace, vector &preRunners) { TempResultMap::iterator it = rm.insert(make_pair(time, PlaceRunner(0, &r))); place = 1; sharedPlace = false; preRunners.clear(); if (it != rm.begin()) { TempResultMap::iterator p_it = it; --p_it; if (p_it->first < it->first) place = p_it->second.place + 1; else { place = p_it->second.place; sharedPlace = true; } int pretime = p_it->first; while(p_it->first == pretime) { preRunners.push_back(p_it->second.runner); if (p_it != rm.begin()) --p_it; else break; } } TempResultMap::iterator p_it = it; if ( (++p_it) != rm.end() && p_it->first == time) sharedPlace = true; int lastplace = place; int cplace = place; // Update remaining while(it != rm.end()) { if (time == it->first) it->second.place = lastplace; else { it->second.place = cplace; lastplace = cplace; time = it->first; } ++cplace; ++it; } } int oEvent::setupTimeLineEvents(int currentTime) { if (currentTime == 0) { updateComputerTime(); currentTime = getComputerTime(); } int nextKnownEvent = 3600*48; vector started; started.reserve(Runners.size()); timeLineEvents.clear(); for(set::iterator it = timelineClasses.begin(); it != timelineClasses.end(); ++it) { int ne = setupTimeLineEvents(*it, currentTime); nextKnownEvent = min(ne, nextKnownEvent); modifiedClasses.erase(*it); } return nextKnownEvent; } int oEvent::setupTimeLineEvents(int classId, int currentTime) { // leg -> started on leg vector< vector > started; started.reserve(32); int nextKnownEvent = 3600*48; int classSize = 0; pClass pc = getClass(classId); if (!pc) return nextKnownEvent; vector skipLegs; skipLegs.resize(max(1, pc->getNumStages())); for (size_t k = 1; k < skipLegs.size(); k++) { LegTypes lt = pc->getLegType(k); if (lt == LTIgnore) { skipLegs[k] = true; } else if (lt == LTParallel || lt == LTParallelOptional) { StartTypes st = pc->getStartType(k-1); if (st != STChange) { // Check that there is no forking vector &cc1 = pc->MultiCourse[k-1]; vector &cc2 = pc->MultiCourse[k]; bool equal = cc1.size() == cc2.size(); for (size_t j = 0; jId != classId) continue; if (r.tStatus == StatusDNS || r.tStatus == StatusCANCEL || r.tStatus == StatusNotCompetiting) continue; // if (r.CardNo == 0) // continue; if (r.tLeg == 0) classSize++; // Count number of starts on first leg if (size_t(r.tLeg) < skipLegs.size() && skipLegs[r.tLeg]) continue; if (r.tStartTime > 0 && r.tStartTime <= currentTime) { if (started.size() <= size_t(r.tLeg)) { started.resize(r.tLeg+1); started.reserve(Runners.size() / (r.tLeg + 1)); } r.tTimeAfter = 0; //Reset time after r.tInitialTimeAfter = 0; started[r.tLeg].push_back(&r); int id = r.tLeg + 100 * r.tStartTime; ++startTimes[id]; } else if (r.tStartTime > currentTime) { nextKnownEvent = min(r.tStartTime, nextKnownEvent); } } if (started.empty()) return nextKnownEvent; size_t firstNonEmpty = 0; while (started[firstNonEmpty].empty()) // Note -- started cannot be empty. firstNonEmpty++; int sLimit = min(4, classSize); if (false && startTimes.size() == 1) { oRunner &r = *started[firstNonEmpty][0]; oTimeLine tl(r.tStartTime, oTimeLine::TLTStart, oTimeLine::PHigh, r.getClassId(true), 0, 0); TimeLineIterator it = timeLineEvents.insert(pair(r.tStartTime, tl)); it->second.setMessage(L"X har startat.#" + r.getClass(true)); } else { for (size_t j = 0; j 1) prio = oTimeLine::PHigh; else if (p == 1) prio = oTimeLine::PMedium; oTimeLine tl(r.tStartTime, oTimeLine::TLTStart, prio, r.getClassId(true), r.getId(), &r); TimeLineIterator it = timeLineEvents.insert(pair(r.tStartTime + 1, tl)); it->second.setMessage(L"har startat."); } else if (!startedClass) { // The entire class started oTimeLine tl(r.tStartTime, oTimeLine::TLTStart, oTimeLine::PHigh, r.getClassId(true), 0, 0); TimeLineIterator it = timeLineEvents.insert(pair(r.tStartTime, tl)); it->second.setMessage(L"X har startat.#" + r.getClass(true)); startedClass = true; } } } } set controlId; getFreeControls(controlId); // Radio controls for each leg, pair is (courseControlId, control) map > > radioControls; for (size_t leg = 0; leggetCourse(false); if (pc) { vector< pair > &rc = radioControls[leg]; for (int j = 0; j < pc->nControls; j++) { int id = pc->Controls[j]->Id; if (controlId.count(id) > 0) { rc.push_back(make_pair(pc->getCourseControlId(j), pc->Controls[j])); } } } } } } for (size_t leg = 0; leg > &rc = radioControls[leg]; int nv = setupTimeLineEvents(started[leg], rc, currentTime, leg + 1 == started.size()); nextKnownEvent = min(nv, nextKnownEvent); } return nextKnownEvent; } wstring getTimeDesc(int t1, int t2); void getTimeAfterDetail(wstring &detail, int timeAfter, int deltaTime, bool wasAfter) { wstring aTimeS = getTimeDesc(timeAfter, 0); if (timeAfter > 0) { if (!wasAfter || deltaTime == 0) detail = L"är X efter#" + aTimeS; else { wstring deltaS = getTimeDesc(deltaTime, 0); if (deltaTime > 0) detail = L"är X efter; har tappat Y#" + aTimeS + L"#" + deltaS; else detail = L"är X efter; har tagit in Y#" + aTimeS + L"#" + deltaS; } } else if (timeAfter < 0) { if (wasAfter && deltaTime != 0) { wstring deltaS = getTimeDesc(deltaTime, 0); if (deltaTime > 0) detail = L"leder med X; har tappat Y.#" + aTimeS + L"#" + deltaS; else if (deltaTime < 0) detail = L"leder med X; sprang Y snabbare än de jagande.#" + aTimeS + L"#" + deltaS; else detail = L"leder med X#" + aTimeS; } } } void oEvent::timeLinePrognose(TempResultMap &results, TimeRunner &tr, int prelT, int radioNumber, const wstring &rname, int radioId) { TempResultMap::iterator place_it = results.lower_bound(prelT); int p = place_it != results.end() ? place_it->second.place : results.size() + 1; int prio = tr.runner->getSpeakerPriority(); if ((radioNumber == 0 && prio > 0) || (radioNumber > 0 && p <= 10) || (radioNumber > 0 && prio > 0 && p <= 20)) { wstring msg; if (radioNumber > 0) { if (p == 1) msg = L"väntas till X om någon minut, och kan i så fall ta ledningen.#" + rname; else msg = L"väntas till X om någon minut, och kan i så fall ta en Y plats.#" + rname + getOrder(p); } else msg = L"väntas till X om någon minut.#" + rname; oTimeLine::Priority mp = oTimeLine::PMedium; if (p <= (3 + prio * 3)) mp = oTimeLine::PHigh; else if (p>6 + prio * 5) mp = oTimeLine::PLow; oTimeLine tl(tr.time, oTimeLine::TLTExpected, mp, tr.runner->getClassId(true), radioId, tr.runner); TimeLineIterator tlit = timeLineEvents.insert(pair(tl.getTime(), tl)); tlit->second.setMessage(msg); } } int oEvent::setupTimeLineEvents(vector &started, const vector< pair > &rc, int currentTime, bool finish) { int nextKnownEvent = 48*3600; vector< vector > radioResults(rc.size()); vector bestLegTime(rc.size() + 1); vector bestTotalTime(rc.size() + 1); vector bestRaceTime(rc.size()); for (size_t j = 0; j < rc.size(); j++) { int id = rc[j].first; vector &radio = radioResults[j]; radio.reserve(started.size()); for (size_t k = 0; k < started.size(); k++) { int rt; oRunner &r = *started[k]; RunnerStatus rs; r.getSplitTime(id, rs, rt); if (rs == StatusOK) radio.push_back(TimeRunner(rt + r.tStartTime, &r)); else radio.push_back(TimeRunner(0, &r)); if (rt > 0) { bestTotalTime[j].addTime(r.getTotalRunningTime(rt + r.tStartTime, true)); bestRaceTime[j].addTime(rt); // Calculate leg time since last radio (or start) int lt = 0; if (j == 0) lt = rt; else if (radioResults[j-1][k].time>0) lt = rt + r.tStartTime - radioResults[j-1][k].time; if (lt>0) bestLegTime[j].addTime(lt); if (j == rc.size()-1 && r.FinishTime>0 && r.tStatus == StatusOK) { // Get best total time bestTotalTime[j+1].addTime(r.getTotalRunningTime(r.FinishTime, true)); // Calculate best time from last radio to finish int ft = r.FinishTime - (rt + r.tStartTime); if (ft > 0) bestLegTime[j+1].addTime(ft); } } } } // Relative speed of a runner vector relSpeed(started.size(), 1.0); // Calculate relative speeds for (size_t k = 0; k < started.size(); k++) { size_t j = 0; int j_radio = -1; int time = 0; while(j < rc.size() && (radioResults[j][k].time > 0 || j_radio == -1)) { if (radioResults[j][k].time > 0) { j_radio = j; time = radioResults[j][k].time - radioResults[j][k].runner->tStartTime; } j++; } if (j_radio >= 0) { int reltime = bestRaceTime[j_radio].getBestTime(); if (reltime == time) reltime = bestRaceTime[j_radio].getSecondBestTime(); relSpeed[k] = double(time) / double(reltime); } } vector< vector > expectedAtNext(rc.size() + 1); // Time before expection to "prewarn" const int pwTime = 60; // First radio int bestLeg = bestLegTime[0].getBestTime(); if (bestLeg > 0 && bestLeg != BestTime::maxtime){ vector &radio = radioResults[0]; vector &expectedRadio = expectedAtNext[0]; expectedRadio.reserve(radio.size()); for (size_t k = 0; k < radio.size(); k++) { oRunner &r = *radio[k].runner; int expected = r.tStartTime + bestLeg; int actual = radio[k].time; if ( (actual == 0 && (expected - pwTime) < currentTime) || (actual > (expected - pwTime)) ) { expectedRadio.push_back(TimeRunner(expected-pwTime, &r)); } } } // Remaining radios and finish for (size_t j = 0; j < rc.size(); j++) { bestLeg = bestLegTime[j+1].getBestTime(); if (bestLeg == 0 || bestLeg == BestTime::maxtime) continue; vector &radio = radioResults[j]; vector &expectedRadio = expectedAtNext[j+1]; expectedRadio.reserve(radio.size()); for (size_t k = 0; k < radio.size(); k++) { oRunner &r = *radio[k].runner; if (radio[k].time > 0) { int expected = radio[k].time + int(bestLeg * relSpeed[k]); int actual = 0; if (j + 1 < rc.size()) actual = radioResults[j+1][k].time; else actual = r.FinishTime; if ( (actual == 0 && (expected - pwTime) <= currentTime) || (actual > (expected - pwTime)) ) { expectedRadio.push_back(TimeRunner(expected-pwTime, &r)); } else if (actual == 0 && (expected - pwTime) > currentTime) { nextKnownEvent = min(nextKnownEvent, expected - pwTime); } } } } vector timeAtRadio(rc.size()); int numbered = 1; for (size_t j = 0; j < rc.size(); j++) { TempResultMap &results = timeAtRadio[j]; vector &radio = radioResults[j]; vector &expectedRadio = expectedAtNext[j]; wstring rname;// = ->getRadioName(rc[j].first); if (rc[j].second->Name.empty()) rname = lang.tl("radio X#" + itos(numbered++)) + L"#"; else { if (rc[j].second->tNumberDuplicates > 1) rname = rc[j].second->Name + L"-" + itow(numbered++) + L"#"; else rname = rc[j].second->Name + L"#"; } // Sort according to pass time sort(radio.begin(), radio.end()); sort(expectedRadio.begin(), expectedRadio.end()); size_t expIndex = 0; for (size_t k = 0; k < radio.size(); k++) { RunnerStatus ts = radio[k].runner->getTotalStatus(); if (ts == StatusMP || ts == StatusDQ) continue; if (radio[k].time > 0) { while (expIndex < expectedRadio.size() && expectedRadio[expIndex].time < radio[k].time) { TimeRunner &tr = expectedRadio[expIndex]; int prelT = tr.runner->getTotalRunningTime(tr.time + pwTime, true); timeLinePrognose(results, tr, prelT, j, rname, rc[j].first); expIndex++; } oRunner &r = *radio[k].runner; int place = 1; bool sharedPlace = false; vector preRunners; int time = radio[k].time - r.tStartTime; int totTime = r.getTotalRunningTime(radio[k].time, true); insertResult(results, r, totTime, place, sharedPlace, preRunners); int leaderTime = results.begin()->first; int timeAfter = totTime - leaderTime; int deltaTime = timeAfter - r.tTimeAfter; wstring timeS = formatTime(time); wstring msg, detail; getTimeAfterDetail(detail, timeAfter, deltaTime, r.tTimeAfter > 0); r.tTimeAfter = timeAfter; if (place == 1) { if (results.size() == 1) msg = L"är först vid X med tiden Y.#" + rname + timeS; else if (!sharedPlace) msg = L"tar ledningen vid X med tiden Y.#" + rname + timeS; else msg = L"går upp i delad ledning vid X med tiden Y.#" + rname + timeS; } else { if (!sharedPlace) { msg = L"stämplar vid X som Y, på tiden Z.#" + rname + getNumber(place) + L"#" + timeS; } else { msg = L"stämplar vid X som delad Y med tiden Z.#" + rname + getNumber(place) + L"#" + timeS; } } oTimeLine::Priority mp = oTimeLine::PLow; if (place <= 2) mp = oTimeLine::PTop; else if (place <= 5) mp = oTimeLine::PHigh; else if (place <= 10) mp = oTimeLine::PMedium; oTimeLine tl(radio[k].time, oTimeLine::TLTRadio, mp, r.getClassId(true), rc[j].first, &r); TimeLineIterator tlit = timeLineEvents.insert(pair(tl.getTime(), tl)); tlit->second.setMessage(msg).setDetail(detail); } } } TempResultMap results; sort(started.begin(), started.end(), compareFinishTime); wstring location, locverb, thelocation; if (finish) { location = L"i mål"; locverb = L"går i mål"; thelocation = lang.tl(L"målet") + L"#"; } else { location = L"vid växeln"; locverb = L"växlar"; thelocation = lang.tl(L"växeln") + L"#"; } vector &expectedFinish = expectedAtNext.back(); // Sort according to pass time sort(expectedFinish.begin(), expectedFinish.end()); size_t expIndex = 0; for (size_t k = 0; kgetTotalRunningTime(tr.time + pwTime, true); timeLinePrognose(results, tr, prelT, 1, thelocation, 0); expIndex++; } int place = 1; bool sharedPlace = false; vector preRunners; int time = r.getTotalRunningTime(r.FinishTime, true); insertResult(results, r, time, place, sharedPlace, preRunners); int leaderTime = results.begin()->first; int timeAfter = time - leaderTime; int deltaTime = timeAfter - r.tInitialTimeAfter; wstring timeS = formatTime(time); wstring msg, detail; getTimeAfterDetail(detail, timeAfter, deltaTime, r.tInitialTimeAfter > 0); // Transfer time after to next runner if (r.tInTeam) { pRunner next = r.tInTeam->getRunner(r.tLeg + 1); if (next) { next->tTimeAfter = timeAfter; next->tInitialTimeAfter = timeAfter; } } if (place == 1) { if (results.size() == 1) msg = L"är först " + location + L" med tiden X.#" + timeS; else if (!sharedPlace) msg = L"tar ledningen med tiden X.#" + timeS; else msg = L"går upp i delad ledning med tiden X.#" + timeS; } else { if (!sharedPlace) { if (preRunners.size() == 1 && place < 10) msg = locverb + L" på X plats, efter Y, på tiden Z.#" + getOrder(place) + L"#" + preRunners[0]->getCompleteIdentification() + L"#" + timeS; else msg = locverb + L" på X plats med tiden Y.#" + getOrder(place) + L"#" + timeS; } else { msg = locverb + L" på delad X plats med tiden Y.#" + getOrder(place) + L"#" + timeS; } } oTimeLine::Priority mp = oTimeLine::PLow; if (place <= 5) mp = oTimeLine::PTop; else if (place <= 10) mp = oTimeLine::PHigh; else if (place <= 20) mp = oTimeLine::PMedium; oTimeLine tl(r.FinishTime, oTimeLine::TLTFinish, mp, r.getClassId(true), r.getId(), &r); TimeLineIterator tlit = timeLineEvents.insert(pair(tl.getTime(), tl)); tlit->second.setMessage(msg).setDetail(detail); } else if (r.getStatus() != StatusUnknown && r.getStatus() != StatusOK) { int t = r.FinishTime; if ( t == 0) t = r.tStartTime; int place = 1000; if (r.FinishTime > 0) { place = results.size() + 1; int rt = r.getTotalRunningTime(r.FinishTime, true); if (rt > 0) { TempResultMap::iterator place_it = results.lower_bound(rt); place = place_it != results.end() ? place_it->second.place : results.size() + 1; } } oTimeLine::Priority mp = r.getSpeakerPriority() > 0 ? oTimeLine::PMedium : oTimeLine::PLow; if (place <= 3) mp = oTimeLine::PTop; else if (place <= 10) mp = oTimeLine::PHigh; else if (place <= 20) mp = oTimeLine::PMedium; oTimeLine tl(r.FinishTime, oTimeLine::TLTFinish, mp, r.getClassId(true), r.getId(), &r); TimeLineIterator tlit = timeLineEvents.insert(pair(t, tl)); wstring msg; if (r.getStatus() != StatusDQ) msg = L"är inte godkänd."; else msg = L"är diskvalificerad."; tlit->second.setMessage(msg); } } return nextKnownEvent; } void oEvent::renderTimeLineEvents(gdioutput &gdi) const { gdi.fillDown(); for (TimeLineMap::const_iterator it = timeLineEvents.begin(); it != timeLineEvents.end(); ++it) { int t = it->second.getTime(); wstring msg = t>0 ? getAbsTime(t) : makeDash(L"--:--:--"); oAbstractRunner *r = it->second.getSource(*this); if (r) { msg += L" (" + r->getClass(true) + L") "; msg += r->getName() + L", " + r->getClub(); } msg += L", " + lang.tl(it->second.getMessage()); gdi.addStringUT(0, msg); } } int oEvent::getTimeLineEvents(const set &classes, vector &events, set<__int64> &stored, int currentTime) { if (currentTime == 0) { updateComputerTime(); currentTime = getComputerTime(); } //OutputDebugString(("GetTimeLine at: " + getAbsTime(getComputerTime()) + "\n").c_str()); const int timeWindowSize = 10*60; int eval = nextTimeLineEvent <= getComputerTime() + 1; for (set::const_iterator it = classes.begin(); it != classes.end(); ++it) { if (timelineClasses.count(*it) == 0) { timelineClasses.insert(*it); eval = true; } if (modifiedClasses.count(*it) != 0) eval = true; } if (eval) { OutputDebugStringA("SetupTimeLine\n"); nextTimeLineEvent = setupTimeLineEvents(currentTime); } // else // OutputDebugString("No change\n"); int time = 0; for (int k = events.size()-1; k>=0; k--) { int t = events[k].getTime(); if (t > 0) { time = t; break; } } TimeLineMap::const_iterator it_start = timeLineEvents.lower_bound(time - timeWindowSize); bool filter = !events.empty(); if (filter) { for (TimeLineMap::const_iterator it = it_start; it != timeLineEvents.end(); ++it) { } } for (TimeLineMap::const_iterator it = it_start; it != timeLineEvents.end(); ++it) { if (it->first <= time && it->second.getType() == oTimeLine::TLTExpected) continue; // Never get old expectations if (classes.count(it->second.getClassId()) == 0) continue; __int64 tag = it->second.getTag(); if (stored.count(tag) == 0) { events.push_back(it->second); stored.insert(tag); } } return nextTimeLineEvent; } void oEvent::classChanged(pClass cls, bool punchOnly) { if (timelineClasses.count(cls->getId()) == 1) { modifiedClasses.insert(cls->getId()); timeLineEvents.clear(); } } static bool orderByTime(const oEvent::ResultEvent &a, const oEvent::ResultEvent &b) { if (a.classId() != b.classId()) return a.classId() < b.classId(); // Sort first by class. return a.time < b.time; } struct LegSetupInfo { int baseLeg; int parCount; bool optional; LegSetupInfo():baseLeg(-1), parCount(0), optional(false) {} LegSetupInfo(int bl, bool opt):baseLeg(bl), parCount(0), optional(opt) {} }; struct TeamLegControl { int teamId; int leg; int control; TeamLegControl() : teamId(0), leg(0), control(0) {} TeamLegControl(int id, int leg, int ctrl) : teamId(id), leg(leg), control(ctrl) {} bool operator<(const TeamLegControl &tlc) const { if (teamId != tlc.teamId) return teamId < tlc.teamId; else if (leg != tlc.leg) return leg < tlc.leg; else return control < tlc.control; } }; void oEvent::getResultEvents(const set &classFilter, const set &punchFilter, vector &results) const { results.clear(); vector teamLegStatusOK; teamLegStatusOK.reserve(Teams.size() * 5); map teamStatusPos; for (oTeamList::const_iterator it = Teams.begin(); it != Teams.end(); ++it) { if (!classFilter.count(it->getClassId(false))) continue; int base = teamLegStatusOK.size(); teamStatusPos[it->getId()] = base; int nr = it->getNumRunners(); bool ok = it->getStatus() == StatusOK || it->getStatus() == StatusUnknown; for(int k = 0; k < nr; k++) { pRunner r = it->getRunner(k); if (r && r->getStatus() != StatusUnknown && r->getStatus() != StatusOK) ok = false; teamLegStatusOK.push_back(ok ? StatusOK : StatusUnknown); } if (!ok) { // A more careful analysis for(int k = 0; k < nr; k++) { teamLegStatusOK[base + k] = it->getLegStatus(k, true); } } } for (oRunnerList::const_iterator it = Runners.begin(); it != Runners.end(); ++it) { const oRunner &r = *it; if (r.isRemoved() || !classFilter.count(r.getClassId(true))) continue; if (r.prelStatusOK() || r.getStatus() != StatusUnknown) { RunnerStatus stat = r.prelStatusOK() ? StatusOK : r.getStatus(); results.push_back(ResultEvent(pRunner(&r), r.getFinishTime(), oPunch::PunchFinish, stat)); } pCard card = r.getCard(); RunnerStatus punchStatus = StatusOK; if (r.tInTeam && r.tLeg > 0) { map::iterator res = teamStatusPos.find(r.tInTeam->getId()); if (res != teamStatusPos.end()) { RunnerStatus prevStat = teamLegStatusOK[res->second + r.tLeg - 1]; if (prevStat != StatusOK && prevStat != StatusUnknown) { results.back().status = StatusNotCompetiting; punchStatus = StatusNotCompetiting; } } } if (card) { oPunchList::const_iterator it; map dupCount; for (it = card->punches.begin(); it != card->punches.end(); ++it) { if (punchFilter.count(it->tMatchControlId)) { int dupC = ++dupCount[it->tMatchControlId]; int courseControlId = oControl::getCourseControlIdFromIdIndex(it->tMatchControlId, dupC-1); results.push_back(ResultEvent(pRunner(&r), it->getAdjustedTime(), courseControlId, punchStatus)); } } } } for (oFreePunchList::const_iterator it = punches.begin(); it != punches.end(); ++it) { const oFreePunch &fp = *it; if (fp.isRemoved() || fp.tRunnerId == 0 || fp.Type == oPunch::PunchCheck || fp.Type == oPunch::PunchStart) continue; pRunner r = getRunner(fp.tRunnerId, 0); if (r == 0 || !classFilter.count(r->getClassId(true)) || r->getCard()) continue; int courseControlId = oFreePunch::getControlIdFromHash(fp.iHashType, true); int ctrl = oControl::getIdIndexFromCourseControlId(courseControlId).first; if (!punchFilter.count(ctrl)) continue; results.push_back(ResultEvent(r, fp.Time, courseControlId, StatusOK)); if (r->tInTeam && r->tLeg > 0) { map::iterator res = teamStatusPos.find(r->tInTeam->getId()); if (res != teamStatusPos.end()) { RunnerStatus prevStat = teamLegStatusOK[res->second + r->tLeg - 1]; if (prevStat != StatusOK && prevStat != StatusUnknown) { results.back().status = StatusNotCompetiting; } } } } for (map, oFreePunch>::const_iterator it = advanceInformationPunches.begin(); it != advanceInformationPunches.end(); ++it) { const oFreePunch &fp = it->second; if (fp.isRemoved() || fp.tRunnerId == 0 || fp.Type == oPunch::PunchCheck || fp.Type == oPunch::PunchStart) continue; pRunner r = getRunner(fp.tRunnerId, 0); if (r == 0 || !classFilter.count(r->getClassId(true))) continue; int courseControlId = oFreePunch::getControlIdFromHash(fp.iHashType, true); int ctrl = oControl::getIdIndexFromCourseControlId(courseControlId).first; if (!punchFilter.count(ctrl)) continue; results.push_back(ResultEvent(r, fp.Time, courseControlId, StatusOK)); if (r->tInTeam && r->tLeg > 0) { map::iterator res = teamStatusPos.find(r->tInTeam->getId()); if (res != teamStatusPos.end()) { RunnerStatus prevStat = teamLegStatusOK[res->second + r->tLeg - 1]; if (prevStat != StatusOK && prevStat != StatusUnknown) { results.back().status = StatusNotCompetiting; } } } } map< int, vector > parLegSetup; for (oClassList::const_iterator it = Classes.begin(); it != Classes.end(); ++it) { if (!classFilter.count(it->getId())) continue; int ns = it->getNumStages(); if (ns == 1) continue; int baseLeg = 0; for (int i = 0; i < ns; i++) { bool optional = it->legInfo[i].isOptional(); bool par = it->legInfo[i].isParallel(); if (optional || par) { vector &ls = parLegSetup[it->getId()]; ls.resize(ns, LegSetupInfo()); ls[i] = LegSetupInfo(baseLeg, optional); if (i > 0) ls[i-1] = ls[i]; } else { baseLeg = i; } } } if (parLegSetup.empty()) return; for (map< int, vector >::iterator it = parLegSetup.begin(); it != parLegSetup.end(); ++it) { vector &setup = it->second; size_t sx = -1; for (size_t k = 0; k < setup.size(); k++) { if (setup[k].baseLeg == -1) { if (sx != -1) { int count = k - sx; while (sx < k) { setup[sx++].parCount = count; } } sx = -1; } else if (sx == -1) { sx = k; } } if (sx != -1) { int count = setup.size() - sx; while (sx < setup.size()) { setup[sx++].parCount = count; } } } sort(results.begin(), results.end(), orderByTime); map countTeamLeg; for (size_t k = 0; k < results.size(); k++) { int clsId = results[k].classId(); map< int, vector >::iterator res = parLegSetup.find(clsId); if (res == parLegSetup.end()) continue; const vector &setup = res->second; int leg = results[k].r->getLegNumber(); if (setup[leg].baseLeg == -1) { results[k].legNumber = leg; continue; } if (results[k].status != StatusOK) continue; results[k].legNumber = setup[leg].baseLeg; int teamId = results[k].r->getTeam()->getId(); int val = ++countTeamLeg[TeamLegControl(teamId, setup[leg].baseLeg, results[k].control)]; if (setup[leg].optional) { results[k].partialCount = val - 1; } else { const int numpar = setup[leg].parCount; // XXX Count legs results[k].partialCount = numpar - val; } } }