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