-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathcontrol.lua
More file actions
909 lines (813 loc) · 41.9 KB
/
control.lua
File metadata and controls
909 lines (813 loc) · 41.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
require "util"
math2d = require "math2d"
require "scripts.lib"
require "scripts.remote"
require "scripts.cutscene"
require "scripts.aai-miners"
require "scripts.resources"
require "scripts.elevators"
require "scripts.enemies"
require "scripts.trains"
migrations = require "migrations-on-update"
max_pollution_move_passive = 64
suffocation_threshold = 400
suffocation_damage = 2.5 -- per 64 ticks (~1 second)
attrition_threshold = 150
attrition_types = {"assembling-machine", "reactor", "mining-drill", "generator", "inserter", "burner-generator", "car", "construction-robot", "lab", "loader", "loader-1x1", "locomotive", "logistic-robot", "power-switch", "pump", "radar", "roboport", "spider-vehicle", "splitter", "transport-belt"}
aai_miners = script.active_mods["aai-vehicles-miner"] ~= nil
subsurface_wall_expressions = {}
subsurface_wall_names = {"subsurface-wall"}
blacklist = {}
function setup_globals()
storage.subsurfaces = storage.subsurfaces or {}
storage.tunnel_links = storage.tunnel_links or {}
storage.heat_elevators = storage.heat_elevators or {}
storage.air_vents = storage.air_vents or {}
storage.aai_digging_miners = storage.aai_digging_miners or {}
storage.prospectors = storage.prospectors or {}
storage.support_lamps = storage.support_lamps or {}
storage.placement_indicators = storage.placement_indicators or {}
storage.selection_indicators = storage.selection_indicators or {}
storage.next_burrowing = storage.next_burrowing or game.map_settings.enemy_expansion.max_expansion_cooldown
if not storage.enemies_above_exposed_underground then init_enemies_global() end
storage.revealed_resources = storage.revealed_resources or {}
storage.train_subways = storage.train_subways or {}
storage.train_transport = storage.train_transport or {}
storage.train_stop_clones = storage.train_stop_clones or {}
storage.exposed_chunks = storage.exposed_chunks or {}
storage.pollution_map = storage.pollution_map or {}
storage.pollution_values = storage.pollution_values or {}
end
function register_subsurface_walls()
for interface, functions in pairs(remote.interfaces) do
if functions["subsurface_register_walls"] then
subsurface_wall_expressions = util.merge({subsurface_wall_expressions, remote.call(interface, "subsurface_register_walls")})
end
end
for k, _ in pairs(subsurface_wall_expressions) do
table.insert(subsurface_wall_names, k)
end
end
script.on_init(function()
setup_globals()
for _, s in pairs(game.surfaces) do
local mgs = s.map_gen_settings
mgs.property_expression_names["subsurface_level"] = get_subsurface_depth(s)
s.map_gen_settings = mgs
if allow_subsurfaces(s) then manipulate_resource_data(s) end
end
register_subsurface_walls()
end)
script.on_configuration_changed(function(config) -- TBC
setup_globals()
if config.mod_changes then
if config.mod_changes["Subsurface"] and config.mod_changes["Subsurface"].old_version then
for v, f in pairs(migrations) do
if helpers.compare_versions(v, config.mod_changes["Subsurface"].old_version) == 1 then f(config) end
end
end
if config.mod_changes["BlackMap-continued"] and not config.mod_changes["BlackMap-continued"].old_version then
for _, s in pairs(storage.subsurfaces) do
remote.call("blackmap", "register", s)
end
end
local new_autoplace_control_prototypes = {}
for control_name, _ in pairs(prototypes.autoplace_control) do
local creating_mod = prototypes.get_history("autoplace-control", control_name).created
if config.mod_changes[creating_mod] and not config.mod_changes[creating_mod].old_version and prototypes.autoplace_control[control_name].category == "resource" then
table.insert(new_autoplace_control_prototypes, control_name)
end
end
if #new_autoplace_control_prototypes > 0 and not game.simulation then
script.on_nth_tick(1, function(event)
for _, s in pairs(game.surfaces) do
for _, control_name in pairs(new_autoplace_control_prototypes) do
if not is_subsurface(s) and allow_subsurfaces(s) and s.map_gen_settings.autoplace_controls and s.map_gen_settings.autoplace_controls[control_name] then
local mgs = s.map_gen_settings
mgs.autoplace_controls[control_name].size = (mgs.autoplace_controls[control_name].size or 0) * size_formula(0)
s.map_gen_settings = mgs
end
end
end
for _, s in pairs(storage.subsurfaces) do
local mgs = s.map_gen_settings
copy_resource_data(mgs, get_top_surface(s), get_subsurface_depth(s))
s.map_gen_settings = mgs
end
script.on_nth_tick(1, nil)
end)
end
end
if config.migrations.tile["grass-4"] or config.migrations.tile["mineral-brown-dirt-2"] then
-- handle removed tiles (they got replaced by grass-1)
for _, s in pairs(storage.subsurfaces) do
local new_tiles = {}
for _, t in ipairs(s.find_tiles_filtered{name = "grass-1"}) do
table.insert(new_tiles, {name = "caveground", position = t.position})
end
s.set_tiles(new_tiles)
for _, t in ipairs(s.find_tiles_filtered{name = "out-of-map", has_hidden_tile = true}) do
if t.hidden_tile == "grass-1" then
s.set_hidden_tile(t.position, "caveground")
end
end
end
game.print("[color=yellow]Subsurface took some time recalculating tiles due to your mod configuration changes.[/color]")
end
end)
script.on_event(defines.events.on_runtime_mod_setting_changed, function(event)
if event.setting == "generate-resources-underground" then
for _, s in pairs(game.surfaces) do
if not is_subsurface(s) and allow_subsurfaces(s) then
local mgs = s.map_gen_settings
for control_name, data in pairs(mgs.autoplace_controls or {}) do
if prototypes.autoplace_control[control_name].category == "resource" then
mgs.autoplace_controls[control_name].size = settings.global["generate-resources-underground"].value and data.size * size_formula(0) or data.size / size_formula(0)
end
end
s.map_gen_settings = mgs
end
end
for _, s in pairs(storage.subsurfaces) do
local mgs = s.map_gen_settings
copy_resource_data(mgs, get_top_surface(s), get_subsurface_depth(s))
s.map_gen_settings = mgs
end
end
end)
script.on_load(function()
register_subsurface_walls()
end)
function get_subsurface(surface, create)
if create == nil then create = true end
if storage.subsurfaces[surface.index] then -- the subsurface already exists
return storage.subsurfaces[surface.index]
elseif create then -- we need to create the subsurface (pattern : <surface>_subsurface_<number>
local subsurface_name = ""
local _, _, topname, depth = string.find(surface.name, "(.+)_subsurface_([0-9]+)$")
if topname == nil then -- surface is not a subsurface
topname = surface.name
depth = 1
else
depth = tonumber(depth) + 1
end
subsurface_name = topname .. "_subsurface_" .. depth
local top_surface = game.get_surface(topname)
local subsurface = game.get_surface(subsurface_name)
if not subsurface then
local mgs = {
seed = surface.map_gen_settings.seed,
width = surface.map_gen_settings.width,
height = surface.map_gen_settings.height,
peaceful_mode = surface.map_gen_settings.peaceful_mode,
no_enemies_mode = surface.map_gen_settings.no_enemies_mode,
autoplace_controls = {},
default_enable_all_autoplace_controls = false,
autoplace_settings = {
decorative = {treat_missing_as_default = false, settings = {
["small-rock"] = {},
["tiny-rock"] = {}
}},
tile = {treat_missing_as_default = false, settings = {
["caveground"] = {},
["mineral-brown-dirt-2"] = {},
["grass-4"] = {},
}},
entity = {treat_missing_as_default = false, settings = {}}
},
property_expression_names = { -- priority is from top to bottom
["tile:caveground:probability"] = 0, -- basic floor
["tile:mineral-brown-dirt-2:probability"] = 0, -- alternative if alienbiomes is active
["tile:grass-4:probability"] = 0, -- 2nd alternative
["decorative:small-rock:probability"] = 0.1,
["decorative:tiny-rock:probability"] = 0.7,
["subsurface_seed"] = bit32.band(bit32.bxor(simple_hash(subsurface_name), surface.map_gen_settings.seed), 0xFFFFFFF),
["subsurface_level"] = depth,
}
}
copy_resource_data(mgs, top_surface, depth)
subsurface = game.create_surface(subsurface_name, mgs)
subsurface.daytime = 0.5
subsurface.freeze_daytime = true
subsurface.show_clouds = false
subsurface.localised_name = {"subsurface.subsurface-name", top_surface.localised_name or (top_surface.planet and top_surface.planet.prototype.localised_name) or topname, depth}
for sp, _ in pairs(prototypes.surface_property) do
subsurface.set_property(sp, surface.get_property(sp))
end
subsurface.set_property("subsurface-level", depth)
subsurface.set_property("pressure", surface.get_property("pressure") * 1.1)
for _, f in pairs(game.forces) do
subsurface.set_default_cover_tile(f, "out-of-map", "caveground")
end
if settings.global["enable-challenges"].value then
local effect = surface.global_effect or {speed = 0, productivity = 0, consumption = 0}
effect.speed = effect.speed + 0.05 * depth
effect.productivity = effect.productivity + 0.05 * depth
effect.consumption = effect.consumption + 0.1 * depth
subsurface.global_effect = effect
end
if remote.interfaces["blackmap"] then remote.call("blackmap", "register", subsurface) end
storage.enemies_above_exposed_underground[surface.index] = {}
storage.exposed_chunks[subsurface.index] = {}
game.forces.enemy.set_evolution_factor(game.forces.enemy.get_evolution_factor(top_surface), top_surface)
end
storage.subsurfaces[surface.index] = subsurface
return subsurface
else return nil
end
end
function get_oversurface(subsurface)
for i, s in pairs(storage.subsurfaces) do -- i is the index of the oversurface
if s == subsurface and game.get_surface(i) then return game.get_surface(i) end
end
return nil
end
function get_top_surface(subsurface)
local _, _, topname, depth = string.find(subsurface.name, "(.+)_subsurface_([0-9]+)$")
if topname == nil then return subsurface -- surface is not a subsurface
else return game.get_surface(topname) end
end
function get_subsurface_depth(surface)
if type(surface) == "userdata" then surface = surface.name end
local _, _, _, depth = string.find(surface, "(.+)_subsurface_([0-9]+)$")
return tonumber(depth or 0)
end
function is_subsurface(surface)
local name = ""
if type(surface) == "userdata" then name = surface.name
elseif type(surface) == "string" then name = surface
elseif type(surface) == "number" then name = game.get_surface(surface).name
end
if string.find(name, "_subsurface_([0-9]+)$") or 0 > 1 then return true
else return false end
end
function create_walls(surface, positions, deconstruct_positions)
local props = {}
local names_ordered = {}
for name, expr in pairs(subsurface_wall_expressions) do
table.insert(names_ordered, name)
table.insert(props, expr)
end
local calcresult = surface.calculate_tile_properties(props, positions)
for pos_i, pos in ipairs(positions) do
local name = "subsurface-wall"
local prio = 0
for j, p in ipairs(props) do
if calcresult[p][pos_i] > prio then
name = names_ordered[j]
prio = calcresult[p][pos_i]
end
end
local w = surface.create_entity{name = name, position = pos, force = game.forces.neutral}
if deconstruct_positions and deconstruct_positions[pos_i] then
for _, f in pairs(game.forces) do
w.order_deconstruction(f)
end
end
end
end
function clear_subsurface(surface, pos, radius, clearing_radius, return_inventory)
if not is_subsurface(surface) then return 0 end
pos = math2d.position.ensure_xy(pos)
local new_tiles = {}
local new_resource_positions = {}
local area = get_area(pos, radius)
if clearing_radius and clearing_radius < radius then -- destroy all entities in this radius except players
local clearing_subsurface_area = get_area(pos, clearing_radius)
for _, entity in ipairs(surface.find_entities(clearing_subsurface_area)) do
if entity.type ~= "player" then entity.destroy()
else entity.teleport(get_safe_position(pos, {x = pos.x + clearing_radius, y = pos.y})) end
end
end
local inventory = game.create_inventory(10)
for x, y in iarea(area) do -- first, replace all out-of-map tiles with their hidden tile which means that it is inside map limits)
if (x-pos.x)^2 + (y-pos.y)^2 < radius^2 and surface.get_hidden_tile({x, y}) then
local wall = surface.find_entities_filtered{name = subsurface_wall_names, position = {x, y}}[1]
if wall then
wall.mine{inventory = return_inventory and inventory or nil, force = true, raise_destroyed = false, ignore_minable = true}
end
table.insert(new_tiles, {name = surface.get_hidden_tile({x, y}), position = {x, y}})
surface.set_hidden_tile({x, y}, nil)
table.insert(new_resource_positions, {x, y})
end
end
surface.set_tiles(new_tiles)
place_resources(surface, new_resource_positions)
local new_wall_positions = {}
local deconstruct_positions = {}
for x, y in iarea(area) do -- second, place a wall where at least one out-of-map is adjacent
if surface.get_tile(x, y).valid and surface.get_tile(x, y).name == "out-of-map" and not surface.find_entities_filtered{name = subsurface_wall_names, position = {x, y}}[1] and not surface.find_entity("subsurface-wall-map-border", {x, y}) then
local adj_tiles = {surface.get_tile(x + 1, y), surface.get_tile(x - 1, y), surface.get_tile(x, y + 1), surface.get_tile(x, y - 1)}
if (adj_tiles[1].valid and adj_tiles[1].name ~= "out-of-map") or (adj_tiles[2].valid and adj_tiles[2].name ~= "out-of-map") or (adj_tiles[3].valid and adj_tiles[3].name ~= "out-of-map") or (adj_tiles[4].valid and adj_tiles[4].name ~= "out-of-map") then
if surface.get_hidden_tile({x, y}) then
table.insert(new_wall_positions, {x, y})
table.insert(deconstruct_positions, adj_tiles[1].has_tile_ghost() or adj_tiles[2].has_tile_ghost() or adj_tiles[3].has_tile_ghost() or adj_tiles[4].has_tile_ghost())
else
local w = surface.create_entity{name = "subsurface-wall-map-border", position = {x, y}, force = game.forces.neutral}
w.destructible = false
end
end
end
end
create_walls(surface, new_wall_positions, deconstruct_positions)
find_enemies_above(surface, pos, radius)
-- expose chunks
area.left_top.x = math.floor(area.left_top.x / 32)
area.left_top.y = math.floor(area.left_top.y / 32)
area.right_bottom.x = math.floor(area.right_bottom.x / 32)
area.right_bottom.y = math.floor(area.right_bottom.y / 32)
for cx, cy in iarea(area) do
storage.exposed_chunks[surface.index][spiral({cx, cy})] = {cx, cy}
end
if return_inventory then return inventory
else inventory.destroy() end
end
function process_chunk_pollution(s, cx, cy)
if not storage.pollution_map[s.index][spiral({cx, cy})] and not storage.exposed_chunks[s.index][spiral({cx, cy})] then
storage.pollution_map[s.index][spiral({cx, cy})] = {s.get_pollution{cx * 32, cy * 32}, cx, cy}
storage.pollution_values[s.index].total = storage.pollution_values[s.index].total + storage.pollution_map[s.index][spiral({cx, cy})][1]
if s.get_pollution{cx * 32, (cy - 1) * 32} > 0 then process_chunk_pollution(s, cx, cy - 1) end
if s.get_pollution{cx * 32, (cy + 1) * 32} > 0 then process_chunk_pollution(s, cx, cy + 1) end
if s.get_pollution{(cx - 1) * 32, cy * 32} > 0 then process_chunk_pollution(s, cx - 1, cy) end
if s.get_pollution{(cx + 1) * 32, cy * 32} > 0 then process_chunk_pollution(s, cx + 1, cy) end
end
end
script.on_event(defines.events.on_tick, function(event)
handle_elevators(event.tick)
handle_subways()
-- POLLUTION (since there is no mechanic to just reflect pollution (no absorption but also no spread) we have to do it for our own. The game's mechanic can't be changed so we need to consider it)
if (event.tick - 1) % 64 == 0 then
for _, subsurface in pairs(storage.subsurfaces) do
storage.pollution_values[subsurface.index] = {total = 0, total_exposed = 0}
storage.pollution_map[subsurface.index] = {}
for _, chunk in pairs(storage.exposed_chunks[subsurface.index]) do
if subsurface.get_pollution{chunk[1] * 32, (chunk[2] - 1) * 32} > 0 then process_chunk_pollution(subsurface, chunk[1], chunk[2] - 1) end
if subsurface.get_pollution{chunk[1] * 32, (chunk[2] + 1) * 32} > 0 then process_chunk_pollution(subsurface, chunk[1], chunk[2] + 1) end
if subsurface.get_pollution{(chunk[1] - 1) * 32, chunk[2] * 32} > 0 then process_chunk_pollution(subsurface, chunk[1] - 1, chunk[2]) end
if subsurface.get_pollution{(chunk[1] + 1) * 32, chunk[2] * 32} > 0 then process_chunk_pollution(subsurface, chunk[1] + 1, chunk[2]) end
storage.pollution_values[subsurface.index].total_exposed = storage.pollution_values[subsurface.index].total_exposed + subsurface.get_pollution{chunk[1] * 32, chunk[2] * 32}
end
end
elseif (event.tick - 2) % 64 == 0 then
for _, subsurface in pairs(storage.subsurfaces) do
if storage.pollution_values[subsurface.index].total_exposed > 0 then
for _, chunk in pairs(storage.pollution_map[subsurface.index]) do
subsurface.set_pollution({chunk[2] * 32, chunk[3] * 32}, 0)
end
for _, chunk in pairs(storage.exposed_chunks[subsurface.index]) do
local new_val = subsurface.get_pollution{chunk[1] * 32, chunk[2] * 32} * (1 + storage.pollution_values[subsurface.index].total / storage.pollution_values[subsurface.index].total_exposed)
subsurface.set_pollution({chunk[1] * 32, chunk[2] * 32}, new_val)
-- machine inefficiency due to pollution
if new_val > attrition_threshold and settings.global["enable-challenges"].value then
for _, e in ipairs(subsurface.find_entities_filtered{type = attrition_types, area = {{chunk[1] * 32, chunk[2] * 32}, {(chunk[1] * 32) + 31, (chunk[2] * 32) + 31}}}) do
if math.random(5) == 1 then e.damage(e.max_health * 0.01, game.forces.neutral, "physical") end
end
end
end
if storage.pollution_values[subsurface.index].total_exposed >= 500 then
for _, force in pairs(game.forces) do
force.script_trigger_research("ventilation")
end
end
end
end
-- next, move pollution using air vents
for i, vent in ipairs(storage.air_vents) do
if vent.valid then
local subsurface = get_subsurface(vent.surface, false)
if subsurface and vent.name == "air-vent" then
local pollution_surface = vent.surface.get_pollution(vent.position)
local pollution_subsurface = subsurface.get_pollution(vent.position)
local diff = pollution_surface - pollution_subsurface
local max_movable_pollution = max_pollution_move_passive * (0.8 ^ (get_subsurface_depth(subsurface) - 1))
if math.abs(diff) > max_movable_pollution then
diff = diff / math.abs(diff) * max_movable_pollution
end
if diff < 0 then -- pollution in subsurface is higher
vent.surface.create_trivial_smoke{name = "light-smoke", position = {vent.position.x, vent.position.y}, force = game.forces.neutral}
end
vent.surface.pollute(vent.position, -diff, vent.name)
subsurface.pollute(vent.position, diff, vent.name)
elseif subsurface and vent.energy > 0 then
local max_movable_pollution = vent.prototype.get_crafting_speed(vent.quality) * (1 + 2 * vent.speed_bonus) * (0.8 ^ (get_subsurface_depth(subsurface) - 1)) * vent.energy / vent.electric_buffer_size -- how much pollution can be moved with the current available energy
local pollution_to_move = math.min(max_movable_pollution, subsurface.get_pollution(vent.position))
subsurface.pollute(vent.position, -pollution_to_move, vent.name)
vent.surface.pollute(vent.position, pollution_to_move, vent.name)
if pollution_to_move > 0 then
vent.surface.create_trivial_smoke{name = "light-smoke", position = {vent.position.x+0.25, vent.position.y}, force = game.forces.neutral}
end
end
else
table.remove(storage.air_vents, i)
end
end
-- player suffocation damage
if settings.global["enable-challenges"].value then
for _, p in pairs(game.players) do
if p.character and is_subsurface(p.surface) and p.surface.get_pollution(p.position) > suffocation_threshold then
p.character.damage(suffocation_damage, game.forces.neutral, "poison")
if (event.tick - 1) % 256 == 0 then p.print({"subsurface.suffocation"}, {1, 0, 0}) end
end
end
end
end
-- handle miners
if aai_miners and event.tick % 10 == 0 then handle_miners(event.tick) end
if event.tick % 20 == 0 and settings.global["enable-challenges"].value and game.map_settings.enemy_expansion.enabled then handle_enemies(event.tick) end
end)
function allow_subsurfaces(surface)
local surface_type = remote.interfaces["space-exploration"] and remote.call("space-exploration", "get_surface_type", {surface_index = surface.index})
if (string.find(surface.name, "[Ff]actory[- ]floor") or 0) > 0 -- disallow factorissimo
or surface_type == "orbit" or surface_type == "asteroid-belt" or surface_type == "asteroid-field" then -- disallow SE space
return false
else
for _, b in ipairs(blacklist) do
if surface.name == b then return false end
end
end
return true
end
function replace_surface_drill_dummy(entity)
local res_name = "subsurface-hole"
local res_amount = 100 * (2 ^ (get_subsurface_depth(entity.surface) - 1))
local res_prio = 0
for interface, functions in pairs(remote.interfaces) do
if functions["subsurface_hole_resource"] then
local prio, name, amount = remote.call(interface, "subsurface_hole_resource", entity.surface, get_subsurface_depth(entity.surface), entity.position)
if prio > res_prio then
res_name = name
res_amount = amount
res_prio = prio
end
end
end
entity.surface.create_entity{name = res_name, position = entity.position, amount = res_amount}
local real_drill = entity.surface.create_entity{name = "surface-drill", position = entity.position, direction = entity.direction, force = entity.force, player = entity.last_user, quality = entity.quality}
if entity.item_request_proxy then
local modules = entity.item_request_proxy.insert_plan
for i, item in ipairs(modules) do
for j, _ in ipairs(item.items.in_inventory) do
modules[i].items.in_inventory[j].inventory = defines.inventory.mining_drill_modules
end
end
entity.surface.create_entity{name = "item-request-proxy", position = entity.position, force = entity.force, target = real_drill, modules = modules}
end
entity.destroy()
get_subsurface(real_drill.surface).request_to_generate_chunks(real_drill.position, 3)
end
function cancel_placement(event, text)
local entity = event.entity
if event.player_index then
local player = game.get_player(event.player_index)
if text then player.create_local_flying_text{text = {text}, position = entity.position} end
player.play_sound{path = "utility/cannot_build", position = entity.position}
local n = entity.name
local q = entity.quality
for _, it in ipairs(event.consumed_items.get_contents()) do
player.insert(it)
end
if not player.cursor_stack.valid_for_read then player.pipette_entity(entity) end
elseif event.robot then
entity.surface.play_sound{path = "utility/cannot_build", position = entity.position}
entity.surface.spill_item_stack{position = entity.position, stack = event.stack, force = event.robot.force, allow_belts = false}
end
entity.destroy()
end
-- build entity only if it is safe in subsurface
function build_safe(event, func, check_for_entities)
if check_for_entities == nil then check_for_entities = true end
-- first, check if the given area is uncovered (ground tiles) and has no entities in it
local entity = event.entity
local subsurface = get_subsurface(entity.surface)
local area = entity.bounding_box
local safe_position = true
if not is_subsurface(subsurface) then safe_position = false end
if not subsurface.is_chunk_generated{entity.position.x / 32, entity.position.y / 32} then safe_position = false end
for _, t in ipairs(subsurface.find_tiles_filtered{area = area}) do
if t.name == "out-of-map" then safe_position = false end
end
if check_for_entities and subsurface.count_entities_filtered{area = area} > 0 then safe_position = false end
if safe_position then func()
else cancel_placement(event, "subsurface.cannot-place-here")
end
end
script.on_event({defines.events.on_built_entity, defines.events.on_robot_built_entity, defines.events.script_raised_built, defines.events.script_raised_revive}, function(event)
local entity = event.entity
if entity.name == "surface-drill-placer" then
local text = ""
if is_subsurface(entity.surface) and get_subsurface_depth(entity.surface) >= settings.global["subsurface-limit"].value then
text = "subsurface.limit-reached"
elseif entity.surface.count_entities_filtered{name = {"tunnel-entrance", "tunnel-exit"}, position = entity.position, radius = 7} > 0 then
text = "subsurface.cannot-place-here"
elseif not allow_subsurfaces(entity.surface) then
text = "subsurface.not-allowed-here"
end
if text == "" then replace_surface_drill_dummy(entity)
else cancel_placement(event, text)
end
elseif entity.name == "prospector" then
local combinator = entity.surface.create_entity{name = "prospector-combinator", position = entity.position, force = entity.force, quality = entity.quality}
storage.prospectors[combinator.unit_number] = {energy_interface = entity, combinator = combinator}
script.register_on_object_destroyed(combinator)
elseif entity.name == "prospector-combinator" then
local energy_interface = entity.surface.create_entity{name = "prospector", position = entity.position, force = entity.force, quality = entity.quality}
storage.prospectors[entity.unit_number] = {energy_interface = energy_interface, combinator = entity}
script.register_on_object_destroyed(entity)
elseif string.sub(entity.name, 1, 13) == "item-elevator" then elevator_built(entity)
elseif entity.name == "fluid-elevator-input" then
if event.tags and event.tags.output then entity = switch_elevator(entity) end
elevator_built(entity)
elseif entity.name == "heat-elevator" then
entity.operable = false
elevator_built(entity)
elseif entity.name == "air-vent" or entity.name == "active-air-vent" or entity.name == "active-air-vent-2" then
table.insert(storage.air_vents, entity)
elseif entity.name == "wooden-support" or entity.name == "steel-support" then
script.register_on_object_destroyed(entity)
storage.support_lamps[entity.unit_number] = entity.surface.create_entity{name = "support-lamp", position = entity.position, quality = entity.quality, force = entity.force}
elseif entity.name == "subway" then
subway_built(entity)
elevator_built(entity)
elseif (entity.type == "train-stop" and entity.connected_rail and entity.connected_rail.name == "subway-rail") or ((entity.type == "rail-signal" or entity.type == "rail-chain-signal") and entity.get_connected_rails()[1] and entity.get_connected_rails()[1].name == "subway-rail") then
cancel_placement(event, "cant-build-reason.cant-build-here")
elseif entity.type == "train-stop" then
create_fake_stops(entity)
elseif is_subsurface(entity.surface) then -- check for placement restrictions
if not script.feature_flags.space_travel and prototypes.mod_data.subsurface_placement_restrictions.data[entity.name] then
cancel_placement(event)
end
end
end)
script.on_event({defines.events.on_player_mined_entity, defines.events.on_robot_mined_entity}, function(event)
if event.entity.name == "surface-drill" then
if event.entity.mining_target then event.entity.mining_target.destroy() end
else
elevator_removed(event.entity)
for _, w in ipairs(subsurface_wall_names) do
if event.entity.valid and event.entity.name == w then clear_subsurface(event.entity.surface, event.entity.position, 1.5) end
end
end
end)
script.on_event(defines.events.on_player_setup_blueprint, function(event)
local player = game.get_player(event.player_index)
local blueprint = event.record or (player.is_cursor_empty() and player.blueprint_to_setup or player.cursor_stack)
local contents = blueprint.get_blueprint_entities()
local modified = false
for _, e in ipairs(contents or {}) do
if e.name == "surface-drill" then
e.name = "surface-drill-placer"
modified = true
elseif e.name == "fluid-elevator-output" then
blueprint.set_blueprint_entity_tag(e.entity_number, "output", true)
end
end
if modified then blueprint.set_blueprint_entities(contents) end
end)
script.on_event(defines.events.on_player_cursor_stack_changed, function(event)
elevator_on_cursor_stack_changed(game.get_player(event.player_index))
end)
script.on_event(defines.events.on_selected_entity_changed, function(event)
local player = game.get_player(event.player_index)
if event.last_entity then elevator_unselected(player, event.last_entity) end
if player.selected then elevator_selected(player, player.selected) end
end)
script.on_event(defines.events.on_player_deconstructed_area, function(event)
if not event.alt then return end
if is_subsurface(event.surface) then
local player = game.get_player(event.player_index)
for _, tile in ipairs(event.surface.find_tiles_filtered{area = event.area, name = "out-of-map"}) do
if not tile.has_tile_ghost() then
event.surface.create_entity{name = "tile-ghost", inner_name = "caveground", position = tile.position, player = player, force = player.force}
local wall = event.surface.find_entities_filtered{name = subsurface_wall_names, position = tile.position, limit = 1}[1]
if wall then wall.order_deconstruction(player.force) end
end
end
end
end)
script.on_event(defines.events.on_entity_died, function(event)
local entity = event.entity
if entity.name == "surface-drill" then
if entity.mining_target then entity.mining_target.destroy() end
entity.surface.create_entity{name = "massive-explosion", position = entity.position}
else elevator_removed(entity)
end
end)
script.on_event(defines.events.on_post_entity_died, function(event)
if event.prototype.name == "fluid-elevator-output" and event.ghost then event.ghost.tags = {output = true} end
end)
script.on_event(defines.events.on_resource_depleted, function(event)
if event.entity.prototype.resource_category == "subsurface-hole" then
local drill = event.entity.surface.find_entities_filtered{name = "surface-drill", position = event.entity.position}[1]
if drill then
local pos = drill.position
-- oversurface entity placing
local entrance_car = drill.surface.create_entity{name = "tunnel-entrance", position = pos, force = drill.force}
local entrance_pole = drill.surface.create_entity{name = "tunnel-entrance-cable", position = pos, force = drill.force}
entrance_car.destructible = false
entrance_pole.destructible = false
-- subsurface entity placing
local subsurface = get_subsurface(drill.surface)
clear_subsurface(subsurface, pos, 4, 1.5)
local exit_car = subsurface.create_entity{name = "tunnel-exit", position = pos, force = drill.force}
local exit_pole = subsurface.create_entity{name = "tunnel-exit-cable", position = pos, force = drill.force}
exit_car.destructible = false
exit_pole.destructible = false
entrance_pole.get_wire_connector(defines.wire_connector_id.pole_copper, true).connect_to(exit_pole.get_wire_connector(defines.wire_connector_id.pole_copper, true), false, defines.wire_origin.script)
entrance_pole.get_wire_connector(defines.wire_connector_id.circuit_red, true).connect_to(exit_pole.get_wire_connector(defines.wire_connector_id.circuit_red, true), false, defines.wire_origin.script)
entrance_pole.get_wire_connector(defines.wire_connector_id.circuit_green, true).connect_to(exit_pole.get_wire_connector(defines.wire_connector_id.circuit_green, true), false, defines.wire_origin.script)
storage.tunnel_links[entrance_pole.unit_number] = exit_pole
storage.tunnel_links[exit_pole.unit_number] = entrance_pole
storage.tunnel_links[entrance_car.unit_number] = exit_car
storage.tunnel_links[exit_car.unit_number] = entrance_car
script.register_on_object_destroyed(entrance_pole)
script.register_on_object_destroyed(exit_pole)
script.register_on_object_destroyed(entrance_car)
script.register_on_object_destroyed(exit_car)
local temp_s = drill.surface
repeat
for _, stop in ipairs(temp_s.find_entities_filtered{type = "train-stop"}) do
if stop.name ~= "subway-stop" then create_fake_stop(subsurface, stop) end
end
temp_s = get_oversurface(temp_s)
until not temp_s
end
else
local pos = {x = math.floor(event.entity.position.x), y = math.floor(event.entity.position.y)}
local chunk_id = spiral({math.floor(pos.x / 32), math.floor(pos.y / 32)})
local pos_i = get_position_index_in_chunk(pos)
storage.revealed_resources[chunk_id] = storage.revealed_resources[chunk_id] or {}
storage.revealed_resources[chunk_id][pos_i] = storage.revealed_resources[chunk_id][pos_i] or {}
storage.revealed_resources[chunk_id][pos_i][event.entity.name] = 0
end
end)
script.on_event(defines.events.on_chunk_generated, function(event)
if not event.surface.map_gen_settings.property_expression_names["subsurface_level"] then
local mgs = event.surface.map_gen_settings
mgs.property_expression_names["subsurface_level"] = get_subsurface_depth(event.surface)
event.surface.map_gen_settings = mgs
end
if is_subsurface(event.surface) then
local set_tiles = {}
local set_hidden_tiles = {}
for x, y in iarea(event.area) do
local tile = event.surface.get_tile(x, y)
tile = tile.valid and tile.name or "out-of-map"
table.insert(set_tiles, {name = "out-of-map", position = {x, y}})
if tile ~= "out-of-map" then table.insert(set_hidden_tiles, {tile, {x, y}}) end
end
event.surface.set_tiles(set_tiles)
for _, p in ipairs(set_hidden_tiles) do -- for performance reasons, first set the tiles and then the hidden tiles
event.surface.set_hidden_tile(p[2], p[1])
end
elseif allow_subsurfaces(event.surface) then
manipulate_resource_entities(event.surface, event.area)
end
end)
script.on_event(defines.events.on_pre_surface_deleted, function(event)
-- delete all its subsurfaces and remove from list
local i = event.surface_index
while(storage.subsurfaces[i]) do -- if surface i has a subsurface
local s = storage.subsurfaces[i] -- s is that subsurface
storage.subsurfaces[i] = nil -- remove from list
i = s.index
game.delete_surface(s) -- delete s
end
if is_subsurface(game.get_surface(event.surface_index)) then -- remove this surface from list
for s, ss in pairs(storage.subsurfaces) do
if ss.index == event.surface_index then storage.subsurfaces[s] = nil end
end
end
end)
script.on_event(defines.events.on_object_destroyed, function(event)
if event.type ~= defines.target_type.entity then return end
if storage.tunnel_links[event.useful_id] then
-- entrances can't be mined, but in case they are destroyed by mods we have to handle it
local opposite = storage.tunnel_links[event.useful_id]
local opposite_other = opposite.surface.find_entities_filtered{name = opposite.type == "car" and {"tunnel-entrance-cable", "tunnel-exit-cable"} or {"tunnel-entrance", "tunnel-exit"}, position = opposite.position, radius = 1}[1]
local other = storage.tunnel_links[opposite_other.unit_number]
storage.tunnel_links[opposite.unit_number] = nil
storage.tunnel_links[opposite_other.unit_number] = nil
storage.tunnel_links[other.unit_number] = nil
opposite.destroy()
opposite_other.destroy()
other.destroy()
storage.tunnel_links[event.useful_id] = nil
elseif storage.support_lamps[event.useful_id] then
storage.support_lamps[event.useful_id].destroy()
elseif storage.train_subways[event.useful_id] then
subway_entity_destroyed(event.useful_id)
elseif storage.train_stop_clones[event.useful_id] then
for _, s in ipairs(storage.train_stop_clones[event.useful_id]) do s.destroy() end
storage.train_stop_clones[event.useful_id] = nil
elseif storage.prospectors[event.useful_id] then
storage.prospectors[event.useful_id].energy_interface.destroy()
storage.prospectors[event.useful_id] = nil
end
end)
script.on_event(defines.events.on_script_trigger_effect, function(event)
local surface = game.get_surface(event.surface_index)
if event.effect_id == "cliff-explosives" then
clear_subsurface(surface, event.target_position, 2.5)
surface.spill_item_stack{position = event.target_position, stack = {name = "stone", count = 20}, enable_looted = true, force = game.forces.neutral}
surface.pollute(event.target_position, 10)
elseif event.effect_id == "cave-sealing" then
-- first, try to seal tunnel entrances
local entrance = surface.find_entities_filtered{name = {"tunnel-entrance", "tunnel-entrance-sealed-0", "tunnel-entrance-sealed-1", "tunnel-entrance-sealed-2"}, position = event.target_position, radius = 3}[1]
if entrance then
local next_stage = {["tunnel-entrance"] = "tunnel-entrance-sealed-0", ["tunnel-entrance-sealed-0"] = "tunnel-entrance-sealed-1", ["tunnel-entrance-sealed-1"] = "tunnel-entrance-sealed-2", ["tunnel-entrance-sealed-2"] = "tunnel-entrance-sealed-3"}
local new_entrance = surface.create_entity{name = next_stage[entrance.name], position = entrance.position, force = game.forces.neutral}
new_entrance.destructible = false
if entrance.name == "tunnel-entrance" then create_walls(get_subsurface(surface), get_area_positions(get_area(event.target_position, 0.2))) end
entrance.destroy()
elseif is_subsurface(surface) then -- place walls: first, prevent resources from being restored, then set out-of-map tiles, then place walls on those spots that have at least one adjacent ground tile
for _, res in ipairs(surface.find_entities(get_area(event.target_position, 1))) do
if res.type == "resource" then
local x = math.floor(res.position.x)
local y = math.floor(res.position.y)
local chunk_id = spiral({math.floor(x / 32), math.floor(y / 32)})
local pos_i = get_position_index_in_chunk({x, y})
storage.revealed_resources[chunk_id] = storage.revealed_resources[chunk_id] or {}
storage.revealed_resources[chunk_id][pos_i] = storage.revealed_resources[chunk_id][pos_i] or {}
storage.revealed_resources[chunk_id][pos_i][res.name] = res.amount
end
end
local set_tiles = {}
local set_hidden_tiles = {}
for x, y in iarea(get_area(event.target_position, 0.2)) do
table.insert(set_tiles, {position = {x, y}, name = "out-of-map"})
local tile = surface.get_tile(x, y)
if tile.name ~= "out-of-map" then table.insert(set_hidden_tiles, {tile.hidden_tile or tile.name, {x, y}}) end
end
surface.set_tiles(set_tiles)
for _, p in ipairs(set_hidden_tiles) do surface.set_hidden_tile(p[2], p[1]) end
for x, y in iarea(get_area(event.target_position, 2)) do
if surface.get_tile(x, y).name == "out-of-map"
and (surface.get_tile(x+1, y).name ~= "out-of-map" or surface.get_tile(x-1, y).name ~= "out-of-map" or surface.get_tile(x, y+1).name ~= "out-of-map" or surface.get_tile(x, y-1).name ~= "out-of-map")
and not surface.find_entities_filtered{name = subsurface_wall_names, position = {x, y}}[1] and not surface.find_entity("subsurface-wall-map-border", {x, y}) then
create_walls(surface, {{x, y}})
for i = 1, 100 do surface.create_trivial_smoke{name = "subsurface-smoke", position = {x + (math.random(-20, 20) / 20), y + (math.random(-21, 19) / 20)}} end
end
end
surface.pollute(event.target_position, 5)
end
end
end)
script.on_event(defines.events.on_entity_renamed, function(event)
if event.entity.type == "train-stop" and event.entity.name ~= "subway-stop" then
for _, s in ipairs(storage.train_stop_clones[event.entity.unit_number] or {}) do s.backer_name = event.entity.backer_name end
end
end)
script.on_event(defines.events.on_entity_color_changed, function(event)
if event.entity.type == "train-stop" and not event.entity.name == "subway-stop" then
for _, s in ipairs(storage.train_stop_clones[event.entity.unit_number] or {}) do s.color = event.entity.color end
end
end)
script.on_event("subsurface-pin", function(event)
local player = game.get_player(event.player_index)
if settings.get_player_settings(player)["subsurface-pin-adjacent"].value and not player.selected then
if get_oversurface(player.surface) then player.add_pin{label = string.format("{%d, %d} %s", event.cursor_position.x, event.cursor_position.y, get_oversurface(player.surface).name), surface = get_oversurface(player.surface), position = event.cursor_position} end
if get_subsurface(player.surface, false) then player.add_pin{label = string.format("{%d, %d} %s", event.cursor_position.x, event.cursor_position.y, get_subsurface(player.surface, false).name), surface = get_subsurface(player.surface, false), position = event.cursor_position} end
end
end)
script.on_event("subsurface-rotate", function(event)
local player = game.get_player(event.player_index)
if player.selected then
for _, r in ipairs(storage.selection_indicators[event.player_index] or {}) do
r.destroy()
end
elevator_rotated(player.selected, player.selected.direction)
if player.selected then elevator_selected(player, player.selected) end
end
end)
script.on_event(defines.events.on_gui_opened, function(event)
if event.entity and event.entity.name == "prospector-combinator" then
game.get_player(event.player_index).opened = create_prospector_gui(game.get_player(event.player_index), event.entity)
end
end)
script.on_event(defines.events.on_gui_closed, function(event)
if event.element and event.element.name == "prospector" then
event.element.destroy()
end
end)
script.on_event(defines.events.on_gui_click, function(event)
if event.element.name == "unit_digging" then
aai_on_gui_click(event)
elseif event.element.name == "prospector_close" then
event.element.parent.parent.parent.destroy()
elseif event.element.name == "prospector_scan" then
prospect_resources(storage.prospectors[event.element.parent.parent.parent.tags.prospector].combinator)
end
end)
script.on_event(defines.events.on_player_driving_changed_state, function(event)
cutscene_on_player_driving_changed_state(event)
aai_on_player_driving_changed_state(event)
end)
script.on_event(defines.events.on_player_controller_changed, function(event)
local player = game.get_player(event.player_index)
if remote.interfaces["space-exploration"] and remote.call("space-exploration", "remote_view_is_active", {player = player}) then
local character = remote.call("space-exploration", "get_player_character", {player = player})
if character and is_subsurface(character.surface) then
player.set_controller{type = defines.controllers.remote, surface = character.surface, position = character.position}
end
end
end)