/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2007-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2010-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // player info attribute conflict resolving // e.g., changing colors if two players have the same // "There must be some easier way to do it"(tm) #include "C4Include.h" #include "control/C4PlayerInfo.h" #include "game/C4Game.h" #include "control/C4Teams.h" #include "lib/StdColors.h" #include "lib/C4Random.h" // number of times trying new player colors const int32_t C4MaxPlayerColorChangeTries = 100; const int32_t C4MaxPlayerNameChangeTries = 100; // *** Helpers DWORD GenerateRandomPlayerColor(int32_t iTry) // generate a random player color for the iTry'th try { // generate a random one biased towards max channel luminance // (for greater color difference and less gray-ish colors) return C4RGB(std::min(SafeRandom(302), 256), std::min(SafeRandom(302), 256), std::min(SafeRandom(302), 256)); } bool IsColorConflict(DWORD dwClr1, DWORD dwClr2) // return whether dwClr1 and dwClr2 are closely together { // NEW COLOR CONFLICT METHOD: u'v'-distance int R1 = 0xff & (dwClr1 >> 16); int G1 = 0xff & (dwClr1 >> 8); int B1 = 0xff & (dwClr1 ); int R2 = 0xff & (dwClr2 >> 16); int G2 = 0xff & (dwClr2 >> 8); int B2 = 0xff & (dwClr2 ); double r1=0,g1=0,b1=0,r2=0,g2=0,b2=0,x1=0,y1=0,Y1=0,x2=0,y2=0,Y2=0,u1=0,v1=0,u2=0,v2=0; RGB2rgb(R1, G1, B1, &r1, &g1, &b1); RGB2rgb(R2, G2, B2, &r2, &g2, &b2); rgb2xyY(r1, g1, b1, &x1, &y1, &Y1); rgb2xyY(r2, g2, b2, &x2, &y2, &Y2); xy2upvp(x1, y1, &u1, &v1); xy2upvp(x2, y2, &u2, &v2); double Y = (Y1+Y2)/2.0; double clrdiff = sqrt((u2-u1)*(u2-u1) + (v2-v1)*(v2-v1)) * Y*Y * 150; double lumdiff = (Abs(Y2-Y1) / std::max(Y*Y*5, 0.5)) / 0.10; return clrdiff + lumdiff < 1.0; } // conflict resolver class C4PlayerInfoListAttributeConflictResolver { private: // info packets that need to be checked yet // info packets sorted by check priority, i.e. last ones checked first C4ClientPlayerInfos **ppCheckInfos; int32_t iCheckInfoCount; // lists to be checked against const C4PlayerInfoList &rPriCheckList, &rSecCheckList; C4ClientPlayerInfos *pSecPacket; // current attribute being checked C4PlayerInfo::Attribute eAttr; // handling of current packet to be adjusted C4ClientPlayerInfos *pResolvePacket; bool fAnyChange; // handling of current info to be adjusted C4PlayerInfo *pResolveInfo; bool fCurrentConflict, fOriginalConflict, fAlternateConflict; C4ClientPlayerInfos *pLowPrioOriginalConflictPacket, *pLowPrioAlternateConflictPacket; public: C4PlayerInfoListAttributeConflictResolver(C4PlayerInfoList &rPriCheckList, const C4PlayerInfoList &rSecCheckList, C4ClientPlayerInfos *pSecPacket); ~C4PlayerInfoListAttributeConflictResolver(); private: void ReaddInfoForCheck(C4ClientPlayerInfos *pCheckAdd); // temp readd info to be checked, because another packet reset its attribute with higher priority int32_t GetAttributePriorityDifference(const C4PlayerInfo *pInfo1, const C4ClientPlayerInfos *pPck1, const C4PlayerInfo *pInfo2, const C4ClientPlayerInfos *pPck2); bool IsAttributeConflict(const C4PlayerInfo *pInfo1, const C4PlayerInfo *pInfo2, C4PlayerInfo::AttributeLevel eLevel); void MarkConflicts(C4ClientPlayerInfos &rCheckPacket, bool fTestOriginal); void MarkConflicts(const C4PlayerInfoList &rCheckList, bool fTestOriginal); void ResolveInInfo(); void ResolveInPacket(); public: void Resolve(); // action go! }; // resolve function calling resolver void C4PlayerInfoList::ResolvePlayerAttributeConflicts(C4ClientPlayerInfos *pSecPacket) { C4PlayerInfoListAttributeConflictResolver r(*this, Game.RestorePlayerInfos, pSecPacket); r.Resolve(); } // implementation of conflict resolver C4PlayerInfoListAttributeConflictResolver::C4PlayerInfoListAttributeConflictResolver(C4PlayerInfoList &rPriCheckList, const C4PlayerInfoList &rSecCheckList, C4ClientPlayerInfos *pSecPacket) : ppCheckInfos(NULL), iCheckInfoCount(0), rPriCheckList(rPriCheckList), rSecCheckList(rSecCheckList), pSecPacket(pSecPacket) { // prepare check array int32_t iMaxCheckCount = rPriCheckList.GetInfoCount() + !!pSecPacket; if (!iMaxCheckCount) return; ppCheckInfos = new C4ClientPlayerInfos *[iMaxCheckCount]; // resolve all primary packets in reverse order, so late clients have lower priority for (int32_t i=0; ipSecPacket = NULL; } } // must check sec packet first if (pSecPacket) ppCheckInfos[iCheckInfoCount++] = pSecPacket; } C4PlayerInfoListAttributeConflictResolver::~C4PlayerInfoListAttributeConflictResolver() { delete [] ppCheckInfos; } void C4PlayerInfoListAttributeConflictResolver::ReaddInfoForCheck(C4ClientPlayerInfos *pCheckAdd) { // do not add twice< for (int32_t i=0; iIsJoined()) return pInfo2->IsJoined() ? 0 : +1; if (pInfo2->IsJoined()) return -1; // Computer players lower priority than user players int32_t iTypeDiff = int(pInfo2->GetType()) - int(pInfo1->GetType()); if (iTypeDiff) return iTypeDiff; // All unjoined: Priority by score int32_t iScoreDiff = pInfo1->getLeagueScore() - pInfo2->getLeagueScore(); if (iScoreDiff) return iScoreDiff; // No scores known. Developers higher than others if (pPck1->IsDeveloperPacket()) return pPck2->IsDeveloperPacket() ? 0 : +1; if (pPck2->IsDeveloperPacket()) return -1; // equal priority return 0; } bool C4PlayerInfoListAttributeConflictResolver::IsAttributeConflict(const C4PlayerInfo *pInfo1, const C4PlayerInfo *pInfo2, C4PlayerInfo::AttributeLevel eLevel) { // check for conflict of colors and names if (eAttr == C4PlayerInfo::PLRATT_Color) { uint32_t dwClr1 = pInfo1->GetColor(), dwClr2 = 0; switch (eLevel) { case C4PlayerInfo::PLRAL_Current: dwClr2 = pInfo2->GetColor(); break; case C4PlayerInfo::PLRAL_Original: dwClr2 = pInfo2->GetOriginalColor(); break; case C4PlayerInfo::PLRAL_Alternate: dwClr2 = pInfo2->GetAlternateColor(); break; } return IsColorConflict(dwClr1, dwClr2); } else if (eAttr == C4PlayerInfo::PLRATT_Name) { const char *szName1 = pInfo1->GetName(), *szName2 = ""; switch (eLevel) { case C4PlayerInfo::PLRAL_Current: szName2 = pInfo2->GetName(); break; case C4PlayerInfo::PLRAL_Original: szName2 = pInfo2->GetOriginalName(); break; default: return SEqualNoCase(szName1, szName2); } } return false; } void C4PlayerInfoListAttributeConflictResolver::MarkConflicts(C4ClientPlayerInfos &rCheckPacket, bool fTestOriginal) { C4PlayerInfo *pCheckAgainstInfo; // check current and original attribute against all player infos for (int32_t j=0; (pCheckAgainstInfo = rCheckPacket.GetPlayerInfo(j)); ++j) { if (pCheckAgainstInfo->IsUsingAttribute(eAttr)) if (!pResolveInfo->GetID() || pResolveInfo->GetID() != pCheckAgainstInfo->GetID()) if (pResolveInfo != pCheckAgainstInfo) { // current conflict is marked only if the checked packet has same of lower priority than the one compared to // if the priority is higher, the attribute shall be changed in the other, low priority info instead! bool fHasHigherPrio = (GetAttributePriorityDifference(pResolveInfo, pResolvePacket, pCheckAgainstInfo, &rCheckPacket) > 0); if (!fHasHigherPrio) if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Current)) fCurrentConflict = true; if (fTestOriginal) { if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Original)) { if (fHasHigherPrio && !fOriginalConflict && !pLowPrioOriginalConflictPacket) { // original attribute is taken by a low prio packet - do not mark an original conflict, but remember the packet // that's blocking it pLowPrioOriginalConflictPacket = &rCheckPacket; } else { // original attribute is taken by either one higher/equal priority by packet, or by two low prio packets // in this case, don't revert to original pLowPrioOriginalConflictPacket = NULL; fOriginalConflict = true; } } if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Alternate)) { if (fHasHigherPrio && !fAlternateConflict && !pLowPrioAlternateConflictPacket) pLowPrioAlternateConflictPacket = &rCheckPacket; else fAlternateConflict = true; } } } } } void C4PlayerInfoListAttributeConflictResolver::MarkConflicts(const C4PlayerInfoList &rCheckList, bool fTestOriginal) { // mark in all infos of given list... for (int32_t i=0; iGetAlternateColor(); // mark as conflict if there is no alternate color/name for (;;) { // check against all other player infos, and given info, too (may be redundant) fCurrentConflict = false; pLowPrioOriginalConflictPacket = pLowPrioAlternateConflictPacket = NULL; MarkConflicts(rPriCheckList, !iTries); // check secondary list, too. But only for colors, not for names, because secondary list is Restore list // and colors are retained in restore while names are always taken from new joins if (eAttr != C4PlayerInfo::PLRATT_Name) MarkConflicts(rSecCheckList, !iTries); // and mark conflicts in additional packet that' sbeen passed if (pSecPacket) MarkConflicts(*pSecPacket, !iTries); // color conflict resolving if (eAttr == C4PlayerInfo::PLRATT_Color) { // original color free but not used? if (!iTries) { if (pResolveInfo->GetColor() != pResolveInfo->GetOriginalColor()) { if (!fOriginalConflict) { // revert to original color! pResolveInfo->SetColor(pResolveInfo->GetOriginalColor()); // in case a lower priority packet was blocking the attribute, re-check that packet // note that the may readd the current resolve packet, but the conflict will occur with the // lower priority packet in the next loop if (pLowPrioOriginalConflictPacket) ReaddInfoForCheck(pLowPrioOriginalConflictPacket); // done with this player (breaking the trial-loop) break; } // neither original nor alternate color used but alternate color free? else if (pResolveInfo->GetColor() != pResolveInfo->GetAlternateColor() && !fAlternateConflict) { // revert to alternate pResolveInfo->SetColor(pResolveInfo->GetAlternateColor()); if (pLowPrioAlternateConflictPacket) ReaddInfoForCheck(pLowPrioAlternateConflictPacket); // done with this player (breaking the trial-loop) break; } } } // conflict found? if (!fCurrentConflict) // done with this player, then - break the trial-loop break; // try to get a new, unused player color uint32_t dwNewClr; if (++iTries > C4MaxPlayerColorChangeTries) { LogF(LoadResStr("IDS_PRC_NOREPLPLRCLR"), pResolveInfo->GetName() ? pResolveInfo->GetName() : ""); // since there's a conflict anyway, change to original pResolveInfo->SetColor(pResolveInfo->GetOriginalColor()); break; } else dwNewClr = GenerateRandomPlayerColor(iTries); pResolveInfo->SetColor(dwNewClr); } else // if (eAttr == PLRATT_Name) { // name conflict resolving // original name free but not used? if (!SEqualNoCase(pResolveInfo->GetName(), pResolveInfo->GetOriginalName())) if (!fOriginalConflict) { // revert to original name! pResolveInfo->SetForcedName(NULL); if (pLowPrioOriginalConflictPacket) ReaddInfoForCheck(pLowPrioOriginalConflictPacket); // done with this player (breaking the trial-loop) break; } // conflict found? if (!fCurrentConflict) // done with this player, then - break the trial-loop break; // generate new name by appending an index if (++iTries > C4MaxPlayerNameChangeTries) break; pResolveInfo->SetForcedName(FormatString("%s (%d)", pResolveInfo->GetOriginalName(), iTries+1).getData()); } } } void C4PlayerInfoListAttributeConflictResolver::ResolveInPacket() { // check all player infos fAnyChange = false; int32_t iCheck = 0; while ((pResolveInfo = pResolvePacket->GetPlayerInfo(iCheck++))) { // not already joined? Joined player must not change their attributes! if (pResolveInfo->HasJoined()) continue; DWORD dwPrevColor = pResolveInfo->GetColor(); StdStrBuf sPrevForcedName; sPrevForcedName.Copy(pResolveInfo->GetForcedName()); // check attributes: Name and color for (eAttr = C4PlayerInfo::PLRATT_Color; eAttr != C4PlayerInfo::PLRATT_Last; eAttr = (C4PlayerInfo::Attribute) (eAttr+1)) { if (eAttr == C4PlayerInfo::PLRATT_Color) { // no color change in savegame associations if (pResolveInfo->GetAssociatedSavegamePlayerID()) continue; // or forced team colors if (Game.Teams.IsTeamColors() && Game.Teams.GetTeamByID(pResolveInfo->GetTeam())) continue; } else if (eAttr == C4PlayerInfo::PLRATT_Name) { // no name change if a league name is used if (pResolveInfo->getLeagueAccount() && *pResolveInfo->getLeagueAccount()) continue; } // not if attributes are otherwise fixed (e.g., for script players) if (pResolveInfo->IsAttributesFixed()) continue; // resolve in this info ResolveInInfo(); } // mark change for return value if anything was changed if (pResolveInfo->GetColor() != dwPrevColor || (pResolveInfo->GetForcedName() != sPrevForcedName)) fAnyChange = true; // next player info check } // mark update if anything was changed if (fAnyChange) pResolvePacket->SetUpdated(); } void C4PlayerInfoListAttributeConflictResolver::Resolve() { // resolve in all packets in list until list is empty // resolving in reverse order, because highest priority first in the list while (iCheckInfoCount) { pResolvePacket = ppCheckInfos[--iCheckInfoCount]; ResolveInPacket(); } }