OpenClonk
C4PlayerInfoConflicts.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2007-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2010-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // player info attribute conflict resolving
17 // e.g., changing colors if two players have the same
18 // "There must be some easier way to do it"(tm)
19 
20 #include "C4Include.h"
21 #include "control/C4PlayerInfo.h"
22 #include "control/C4Teams.h"
23 #include "lib/StdColors.h"
24 #include "lib/C4Random.h"
25 
26 // number of times trying new player colors
27 const int32_t C4MaxPlayerColorChangeTries = 100;
28 const int32_t C4MaxPlayerNameChangeTries = 100;
29 
30 // *** Helpers
31 
32 DWORD GenerateRandomPlayerColor(int32_t iTry) // generate a random player color for the iTry'th try
33 {
34  // generate a random one biased towards max channel luminance
35  // (for greater color difference and less gray-ish colors)
36  return C4RGB(std::min<int>(UnsyncedRandom(302), 256), std::min<int>(UnsyncedRandom(302), 256), std::min<int>(UnsyncedRandom(302), 256));
37 }
38 
39 bool IsColorConflict(DWORD dwClr1, DWORD dwClr2) // return whether dwClr1 and dwClr2 are closely together
40 {
41  // NEW COLOR CONFLICT METHOD: u'v'-distance
42  int R1 = 0xff & (dwClr1 >> 16);
43  int G1 = 0xff & (dwClr1 >> 8);
44  int B1 = 0xff & (dwClr1 );
45  int R2 = 0xff & (dwClr2 >> 16);
46  int G2 = 0xff & (dwClr2 >> 8);
47  int B2 = 0xff & (dwClr2 );
48  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;
49  RGB2rgb(R1, G1, B1, &r1, &g1, &b1);
50  RGB2rgb(R2, G2, B2, &r2, &g2, &b2);
51  rgb2xyY(r1, g1, b1, &x1, &y1, &Y1);
52  rgb2xyY(r2, g2, b2, &x2, &y2, &Y2);
53  xy2upvp(x1, y1, &u1, &v1);
54  xy2upvp(x2, y2, &u2, &v2);
55  double Y = (Y1+Y2)/2.0;
56  double clrdiff = sqrt((u2-u1)*(u2-u1) + (v2-v1)*(v2-v1)) * Y*Y * 150;
57  double lumdiff = (Abs<double>(Y2-Y1) / std::max<double>(Y*Y*5, 0.5)) / 0.10;
58  return clrdiff + lumdiff < 1.0;
59 }
60 
61 
62 // conflict resolver
64 {
65 
66 private:
67  // info packets that need to be checked yet
68  // info packets sorted by check priority, i.e. last ones checked first
69  C4ClientPlayerInfos **ppCheckInfos;
70  int32_t iCheckInfoCount;
71 
72  // lists to be checked against
73  const C4PlayerInfoList &rPriCheckList, &rSecCheckList;
74  C4ClientPlayerInfos *pSecPacket;
75 
76  // current attribute being checked
78 
79  // handling of current packet to be adjusted
80  C4ClientPlayerInfos *pResolvePacket;
81  bool fAnyChange;
82 
83  // handling of current info to be adjusted
84  C4PlayerInfo *pResolveInfo;
85  bool fCurrentConflict, fOriginalConflict, fAlternateConflict;
86  C4ClientPlayerInfos *pLowPrioOriginalConflictPacket, *pLowPrioAlternateConflictPacket;
87 
88 public:
91 
92 private:
93  void ReaddInfoForCheck(C4ClientPlayerInfos *pCheckAdd); // temp readd info to be checked, because another packet reset its attribute with higher priority
94  int32_t GetAttributePriorityDifference(const C4PlayerInfo *pInfo1, const C4ClientPlayerInfos *pPck1, const C4PlayerInfo *pInfo2, const C4ClientPlayerInfos *pPck2);
95  bool IsAttributeConflict(const C4PlayerInfo *pInfo1, const C4PlayerInfo *pInfo2, C4PlayerInfo::AttributeLevel eLevel);
96  void MarkConflicts(C4ClientPlayerInfos &rCheckPacket, bool fTestOriginal);
97  void MarkConflicts(const C4PlayerInfoList &rCheckList, bool fTestOriginal);
98  void ResolveInInfo();
99  void ResolveInPacket();
100 
101 public:
102  void Resolve(); // action go!
103 };
104 
105 // resolve function calling resolver
107 {
109  r.Resolve();
110 }
111 
112 // implementation of conflict resolver
114  : ppCheckInfos(nullptr), iCheckInfoCount(0), rPriCheckList(rPriCheckList), rSecCheckList(rSecCheckList), pSecPacket(pSecPacket)
115 {
116  // prepare check array
117  int32_t iMaxCheckCount = rPriCheckList.GetInfoCount() + !!pSecPacket;
118  if (!iMaxCheckCount) return;
119  ppCheckInfos = new C4ClientPlayerInfos *[iMaxCheckCount];
120  // resolve all primary packets in reverse order, so late clients have lower priority
121  for (int32_t i=0; i<rPriCheckList.GetInfoCount(); ++i)
122  {
123  C4ClientPlayerInfos *pInfos = rPriCheckList.GetIndexedInfo(i);
124  if (pInfos != pSecPacket) // skip packet if it was explicitly passed for update
125  ppCheckInfos[iCheckInfoCount++] = pInfos;
126  else
127  {
128  // if the additional packet is in the list already, it needn't be a check packed (only resolve packet)
129  this->pSecPacket = nullptr;
130  }
131  }
132  // must check sec packet first
133  if (pSecPacket) ppCheckInfos[iCheckInfoCount++] = pSecPacket;
134 }
135 
137 {
138  delete [] ppCheckInfos;
139 }
140 
141 void C4PlayerInfoListAttributeConflictResolver::ReaddInfoForCheck(C4ClientPlayerInfos *pCheckAdd)
142 {
143  // do not add twice<
144  for (int32_t i=0; i<iCheckInfoCount; ++i) if (ppCheckInfos[i] == pCheckAdd) return;
145  // readd it - must have been in there before, so array is large enough
146  // and it must have been at the head of the list
147  ppCheckInfos[iCheckInfoCount++] = pCheckAdd;
148 }
149 
150 int32_t C4PlayerInfoListAttributeConflictResolver::GetAttributePriorityDifference(const C4PlayerInfo *pInfo1, const C4ClientPlayerInfos *pPck1, const C4PlayerInfo *pInfo2, const C4ClientPlayerInfos *pPck2)
151 {
152  // return info1.prio - info2.prio
153  // highest priority if joined already and attributes may not be changed
154  if (pInfo1->IsJoined())
155  return pInfo2->IsJoined() ? 0 : +1;
156  if (pInfo2->IsJoined())
157  return -1;
158  // Computer players lower priority than user players
159  int32_t iTypeDiff = int(pInfo2->GetType()) - int(pInfo1->GetType());
160  if (iTypeDiff) return iTypeDiff;
161  // All unjoined: Priority by score
162  int32_t iScoreDiff = pInfo1->getLeagueScore() - pInfo2->getLeagueScore();
163  if (iScoreDiff) return iScoreDiff;
164  // No scores known. Developers higher than others
165  if (pPck1->IsDeveloperPacket())
166  return pPck2->IsDeveloperPacket() ? 0 : +1;
167  if (pPck2->IsDeveloperPacket())
168  return -1;
169  // equal priority
170  return 0;
171 }
172 
173 bool C4PlayerInfoListAttributeConflictResolver::IsAttributeConflict(const C4PlayerInfo *pInfo1, const C4PlayerInfo *pInfo2, C4PlayerInfo::AttributeLevel eLevel)
174 {
175  // check for conflict of colors and names
176  if (eAttr == C4PlayerInfo::PLRATT_Color)
177  {
178  uint32_t dwClr1 = pInfo1->GetColor(), dwClr2 = 0;
179  switch (eLevel)
180  {
181  case C4PlayerInfo::PLRAL_Current: dwClr2 = pInfo2->GetColor(); break;
182  case C4PlayerInfo::PLRAL_Original: dwClr2 = pInfo2->GetOriginalColor(); break;
183  case C4PlayerInfo::PLRAL_Alternate: dwClr2 = pInfo2->GetAlternateColor(); break;
184  }
185  return IsColorConflict(dwClr1, dwClr2);
186  }
187  else if (eAttr == C4PlayerInfo::PLRATT_Name)
188  {
189  const char *szName1 = pInfo1->GetName(), *szName2 = "";
190  switch (eLevel)
191  {
192  case C4PlayerInfo::PLRAL_Current: szName2 = pInfo2->GetName(); break;
193  case C4PlayerInfo::PLRAL_Original: szName2 = pInfo2->GetOriginalName(); break;
194  default: return SEqualNoCase(szName1, szName2);
195  }
196  }
197  return false;
198 }
199 
200 void C4PlayerInfoListAttributeConflictResolver::MarkConflicts(C4ClientPlayerInfos &rCheckPacket, bool fTestOriginal)
201 {
202  C4PlayerInfo *pCheckAgainstInfo;
203  // check current and original attribute against all player infos
204  for (int32_t j=0; (pCheckAgainstInfo = rCheckPacket.GetPlayerInfo(j)); ++j)
205  {
206  if (pCheckAgainstInfo->IsUsingAttribute(eAttr)) if (!pResolveInfo->GetID() || pResolveInfo->GetID() != pCheckAgainstInfo->GetID()) if (pResolveInfo != pCheckAgainstInfo)
207  {
208  // current conflict is marked only if the checked packet has same of lower priority than the one compared to
209  // if the priority is higher, the attribute shall be changed in the other, low priority info instead!
210  bool fHasHigherPrio = (GetAttributePriorityDifference(pResolveInfo, pResolvePacket, pCheckAgainstInfo, &rCheckPacket) > 0);
211  if (!fHasHigherPrio)
212  if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Current))
213  fCurrentConflict = true;
214  if (fTestOriginal)
215  {
216  if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Original))
217  {
218  if (fHasHigherPrio && !fOriginalConflict && !pLowPrioOriginalConflictPacket)
219  {
220  // original attribute is taken by a low prio packet - do not mark an original conflict, but remember the packet
221  // that's blocking it
222  pLowPrioOriginalConflictPacket = &rCheckPacket;
223  }
224  else
225  {
226  // original attribute is taken by either one higher/equal priority by packet, or by two low prio packets
227  // in this case, don't revert to original
228  pLowPrioOriginalConflictPacket = nullptr;
229  fOriginalConflict = true;
230  }
231  }
232  if (IsAttributeConflict(pCheckAgainstInfo, pResolveInfo, C4PlayerInfo::PLRAL_Alternate))
233  {
234  if (fHasHigherPrio && !fAlternateConflict && !pLowPrioAlternateConflictPacket)
235  pLowPrioAlternateConflictPacket = &rCheckPacket;
236  else
237  fAlternateConflict = true;
238  }
239  }
240  }
241  }
242 }
243 
244 void C4PlayerInfoListAttributeConflictResolver::MarkConflicts(const C4PlayerInfoList &rCheckList, bool fTestOriginal)
245 {
246  // mark in all infos of given list...
247  for (int32_t i=0; i<rCheckList.GetInfoCount(); ++i)
248  MarkConflicts(*rCheckList.GetIndexedInfo(i), fTestOriginal);
249 }
250 
251 void C4PlayerInfoListAttributeConflictResolver::ResolveInInfo()
252 {
253  // trial-loop for assignment of new player colors/names
254  int32_t iTries = 0;
255  // original/alternate conflict evaluated once only
256  fOriginalConflict = false;
257  fAlternateConflict = (eAttr == C4PlayerInfo::PLRATT_Name) || !pResolveInfo->GetAlternateColor(); // mark as conflict if there is no alternate color/name
258  for (;;)
259  {
260  // check against all other player infos, and given info, too (may be redundant)
261  fCurrentConflict = false;
262  pLowPrioOriginalConflictPacket = pLowPrioAlternateConflictPacket = nullptr;
263  MarkConflicts(rPriCheckList, !iTries);
264  // check secondary list, too. But only for colors, not for names, because secondary list is Restore list
265  // and colors are retained in restore while names are always taken from new joins
266  if (eAttr != C4PlayerInfo::PLRATT_Name) MarkConflicts(rSecCheckList, !iTries);
267  // and mark conflicts in additional packet that' sbeen passed
268  if (pSecPacket) MarkConflicts(*pSecPacket, !iTries);
269  // color conflict resolving
270  if (eAttr == C4PlayerInfo::PLRATT_Color)
271  {
272  // original color free but not used?
273  if (!iTries)
274  {
275  if (pResolveInfo->GetColor() != pResolveInfo->GetOriginalColor())
276  {
277  if (!fOriginalConflict)
278  {
279  // revert to original color!
280  pResolveInfo->SetColor(pResolveInfo->GetOriginalColor());
281  // in case a lower priority packet was blocking the attribute, re-check that packet
282  // note that the may readd the current resolve packet, but the conflict will occur with the
283  // lower priority packet in the next loop
284  if (pLowPrioOriginalConflictPacket) ReaddInfoForCheck(pLowPrioOriginalConflictPacket);
285  // done with this player (breaking the trial-loop)
286  break;
287  }
288  // neither original nor alternate color used but alternate color free?
289  else if (pResolveInfo->GetColor() != pResolveInfo->GetAlternateColor() && !fAlternateConflict)
290  {
291  // revert to alternate
292  pResolveInfo->SetColor(pResolveInfo->GetAlternateColor());
293  if (pLowPrioAlternateConflictPacket) ReaddInfoForCheck(pLowPrioAlternateConflictPacket);
294  // done with this player (breaking the trial-loop)
295  break;
296  }
297  }
298  }
299  // conflict found?
300  if (!fCurrentConflict)
301  // done with this player, then - break the trial-loop
302  break;
303  // try to get a new, unused player color
304  uint32_t dwNewClr;
305  if (++iTries > C4MaxPlayerColorChangeTries)
306  {
307  LogF(LoadResStr("IDS_PRC_NOREPLPLRCLR"), pResolveInfo->GetName() ? pResolveInfo->GetName() : "<NONAME>");
308  // since there's a conflict anyway, change to original
309  pResolveInfo->SetColor(pResolveInfo->GetOriginalColor());
310  break;
311  }
312  else
313  dwNewClr = GenerateRandomPlayerColor(iTries);
314  pResolveInfo->SetColor(dwNewClr);
315  }
316  else // if (eAttr == PLRATT_Name)
317  {
318  // name conflict resolving
319  // original name free but not used?
320  if (!SEqualNoCase(pResolveInfo->GetName(), pResolveInfo->GetOriginalName()))
321  if (!fOriginalConflict)
322  {
323  // revert to original name!
324  pResolveInfo->SetForcedName(nullptr);
325  if (pLowPrioOriginalConflictPacket) ReaddInfoForCheck(pLowPrioOriginalConflictPacket);
326  // done with this player (breaking the trial-loop)
327  break;
328  }
329  // conflict found?
330  if (!fCurrentConflict)
331  // done with this player, then - break the trial-loop
332  break;
333  // generate new name by appending an index
334  if (++iTries > C4MaxPlayerNameChangeTries) break;
335  pResolveInfo->SetForcedName(FormatString("%s (%d)", pResolveInfo->GetOriginalName(), iTries+1).getData());
336  }
337  }
338 }
339 
340 void C4PlayerInfoListAttributeConflictResolver::ResolveInPacket()
341 {
342  // check all player infos
343  fAnyChange = false;
344  int32_t iCheck = 0;
345  while ((pResolveInfo = pResolvePacket->GetPlayerInfo(iCheck++)))
346  {
347  // not already joined? Joined player must not change their attributes!
348  if (pResolveInfo->HasJoined()) continue;
349  DWORD dwPrevColor = pResolveInfo->GetColor();
350  StdStrBuf sPrevForcedName; sPrevForcedName.Copy(pResolveInfo->GetForcedName());
351  // check attributes: Name and color
352  for (eAttr = C4PlayerInfo::PLRATT_Color; eAttr != C4PlayerInfo::PLRATT_Last; eAttr = (C4PlayerInfo::Attribute) (eAttr+1))
353  {
354  if (eAttr == C4PlayerInfo::PLRATT_Color)
355  {
356  // no color change in savegame associations
357  if (pResolveInfo->GetAssociatedSavegamePlayerID()) continue;
358  // or forced team colors
359  if (Game.Teams.IsTeamColors() && Game.Teams.GetTeamByID(pResolveInfo->GetTeam())) continue;
360  }
361  else if (eAttr == C4PlayerInfo::PLRATT_Name)
362  {
363  // no name change if a league name is used
364  if (pResolveInfo->getLeagueAccount() && *pResolveInfo->getLeagueAccount()) continue;
365  }
366  // not if attributes are otherwise fixed (e.g., for script players)
367  if (pResolveInfo->IsAttributesFixed()) continue;
368  // resolve in this info
369  ResolveInInfo();
370  }
371  // mark change for return value if anything was changed
372  if (pResolveInfo->GetColor() != dwPrevColor || (pResolveInfo->GetForcedName() != sPrevForcedName))
373  fAnyChange = true;
374  // next player info check
375  }
376  // mark update if anything was changed
377  if (fAnyChange) pResolvePacket->SetUpdated();
378 }
379 
381 {
382  // resolve in all packets in list until list is empty
383  // resolving in reverse order, because highest priority first in the list
384  while (iCheckInfoCount)
385  {
386  pResolvePacket = ppCheckInfos[--iCheckInfoCount];
387  ResolveInPacket();
388  }
389 }
C4Game Game
Definition: C4Globals.cpp:52
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
const int32_t C4MaxPlayerNameChangeTries
bool IsColorConflict(DWORD dwClr1, DWORD dwClr2)
DWORD GenerateRandomPlayerColor(int32_t iTry)
const int32_t C4MaxPlayerColorChangeTries
uint32_t UnsyncedRandom()
Definition: C4Random.cpp:58
uint32_t DWORD
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:213
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
#define C4RGB(r, g, b)
Definition: StdColors.h:26
bool rgb2xyY(double r, double g, double b, double *px, double *py, double *pY)
Definition: StdColors.h:148
bool xy2upvp(double x, double y, double *pu, double *pv)
Definition: StdColors.h:166
bool RGB2rgb(int R, int G, int B, double *pr, double *pg, double *pb, double gamma=2.2)
Definition: StdColors.h:175
C4PlayerInfo * GetPlayerInfo(int32_t iIndex) const
bool IsDeveloperPacket() const
Definition: C4PlayerInfo.h:262
C4TeamList & Teams
Definition: C4Game.h:70
C4PlayerInfoList & RestorePlayerInfos
Definition: C4Game.h:72
bool IsUsingAttribute(Attribute eAttr) const
Definition: C4PlayerInfo.h:171
int32_t GetTeam() const
Definition: C4PlayerInfo.h:195
uint32_t GetOriginalColor() const
Definition: C4PlayerInfo.h:155
uint32_t GetColor() const
Definition: C4PlayerInfo.h:153
bool IsAttributesFixed() const
Definition: C4PlayerInfo.h:174
const char * getLeagueAccount() const
Definition: C4PlayerInfo.h:184
bool HasJoined() const
Definition: C4PlayerInfo.h:165
int32_t GetAssociatedSavegamePlayerID() const
Definition: C4PlayerInfo.h:126
uint32_t GetAlternateColor() const
Definition: C4PlayerInfo.h:156
const char * GetOriginalName() const
Definition: C4PlayerInfo.h:158
int32_t getLeagueScore() const
Definition: C4PlayerInfo.h:185
int32_t GetID() const
Definition: C4PlayerInfo.h:194
void SetColor(DWORD dwUseClr)
Definition: C4PlayerInfo.h:115
const char * GetForcedName() const
Definition: C4PlayerInfo.h:159
void SetForcedName(const char *szNewName)
Definition: C4PlayerInfo.h:147
const char * GetName() const
Definition: C4PlayerInfo.h:157
C4PlayerType GetType() const
Definition: C4PlayerInfo.h:152
bool IsJoined() const
Definition: C4PlayerInfo.h:166
C4PlayerInfoListAttributeConflictResolver(C4PlayerInfoList &rPriCheckList, const C4PlayerInfoList &rSecCheckList, C4ClientPlayerInfos *pSecPacket)
C4ClientPlayerInfos * GetIndexedInfo(int32_t iIndex) const
Definition: C4PlayerInfo.h:358
void ResolvePlayerAttributeConflicts(C4ClientPlayerInfos *pSecPacket)
int32_t GetInfoCount() const
Definition: C4PlayerInfo.h:357
C4Team * GetTeamByID(int32_t iID) const
Definition: C4Teams.cpp:383
bool IsTeamColors() const
Definition: C4Teams.h:169
const char * getData() const
Definition: StdBuf.h:442
void Copy()
Definition: StdBuf.h:467