1
+ -- Grief detection
2
+ -- Written by Mylon, 2017-2018
3
+ -- MIT License
4
+
5
+ antigrief = {}
6
+ global .antigrief_cooldown = {}
7
+ global .antigrief = {warned = {}}
8
+
9
+ antigrief .TROLL_TIMER = 60 * 60 * 30 -- 30 minutes. Players must be online this long to not throw some warnings.
10
+ antigrief .SPAM_TIMER = 60 * 60 * 2 -- 10 minutes. Limit inventory related messages to once per 10m.
11
+
12
+
13
+
14
+ -- ACTIVE functions
15
+ function antigrief .arty_remote_ban (event )
16
+ local player = game .players [event .player_index ]
17
+ local area = {{event .position .x - 20 , event .position .y - 20 }, {event .position .x + 20 , event .position .y + 20 }}
18
+ local count = player .surface .count_entities_filtered {force = player .force , area = area }
19
+ local ghosts = player .surface .count_entities_filtered {name = " entity-ghost" , force = player .force , area = area }
20
+
21
+ -- Ghosts don't count! They can't be damaged.
22
+ count = count - ghosts
23
+
24
+ if event .item .name == " artillery-targeting-remote" and count > 50 then
25
+ antigrief .banhammer (player )
26
+ antigrief .alert (player .name .. " is using an artillery remote maliciously." , player .character )
27
+ elseif string.find (event .item .name , " grenade" ) and player .surface .count_entities_filtered {force = player .force , area = area , name = " steam-engine" } > 20 then -- Grenading power
28
+ antigrief .banhammer (player )
29
+ antigrief .alert (player .name .. " is using grenading power." , player .character )
30
+ end
31
+ end
32
+
33
+ function antigrief .banhammer (player )
34
+ -- If player is > level 5, then warn first.
35
+ -- What permission group is the player in?
36
+ if player .permissions_group .name == " trusted" then -- Kick first.
37
+ if not global .antigrief .warned [player .name ] then
38
+ global .antigrief .warned [player .name ] = true
39
+ game .kick_player (player , " griefing (automoderator)" )
40
+ return
41
+ end
42
+ else
43
+ game .ban_player (player , " griefing (automoderator)" )
44
+ end
45
+ end
46
+
47
+
48
+ -- PASSIVE functions
49
+ -- Common tactic is to remove pump. So if someone landfills a pump and removes it... That's a huge red flag.
50
+ function antigrief .pump (event )
51
+ if not event .entity and not event .entity .valid then
52
+ return
53
+ end
54
+ -- Only check for entities in a specific list.
55
+ if antigrief .is_well_pump (event .entity ) then
56
+ local player = game .players [event .player_index ]
57
+ antigrief .alert (player .name .. " has mined a well-water pump." , event .entity )
58
+ end
59
+ end
60
+
61
+ -- Look for players mining ghosts far away.
62
+ -- Spooky action at a distance!
63
+ function antigrief .ghosting (event )
64
+ local player = game .players [event .player_index ]
65
+ if not event .entity and not event .entity .valid and not player and not player .valid then
66
+ return
67
+ end
68
+ if event .entity .type == " entity-ghost" then
69
+ -- Look for units mined 200 tiles away.
70
+ if math.abs (event .entity .position .x - player .position .x ) + math.abs (event .entity .position .y - player .position .y ) > 200 then
71
+ if antigrief .check_cooldown (event .player_index , " ghosting" ) then
72
+ antigrief .alert (player .name .. " is removing blueprint ghosts." , event .entity )
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ -- When someone decons > 150 entities, fire an alert
79
+ function antigrief .decon (event )
80
+ if event .alt then -- This is a cancel order.
81
+ return
82
+ end
83
+ if event .area .left_top .x == event .area .right_bottom .x or event .area .left_top .y == event .area .right_bottom .y then
84
+ -- log("Antigrief: Deconstruction area is of zero size.")
85
+ return
86
+ end
87
+ local player = game .players [event .player_index ]
88
+ local count = player .surface .count_entities_filtered {area = event .area , force = player .force }
89
+ if count >= 150 then
90
+ -- Need a proper check of entities. Most might be filtered out and not actually deconned.
91
+ local ents = player .surface .find_entities_filtered {area = event .area , force = player .force }
92
+ count = 0
93
+ for k , v in pairs (ents ) do
94
+ if v .to_be_deconstructed (player .force ) then
95
+ count = count + 1
96
+ end
97
+ end
98
+ if count >= 150 then
99
+ antigrief .alert (player .name .. " has deconstructed " .. count .. " entities." , player .character )
100
+ return
101
+ end
102
+ end
103
+ -- Check to see if a off-shore pump was targetted.
104
+ local ents = player .surface .find_entities_filtered {area = event .area , force = player .force , name = " offshore-pump" }
105
+ for k , v in pairs (ents ) do
106
+ if v .to_be_deconstructed (player .force ) and antigrief .is_well_pump (v ) then
107
+ antigrief .alert (player .name .. " has marked a well-water pump for deconstruction" , v )
108
+ return
109
+ end
110
+ end
111
+ end
112
+
113
+ -- If new players equip an atomic bomb... Throw a warning!
114
+ function antigrief .da_bomb (event )
115
+ local player = game .players [event .player_index ]
116
+ if player .online_time > antigrief .TROLL_TIMER then
117
+ return
118
+ end
119
+ if player .get_item_count (" atomic-bomb" ) > 0 then
120
+ if antigrief .check_cooldown (event .player_index , " atomic" ) then
121
+ antigrief .alert (player .name .. " has equipped an Atomic Bomb." , player .character )
122
+ end
123
+ end
124
+ end
125
+
126
+ -- If new players equip an artillery remote... Throw a warning!
127
+ function antigrief .remote (event )
128
+ local player = game .players [event .player_index ]
129
+ if player .online_time > antigrief .TROLL_TIMER then
130
+ return
131
+ end
132
+ if player .cursor_stack .valid_for_read and player .cursor_stack .name == " artillery-targeting-remote" then
133
+ if antigrief .check_cooldown (event .player_index , " artillery" ) then
134
+ antigrief .alert (player .name .. " has equipped an artillery remote." , player .character )
135
+ end
136
+ end
137
+ end
138
+
139
+ -- Look for players hoarding high value items.
140
+ function antigrief .hoarder (event )
141
+ local player = game .players [event .player_index ]
142
+ if player .online_time > antigrief .TROLL_TIMER then
143
+ return
144
+ end
145
+ if ( player .get_item_count (" speed-module-3" ) > 10 or
146
+ player .get_item_count (" productivity-module-3" ) > 10 or
147
+ player .get_item_count (" effectivity-module-3" ) > 10 ) and
148
+ antigrief .check_cooldown (event .player_index , " hoarding" ) then
149
+ antigrief .alert (player .name .. " is hoarding T3 modules." , player .character )
150
+ end
151
+ if player .get_item_count (" uranium-235" ) > 30 and antigrief .check_cooldown (event .player_index , " hoarding" ) then
152
+ antigrief .alert (player .name .. " is hoarding " .. player .get_item_count (" uranium-235" ) .. " U-235." , player .character )
153
+ end
154
+ if player .get_item_count (" power-armor-mk2" ) >= 2 and antigrief .check_cooldown (event .player_index , " hoarding" ) then
155
+ antigrief .alert (player .name .. " is hoarding power armor mk2s." , player .character )
156
+ end
157
+ end
158
+
159
+ -- Did someone craft/request Mk2 power armor and then log out?
160
+ function antigrief .armor_drop (event )
161
+ local player = game .players [event .player_index ]
162
+ if player .online_time > antigrief .TROLL_TIMER then
163
+ return
164
+ end
165
+ if player .get_item_count (" power-armor-mk2" ) >= 1 then
166
+ local armor = player .get_inventory (defines .inventory .player_armor ).find_item_stack (" power-armor-mk2" ) or
167
+ player .get_inventory (defines .inventory .player_main ).find_item_stack (" power-armor-mk2" ) or
168
+ player .get_inventory (defines .inventory .player_quickbar ).find_item_stack (" power-armor-mk2" ) or
169
+ player .get_inventory (defines .inventory .player_trash ).find_item_stack (" power-armor-mk2" )
170
+
171
+ if armor then
172
+ local item = player .surface .spill_item_stack (player .position , armor ) -- This could be used to duplicate equipment if we remove the wrong PA2. But such a weird edge case...
173
+ player .remove_item (" power-armor-mk2" )
174
+ if item and item .valid then -- It may have dropped on a belt
175
+ item .order_deconstruction (player .force )
176
+ end
177
+ else -- Something went wrong. We should have found the armor. God inventory?
178
+ log (" Antigrief: Power Armor mk2 detected but not found" )
179
+ end
180
+ end
181
+ end
182
+
183
+ -- Look for players merging roboport networks
184
+ function antigrief .check_size_loginet_size (event )
185
+ if not (event .entity and event .entity .valid and event .entity .type == " roboport" ) then
186
+ return
187
+ end
188
+ if not (event .entity .last_user ) then
189
+ -- How did we get here?
190
+ return
191
+ end
192
+ local network = event .entity .logistic_network
193
+ local cells = network .cells
194
+ if not (cells [1 ] and cells [1 ].valid ) then
195
+ return
196
+ end
197
+ local minx , miny , maxx , maxy = cells [1 ].owner .position .x , cells [1 ].owner .position .y , cells [1 ].owner .position .x , cells [1 ].owner .position .y
198
+ for k , v in pairs (cells ) do
199
+ if v .owner .position .x < minx then
200
+ minx = v .owner .position .x
201
+ elseif v .owner .position .x > maxx then
202
+ maxx = v .owner .position .x
203
+ end
204
+ if v .owner .position .y < miny then
205
+ miny = v .owner .position .y
206
+ elseif v .owner .position .y > maxy then
207
+ maxy = v .owner .position .y
208
+ end
209
+ end
210
+
211
+ if math.abs (maxx - minx ) > 2000 or math.abs (maxy - miny ) then
212
+ antigrief .alert (event .entity .last_user .name .. " has placed a roboport in a large network." , event .entity )
213
+ end
214
+ end
215
+
216
+ -- Print text to online admins and write to the log.
217
+ function antigrief .alert (text , cause )
218
+ for n , p in pairs (game .players ) do
219
+ if p .admin then
220
+ p .print (text )
221
+ if cause then
222
+ p .add_custom_alert (cause , {type = " virtual" , name = " signal-A" }, text , true )
223
+ end
224
+ end
225
+ end
226
+ log (" Antigrief: " .. text )
227
+ end
228
+
229
+ -- Check if a message has been generated about this player recently. If true, set cooldown.
230
+ function antigrief .check_cooldown (player_index , type )
231
+ if not global .antigrief_cooldown [player_index ] then
232
+ global .antigrief_cooldown [player_index ] = {}
233
+ end
234
+ local cooldowns = global .antigrief_cooldown [player_index ]
235
+ -- Type matches? Check CD
236
+ if not cooldowns [type ] then cooldowns [type ] = - antigrief .SPAM_TIMER end
237
+ local tick = cooldowns [type ]
238
+ if tick < game .tick then
239
+ cooldowns [type ] = game .tick + antigrief .SPAM_TIMER
240
+ global .antigrief_cooldown [player_index ] = cooldowns
241
+ return true
242
+ end
243
+ return false
244
+ end
245
+
246
+ -- Is this a water-well pump?
247
+ function antigrief .is_well_pump (entity )
248
+ if entity .name ~= " offshore-pump" then
249
+ return false
250
+ end
251
+ if not (entity .surface .get_tile (entity .position .x + 1 , entity .position .y ).collides_with (" water-tile" ) or
252
+ entity .surface .get_tile (entity .position .x , entity .position .y + 1 ).collides_with (" water-tile" ) or
253
+ entity .surface .get_tile (entity .position .x - 1 , entity .position .y ).collides_with (" water-tile" ) or
254
+ entity .surface .get_tile (entity .position .x , entity .position .y - 1 ).collides_with (" water-tile" )) then
255
+
256
+ return true
257
+ end
258
+ end
259
+
260
+ function antigrief .wanton_destruction (event )
261
+ if not (event .entity and event .entity .valid ) then
262
+ return
263
+ end
264
+ if not (event .cause and event .cause .type == " player" ) then
265
+ return
266
+ end
267
+ if event .cause .force == event .entity .force then
268
+ -- Friendly fire detected!
269
+ if antigrief .is_well_pump (event .entity ) then
270
+ antigrief .alert (event .cause .player .name .. " destroyed a well-water pump" , event .entity )
271
+ return
272
+ end
273
+ if event .entity .type == " player" and event .entity .player then
274
+ antigrief .alert (event .cause .player .name .. " killed " .. event .entity .player .name , event .entity )
275
+ return
276
+ end
277
+ if antigrief .check_cooldown (event .cause .player .index , " destruction" ) then
278
+ antigrief .alert (event .cause .player .name .. " is destroying friendly entites." , event .entity )
279
+ end
280
+ end
281
+ end
282
+
283
+ Event .register (defines .events .on_player_used_capsule , antigrief .arty_remote_ban )
284
+ Event .register (defines .events .on_player_ammo_inventory_changed , antigrief .da_bomb )
285
+ Event .register (defines .events .on_player_cursor_stack_changed , antigrief .remote )
286
+ Event .register (defines .events .on_player_main_inventory_changed , antigrief .hoarder )
287
+ Event .register (defines .events .on_player_left_game , antigrief .armor_drop )
288
+ Event .register (defines .events .on_player_mined_entity , antigrief .pump )
289
+ Event .register (defines .events .on_player_mined_entity , antigrief .ghosting )
290
+ Event .register (defines .events .on_entity_died , antigrief .wanton_destruction )
291
+ Event .register (defines .events .on_built_entity , antigrief .check_size_loginet_size )
292
+ Event .register (defines .events .on_robot_built_entity , antigrief .check_size_loginet_size )
293
+ Event .register (defines .events .on_player_deconstructed_area , antigrief .decon )
0 commit comments