lzrtag-base 1.0.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e1aba4b3577f72ee4c4aca45caca65e157cfa522
4
- data.tar.gz: 9582f9f9a6c478c97de4c837ad6eadfb2c44c451
2
+ SHA256:
3
+ metadata.gz: ecf85ea86bb74b299da65766ec5c0d653416b605863e93ba833970e0db78fbbc
4
+ data.tar.gz: 460e8d9cdc4b01522e1373efc4a3b3417161e717c8848d4c2abce8fb3c4c7fb8
5
5
  SHA512:
6
- metadata.gz: a2a6bcaeefeddece09a26ee91e9dd608aa5d8450dc4ff943b23d01ad995f07533cdcc278336b37e92873de1f9cc4ce468e66df5806225e01c8ce23acb18eb313
7
- data.tar.gz: f5dea5415de79a54ab9c4d5bd7e81e774daae5498d4b6005c21a68c5d9ddba6f9ca7540b1159f78cffa786507d19d25f52a1d3153320e70c64234db833616f64
6
+ metadata.gz: 10676d8495f7148b646fc3cc4a4025c2a0f4d37e8c4e75543668fc19d9bad2f8dbffae747393a5670c34f54021833d67ed77d3086dca8f15aca4e1bcadf5698c
7
+ data.tar.gz: d64f580c72c66e25b13dafa2e8dfcc93cd2cacb6265416caecc1312bc8f794b351bf5254ac579af099c1bac50ba1113dab3e613197d305e7b9aad3a2148841cd
data/README.md CHANGED
@@ -25,6 +25,7 @@ ruby console. It's a great way to see the immediate effects of various commands
25
25
  on the set, and is kinda fun, too.
26
26
 
27
27
  Let's first start up the console and require the library.
