lzrtag-base 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+
2
+ require 'json'
3
+ require_relative 'base_handler.rb'
4
+
5
+ module LZRTag
6
+ module Handler
7
+ # Hit arbitration handling class.
8
+ # This class extends the Handler::Base class, adding important features
9
+ # to ensure that each player shot is only counted once.
10
+ # Additionally, hooks can manipulate this behavior and prevent friendly-fire,
11
+ # or enable multiple hits in the case of a shotgun!
12
+ #
13
+ # Shot arbitration is performed by listening to "Lasertag/Game/Events",
14
+ # waiting for a 'type: "hit"'
15
+ # If such a JSON payload is found, hit and source players will be
16
+ # determined, and every available hook's "process_raw_hit" function
17
+ # is called. If this function returns false, the hit will be "vetoed" and
18
+ # does not count at all. However, a hook can raise {LZRTag::Handler::NoArbitration},
19
+ # preventing this shot from being logged and thusly enabling multiple hits
20
+ # @see Base
21
+ class HitArb < Base
22
+ def initialize(*data, **options)
23
+ super(*data, **options);
24
+
25
+ @mqtt.subscribe_to "Lasertag/Game/Events" do |data|
26
+ begin
27
+ data = JSON.parse(data, symbolize_names: true);
28
+
29
+ if(data[:type] == "hit")
30
+ _handle_hitArb(data);
31
+ end
32
+ rescue JSON::ParserError
33
+ end
34
+ end
35
+ end
36
+
37
+ def process_raw_hit(*)
38
+ return true;
39
+ end
40
+
41
+ def _handle_hitArb(data)
42
+ unless (hitPlayer = get_player(data[:target])) and
43
+ (sourcePlayer = get_player(data[:shooterID])) and
44
+ (arbCode = data[:arbCode])
45
+ return
46
+ end
47
+
48
+ return if (sourcePlayer.hitIDTimetable[arbCode] + 1) > Time.now();
49
+
50
+ veto = false;
51
+ arbitrateShot = true;
52
+
53
+ hookList = Array.new();
54
+ hookList << @hooks;
55
+ if(@currentGame)
56
+ hookList << @currentGame.hookList
57
+ end
58
+ hookList.flatten
59
+
60
+ hookList.each do |h|
61
+ begin
62
+ veto |= !(h.process_raw_hit(hitPlayer, sourcePlayer));
63
+ rescue NoArbitration
64
+ arbitrateShot = false;
65
+ end
66
+ end
67
+ return if veto;
68
+
69
+ if arbitrateShot
70
+ sourcePlayer.hitIDTimetable[arbCode] = Time.now();
71
+ end
72
+
73
+ send_event(:playerHit, hitPlayer, sourcePlayer);
74
+ end
75
+ end
76
+
77
+ class NoArbitration < Exception
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,143 @@
1
+
2
+ module LZRTag
3
+ module Hook
4
+ =begin
5
+ Base class for all game hooks, implements DSL.
6
+ This class shall be used as a base class for any DIY game hooks.
7
+ The purpose of any hook is to implement a specific element of a game,
8
+ such as damaging players, regenerating them, handing out teams, etc.
9
+
10
+ This hook base class implements a DSL that makes it very easy for the application
11
+ to implement their own behavior in a modular fashion.
12
+
13
+ @example
14
+ class MyHook < LZRTag::Base::Hook
15
+ # Hooks can register which parameters are configurable, and how
16
+ # This will be used to reconfigure them on the fly, but it is not mandatory
17
+ # to register options.
18
+ describe_option :myValue, "A description of my Value";
19
+
20
+ def initialize(handler, **options)
21
+ super(handler);
22
+
23
+ @myValue = options[:myValue] || "default";
24
+ end
25
+
26
+ # The "on" DSL makes it easy to perform tasks on
27
+ # any arbitrary event
28
+ on :playerKilled do |player|
29
+ puts "Player #{player.name} was killed :C";
30
+ end
31
+ end
32
+
33
+ class MyGame < LZRTag::Game::Base
34
+ # A game can register that it wants to use this hook, and
35
+ # even which options to use for it.
36
+ hook :aHook, MyHook, {myValue: "Not a default!"};
37
+ end
38
+
39
+ # Alternatively, the hook can be added to the game directly
40
+ handler.add_hook(MyHook);
41
+ =end
42
+ class Base
43
+ def self.getCBs()
44
+ @globalCBList ||= Hash.new();
45
+ return @globalCBList;
46
+ end
47
+ def self.getOptionDescriptions()
48
+ @globalOptionDescriptions ||= Hash.new();
49
+ return @globalOptionDescriptions
50
+ end
51
+
52
+ def initialize(handler)
53
+ @localCBList = Hash.new();
54
+
55
+ @handler = handler
56
+ end
57
+
58
+ # DSL function to describe an option of this hook.
59
+ # The application can use this DSL to describe a given option,
60
+ # identified by optionSymbol. The extraDetails hash is optional,
61
+ # but in the future will allow this hook to be reconfigured remotely
62
+ # via MQTT!
63
+ # @param optionSymbol [Symbol] The Symbol used for this option
64
+ # @param descString [String] String description of this option.
65
+ # @param extraDetails [Hash] Optional hash to provide further details
66
+ # of this option, such as "min", "max", "type", etc.
67
+ def self.describe_option(optionSymbol, descString, extraDetails = {})
68
+ raise ArgumentError, "Option shall be a symbol!" unless optionSymbol.is_a? Symbol
69
+ raise ArgumentError, "Description should be a string!" unless descString.is_a? String
70
+ getOptionDescriptions()[optionSymbol] = extraDetails;
71
+ getOptionDescriptions()[optionSymbol][:desc] = descString;
72
+ end
73
+
74
+ # DSL function to add a block on an event.
75
+ # The application can provide a block to this function that will be executed
76
+ # for every "evtName" game event
77
+ # @param evtName [Symbol] Event name to trigger this block on
78
+ def self.on(evtName, &block)
79
+ raise ArgumentError, "Block needs to be given!" unless block_given?
80
+
81
+ evtName = [evtName].flatten
82
+ evtName.each do |evt|
83
+ unless (evt.is_a? Symbol)
84
+ raise ArgumentError, "Event needs to be a symbol or array of symbols!"
85
+ end
86
+ getCBs()[evt] ||= Array.new();
87
+ getCBs()[evt] << block;
88
+ end
89
+ end
90
+
91
+ # Function to add hooks to the already instantiated hook.
92
+ # This function works exactly like {self.on}, except that it
93
+ # acts on an instance of hook, and allows the app to extend a standard
94
+ # hook by extending it afterwards.
95
+ def on(evtName, &block)
96
+ raise ArgumentError, "Block needs to be given!" unless block_given?
97
+
98
+ evtName = [evtName].flatten
99
+ evtName.each do |evt|
100
+ unless (evt.is_a? Symbol)
101
+ raise ArgumentError, "Event needs to be a symbol or array of symbols!"
102
+ end
103
+ @localCBList[evt] ||= Array.new();
104
+ @localCBList[evt] << block;
105
+ end
106
+ end
107
+
108
+ # @private
109
+ def consume_event(evtName, data)
110
+ if(cbList = self.class.getCBs()[evtName])
111
+ cbList.each do |cb|
112
+ begin
113
+ instance_exec(*data, &cb);
114
+ rescue StandardError => e
115
+ puts e.message
116
+ puts e.backtrace.inspect
117
+ end
118
+ end
119
+ end
120
+ if(cbList = @localCBList[evtName]) then
121
+ cbList.each do |cb|
122
+ begin
123
+ cb.call(*data);
124
+ rescue StandardError => e
125
+ puts e.message
126
+ puts e.backtrace.inspect
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def on_hookin(handler)
133
+ @handler = handler;
134
+ end
135
+ def on_hookout()
136
+ end
137
+
138
+ def process_raw_hit(*)
139
+ return true;
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,174 @@
1
+
2
+ require_relative 'base_hook.rb'
3
+
4
+ module LZRTag
5
+ module Hook
6
+ class Debug < Base
7
+ attr_accessor :eventWhitelist
8
+ attr_accessor :eventBlacklist
9
+
10
+ def initialize()
11
+ super();
12
+
13
+ @eventWhitelist = Array.new();
14
+ @eventBlacklist = Array.new();
15
+ end
16
+
17
+ def consume_event(evtName, data)
18
+ super(evtName, data);
19
+
20
+ return if @eventBlacklist.include? evtName
21
+ unless(@eventWhitelist.empty?)
22
+ return unless @eventWhitelist.include? evtName
23
+ end
24
+
25
+ puts "Caught event: #{evtName} with data: #{data}";
26
+ end
27
+ end
28
+
29
+ class TeamSelector < Base
30
+ describe_option :possibleTeams, "List of teams that can be selected", {
31
+ type: Array
32
+ }
33
+
34
+ def initialize(handler, possibleTeams: [1, 2, 3, 4])
35
+ super(handler);
36
+
37
+ @possibleTeams = possibleTeams;
38
+ end
39
+
40
+ on :gamePhaseEnds do |oldPhase, nextPhase|
41
+ if((oldPhase == :teamSelect) && (nextPhase != :idle))
42
+ puts "Selecting active players!"
43
+
44
+ @handler.gamePlayers = Array.new();
45
+ @handler.each do |pl|
46
+ if(pl.brightness == :active)
47
+ @handler.gamePlayers << pl;
48
+ end
49
+ end
50
+
51
+ puts "Game players are: #{@handler.gamePlayers}"
52
+ end
53
+ end
54
+
55
+ on :gamePhaseStarts do |nextPhase, oldPhase|
56
+ case(nextPhase)
57
+ when :teamSelect
58
+ @handler.each do |pl|
59
+ pl.brightness = :idle;
60
+
61
+ if(!@possibleTeams.include?(pl.team))
62
+ pl.team = @possibleTeams.sample();
63
+ end
64
+ end
65
+ when :idle
66
+ @handler.each do |pl|
67
+ pl.brightness = :idle;
68
+ end
69
+ end
70
+ end
71
+
72
+ on :poseChanged do |pl, nPose|
73
+ next if(@handler.gamePhase != :teamSelect)
74
+ next if(pl.brightness == :active)
75
+
76
+ pl.brightness = (pl.gyroPose == :laidDown) ? :idle : :teamSelect;
77
+ end
78
+
79
+ on :navSwitchPressed do |player, dir|
80
+ next if(@handler.gamePhase != :teamSelect)
81
+ next unless [:teamSelect, :idle].include? player.brightness
82
+
83
+ newTeam = @possibleTeams.find_index(player.team) || 0;
84
+
85
+ newTeam += 1 if(dir == 2)
86
+ newTeam -= 1 if(dir == 3)
87
+
88
+ player.team = @possibleTeams[newTeam % @possibleTeams.length]
89
+ if(dir == 1)
90
+ player.brightness = :active
91
+ else
92
+ player.brightness = :teamSelect
93
+ end
94
+ end
95
+ end
96
+
97
+ class Regenerator < Base
98
+
99
+ describe_option :regRate, "Regeneraton rate, HP per second"
100
+ describe_option :regDelay, "Healing delay, in s, after a player was hit"
101
+ describe_option :healDead, "Whether or not to heal dead players"
102
+
103
+ describe_option :autoReviveThreshold, "The HP a player needs before he is revived"
104
+
105
+ describe_option :teamFilter, "Which teams this regenerator belongs to"
106
+ describe_option :phaseFilter, "During which phases this hook should be active"
107
+
108
+ def initialize(handler, **options)
109
+ super(handler);
110
+
111
+ @regRate = options[:regRate] || 1;
112
+ @regDelay = options[:regDelay] || 10;
113
+
114
+ @healDead = options[:healDead] || false;
115
+ @autoReviveThreshold = options[:autoReviveThreshold] || 30;
116
+
117
+ @teamFilter = options[:teamFilter] || (0..7).to_a
118
+ @phaseFilter = options[:phaseFilter] || [:running]
119
+ end
120
+
121
+ on :gameTick do |dT|
122
+ next unless @phaseFilter.include? @handler.gamePhase
123
+
124
+ @handler.each_participating do |pl|
125
+ next unless @teamFilter.include? pl.team
126
+
127
+ if((Time.now() - pl.lastDamageTime) >= @regDelay)
128
+ pl.regenerate(dT * @regRate);
129
+ end
130
+
131
+ if(pl.dead and pl.life >= @autoReviveThreshold)
132
+ pl.dead = false;
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ class Damager < Base
139
+ describe_option :dmgPerShot, "Base damage per shot"
140
+ describe_option :useDamageMultiplier, "Shall shots be adjusted per-gun?"
141
+ describe_option :friendlyFire, "Shall friendly-fire be enabled"
142
+ describe_option :hitThreshold, "Limit below dead players will not be hit"
143
+
144
+ def initialize(handler, **options)
145
+ super(handler);
146
+
147
+ @dmgPerShot = options[:dmgPerShot] || 40;
148
+ @useDamageMultiplier = options[:useDamageMultiplier] || true;
149
+ @friendlyFire = options[:friendlyFire] || false;
150
+ @hitThreshold = options[:hitThreshold] || 10;
151
+ end
152
+
153
+ def process_raw_hit(hitPlayer, sourcePlayer)
154
+ unless(@friendlyFire)
155
+ return false if hitPlayer.team == sourcePlayer.team
156
+ end
157
+ return false if(hitPlayer.dead && (hitPlayer.life < @hitThreshold));
158
+
159
+ return true;
160
+ end
161
+
162
+ on :playerHit do |hitPlayer, sourcePlayer|
163
+ shotMultiplier = 1;
164
+
165
+ if((@useDamageMultiplier) && (!sourcePlayer.nil?))
166
+ shotMultiplier = sourcePlayer.gunDamage();
167
+ end
168
+
169
+ hitPlayer.damage_by(@dmgPerShot * shotMultiplier, sourcePlayer);
170
+ hitPlayer.hit();
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,48 @@
1
+
2
+ require_relative 'map_zone.rb'
3
+ require_relative 'myMaps_parser.rb'
4
+
5
+ module LZRTag
6
+ module Map
7
+ class Set
8
+ attr_reader :zones
9
+
10
+ attr_accessor :centerpoint
11
+
12
+ def initialize(mqtt, zones = Array.new())
13
+ @mqtt = mqtt;
14
+ @zones = zones;
15
+
16
+ @centerpoint = Array.new();
17
+ end
18
+
19
+ def to_h()
20
+ outData = Hash.new();
21
+
22
+ if(@centerpoint.length != 3)
23
+ raise ArgumentError, "Center point needs to be set!"
24
+ end
25
+
26
+ outData[:centerpoint] = @centerpoint
27
+
28
+ outData[:zones] = Array.new();
29
+ @zones.each do |z|
30
+ outData[:zones] << z.to_h;
31
+ end
32
+
33
+ return outData;
34
+ end
35
+
36
+ def to_json()
37
+ return self.to_h().to_json();
38
+ end
39
+
40
+ def publish()
41
+ @mqtt.publish_to "Lasertag/Zones", self.to_json, qos: 1, retain: true;
42
+ end
43
+ def clear()
44
+ @mqtt.publish_to "Lasertag/Game/Zones", "", retain: true;
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,95 @@
1
+
2
+ module LZRTag
3
+ module Map
4
+ class Zone
5
+ attr_accessor :tag
6
+
7
+ attr_accessor :centerPoint, :radius
8
+ attr_accessor :polygon
9
+ attr_accessor :coordinatesAsGPS
10
+
11
+ attr_accessor :teamMask
12
+
13
+ attr_accessor :data
14
+
15
+ attr_accessor :style
16
+
17
+ def initialize()
18
+ @centerPoint = [0, 0];
19
+ @radius = 0;
20
+ @polygon = Array.new();
21
+ @coordinatesAsGPS = false;
22
+
23
+ @teamMask = 255;
24
+
25
+ @style = {
26
+ color: "transparent",
27
+ borderColor: "black"
28
+ }
29
+
30
+ @data = Hash.new();
31
+ end
32
+
33
+ def affects_teams(teamList)
34
+ @teamMask = 0;
35
+ [teamList].flatten.each do |t|
36
+ @teamMask += (2<<t);
37
+ end
38
+ end
39
+ def ignores_teams(teamList)
40
+ @teamMask = 255;
41
+ [teamList].flatten.each do |t|
42
+ @teamMask -= (2<<t);
43
+ end
44
+ end
45
+
46
+ def self.from_raw_zone(rawZone)
47
+ outZone = Zone.new();
48
+
49
+ outZone.tag = rawZone[:arguments]["tag"] || rawZone[:name];
50
+
51
+ outZone.polygon = rawZone[:polygon];
52
+ outZone.coordinatesAsGPS = true;
53
+
54
+ if(rawZone[:style])
55
+ outZone.style = rawZone[:style];
56
+ end
57
+
58
+ if(tMask = rawZone[:arguments]["teamMask"])
59
+ outZone.teamMask = tMask.to_i;
60
+ rawZone[:arguments].delete "teamMask"
61
+ end
62
+
63
+ outZone.data = rawZone[:arguments];
64
+
65
+ return outZone
66
+ end
67
+
68
+ def to_h()
69
+ outHash = Hash.new();
70
+
71
+ raise ArgumentError, "Tag needs to be set!" if(@tag.nil?);
72
+ outHash[:tag] = @tag;
73
+ outHash[:teamMask] = @teamMask;
74
+
75
+ if(@radius > 0.1)
76
+ outHash[:centerPoint] = @centerPoint;
77
+ outHash[:radius] = @radius;
78
+ else
79
+ outHash[:polygon] = @polygon;
80
+ end
81
+ outHash[:coordinatesAsGPS] = @coordinatesAsGPS
82
+
83
+ outHash[:style] = @style;
84
+
85
+ outHash[:data] = @data;
86
+
87
+ return outHash;
88
+ end
89
+
90
+ def inspect()
91
+ return "#<Zone: #{to_h}>";
92
+ end
93
+ end
94
+ end
95
+ end