-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontrol.lua
More file actions
704 lines (634 loc) · 22.4 KB
/
Copy pathcontrol.lua
File metadata and controls
704 lines (634 loc) · 22.4 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
local C = 299792458
local GUI_ROOT = "interstellar_fleets_root"
local DUST_BACKLOG_CAP = 20000
-- Entities that come and go on their own (cargo pods in transit, ghosts while
-- the hub auto-builds, robots, corpses, spilled items). Including them in the
-- layout signature makes identical platforms spuriously fail merge checks.
local TRANSIENT_TYPES = {
["character"] = true,
["cargo-pod"] = true,
["construction-robot"] = true,
["logistic-robot"] = true,
["combat-robot"] = true,
["entity-ghost"] = true,
["tile-ghost"] = true,
["item-request-proxy"] = true,
["item-entity"] = true,
["resource"] = true,
["corpse"] = true,
["character-corpse"] = true,
["projectile"] = true,
["beam"] = true,
["stream"] = true,
["explosion"] = true,
["smoke-with-trigger"] = true,
["sticker"] = true,
["speech-bubble"] = true,
["highlight-box"] = true,
["rocket-silo-rocket"] = true,
["rocket-silo-rocket-shadow"] = true
}
local function init_storage()
storage.fleets = storage.fleets or {}
storage.shattered_reached = storage.shattered_reached or {}
end
-- Every technology this mod adds. They all start disabled in the data stage
-- and are only enabled once a force lands a platform at the shattered planet.
local INTERSTELLAR_TECHS = {
"interstellar-fleets",
"quantum-replication",
"antimatter-containment",
"interstellar-xenobiology",
"quantum-fabrication",
"orbital-industry",
"fleet-printing",
"interstellar-dust-crushing",
"deep-dust-prospecting",
"stellar-fusion-drive-efficiency",
"interstellar-dust-collection-productivity",
"quantum-replication-productivity",
"fleet-coordination",
"antimatter-drive-efficiency"
}
local function set_interstellar_techs_enabled(force, enabled)
for _, name in pairs(INTERSTELLAR_TECHS) do
local technology = force.technologies[name]
if technology then
technology.enabled = enabled
end
end
end
local function mark_shattered_planet_reached(force, platform_name)
init_storage()
if storage.shattered_reached[force.name] then
return
end
storage.shattered_reached[force.name] = true
set_interstellar_techs_enabled(force, true)
-- The base tech is free: researching it here applies its effects,
-- including unlock-space-location for the galactic center.
local base = force.technologies["interstellar-fleets"]
if base and not base.researched then
base.researched = true
end
force.unlock_space_location("galactic-center")
game.print({"interstellar-fleets.shattered-planet-reached", platform_name})
for _, player in pairs(force.players) do
player.unlock_achievement("interstellar-shattered-planet")
end
end
local function check_platform_location(platform)
if platform.valid and platform.space_location and platform.space_location.name == "shattered-planet" then
mark_shattered_planet_reached(platform.force, platform.name)
end
end
-- Saves created before the gate existed loaded these techs enabled, and the
-- runtime enabled flag persists in the save. Re-sync it: forces that already
-- researched any mod tech count as having reached the shattered planet.
local function sync_interstellar_tech_gate()
init_storage()
for _, force in pairs(game.forces) do
local reached = storage.shattered_reached[force.name] == true
if not reached then
for _, name in pairs(INTERSTELLAR_TECHS) do
local technology = force.technologies[name]
if technology and technology.researched then
reached = true
break
end
end
if reached then
storage.shattered_reached[force.name] = true
end
end
set_interstellar_techs_enabled(force, reached)
if reached then
local base = force.technologies["interstellar-fleets"]
if base and not base.researched then
base.researched = true
end
force.unlock_space_location("galactic-center")
else
force.lock_space_location("galactic-center")
end
end
end
script.on_init(sync_interstellar_tech_gate)
script.on_configuration_changed(sync_interstellar_tech_gate)
script.on_event(defines.events.on_force_created, function(event)
init_storage()
if not storage.shattered_reached[event.force.name] then
event.force.lock_space_location("galactic-center")
end
end)
script.on_event(defines.events.on_space_platform_changed_state, function(event)
check_platform_location(event.platform)
end)
local function get_platform_for_player(player)
if player.surface and player.surface.platform then
return player.surface.platform
end
if player.controller_type == defines.controllers.remote and player.physical_surface and player.physical_surface.platform then
return player.physical_surface.platform
end
return nil
end
local function get_fleet(platform)
init_storage()
local key = tostring(platform.index)
storage.fleets[key] = storage.fleets[key] or {
size = 1,
speed_c = 0.01,
distance_m = 0,
blueprint_hash = nil,
auto_boost = false,
dust_backlog = 0
}
if storage.fleets[key].auto_boost == nil then
storage.fleets[key].auto_boost = false
end
if storage.fleets[key].dust_backlog == nil then
storage.fleets[key].dust_backlog = 0
end
return storage.fleets[key]
end
local function count_entities(surface, names)
local total = 0
for name, _ in pairs(names) do
total = total + surface.count_entities_filtered({name = name})
end
return total
end
local function completed_research_levels(force, name)
local technology = force.technologies[name]
if not technology then
return 0
end
if technology.level and technology.level > 1 then
return technology.level - 1
end
if technology.researched then
return 1
end
return 0
end
local function drive_efficiency_multiplier(force, technology_name, reduction_per_level)
local levels = completed_research_levels(force, technology_name)
return math.max(0.2, 1 - levels * reduction_per_level)
end
local function research_multiplier(force, technology_name, bonus_per_level)
return 1 + completed_research_levels(force, technology_name) * bonus_per_level
end
local function notify(player, message)
if player and player.valid then
player.print(message)
end
end
local function platform_signature(surface, force)
local parts = {}
for _, entity in pairs(surface.find_entities_filtered({force = force})) do
if entity.valid and not TRANSIENT_TYPES[entity.type] then
parts[#parts + 1] = table.concat({
entity.name,
math.floor(entity.position.x),
math.floor(entity.position.y),
entity.direction or 0
}, ":")
end
end
table.sort(parts)
return table.concat(parts, "|")
end
-- Deliver dust to the hub, buffering overflow in a bounded per-fleet backlog.
-- A full hub used to spill overflow as one item-on-ground entity per item,
-- which at fleet scale creates thousands of entities per second and destroys
-- UPS. Instead the backlog drains into the hub as space frees up; once the
-- backlog cap is reached, collection pauses like any output-blocked machine.
local function deliver_dust(platform, fleet, amount)
local hub = platform.hub
if not hub or not hub.valid then
return
end
local pending = math.min(fleet.dust_backlog + amount, DUST_BACKLOG_CAP)
if pending <= 0 then
return
end
local inserted = hub.insert({name = "interstellar-dust", count = pending})
fleet.dust_backlog = pending - inserted
end
local function each_platform(callback)
for _, force in pairs(game.forces) do
for _, platform in pairs(force.platforms) do
callback(platform)
end
end
end
local function find_platform(platform_index)
local target = tonumber(platform_index)
if not target then
return nil
end
local found
each_platform(function(platform)
if platform.valid and platform.index == target then
found = platform
end
end)
return found
end
local function update_caption(player)
local root = player.gui.screen[GUI_ROOT]
if not root then
return
end
local platform = get_platform_for_player(player)
if not platform then
root.status.caption = {"interstellar-fleets.no-platform"}
return
end
local fleet = get_fleet(platform)
root.status.caption = {
"interstellar-fleets.status",
platform.name,
fleet.size,
string.format("%.4f", fleet.speed_c),
string.format("%.2f", fleet.distance_m / 1000000000)
}
if root.interstellar_fleets_auto_boost then
root.interstellar_fleets_auto_boost.state = fleet.auto_boost
end
end
local function open_gui(player)
local root = player.gui.screen[GUI_ROOT]
if root then
root.destroy()
return
end
root = player.gui.screen.add({type = "frame", name = GUI_ROOT, direction = "vertical", caption = {"interstellar-fleets.title"}})
root.auto_center = true
root.add({type = "label", name = "status", caption = ""})
local controls = root.add({type = "flow", name = "controls", direction = "horizontal"})
controls.add({type = "button", name = "interstellar_fleets_merge", caption = {"interstellar-fleets.merge"}})
controls.add({type = "button", name = "interstellar_fleets_split", caption = {"interstellar-fleets.split"}})
controls.add({type = "button", name = "interstellar_fleets_update_blueprint", caption = {"interstellar-fleets.update-blueprint"}})
controls.add({type = "button", name = "interstellar_fleets_boost", caption = {"interstellar-fleets.boost"}})
root.add({type = "checkbox", name = "interstellar_fleets_auto_boost", caption = {"interstellar-fleets.auto-boost"}, state = false})
update_caption(player)
end
script.on_event("interstellar-fleets-toggle", function(event)
local player = game.get_player(event.player_index)
if player then
open_gui(player)
end
end)
script.on_event(defines.events.on_lua_shortcut, function(event)
if event.prototype_name ~= "interstellar-fleets-toggle" then
return
end
local player = game.get_player(event.player_index)
if player then
open_gui(player)
end
end)
local function clear_progress(surface)
for _, entity in pairs(surface.find_entities_filtered({type = {"assembling-machine", "furnace", "rocket-silo", "lab"}})) do
if entity.valid then
pcall(function()
entity.crafting_progress = 0
end)
pcall(function()
entity.bonus_progress = 0
end)
end
end
end
local function get_platform_area(surface, force)
local min_x, min_y, max_x, max_y
local function include_position(position)
min_x = math.min(min_x or position.x, position.x)
min_y = math.min(min_y or position.y, position.y)
max_x = math.max(max_x or position.x, position.x)
max_y = math.max(max_y or position.y, position.y)
end
for _, entity in pairs(surface.find_entities_filtered({force = force})) do
if entity.valid and entity.name ~= "character" then
include_position(entity.position)
end
end
for _, tile in pairs(surface.find_tiles_filtered({name = "space-platform-foundation"})) do
include_position(tile.position)
end
if not min_x then
return nil
end
return {
{math.floor(min_x) - 4, math.floor(min_y) - 4},
{math.ceil(max_x) + 4, math.ceil(max_y) + 4}
}
end
local function clone_platform_layout(source_platform, destination_platform)
if not source_platform.surface or not destination_platform.surface then
return false
end
local area = get_platform_area(source_platform.surface, source_platform.force)
if not area then
return false
end
return pcall(function()
source_platform.surface.clone_area({
source_area = area,
destination_area = area,
destination_surface = destination_platform.surface,
destination_force = destination_platform.force,
clone_tiles = true,
clone_entities = true,
clear_destination_entities = true,
clear_destination_decoratives = true,
expand_map = true,
create_build_effect_smoke = false
})
end)
end
local function merge_fleet(player, platform, fleet)
local hub = platform.hub
if not hub or not hub.valid then
notify(player, {"interstellar-fleets.no-hub"})
return
end
local signature = platform_signature(platform.surface, platform.force)
if fleet.blueprint_hash and fleet.blueprint_hash ~= signature then
notify(player, {"interstellar-fleets.blueprint-mismatch"})
return
end
if hub.get_item_count("ship-starter-pack") < 1 then
notify(player, {"interstellar-fleets.need-pack"})
return
end
fleet.blueprint_hash = signature
hub.remove_item({name = "ship-starter-pack", count = 1})
fleet.size = fleet.size + 1
notify(player, {"interstellar-fleets.merged", fleet.size})
end
local function split_fleet(player, platform, fleet)
if fleet.size < 2 then
notify(player, {"interstellar-fleets.cannot-split"})
return
end
local split_size = math.floor(fleet.size / 2)
fleet.size = fleet.size - split_size
clear_progress(platform.surface)
local location = platform.space_location or platform.last_visited_space_location
local force = player and player.valid and player.force or platform.force
local function try_create_platform(location_name)
local ok, created = pcall(function()
return force.create_space_platform({
name = platform.name .. " split",
planet = location_name,
starter_pack = "space-platform-starter-pack"
})
end)
if ok and created then
return created
end
return nil
end
-- Prefer the fleet's current location, but fall back to Nauvis so splitting
-- still works at locations that cannot host a new platform (for example
-- fly-condition destinations like the Galactic Center).
local new_platform = location and try_create_platform(location.name) or nil
if not new_platform then
new_platform = try_create_platform("nauvis")
end
if not new_platform then
fleet.size = fleet.size + split_size
notify(player, {"interstellar-fleets.split-failed"})
return
end
pcall(function()
new_platform.apply_starter_pack()
end)
clone_platform_layout(platform, new_platform)
local new_fleet = get_fleet(new_platform)
new_fleet.size = split_size
new_fleet.speed_c = fleet.speed_c
new_fleet.distance_m = fleet.distance_m
new_fleet.blueprint_hash = fleet.blueprint_hash
notify(player, {"interstellar-fleets.split-complete", fleet.size, split_size})
end
local function boost_fleet(player, platform, fleet, quiet)
local fusion_drives = count_entities(platform.surface, {["stellar-fusion-drive"] = true})
local antimatter_drives = count_entities(platform.surface, {["antimatter-drive"] = true})
local fusion_drive_power = fusion_drives
local antimatter_drive_power = antimatter_drives * 4
local drive_power = fusion_drive_power + antimatter_drive_power
if drive_power == 0 then
if not quiet then
notify(player, {"interstellar-fleets.no-drives"})
end
return false
end
local hub = platform.hub
if not hub or not hub.valid then
if not quiet then
notify(player, {"interstellar-fleets.no-hub"})
end
return false
end
local gamma = 1 / math.sqrt(math.max(0.0001, 1 - fleet.speed_c * fleet.speed_c))
local fusion_efficiency = drive_efficiency_multiplier(platform.force, "stellar-fusion-drive-efficiency", 0.08)
local antimatter_efficiency = drive_efficiency_multiplier(platform.force, "antimatter-drive-efficiency", 0.1)
local fusion_cell_cost = fusion_drives > 0 and math.max(1, math.ceil(fleet.size * gamma * fusion_drive_power * fusion_efficiency)) or 0
local antimatter_cost = antimatter_drives > 0 and math.max(1, math.ceil(fleet.size * gamma * antimatter_drives * antimatter_efficiency)) or 0
if fusion_cell_cost > 0 and hub.get_item_count("fusion-power-cell") < fusion_cell_cost then
if not quiet then
notify(player, {"interstellar-fleets.need-fusion-cells", fusion_cell_cost})
end
return false
end
if antimatter_cost > 0 and hub.get_item_count("antimatter") < antimatter_cost then
if not quiet then
notify(player, {"interstellar-fleets.need-antimatter", antimatter_cost})
end
return false
end
if fusion_cell_cost > 0 then
hub.remove_item({name = "fusion-power-cell", count = fusion_cell_cost})
end
if antimatter_cost > 0 then
hub.remove_item({name = "antimatter", count = antimatter_cost})
end
local acceleration = drive_power * 0.00005 / gamma
fleet.speed_c = math.min(0.999, fleet.speed_c + acceleration)
if not quiet then
notify(player, {"interstellar-fleets.boosted", string.format("%.4f", fleet.speed_c), fusion_cell_cost, antimatter_cost})
end
return true
end
local function update_fleet_blueprint(player, platform, fleet)
fleet.blueprint_hash = platform_signature(platform.surface, platform.force)
clear_progress(platform.surface)
notify(player, {"interstellar-fleets.blueprint-updated"})
end
script.on_event(defines.events.on_gui_click, function(event)
local element = event.element
if not element or not element.valid then
return
end
local player = game.get_player(event.player_index)
if not player then
return
end
if element.name == "interstellar_fleets_merge" or element.name == "interstellar_fleets_split" or element.name == "interstellar_fleets_update_blueprint" or element.name == "interstellar_fleets_boost" then
local platform = get_platform_for_player(player)
if not platform then
player.print({"interstellar-fleets.no-platform"})
return
end
local fleet = get_fleet(platform)
if element.name == "interstellar_fleets_merge" then
merge_fleet(player, platform, fleet)
elseif element.name == "interstellar_fleets_split" then
split_fleet(player, platform, fleet)
elseif element.name == "interstellar_fleets_update_blueprint" then
update_fleet_blueprint(player, platform, fleet)
elseif element.name == "interstellar_fleets_boost" then
boost_fleet(player, platform, fleet)
end
update_caption(player)
end
end)
script.on_event(defines.events.on_gui_checked_state_changed, function(event)
local element = event.element
if not element or not element.valid or element.name ~= "interstellar_fleets_auto_boost" then
return
end
local player = game.get_player(event.player_index)
if not player then
return
end
local platform = get_platform_for_player(player)
if not platform then
player.print({"interstellar-fleets.no-platform"})
element.state = false
return
end
local fleet = get_fleet(platform)
fleet.auto_boost = element.state
player.print(fleet.auto_boost and {"interstellar-fleets.auto-boost-enabled"} or {"interstellar-fleets.auto-boost-disabled"})
update_caption(player)
end)
script.on_nth_tick(60, function()
init_storage()
local live_platform_keys = {}
each_platform(function(platform)
if platform.valid then
live_platform_keys[tostring(platform.index)] = true
check_platform_location(platform)
end
if platform.valid and platform.surface and platform.surface.valid then
local fleet = get_fleet(platform)
local surface = platform.surface
local speed_bonus = math.max(0, fleet.size - 1)
local coordination_multiplier = research_multiplier(platform.force, "fleet-coordination", 0.03)
local speed_effect = speed_bonus * coordination_multiplier
-- Only touch global_effect when the fleet bonus actually changes.
-- Rewriting it every second dirties every effect receiver on the
-- surface and stomps effects other mods may have applied.
if fleet.applied_speed_effect ~= speed_effect or fleet.applied_consumption_effect ~= speed_bonus then
if speed_bonus > 0 then
surface.global_effect = {
speed = speed_effect,
consumption = speed_bonus
}
elseif fleet.applied_speed_effect then
surface.global_effect = nil
end
fleet.applied_speed_effect = speed_effect
fleet.applied_consumption_effect = speed_bonus
end
local collectors = count_entities(surface, {["interstellar-dust-collector"] = true})
if collectors > 0 then
local dust_multiplier = research_multiplier(platform.force, "interstellar-dust-collection-productivity", 0.08)
local dust = math.floor(math.max(1, collectors * fleet.size * fleet.speed_c * 25 * dust_multiplier))
deliver_dust(platform, fleet, dust)
elseif fleet.dust_backlog > 0 then
deliver_dust(platform, fleet, 0)
end
if fleet.auto_boost then
boost_fleet(nil, platform, fleet, true)
end
local drives = count_entities(surface, {["stellar-fusion-drive"] = true, ["antimatter-drive"] = true})
if drives > 0 then
fleet.distance_m = fleet.distance_m + fleet.speed_c * C
end
end
end)
for key in pairs(storage.fleets) do
if not live_platform_keys[key] then
storage.fleets[key] = nil
end
end
for _, player in pairs(game.connected_players) do
update_caption(player)
end
end)
remote.add_interface("interstellar-fleets", {
get_fleet = function(platform_index)
local platform = find_platform(platform_index)
if not platform then
return nil
end
local fleet = get_fleet(platform)
return {
size = fleet.size,
speed_c = fleet.speed_c,
distance_m = fleet.distance_m,
blueprint_hash = fleet.blueprint_hash,
auto_boost = fleet.auto_boost
}
end,
merge = function(player_index, platform_index)
local player = player_index and game.get_player(player_index) or nil
local platform = find_platform(platform_index)
if not platform then
return false
end
merge_fleet(player, platform, get_fleet(platform))
return true
end,
split = function(player_index, platform_index)
local player = player_index and game.get_player(player_index) or nil
local platform = find_platform(platform_index)
if not platform then
return false
end
split_fleet(player, platform, get_fleet(platform))
return true
end,
update_blueprint = function(player_index, platform_index)
local player = player_index and game.get_player(player_index) or nil
local platform = find_platform(platform_index)
if not platform then
return false
end
update_fleet_blueprint(player, platform, get_fleet(platform))
return true
end,
boost = function(player_index, platform_index)
local player = player_index and game.get_player(player_index) or nil
local platform = find_platform(platform_index)
if not platform then
return false
end
boost_fleet(player, platform, get_fleet(platform))
return true
end,
set_auto_boost = function(platform_index, enabled)
local platform = find_platform(platform_index)
if not platform then
return false
end
get_fleet(platform).auto_boost = enabled and true or false
return true
end
})