lzrtag-base 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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