28
+
28
29
  ```IRB
29
30
  irb(main):001:0> require 'lzrtag.rb'
30
31
  => true
@@ -15,6 +15,11 @@ It is only triggered when a game is present.
15
15
  *Game class alternative:* Describe per-tick code using the
16
16
  "phase" DSL. It allows easy and clean specification of tick code.
17
17
 
18
+ ###### :slowTick
19
+ Sent in regular intervals (0.5s) as a slower alternative to :gameTick for less
20
+ critical tasks. This for example handles player beacon entering/leaving as well
21
+ as slow timeouts.
22
+
18
23
  ###### :gameStarting
19
24
  Triggered when a new game is selected, either in Ruby or via MQTT.
20
25
  Payload is [newGame]
@@ -86,6 +91,22 @@ Payload is [thePlayer, newGun, oldGun]
86
91
  *TODO:* The gun number should be replaced by an ID string, when the
87
92
  gun configuration is moved to a Filesystem-based one (possibly by loading from JSON)
88
93
 
94
+ ###### :playerEnteredBeacon
95
+ Triggered once when a player receives a new IR Beacon signal, of a beacon that
96
+ he has not seen for the last 3s, to indicate that the Player entered a new zone.
97
+ Payload is [thePlayer, beaconID]
98
+
99
+ ###### :playerInBeacon
100
+ Triggered for every single beacon pulse a player receives from a IR Beacon.
101
+ This can be useful to handle time-continuous beacon interaction, such as being
102
+ healed by a beacon, or a capture point.
103
+ Payload is [thePlayer, beaconID]
104
+
105
+ ###### :playerLeftBeacon
106
+ Triggered once when a player does not see a beacon ID for at least 3s, indicating
107
+ that the player has left a zone.
108
+ Payload is [thePlayer, beaconID]
109
+
89
110
  ## Player life signals
90
111
  These signals are sent whenever the player's HP count or life status is changed,
91
112
  and let the application handle things like scoring damage and kills, and triggering
@@ -1,5 +1,17 @@
1
1
  # MQTT Branch setup description
2
2
 
3
+ ## Game Branches
4
+
5
+ - Lasertag/Game/Events/Hits
6
+ - Lasertag/Game/Controls/+
7
+ - SetPhase
8
+ - SetGame
9
+ - Lasertag/Game/Phase/Valid
10
+ - Lasertag/Game/ParticipatingPlayers
11
+ - Lasertag/Game/KnownGames
12
+ - Lasertag/Game/CurrentGame
13
+ - Lasertag/Game/Phase/Current
14
+
3
15
  ## Weapon Branches
4
16
  The weapons are the main unit of the Lasertag system. As such, they use a specific
5
17
  branch setup to minimize traffic to them, while making code maintaining easier.
@@ -39,6 +51,7 @@ They are slow-changing configuration parameters for the weapons.
39
51
  |Heartbeat|"1" or "0"|Enables/Disables heartbeat of the weapon|Yes|
40
52
  |Hit|STR Number|Number, in seconds, to make the gun show that it's hit|No|
41
53
  |Vibrate|STR Number|Number, in seconds, for which to make the gun vibrate|No|
54
+ |Reload|"1"|Trigger a gun-side reloading|No|
42
55
 
43
56
  ### Sound attributes
44
57
 
@@ -60,6 +73,7 @@ They all relate to the hardware of the system, such as battery etc.
60
73
  |Ping|uint32_t[3]|Battery, free-heap and ping data|No|
61
74
  |NSwitch|STR Number|Navigation switch press info (0-3)|No|
62
75
  |Gyro|String|Clear string of the pose the gyroscope is reporting|Yes|
76
+ |BeaconDetect|STR Number|Number of the beacon signal that was detected (0-255)|No|
63
77
 
64
78
  ### Statistics Attributes
65
79
 
@@ -70,5 +84,6 @@ They all relate to various game statistics that do not directly influence the ha
70
84
  |Key|Data|Descrition|Retained|
71
85
  |--|--|--|--|
72
86
  |HP|STR Number|Float number, 0 to 100 (or more with shields)|Yes|
73
- |Ammo|uint32_t[2]|Current and Max ammo of the weapon|Yes|
74
- |Ammo/Set|STR Number|Give the player some ammo (*WIP*)|No|
87
+ |Ammo|uint32_t[3]|Current Clip ammo, max clip size and reserve ammo|No|
88
+ |Ammo/SetClip|STR Number|Set the amount of ammo in the player's clip|No|
89
+ |Ammo/SetReserve|STR Number| Set the amount of ammo in the player's reserve|No|
@@ -17,7 +17,7 @@ module LZRTag
17
17
  #
18
18
  # hook :dmgHook, LZRTag::Hook::Damager
19
19
  #
20
- # phase :running do |deltaTick|
20
+ # phase :starting do |deltaTick|
21
21
  # puts "I tick!"
22
22
  # end
23
23
  #
@@ -81,9 +81,9 @@ module LZRTag
81
81
  def initialize(handler)
82
82
  super(handler)
83
83
 
84
- @hookList = Array.new();
84
+ @hookList = Hash.new();
85
85
  self.class.get_hooks().each do |hookID, hookData|
86
- @hookList << hookData[0].new(@handler, **hookData[1])
86
+ @hookList[hookID] = hookData[0].new(@handler, **hookData[1])
87
87
  end
88
88
 
89
89
  @tickTime = 0.1;
@@ -113,7 +113,7 @@ module LZRTag
113
113
  raise ArgumentError, "Hook needs to be a LZR::Hook!"
114
114
  end
115
115
  raise ArgumentError, "Hook options need to be a hash" unless hookOptions.is_a? Hash
116
- get_hooks()[hookID] << [hookType, hookOptions];
116
+ get_hooks()[hookID] = [hookType, hookOptions];
117
117
  end
118
118
 
119
119
  # DSL function to provide a phase tick code to this game
@@ -188,7 +188,7 @@ module LZRTag
188
188
  end
189
189
  end
190
190
 
191
- @hookList.each do |hook|
191
+ @hookList.each do |hookID, hook|
192
192
  hook.consume_event(evt, data);
193
193
  end
194
194
  end
@@ -1,9 +1,11 @@
1
1
 
2
2
  require 'mqtt/sub_handler'
3
3
 
4
+ require 'xasin_logger'
5
+
4
6
  require_relative '../hooks/base_hook.rb'
5
7
 
6
- require_relative '../player/life_player.rb'
8
+ require_relative '../player/stats_player.rb'
7
9
 
8
10
  module LZRTag
9
11
  module Handler
@@ -20,13 +22,17 @@ module LZRTag
20
22
  # # Using LZRTag.Handler instead of LZRTag::Handler::Base to fetch the latest handler
21
23
  # handler = LZRTag.Handler.new(mqttConn);
22
24
  class Base
25
+ include XasLogger::Mix
26
+
23
27
  # Returns the MQTT connection
24
28
  attr_reader :mqtt
25
29
 
26
30
  # Returns the ID-Table, a Hash of Players and their matched IDs
27
31
  attr_reader :idTable
28
32
 
29
- def initialize(mqtt, playerClass = Player::Life, clean_on_exit: true)
33
+ def initialize(mqtt, playerClass = Player::Statistics, clean_on_exit: true)
34
+ init_x_log("LZRTag Base", nil);
35
+
30
36
  @mqtt = mqtt;
31
37
 
32
38
  @playerClass = playerClass;
@@ -48,6 +54,12 @@ module LZRTag
48
54
  end
49
55
  end
50
56
  @eventThread.abort_on_exception = true;
57
+ Thread.new do
58
+ loop do
59
+ sleep 0.5;
60
+ send_event(:slowTick);
61
+ end
62
+ end
51
63
 
52
64
  @mqtt.subscribe_to "Lasertag/Players/#" do |data, topic|
53
65
  dID = topic[0];
@@ -56,6 +68,7 @@ module LZRTag
56
68
  @players[dID] = @playerClass.new(dID, self);
57
69
  }
58
70
  send_event(:playerRegistered, @players[dID]);
71
+ x_logi("New player registered: #{dID}");
59
72
  end
60
73
 
61
74
  @players[dID].on_mqtt_data(data, topic);
@@ -73,7 +86,7 @@ module LZRTag
73
86
  }
74
87
  end
75
88
 
76
- puts "I LZR::Handler init finished".green
89
+ x_logi("Initialisation complete");
77
90
  end
78
91
 
79
92
  # Send an event into the event loop.
@@ -99,6 +112,10 @@ module LZRTag
99
112
  player = data[0];
100
113
  @idTable[player.id] = nil;
101
114
  player.id = nil;
115
+ when :slowTick
116
+ self.each do |pl|
117
+ pl._tick_connection();
118
+ end
102
119
  end
103
120
  end
104
121
 
@@ -12,22 +12,30 @@ module LZRTag
12
12
  class Count < HitArb
13
13
  # Returns a Hash with keys 0..7, describing which teams have
14
14
  # how many players
15
- attr_reader :teamCount
15
+ attr_reader :teamMap
16
16
  # Returns a Hash with keys equal to player's brightnesses, describing
17
17
  # how many players have which brightness
18
- attr_reader :brightnessCount
18
+ attr_reader :brightnessMap
19
+
20
+ # Returns a hash with keys of beacon numbers, describing
21
+ # how many players are in which beacon
22
+ attr_reader :beaconMap
19
23
 
20
24
  def initialize(*args, **argHash)
21
25
  super(*args, **argHash);
22
26
 
23
- @teamCount = Hash.new();
27
+ @teamMap = Hash.new();
24
28
  7.times do |i|
25
- @teamCount[i] = 0;
29
+ @teamMap[i] = Array.new;
26
30
  end
27
31
 
28
- @brightnessCount = Hash.new();
32
+ @brightnessMap = Hash.new();
29
33
  Player::Hardware.getBrightnessKeys().each do |bKey|
30
- @brightnessCount[bKey] = 0;
34
+ @brightnessMap[bKey] = Array.new;
35
+ end
36
+
37
+ @beaconMap = Hash.new() do |h, k|
38
+ h[k] = Array.new();
31
39
  end
32
40
  end
33
41
 
@@ -37,19 +45,32 @@ module LZRTag
37
45
 
38
46
  case evtName
39
47
  when :playerRegistered
40
- @teamCount[data[0].team] += 1;
41
- @brightnessCount[data[0].brightness] += 1;
48
+ @teamMap[data[0].team] << data[0];
49
+ @brightnessMap[data[0].brightness] << data[0];
42
50
  when :playerUnregistered
43
- @teamCount[data[0].team] -= 1;
44
- @brightnessCount[data[0].brightness] -= 1;
51
+ @teamMap[data[0].team].delete data[0];
52
+ @brightnessMap[data[0].brightness].delete data[0];
45
53
  when :playerTeamChanged
46
- @teamCount[data[1]] -= 1;
47
- @teamCount[data[0].team] += 1;
54
+ @teamMap[data[1]].delete data[0];
55
+ @teamMap[data[0].team] << data[0];
48
56
  when :playerBrightnessChanged
49
- @brightnessCount[data[1]] -= 1;
50
- @brightnessCount[data[0].brightness] += 1;
57
+ @brightnessMap[data[1]].delete data[0];
58
+ @brightnessMap[data[0].brightness] << data[0];
59
+ when :playerEnteredBeacon
60
+ @beaconMap[data[1]] << data[0];
61
+ when :playerLeftBeacon
62
+ @beaconMap[data[1]].delete data[0];
51
63
  end
52
64
  end
65
+
66
+ def get_team_killcount(team)
67
+ total = 0;
68
+ @teamMap[team].each do |pl|
69
+ total += pl.stats["Kills"];
70
+ end
71
+
72
+ return total;
73
+ end
53
74
  end
54
75
  end
55
76
  end
@@ -105,7 +105,8 @@ module LZRTag
105
105
  send_event(:gameTick, dT);
106
106
  end
107
107
 
108
- puts "Stopping current game.".green
108
+ x_logi("Stopping game!");
109
+
109
110
  set_phase(:idle);
110
111
  sleep 1;
111
112
  @currentGame = nil;
@@ -122,12 +123,15 @@ module LZRTag
122
123
  # A list of games is published to Lasertag/Game/KnownGames
123
124
  # @param gameTag [String] Cleartext name of the game
124
125
  # @param game [LZRTag::Game::Base] The game class to register
126
+ # @see start_game
125
127
  def register_game(gameTag, game)
126
128
  raise ArgumentError, "Game Tag must be a string!" unless gameTag.is_a? String
127
129
  raise ArgumentError, "Game must be a LZRTag::Game class" unless game <= LZRTag::Game::Base
128
130
 
129
131
  @knownGames[gameTag] = game;
130
132
 
133
+ x_logi("Game registered: #{gameTag}");
134
+
131
135
  @mqtt.publish_to "Lasertag/Game/KnownGames", @knownGames.keys.to_json, retain: true;
132
136
  end
133
137
 
@@ -143,6 +147,10 @@ module LZRTag
143
147
  # This function will take either a String (as registered with register_game),
144
148
  # or a LZRTag::Game::Base class, instantiate it, and start it.
145
149
  # If no fitting game was found, the game is instead stopped.
150
+ #
151
+ # @note The first phase that is started by default is :starting, the Game
152
+ # class must define at least a phase_prep hook to change the phase and
153
+ # configure the game!
146
154
  # @param game [String,LZRTag::Game::Base] The game, or game name, to start
147
155
  def start_game(game = @lastGame)
148
156
  @lastGame = game;
@@ -156,7 +164,7 @@ module LZRTag
156
164
 
157
165
  if(gKey = @knownGames.key(game))
158
166
  @mqtt.publish_to "Lasertag/Game/CurrentGame", gKey, retain: true
159
- puts "Starting game #{gKey}!".green
167
+ x_logi("Starting game #{gKey}");
160
168
  else
161
169
  @mqtt.publish_to "Lasertag/Game/CurrentGame", "", retain: true
162
170
  end
@@ -202,7 +210,7 @@ module LZRTag
202
210
 
203
211
  raise ArgumentError, "Phase must be valid!" unless allowedPhases.include? nextPhase
204
212
 
205
- puts "Phase started: #{nextPhase}!".green;
213
+ x_logi("Starting phase #{nextPhase}");
206
214
 
207
215
  oldPhase = @gamePhase
208
216
  send_event(:gamePhaseEnds, oldPhase, nextPhase)
@@ -221,24 +229,24 @@ module LZRTag
221
229
  # @param [Array<LZRTag::Player::Base] Array of active players
222
230
  def gamePlayers=(newPlayers)
223
231
  raise ArgumentError, "Game player list shall be an array!" unless newPlayers.is_a? Array
232
+ oldGamePlayers = @gamePlayers.dup
224
233
  @gamePlayers = newPlayers.dup;
225
234
 
226
235
  @playerNames = Array.new();
227
236
  plNameArray = Array.new();
228
237
  @gamePlayers.each do |pl|
229
- plNameArray << pl.deviceID();
238
+ plNameArray << pl.DeviceID();
230
239
  end
231
240
 
232
- newPlayers = @gamePlayers - @oldGamePlayers;
241
+ newPlayers = @gamePlayers - oldGamePlayers;
233
242
  newPlayers.each do |pl|
234
243
  send_event :playerEnteredGame, pl;
235
244
  end
236
- oldPlayers = @oldGamePlayers - @gamePlayers;
245
+ oldPlayers = oldGamePlayers - @gamePlayers;
237
246
  oldPlayers.each do |pl|
238
247
  send_event :playerLeftGame, pl;
239
248
  end
240
249
 
241
- @oldGamePlayers = @gamePlayers.dup;
242
250
  @mqtt.publish_to "Lasertag/Game/ParticipatingPlayers", plNameArray.to_json(), retain: true
243
251
  end
244
252
 
@@ -22,13 +22,11 @@ module LZRTag
22
22
  def initialize(*data, **options)
23
23
  super(*data, **options);
24
24
 
25
- @mqtt.subscribe_to "Lasertag/Game/Events" do |data|
25
+ @mqtt.subscribe_to "Lasertag/Game/Events/Hits" do |data|
26
26
  begin
27
27
  data = JSON.parse(data, symbolize_names: true);
28
28
 
29
- if(data[:type] == "hit")
30
- _handle_hitArb(data);
31
- end
29
+ _handle_hitArb(data);
32
30
  rescue JSON::ParserError
33
31
  end
34
32
  end
@@ -38,6 +36,15 @@ module LZRTag
38
36
  return true;
39
37
  end
40
38
 
39
+ def consume_event(evt, data)
40
+ super(evt, data)
41
+ if(evt == :slowTick)
42
+ self.each do |pl|
43
+ pl.check_beacons();
44
+ end
45
+ end
46
+ end
47
+
41
48
  def _handle_hitArb(data)
42
49
  unless (hitPlayer = get_player(data[:target])) and
43
50
  (sourcePlayer = get_player(data[:shooterID])) and
@@ -53,9 +60,9 @@ module LZRTag
53
60
  hookList = Array.new();
54
61
  hookList << @hooks;
55
62
  if(@currentGame)
56
- hookList << @currentGame.hookList
63
+ hookList << @currentGame.hookList.values
57
64
  end
58
- hookList.flatten
65
+ hookList.flatten!
59
66
 
60
67
  hookList.each do |h|
61
68
  begin
@@ -1,4 +1,6 @@
1
1
 
2
+ require 'xasin_logger'
3
+
2
4
  module LZRTag
3
5
  module Hook
4
6
  =begin
@@ -40,6 +42,8 @@ module LZRTag
40
42
  handler.add_hook(MyHook);
41
43
  =end
42
44
  class Base
45
+ include XasLogger::Mix
46
+
43
47
  def self.getCBs()
44
48
  @globalCBList ||= Hash.new();
45
49
  return @globalCBList;
@@ -49,10 +53,12 @@ module LZRTag
49
53
  return @globalOptionDescriptions
50
54
  end
51
55
 
52
- def initialize(handler)
56
+ def initialize(handler, logName = self)
53
57
  @localCBList = Hash.new();
54
58
 
55
59
  @handler = handler
60
+
61
+ init_x_log(logName.to_s, nil);
56
62
  end
57
63
 
58
64
  # DSL function to describe an option of this hook.
@@ -7,11 +7,11 @@ module LZRTag
7
7
  attr_accessor :eventWhitelist
8
8
  attr_accessor :eventBlacklist
9
9
 
10
- def initialize()
11
- super();
10
+ def initialize(handler)
11
+ super(handler, "DBG");
12
12
 
13
13
  @eventWhitelist = Array.new();
14
- @eventBlacklist = Array.new();
14
+ @eventBlacklist = [:slowTick, :gameTick, :playerInBeacon];
15
15
  end
16
16
 
17
17
  def consume_event(evtName, data)
@@ -22,7 +22,7 @@ module LZRTag
22
22
  return unless @eventWhitelist.include? evtName
23
23
  end
24
24
 
25
- puts "Caught event: #{evtName} with data: #{data}";
25
+ x_logd "Event: #{evtName} with data: #{data}";
26
26
  end
27
27
  end
28
28
 
@@ -32,23 +32,35 @@ module LZRTag
32
32
  }
33
33
 
34
34
  def initialize(handler, possibleTeams: [1, 2, 3, 4])
35
- super(handler);
35
+ super(handler, "Team Selector");
36
36
 
37
37
  @possibleTeams = possibleTeams;
38
38
  end
39
39
 
40
+ def in_phase
41
+ return @handler.gamePhase == :teamSelect;
42
+ end
43
+ def is_selecting(pl)
44
+ return ([:idle, :teamSelect].include? pl.brightness)
45
+ end
46
+
40
47
  on :gamePhaseEnds do |oldPhase, nextPhase|
41
48
  if((oldPhase == :teamSelect) && (nextPhase != :idle))
42
- puts "Selecting active players!"
49
+ x_logi("Selecting active players!");
43
50
 
44
- @handler.gamePlayers = Array.new();
51
+ nextPlayers = Array.new();
45
52
  @handler.each do |pl|
46
- if(pl.brightness == :active)
47
- @handler.gamePlayers << pl;
53
+ if([:active, :teamSelect].include? pl.brightness)
54
+ nextPlayers << pl;
55
+ else
56
+ pl.team = 0;
57
+ pl.brightness = :idle;
48
58
  end
49
59
  end
50
60
 
51
- puts "Game players are: #{@handler.gamePlayers}"
61
+ @handler.gamePlayers = nextPlayers;
62
+
63
+ x_logd("Participating players are: #{@handler.gamePlayers}")
52
64
  end
53
65
  end
54
66
 
@@ -58,27 +70,22 @@ module LZRTag
58
70
  @handler.each do |pl|
59
71
  pl.brightness = :idle;
60
72
 
61
- if(!@possibleTeams.include?(pl.team))
73
+ unless(@possibleTeams.include?(pl.team))
62
74
  pl.team = @possibleTeams.sample();
63
75
  end
64
76
  end
65
- when :idle
66
- @handler.each do |pl|
67
- pl.brightness = :idle;
68
- end
69
77
  end
70
78
  end
71
79
 
72
80
  on :poseChanged do |pl, nPose|
73
- next if(@handler.gamePhase != :teamSelect)
74
- next if(pl.brightness == :active)
81
+ next unless in_phase
82
+ next unless is_selecting(pl)
75
83
 
76
84
  pl.brightness = (pl.gyroPose == :laidDown) ? :idle : :teamSelect;
77
85
  end
78
86
 
79
87
  on :navSwitchPressed do |player, dir|
80
- next if(@handler.gamePhase != :teamSelect)
81
- next unless [:teamSelect, :idle].include? player.brightness
88
+ next unless in_phase
82
89
 
83
90
  newTeam = @possibleTeams.find_index(player.team) || 0;
84
91
 
@@ -92,6 +99,26 @@ module LZRTag
92
99
  player.brightness = :teamSelect
93
100
  end
94
101
  end
102
+
103
+ on :playerEnteredBeacon do |pl, beacon|
104
+ next unless in_phase
105
+
106
+ next unless is_selecting(pl)
107
+ next unless(@possibleTeams.include? beacon)
108
+
109
+ pl.team = beacon;
110
+ pl.brightness = :teamSelect;
111
+ end
112
+
113
+ on :playerLeftBeacon do |pl, beacon|
114
+ next unless in_phase
115
+
116
+ next unless(pl.team == beacon)
117
+ next unless is_selecting(pl)
118
+
119
+ pl.team = 0;
120
+ pl.brightness = :idle
121
+ end
95
122
  end
96
123
 
97
124
  class Regenerator < Base
@@ -139,7 +166,7 @@ module LZRTag
139
166
  describe_option :dmgPerShot, "Base damage per shot"
140
167
  describe_option :useDamageMultiplier, "Shall shots be adjusted per-gun?"
141
168
  describe_option :friendlyFire, "Shall friendly-fire be enabled"
142
- describe_option :hitThreshold, "Limit below dead players will not be hit"
169
+ describe_option :hitThreshold, "Limit below which dead players will not be hit"
143
170
 
144
171
  def initialize(handler, **options)
145
172
  super(handler);
@@ -170,5 +197,33 @@ module LZRTag
170
197
  hitPlayer.hit();
171
198
  end
172
199
  end
200
+
201
+ class GunSelector < Base
202
+ describe_option :phaseFilter, "Which phase to be active during"
203
+ describe_option :teamFilter, "Which team to be active for"
204
+
205
+ def initialize(handler, **opts)
206
+ super(handler);
207
+
208
+ @phaseFilter = opts[:phaseFilter] || [:running]
209
+ @teamFilter = opts[:teamFilter] || (0..7).to_a
210
+
211
+ @guns = [1, 2];
212
+ end
213
+
214
+ on :navSwitchPressed do |pl, dir|
215
+ next unless @phaseFilter.include? @handler.gamePhase
216
+ next unless @teamFilter.include? pl.team
217
+
218
+ if(dir == 1)
219
+ pl.reload
220
+ elsif dir == 2
221
+ pl.gunNo = 3;
222
+ elsif dir == 3
223
+ oldInd = @guns.find_index(pl.gunNo) || 0;
224
+ pl.gunNo = @guns[(1+oldInd) % @guns.length] || 1
225
+ end
226
+ end
227
+ end
173
228
  end
174
229
  end
@@ -19,6 +19,11 @@ module LZRTag
19
19
  # @return [String] status-string of the player. Should be "OK"
20
20
  attr_reader :status
21
21
 
22
+ attr_reader :connected
23
+
24
+ # @return [Time] Time at which the player status was last updated
25
+ attr_reader :last_status_update
26
+
22
27
  # @return [Integer] 0..255, shot ID of the player
23
28
  attr_reader :id
24
29
  # @return [Hash<Time>] Hash of the last few recorded shot times,
@@ -34,11 +39,14 @@ module LZRTag
34
39
  @status = "";
35
40
  @name = "";
36
41
 
42
+ @last_status_update = Time.at(0);
43
+ @connected = false;
44
+
37
45
  @hitIDTimetable = Hash.new(Time.new(0));
38
46
  end
39
47
 
40
48
  def _pub_to(key, data, retain: false)
41
- @mqtt.publish_to("Lasertag/Players/#{@DeviceID}/#{key}", data, retain: retain);
49
+ @mqtt.publish_to("Lasertag/Players/#{@DeviceID}/#{key}", data, retain: retain, qos: 1);
42
50
  end
43
51
  private :_pub_to
44
52
 
@@ -47,21 +55,38 @@ module LZRTag
47
55
  case topic[1..topic.length].join("/")
48
56
  when "Connection"
49
57
  return if @status == data;
50
- oldStatus = @status;
51
58
  @status = data;
52
- if(@status == "OK")
53
- @handler.send_event(:playerConnected, self);
54
- elsif(oldStatus == "OK")
55
- @handler.send_event(:playerDisconnected, self);
56
- end
59
+
60
+ return if @status == "OK"
61
+ return if @status == ""
62
+ return if !@connected
63
+ @connected = false;
64
+ @handler.send_event(:playerDisconnected, self);
65
+
57
66
  when "CFG/Name"
58
67
  @name = data;
68
+ when "Ping"
69
+ @last_status_update = Time.now();
70
+
71
+ if(@status == "OK" && (!@connected))
72
+ @connected = true
73
+ @handler.send_event(:playerConnected, self);
74
+ end
75
+ end
76
+ end
77
+
78
+ def _tick_connection()
79
+ return unless @connected
80
+
81
+ if((Time.now() - @last_status_update) > 60)
82
+ @connected = false;
83
+ @handler.send_event(:playerDisconnected, self);
59
84
  end
60
85
  end
61
86
 
62
87
  # @return [Boolean] Whether this player is connected
63
88
  def connected?()
64
- return @status == "OK"
89
+ return @connected
65
90
  end
66
91
 
67
92
  # Set the Shot ID of the player.
@@ -41,16 +41,22 @@ module LZRTag
41
41
  # Especially useful to determine when to revive a player
42
42
  attr_reader :deathChangeTime
43
43
 
44
- # Current ammo of the weapon.
45
- # TODO one day this should be settable. Right now, it's just reading
46
- attr_reader :ammo
47
- # Maximum ammo the weapon can have with the currently equipped gun.
48
- attr_reader :maxAmmo
49
44
  # Number of the current gun.
50
45
  # The application can freely choose which gun profile the set is using,
51
46
  # which influences shot speeds, sounds, reloading, etc.
52
47
  attr_reader :gunNo
53
48
 
49
+ # Current amount of reserve ammo (i.e. clips the player can reload with)
50
+ # Can be set to -1 to enable infinite reserve ammo (default), or
51
+ # any other value to give the player limited ammo
52
+ attr_reader :reserveAmmo
53
+ # Current ammo in the clip. Can be set to -1 for infinite shots (no
54
+ # reloading necessary at all), or any other numeric value. Default is
55
+ # a single clip (gun-dependant)
56
+ attr_reader :clipAmmo
57
+ # Size of the clip of the currently equipped gun. Can not be set!
58
+ attr_reader :clipSize
59
+
54
60
  # Returns the gyro pose of the set.
55
61
  # This is either:
56
62
  # - :active
@@ -61,6 +67,9 @@ module LZRTag
61
67
  # The :poseChanged event is sent on change with [player, newPose] data
62
68
  attr_reader :gyroPose
63
69
 
70
+ # Returns a Hash of the last timestamps when a beacon was detected
71
+ attr_reader :beaconTimes
72
+
64
73
  attr_reader :battery, :ping, :heap
65
74
 
66
75
  def self.getBrightnessKeys()
@@ -76,8 +85,10 @@ module LZRTag
76
85
  @dead = false;
77
86
  @deathChangeTime = Time.now();
78
87
 
79
- @ammo = 0;
80
- @maxAmmo = 0;
88
+ @reserveAmmo = 0;
89
+ @clipAmmo = 0;
90
+ @clipSize = 0;
91
+
81
92
  @gunNo = 0;
82
93
 
83
94
  @gyroPose = :unknown;
@@ -85,6 +96,9 @@ module LZRTag
85
96
  @position = {x: 0, y: 0}
86
97
  @zoneIDs = Hash.new();
87
98
 
99
+ @beaconTimes = Hash.new();
100
+ @BeaconTimeout = 3;
101
+
88
102
  @battery = 0; @ping = 0; @heap = 0;
89
103
 
90
104
  @BrightnessMap = self.class.getBrightnessKeys();
@@ -120,11 +134,12 @@ module LZRTag
120
134
 
121
135
  @handler.send_event(@dead ? :playerKilled : :playerRevived, self);
122
136
  when "Stats/Ammo"
123
- return if(data.size != 8)
137
+ return if(data.size != 12)
124
138
 
125
139
  outData = data.unpack("L<*");
126
- @ammo = outData[0];
127
- @maxAmmo = outData[1];
140
+ @clipAmmo = outData[0];
141
+ @clipSize = outData[1];
142
+ @reserveAmmo = outData[2];
128
143
  when "Position"
129
144
  begin
130
145
  @position = JSON.parse(data, symbolize_names: true);
@@ -135,6 +150,13 @@ module LZRTag
135
150
  when "HW/Gyro"
136
151
  @gyroPose = data.to_sym
137
152
  @handler.send_event(:poseChanged, self, @gyroPose);
153
+ when "HW/BeaconDetect"
154
+ beaconID = data.to_i;
155
+ unless(@beaconTimes[beaconID])
156
+ @handler.send_event(:playerEnteredBeacon, self, beaconID);
157
+ end
158
+ @beaconTimes[beaconID] = Time.now();
159
+ @handler.send_event(:playerInBeacon, self, beaconID);
138
160
  when "ZoneUpdate"
139
161
  begin
140
162
  data = JSON.parse(data, symbolize_names: true);
@@ -184,13 +206,14 @@ module LZRTag
184
206
 
185
207
  def _set_dead(d, player = nil)
186
208
  dead = (d ? true : false);
187
- return if @dead == dead;
209
+ return false if @dead == dead;
188
210
  @dead = dead;
189
211
 
190
212
  @deathChangeTime = Time.now();
191
213
 
192
214
  _pub_to "CFG/Dead", @dead ? "1" : "0", retain: true;
193
215
  @handler.send_event(@dead ? :playerKilled : :playerRevived, self, player);
216
+ return true;
194
217
  end
195
218
  def dead=(d)
196
219
  _set_dead(d);
@@ -204,14 +227,19 @@ module LZRTag
204
227
  _set_dead(false, player)
205
228
  end
206
229
 
207
- def ammo=(n)
208
- unless (n.is_a?(Integer) and (n >= 0))
209
- raise ArgumentError, "Ammo amount needs to be a positive number!"
230
+ def reserveAmmo=(n)
231
+ unless (n.is_a?(Integer))
232
+ raise ArgumentError, "Ammo amount needs to be a number!"
210
233
  end
211
234
 
212
- @ammo = n;
235
+ _pub_to("Stats/Ammo/SetReserve", n);
236
+ end
237
+ def clipAmmo=(n)
238
+ unless (n.is_a?(Integer))
239
+ raise ArgumentError, "Ammo amount needs to be a number!"
240
+ end
213
241
 
214
- _pub_to("Stats/Ammo/Set", n);
242
+ _pub_to("Stats/Ammo/SetClip", n);
215
243
  end
216
244
 
217
245
  def gunNo=(n)
@@ -239,6 +267,20 @@ module LZRTag
239
267
  return @GunDamageMultipliers[number-1] || 1;
240
268
  end
241
269
 
270
+ def reload()
271
+ _pub_to("CFG/Reload", "1");
272
+ end
273
+
274
+ def check_beacons()
275
+ bIDs = @beaconTimes.keys;
276
+ bIDs.each do |bID|
277
+ if((Time.now() - @beaconTimes[bID]) > @BeaconTimeout)
278
+ @handler.send_event(:playerLeftBeacon, self, bID);
279
+ @beaconTimes.delete(bID)
280
+ end
281
+ end
282
+ end
283
+
242
284
  def clear_all_topics()
243
285
  super();
244
286
 
@@ -18,7 +18,7 @@ module LZRTag
18
18
 
19
19
  # Returns the last time the player was damaged.
20
20
  # This can be useful to apply buffs and other data, as well as
21
- # "post-damage regenration delay"
21
+ # "post-damage regeneration delay"
22
22
  attr_reader :lastDamageTime
23
23
 
24
24
  def initialize(*data)
@@ -45,17 +45,21 @@ module LZRTag
45
45
 
46
46
  nLife = @life + amount;
47
47
  nLife = @maxLife if(nLife > @maxLife)
48
- return if nLife == @life;
48
+ return 0 if nLife == @life;
49
49
 
50
50
  oLife = @life;
51
51
  @life = nLife;
52
52
 
53
- @handler.send_event(:playerRegenerated, self, @life - oLife, source);
53
+ lifeDiff = @life - oLife;
54
+
55
+ @handler.send_event(:playerRegenerated, self, lifeDiff, source);
54
56
  if(@life == maxLife)
55
57
  @handler.send_event(:playerFullyRegenerated, self, source);
56
58
  end
57
59
 
58
60
  _pub_to("Stats/HP", @life.to_s, retain: true);
61
+
62
+ return lifeDiff
59
63
  end
60
64
 
61
65
  # Damage a player by a given amount with given source.
@@ -0,0 +1,107 @@
1
+
2
+ require_relative 'life_player.rb'
3
+
4
+ module LZRTag
5
+ module Player
6
+ class Statistics < Life
7
+ attr_reader :stats
8
+
9
+ def initialize(*args)
10
+ @stats = {
11
+ "Kills" => 0,
12
+ "Deaths" => 0,
13
+ "DamageDone" => 0,
14
+ "DamageReceived" => 0,
15
+ "Healed" => 0,
16
+ }
17
+
18
+ super(*args);
19
+ end
20
+
21
+ def _update_stat(key, value)
22
+ return unless(@stats.keys.include? key);
23
+ return if(@stats[key] == value);
24
+
25
+ @stats[key] = value;
26
+ _pub_to("Stats/#{key}", value, retain: true);
27
+ end
28
+ def _increment_stat(key, value)
29
+ _update_stat(key, @stats[key] + value);
30
+ end
31
+
32
+ def reset()
33
+ @stats.keys.each do |k|
34
+ @stats[k] = 0;
35
+ _pub_to("Stats/#{k}", 0, retain: true);
36
+ end
37
+
38
+ # super(); // TO DO!
39
+ end
40
+
41
+ def regenerate(*args)
42
+ healDone = super(*args);
43
+
44
+ _increment_stat("Healed", healDone);
45
+ end
46
+ def damage_by(*args)
47
+ dmg_done = super(*args);
48
+
49
+ _increment_stat("DamageReceived", dmg_done);
50
+ args[1]._increment_stat("DamageDone", dmg_done) if(args[1])
51
+ end
52
+
53
+ def _set_dead(d, player = nil)
54
+ return unless super(d, player);
55
+ return unless d;
56
+
57
+ player._increment_stat("Kills", 1) if(player);
58
+
59
+ _increment_stat("Deaths", 1);
60
+ end
61
+
62
+ def ratio_kd()
63
+ return @stats["Kills"]/([1, @stats["Deaths"]].max);
64
+ end
65
+
66
+
67
+ def self.print_start_line()
68
+ stats = ["Kills", "Deaths", "DMG Done", "DMG Rec", "Healed"]
69
+ sep = "\u2501" * 10;
70
+
71
+ puts "\u250F" + sep + ("\u252F" + sep) * (stats.count) + "\u2513";
72
+ puts "\u2503" + (" %8s \u2502" * (stats.count) + " %8s \u2503") % ["Name", stats].flatten;
73
+ puts "\u2523" + sep + ("\u253F" + sep)*(stats.count) + "\u252B";
74
+ end
75
+
76
+ def self.print_end_line()
77
+ stats = ["Kills", "Deaths", "DMG Done", "DMG Rec", "Healed"]
78
+ sep = "\u2501" * 10;
79
+
80
+ puts "\u2517" + sep + ("\u2537" + sep)*(stats.count) + "\u251B";
81
+ end
82
+
83
+ def print_stat_line()
84
+ outStr = "\u2503 %8s ";
85
+ strArgs = [@name];
86
+
87
+ @stats.each do |key, val|
88
+ strArgs << val;
89
+ outStr += "\u2502 %8d ";
90
+ end
91
+ outStr += "\u2503";
92
+
93
+ puts outStr % strArgs;
94
+ end
95
+
96
+
97
+
98
+ def clear_all_topics()
99
+ super();
100
+
101
+ @stats.keys.each do |k|
102
+ _pub_to("Stats/#{k}", "", retain: true);
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
metadata CHANGED
@@ -1,79 +1,65 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lzrtag-base
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xasin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-19 00:00:00.000000000 Z
11
+ date: 2019-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: mqtt-sub_handler
14
+ name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.4
19
+ version: '2.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.4
26
+ version: '2.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: xml-simple
28
+ name: mqtt-sub_handler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 0.1.6
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: minitest
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
38
+ - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: '0'
40
+ version: 0.1.6
55
41
  - !ruby/object:Gem::Dependency
56
- name: guard
42
+ name: xasin-logger
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
- - - ">="
45
+ - - "~>"
60
46
  - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
47
+ version: '0.1'
48
+ type: :runtime
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
- - - ">="
52
+ - - "~>"
67
53
  - !ruby/object:Gem::Version
68
- version: '0'
54
+ version: '0.1'
69
55
  - !ruby/object:Gem::Dependency
70
- name: guard-minitest
56
+ name: xml-simple
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
59
  - - ">="
74
60
  - !ruby/object:Gem::Version
75
61
  version: '0'
76
- type: :development
62
+ type: :runtime
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
@@ -104,6 +90,7 @@ files:
104
90
  - lib/lzrtag/player/effects_player.rb
105
91
  - lib/lzrtag/player/hardware_player.rb
106
92
  - lib/lzrtag/player/life_player.rb
93
+ - lib/lzrtag/player/stats_player.rb
107
94
  homepage: https://github.com/XasWorks/LZRTag/
108
95
  licenses:
109
96
  - GPL-3.0
@@ -123,8 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
110
  - !ruby/object:Gem::Version
124
111
  version: '0'
125
112
  requirements: []
126
- rubyforge_project:
127
- rubygems_version: 2.5.2.1
113
+ rubygems_version: 3.0.6
128
114
  signing_key:
129
115
  specification_version: 4
130
116
  summary: Base classes for Xasin's LZRTag system