openclonk/src/control/C4PlayerInfoConflicts.cpp

391 lines
15 KiB
C++

/*
* 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<int>(SafeRandom(302), 256), std::min<int>(SafeRandom(302), 256), std::min<int>(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<double>(Y2-Y1) / std::max<double>(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; i<rPriCheckList.GetInfoCount(); ++i)
{
C4ClientPlayerInfos *pInfos = rPriCheckList.GetIndexedInfo(i);
if (pInfos != pSecPacket) // skip packet if it was explicitly passed for update
ppCheckInfos[iCheckInfoCount++] = pInfos;
else
{
// if the additional packet is in the list already, it needn't be a check packed (only resolve packet)
this->pSecPacket = 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; i<iCheckInfoCount; ++i) if (ppCheckInfos[i] == pCheckAdd) return;
// readd it - must have been in there before, so array is large enough
// and it must have been at the head of the list
ppCheckInfos[iCheckInfoCount++] = pCheckAdd;
}
int32_t C4PlayerInfoListAttributeConflictResolver::GetAttributePriorityDifference(const C4PlayerInfo *pInfo1, const C4ClientPlayerInfos *pPck1, const C4PlayerInfo *pInfo2, const C4ClientPlayerInfos *pPck2)
{
// return info1.prio - info2.prio
// highest priority if joined already and attributes may not be changed
if (pInfo1->IsJoined())
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; i<rCheckList.GetInfoCount(); ++i)
MarkConflicts(*rCheckList.GetIndexedInfo(i), fTestOriginal);
}
void C4PlayerInfoListAttributeConflictResolver::ResolveInInfo()
{
// trial-loop for assignment of new player colors/names
int32_t iTries = 0;
// original/alternate conflict evaluated once only
fOriginalConflict = false;
fAlternateConflict = (eAttr == C4PlayerInfo::PLRATT_Name) || !pResolveInfo->GetAlternateColor(); // 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() : "<NONAME>");
// 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();
}
}