1 /**
2  * Retrograde Engine
3  *
4  * Authors:
5  *  Mike Bierlee, m.bierlee@lostmoment.com
6  * Copyright: 2014-2021 Mike Bierlee
7  * License:
8  *  This software is licensed under the terms of the MIT license.
9  *  The full terms of the license can be found in the LICENSE.txt file.
10  */
11 
12 module retrograde.player;
13 
14 import retrograde.input;
15 import retrograde.messaging;
16 import retrograde.option;
17 import retrograde.stringid;
18 import retrograde.entity;
19 import retrograde.option;
20 
21 import poodinis;
22 
23 import std.exception;
24 
25 interface PlayerData {}
26 
27 struct Player {
28     uint id;
29     Device device;
30     PlayerData data;
31 }
32 
33 enum PlayerLifecycleCommand : StringId {
34     addPlayer = sid("cmd_add_player"),
35     removePlayer = sid("cmd_remove_player"),
36 }
37 
38 public void registerPlayerLifecycleDebugSids(SidMap sidMap) {
39     sidMap.add("cmd_add_player");
40     sidMap.add("cmd_remove_player");
41 }
42 
43 class PlayerAddCommandData : MessageData {
44     private Device _device;
45     private PlayerData _playerData;
46 
47     public @property device() {
48         return _device;
49     }
50 
51     public @property playerData() {
52         return _playerData;
53     }
54 
55     this(Device device, PlayerData playerData = null) {
56         this._device = device;
57         this._playerData = playerData;
58     }
59 }
60 
61 class PlayerRemoveCommandData : MessageData {
62     private Player _player;
63 
64     public @property player() {
65         return _player;
66     }
67 
68     this(Player player) {
69         this._player = player;
70     }
71 }
72 
73 class PlayerLifecycleCommandChannel : CommandChannel {}
74 
75 class PlayerLifecycleManager {
76 
77     @Autowire
78     private PlayerLifecycleCommandChannel lifecycleChannel;
79 
80     @Autowire
81     private PlayerRegistry registry;
82 
83     private void handleCommand(const(Command) command) {
84         switch(command.type) {
85             case PlayerLifecycleCommand.addPlayer:
86                 if (command.magnitude > 0) {
87                     auto data = cast(PlayerAddCommandData) command.data;
88                     enforce(data !is null, "cmd_add_player emitted on PlayerLifecycleCommandChannel without data");
89                     registry.registerPlayer(data.device, data.playerData);
90                 }
91                 break;
92 
93             case PlayerLifecycleCommand.removePlayer:
94                 if (command.magnitude > 0) {
95                     auto data = cast(PlayerRemoveCommandData) command.data;
96                     enforce(data !is null, "cmd_remove_player emitted on PlayerLifecycleCommandChannel without data");
97                     registry.removePlayer(data.player);
98                 }
99                 break;
100 
101             default:
102                 break;
103         }
104     }
105 
106     public void initialize() {
107         lifecycleChannel.connect(&handleCommand);
108     }
109 }
110 
111 class PlayerRegistry {
112     private uint playerId = 0;
113     private Player[uint] playersbyId;
114     private Player[Device] playersbyDevice;
115 
116     public @property Player[] players() {
117         return playersbyId.values;
118     }
119 
120     public Player registerPlayer(Device device, PlayerData data = null) {
121         if (hasPlayer(device)) {
122             return get(device).get();
123         }
124 
125         auto player = Player(++playerId, device, data);
126         playersbyId[player.id] = player;
127         playersbyDevice[device] = player;
128         return player;
129     }
130 
131     public void removePlayer(uint id) {
132         auto player = id in playersbyId;
133         if (player) {
134             playersbyId.remove(id);
135             playersbyDevice.remove((*player).device);
136         }
137     }
138 
139     public void removePlayer(Device device) {
140         auto player = device in playersbyDevice;
141         if (player) {
142             playersbyId.remove((*player).id);
143             playersbyDevice.remove(device);
144         }
145     }
146 
147     public void removePlayer(Player player) {
148         if (hasPlayer(player)) {
149             removePlayer(player.id);
150         }
151     }
152 
153     public Option!Player get(uint id) {
154         return hasPlayer(id) ? Some!Player(playersbyId[id]) : None!Player();
155     }
156 
157     public Option!Player get(Device device) {
158         return hasPlayer(device) ? Some!Player(playersbyDevice[device]) : None!Player();
159     }
160 
161     public Option!Player get(ref const(Event) event) {
162         auto inputData = cast(InputMessageData) event.data;
163         if (inputData) {
164             return get(inputData.device);
165         }
166 
167         return None!Player();
168     }
169 
170     public bool hasPlayer(uint id) {
171         return (id in playersbyId) !is null;
172     }
173 
174     public bool hasPlayer(Device device) {
175         return (device in playersbyDevice) !is null;
176     }
177 
178     public bool hasPlayer(Player player) {
179         bool result = false;
180         get(player.id).ifNotEmpty((p) {
181             result = p.device == player.device;
182         });
183 
184         return result;
185     }
186 
187 }
188 
189 class PlayerEntityComponent : EntityComponent {
190     mixin EntityComponentIdentity!"PlayerEntityComponent";
191 
192     public Option!Player player = None!Player();
193 
194     this() {}
195 
196     this(Player player) {
197         this.player = Some!Player(player);
198     }
199 }