Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
LocalPlayers.cpp
Go to the documentation of this file.
1#include "LocalPlayers.hpp"
2
3#include "LegacyTextIds.hpp"
4#include "Messaging.hpp"
5
6#include <array>
7#include <cstddef>
8#include <cstdint>
9#include <string>
10#include <string_view>
11
13{
14 namespace
15 {
18 using rules::GameState;
24
25
26 constexpr std::uint8_t OffBoardSquare = 41;
27
28
29 struct LocalEntry
30 {
31 std::wstring name{};
32
33 PlayerNumber slot =
34 NobodyPlayer;
35 };
36
37
38 std::array<
39 LocalEntry,
41 > localPlayers{};
42
43
44 std::size_t numberOfLocalPlayers = 0;
45
46
47 std::array<bool, MaxPlayers>
48 localSlots{};
49
50
51 std::array<bool, MaxPlayers>
53
54
55 std::array<bool, MaxPlayers>
57
58
61
62
63 std::wstring messageText(
64 const actions::Message& message)
65 {
66 if (message.stringA.data() == nullptr)
67 {
68 return {};
69 }
70
71 return std::wstring(
72 message.stringA.data()
73 );
74 }
75
76
77 void clearSlot(
78 PlayerNumber slot)
79 {
80 if (slot >= MaxPlayers)
81 {
82 return;
83 }
84
85 localSlots[slot] = false;
86 localHumanSlots[slot] = false;
87 localAISlots[slot] = false;
88
89
91 {
94 }
95 }
96
97
98 void removeEntryAt(
99 std::size_t index)
100 {
101 if (
102 index >=
104 {
105 return;
106 }
107
108
109 clearSlot(
110 localPlayers[index].slot
111 );
112
113
114 if (numberOfLocalPlayers > 0)
115 {
117
118
119 // RemoveLocalPlayerByName() original bouche le trou avec
120 // le dernier joueur local; il ne decale pas toute la liste.
122 {
123 localPlayers[index] =
124 std::move(
125 localPlayers[numberOfLocalPlayers]
126 );
127 }
128
129 localPlayers[
131 ] = {};
132 }
133 }
134
135
137 std::wstring_view name)
138 {
139 for (
140 std::size_t i = 0;
142 ++i)
143 {
144 if (
145 localPlayers[i].name ==
146 name)
147 {
149 return;
150 }
151 }
152 }
153
154
155 std::size_t findLocalName(
156 std::wstring_view name)
157 {
158 for (
159 std::size_t i = 0;
161 ++i)
162 {
163 if (
164 localPlayers[i].name ==
165 name)
166 {
167 return i;
168 }
169 }
170
171 return MaxLocalPlayers;
172 }
173
174
176 const GameState& uiState,
177 std::uint8_t token,
178 bool takeOverAI)
179 {
180 // AddLocalPlayer() original.
181 //
182 // Priorité :
183 // 1. AI non-bankrupt avec même token
184 // 2. AI non-bankrupt
185 // 3. AI bankrupt
186 // 4. RULE_NOBODY_PLAYER = nouveau slot.
187
190
191
192 if (!takeOverAI)
193 {
194 return bestPlayer;
195 }
196
197
198 for (
199 PlayerNumber player = 0;
200 player <
201 uiState.numberOfPlayers;
202 ++player)
203 {
204 const auto& info =
205 uiState.players[player];
206
207
208 if (info.aiPlayerLevel == 0)
209 {
210 continue;
211 }
212
213
214 if (
215 info.currentSquare ==
216 OffBoardSquare)
217 {
218 if (
219 bestPlayer ==
220 NobodyPlayer ||
221 (
222 uiState.players[
224 ].currentSquare ==
225 OffBoardSquare &&
226 uiState.players[
228 ].token != token
229 ))
230 {
231 bestPlayer =
232 player;
233 }
234 }
235 else
236 {
237 bestPlayer =
238 player;
239 }
240
241
242 if (
243 bestPlayer !=
244 NobodyPlayer &&
245 uiState.players[
247 ].currentSquare !=
248 OffBoardSquare &&
249 uiState.players[
251 ].token == token)
252 {
253 break;
254 }
255 }
256
257
258 return bestPlayer;
259 }
260
261
263 const actions::Message& message)
264 {
265 if (
266 message.numberA !=
268 message.numberA !=
270 message.numberA !=
272 {
273 return;
274 }
275
276
277 const std::wstring name =
278 messageText(message);
279
280
281 for (
282 std::size_t i = 0;
284 ++i)
285 {
286 if (
287 localPlayers[i].name ==
288 name &&
289 localPlayers[i].slot ==
290 NobodyPlayer)
291 {
293 return;
294 }
295 }
296 }
297
298
300 GameState& uiState,
301 const actions::Message& message)
302 {
303 const PlayerNumber newSlot =
304 static_cast<PlayerNumber>(
305 message.numberA
306 );
307
308
309 if (newSlot >= MaxPlayers)
310 {
311 return;
312 }
313
314
315 auto& player =
316 uiState.players[newSlot];
317
318
319 const std::uint8_t oldAILevel =
320 player.aiPlayerLevel;
321
322 const std::uint8_t oldColour =
323 player.colour;
324
325 const std::uint8_t oldToken =
326 player.token;
327
328
329 const std::wstring newName =
330 messageText(message);
331
332
333 // ------------------------------------------------
334 // UICurrentGameState est mis à jour AVANT de
335 // déterminer si le joueur est local.
336 // ------------------------------------------------
337
338 player.token =
339 static_cast<std::uint8_t>(
340 message.numberB
341 );
342
343
344 player.colour =
345 static_cast<std::uint8_t>(
346 message.numberC
347 );
348
349
350 player.aiPlayerLevel =
351 static_cast<std::uint8_t>(
352 message.numberD
353 );
354
355
356 player.name =
357 newName.substr(
358 0,
359 MaxPlayerNameLength
360 );
361
362
363 // ------------------------------------------------
364 // Est-ce l'un de nos noms locaux ?
365 // ------------------------------------------------
366
367 const std::size_t localIndex =
369 player.name
370 );
371
372
373 if (
374 localIndex <
376 {
377 const PlayerNumber oldSlot =
378 localPlayers[
380 ].slot;
381
382
383 // Notification identique déjà appliquée :
384 // le code 1999 l'ignore.
385 if (
387 oldSlot == newSlot &&
388 oldAILevel ==
389 player.aiPlayerLevel &&
390 oldToken ==
391 player.token &&
392 oldColour ==
393 player.colour)
394 {
395 return;
396 }
397
398
399 // Aucun autre joueur local ne doit rester
400 // associé au nouveau slot ou à l'ancien slot.
401
402 for (
403 std::size_t i = 0;
405 ++i)
406 {
407 if (
408 localPlayers[i].slot ==
409 newSlot ||
410 localPlayers[i].slot ==
411 oldSlot)
412 {
413 localPlayers[i].slot =
415 }
416 }
417
418
419 if (
420 oldSlot !=
421 NobodyPlayer)
422 {
423 clearSlot(
424 oldSlot
425 );
426 }
427
428
429 // Assigner le joueur à son nouveau slot.
431 true;
432
433
434 localPlayers[
436 ].slot =
437 newSlot;
438
439
440 if (
441 player.aiPlayerLevel != 0)
442 {
444 true;
445
447 false;
448
449
450 // AI_Load_AI() appartenait à l'ancien
451 // sous-système AI DLL.
452 //
453 // Le slot local AI est néanmoins conservé
454 // exactement ici ; le moteur RULE possède
455 // déjà aiPlayerLevel et les blobs AI.
456 }
457 else
458 {
460 true;
461
463 false;
464 }
465
466
467 return;
468 }
469
470
471 // ------------------------------------------------
472 // Ce joueur appartient à quelqu'un d'autre.
473 //
474 // Conserver nos noms locaux mais les rendre
475 // "slotless" si un reorder leur a pris ce slot.
476 // ------------------------------------------------
477
478 for (
479 std::size_t i = 0;
481 ++i)
482 {
483 if (
484 localPlayers[i].slot ==
485 newSlot)
486 {
487 localPlayers[i].slot =
489 }
490 }
491
492
494 }
495
496
498 const actions::Message& message)
499 {
501 messageText(message)
502 );
503 }
504
505
507 const actions::Message& message)
508 {
509 const PlayerNumber newSlot =
510 static_cast<PlayerNumber>(
511 message.numberA
512 );
513
514
515 if (newSlot >= MaxPlayers)
516 {
517 return;
518 }
519
520
521 const std::wstring name =
522 messageText(message);
523
524
525 // Supprimer une ancienne occurrence du même nom.
527 name
528 );
529
530
531 // Supprimer aussi tout joueur local qui occupait
532 // déjà le slot destination.
533
534 for (
535 std::size_t i = 0;
537 {
538 if (
539 localPlayers[i].slot ==
540 newSlot)
541 {
543 continue;
544 }
545
546 ++i;
547 }
548
549
550 // Userifce.cpp :
551 //
552 // uniquement le serveur ajoute ce joueur à la
553 // liste locale. NOTIFY_NAME_PLAYER attribuera
554 // ensuite son véritable slot.
555
556 if (
560 {
561 localPlayers[
563 ].name =
564 name.substr(
565 0,
566 MaxPlayerNameLength
567 );
568
569
570 localPlayers[
572 ].slot =
574
575
577 }
578 }
579 }
580
581
582 void reset()
583 {
584 for (auto& player :
585 localPlayers)
586 {
587 player = {};
588
589 player.slot =
590 NobodyPlayer;
591 }
592
593
595
596
597 localSlots.fill(false);
598 localHumanSlots.fill(false);
599 localAISlots.fill(false);
600
601
603 NobodyPlayer;
604 }
605
606
607 std::size_t count()
608 {
609 return
611 }
612
613
614 std::size_t humanCount()
615 {
616 std::size_t result = 0;
617
618
619 for (PlayerNumber player = 0;
620 player < MaxPlayers;
621 ++player)
622 {
623 if (localHumanSlots[player])
624 {
625 ++result;
626 }
627 }
628
629
630 return result;
631 }
632
633 PlayerNumber slotAt(
634 std::size_t localIndex)
635 {
636 if (
637 localIndex >=
639 {
640 return NobodyPlayer;
641 }
642
643
644 return
645 localPlayers[
647 ].slot;
648 }
649
650
652 PlayerNumber slot)
653 {
654 return
655 slot < MaxPlayers &&
657 }
658
659
661 PlayerNumber slot)
662 {
663 return
664 slot < MaxPlayers &&
666 }
667
668
670 PlayerNumber slot)
671 {
672 return
673 slot < MaxPlayers &&
675 }
676
677
679 PlayerNumber recipient)
680 {
681 // Userifce.cpp original, AdvanceTimeStep(): seules les
682 // notifications broadcast ou destinees a un slot local
683 // traversent ProcessPlayersUI().
684 return
685 recipient == AllPlayers ||
686 (recipient < MaxPlayers &&
688 }
689
690
691 PlayerNumber currentUIPlayer()
692 {
693 return
695 }
696
697
699 PlayerNumber player)
700 {
701 // SetCurrentUIPlayerFromPlayerNumber() original.
702
704 NobodyPlayer;
705
706
707 if (
708 player < MaxPlayers &&
709 localHumanSlots[player])
710 {
712 player;
713 }
714 }
715
716
718 const GameState& uiState,
719 std::uint32_t playerSet)
720 {
721 // Userifce.cpp::SetCurrentUIPlayerFromPlayerSet.
722 selectedUIPlayer = NobodyPlayer;
723
724 for (PlayerNumber player = 0; player < uiState.numberOfPlayers; ++player)
725 {
726 if ((playerSet & (1u << player)) != 0 && localHumanSlots[player])
727 {
728 selectedUIPlayer = player;
729 break;
730 }
731 }
732 }
733
734
735 PlayerNumber anyLocalPlayer(
736 const GameState& uiState)
737 {
738 // AnyLocalPlayer() original.
739 //
740 // Inclut les IA locales.
741
742 for (
743 PlayerNumber player = 0;
744 player <
745 uiState.numberOfPlayers;
746 ++player)
747 {
748 if (localSlots[player])
749 {
750 return player;
751 }
752 }
753
754
755 return NobodyPlayer;
756 }
757
758
759 PlayerNumber tradeSourcePlayer(
760 const GameState& uiState,
761 PlayerNumber iBarCurrentPlayer)
762 {
763 // UDTrade_GetPlayerToTradeFrom() original : partir du joueur IBar,
764 // exclure la banque, puis parcourir vers l'arriere jusqu'au premier
765 // humain local encore sur le plateau. RULE_MAX_PLAYERS signifie aucun.
766 const PlayerNumber count =
767 std::min(uiState.numberOfPlayers, MaxPlayers);
768 if (count == 0)
769 {
770 return MaxPlayers;
771 }
772
773 PlayerNumber player = iBarCurrentPlayer;
774 if (player >= count)
775 {
776 player = static_cast<PlayerNumber>(count - 1);
777 }
778
779 const PlayerNumber start = player;
780 do
781 {
782 if (localHumanSlots[player] &&
783 uiState.players[player].currentSquare < OffBoardSquare)
784 {
785 return player;
786 }
787
788 player = player == 0
789 ? static_cast<PlayerNumber>(count - 1)
790 : static_cast<PlayerNumber>(player - 1);
791 }
792 while (player != start);
793
794 return MaxPlayers;
795 }
796
797
799 const GameState& uiState,
800 PlayerNumber originalBuyer,
801 std::uint32_t allowedPlayers)
802 {
803 // UDIBar.cpp NOTIFY_HOUSING_SHORTAGE: remove the original buyer,
804 // prefer the first eligible local human in ascending slot order, then
805 // fall back to the first remaining player so remote/AI games can watch.
806 const PlayerNumber count = std::min(uiState.numberOfPlayers, MaxPlayers);
807 if (count == 0)
808 return NobodyPlayer;
809
810 const std::uint32_t validMask = (1u << count) - 1u;
812 if (originalBuyer < count)
813 allowedPlayers &= ~(1u << originalBuyer);
814
815 for (PlayerNumber player = 0; player < count; ++player)
816 {
817 if ((allowedPlayers & (1u << player)) && localHumanSlots[player])
818 return player;
819 }
820
821 for (PlayerNumber player = 0; player < count; ++player)
822 {
823 if (allowedPlayers & (1u << player))
824 return player;
825 }
826
827 return NobodyPlayer;
828 }
829
830
832 const GameState& uiState,
833 PlayerNumber tradeBPlayer,
834 std::uint32_t pendingPlayers)
835 {
836 // UDTrade.cpp NOTIFY_TRADE_ACCEPTANCE_DECISION: first try the
837 // displayed TradeB player if that exact slot is a local human; if not,
838 // show the first player still present in numberA so remote trades can
839 // be watched.
840 const PlayerNumber count = std::min(uiState.numberOfPlayers, MaxPlayers);
841 if (tradeBPlayer < count && localHumanSlots[tradeBPlayer])
842 return tradeBPlayer;
843
844 const std::uint32_t validMask = count == 0 ? 0u : (1u << count) - 1u;
846 for (PlayerNumber player = 0; player < count; ++player)
847 {
848 if (pendingPlayers & (1u << player))
849 return player;
850 }
851 return NobodyPlayer;
852 }
853
854
856 const GameState& uiState,
857 std::wstring_view name,
858 std::uint8_t token,
859 std::uint8_t colour,
860 std::uint8_t aiLevel,
861 bool takeOverAI)
862 {
863 // ====================================================
864 // AddLocalPlayer() original.
865 // ====================================================
866
867 if (
870 {
871 return false;
872 }
873
874
875 if (
876 name.empty() ||
877 name.size() >
878 MaxPlayerNameLength)
879 {
880 return false;
881 }
882
883
884 const std::wstring ownedName(
885 name
886 );
887
888
889 // ----------------------------------------------------
890 // Mémoriser le nom seulement s'il n'est pas déjà
891 // dans notre liste locale.
892 // ----------------------------------------------------
893
894 if (
897 {
898 localPlayers[
900 ].name =
901 ownedName;
902
903
904 localPlayers[
906 ].slot =
907 NobodyPlayer;
908
909
911 }
912
913
914 // ----------------------------------------------------
915 // Déterminer le joueur AI éventuellement remplacé.
916 // ----------------------------------------------------
917
918 const PlayerNumber bestPlayer =
920 uiState,
921 token,
923 );
924
925
926 // ----------------------------------------------------
927 // IMPORTANT :
928 //
929 // fromPlayer = RULE_SPECTATOR_PLAYER (9)
930 //
931 // et NON RULE_NOBODY_PLAYER.
932 // ----------------------------------------------------
933
936 SpectatorPlayer,
937 BankPlayer,
938
940 aiLevel,
941 token,
942 colour,
943
945 );
946
947
948 return true;
949 }
950
951
953 const GameState& uiState,
954 PlayerNumber player)
955 {
956 // RemoveLocalPlayer() original.
957
958 if (
959 player >=
960 uiState.numberOfPlayers ||
962 {
963 return false;
964 }
965
966
967 const auto& info =
968 uiState.players[player];
969
970
971 // L'original retire immédiatement le nom de la liste
972 // locale avant que RULE confirme la suppression.
974 info.name
975 );
976
977
978 // Naming with a null string effects a delete.
981 player,
982 BankPlayer,
983
984 player,
985 info.aiPlayerLevel,
986 info.token,
987 info.colour,
988
989 L""
990 );
991
992
993 return true;
994 }
995
997 GameState& uiState,
998 const actions::Message& message)
999 {
1000 switch (message.action)
1001 {
1004 message
1005 );
1006 return;
1007
1008
1011 uiState,
1012 message
1013 );
1014 return;
1015
1016
1019 message
1020 );
1021 return;
1022
1023
1026 message
1027 );
1028 return;
1029
1030
1031 default:
1032 return;
1033 }
1034 }
1035}
1036
const char * name
PlayerNumber slot
unsigned index
int token
int colour
constexpr std::int64_t ErrorNotYourPlayer
constexpr std::int64_t ErrorNoMorePlayers
constexpr std::int64_t ErrorNameInUse
bool sendAction(const actions::Message &message)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber SpectatorPlayer
Definition RuleTypes.hpp:22
constexpr std::size_t MaxPlayerNameLength
Definition RuleTypes.hpp:26
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber NobodyPlayer
Definition RuleTypes.hpp:16
constexpr PlayerNumber AllPlayers
Definition RuleTypes.hpp:17
bool requestRemoveLocalPlayer(const GameState &uiState, PlayerNumber player)
PlayerNumber anyLocalPlayer(const GameState &uiState)
PlayerNumber slotAt(std::size_t localIndex)
PlayerNumber tradeSourcePlayer(const GameState &uiState, PlayerNumber iBarCurrentPlayer)
void setCurrentUIPlayerFromPlayerSet(const GameState &uiState, std::uint32_t playerSet)
bool slotIsLocalAIPlayer(PlayerNumber slot)
bool isLocalRecipient(PlayerNumber recipient)
constexpr std::size_t MaxLocalPlayers
bool slotIsLocalHumanPlayer(PlayerNumber slot)
bool slotIsLocalPlayer(PlayerNumber slot)
void setCurrentUIPlayerFromPlayerNumber(PlayerNumber player)
PlayerNumber tradeAcceptanceIBarPlayer(const GameState &uiState, PlayerNumber tradeBPlayer, std::uint32_t pendingPlayers)
PlayerNumber housingShortageIBarPlayer(const GameState &uiState, PlayerNumber originalBuyer, std::uint32_t allowedPlayers)
bool requestAddLocalPlayer(const GameState &uiState, std::wstring_view name, std::uint8_t token, std::uint8_t colour, std::uint8_t aiLevel, bool takeOverAI)
void processRuleMessage(GameState &uiState, const actions::Message &message)