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 }