-
Notifications
You must be signed in to change notification settings - Fork 0
/
optimize.js
1585 lines (1492 loc) · 67.1 KB
/
optimize.js
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
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
creep对穿+跨房间寻路+寻路缓存
跑的比香港记者还快从你做起
应用此模块会导致creep.moveTo可选参数中这些项失效:reusePath、serializeMemory、noPathFinding、ignore、avoid、serialize
保留creep.moveTo中其他全部可选参数如visualizePathStyle、range、ignoreDestructibleStructures、ignoreCreeps、ignoreRoad等
新增creep.moveTo中可选参数ignoreSwamps,会无视swamp与road的移动力损耗差异,一律与plain相同处理,用于方便pc和眼,默认false
例:creep.moveTo(controller, {ignoreSwamps: true});
新增creep.moveTo中可选参数bypassHostileCreeps,被creep挡路时若此项为true则绕过别人的creep,默认为true,设为false用于近战攻击
例:creep.moveTo(controller, {bypassHostileCreeps: false});
新增creep.moveTo中可选参数bypassRange,被creep挡路准备绕路时的绕路半径,默认为5
例:creep.moveTo(controller, {bypassRange: 10});
新增creep.moveTo中可选参数noPathDelay,寻得的路是不完全路径时的再次寻路延迟,默认为10
例:creep.moveTo(controller, {noPathDelay: 5});
新增返回值ERR_INVALID_ARGS,表示range或者bypassRange类型错误
遇到己方creep自动进行对穿,遇到自己设置了不想被对穿的creep(或bypassHostileCreeps设为true时遇到他人creep)会自动绕过
会将新手墙和部署中的invaderCore处理为无法通过
会绕过非终点的portal,不影响creep.moveTo(portal)
不使用Memory及global,不会因此干扰外部代码
不会在Creep.prototype、PowerCreep.prototype上增加官方未有的键值,不会因此干扰外部代码
本模块不可用于sim,在sim会因为房间名格式不对返回ERR_INVALID_TARGET
模块参数见代码头部,模块接口见代码尾部
版本号规则:alpha test = 0.1.x,beta test = 0.9.x,publish >= 1.0.0
author: Scorpior
debug helpers: fangxm, czc
inspired by: Yuandiaodiaodiao
date: 2020/3/30
version: 0.9.4(beta test)
Usage:
module :main
require('超级移动优化');
module.exports.loop=function() {
//your codes go here
}
changelog:
0.1.0: maybe not runnable
0.1.1: still maybe not runnable,修了一些typo,完成正向移动,修改isObstacleStructure
0.1.2: maybe runnable,some bugs are fixed
0.1.3: 修正工地位置寻路错误,调整打印格式
0.1.4: 补充pc对穿,打印中增加cache hits统计
0.9.0: 启用自动清理缓存,保留ignoreCreeps参数,调整对穿顺序+增加在storage附近检查对穿,
正确识别敌对rampart,正确查询带range路径,打印中增加对穿频率统计
0.9.1: 增加正常逻辑开销统计,修改cache搜索开销统计为cache miss开销统计,绕路bugfix,跨房检测bugfix,other bugfix
0.9.2: 修改缓存策略减少查找耗时增加命中率,增加核心区对穿次数统计,对穿bugfix,other bugfix
0.9.3: 取消路径反向复用避免偶发的复用非最优路径的情况,改进识别被新手墙封闭的房间,增加avoidRooms设置,
增加远距离跨房寻路成功率,房间出口处对穿bug fix
0.9.4: 优化路径复用避免偶发的复用非最优路径的情况,删除运行时参数中neutralCostMatrixClearDelay,
自动根据挡路建筑情况设置中立房间costMatrix过期时间,增加ob寻路(检查房间是否可走),
提供deletePathInRoom接口(使用方式见下方ps),print()中增加平均每次查找缓存时检查的路径数量统计,
findRoute遇到过道新手墙时bugfix,偏移路径bugfix
0.9.5: TODO:ignoreSwamp避开路,提供deletePathFromRoom、deletePathToRoom接口,增加自动visual,betterMove
ps:
1.默认ignoreCreeps为true,主动设置ignoreCreeps为false会在撞到creep时重新寻路
2.对于不想被对穿的creep(比如没有脚的中央搬运工), 设置memory:
creep.memory.dontPullMe = true;
3.修路后希望手动更新房间内路径,可执行如下代码:
require('超级移动优化').deletePathInRoom(roomName);
4.战斗中遇到敌方pc不断产生新rampart挡路的情况,目前是撞上建筑物才重新寻路(原版moveTo撞上也继续撞),如果觉得需要手动提前激活重新寻路则联系我讨论
5.在控制台输入require('超级移动优化').print()获取性能信息,鼓励发给作者用于优化
*/
/***************************************
* 模块参数
*/
// 初始化参数
let config = {
地图房号最大数字超过100: false,
changeMove: true, // 【未启用】为creep.move增加对穿能力
changeMoveTo: true, // 全面优化creep.moveTo,跨房移动也可以一个moveTo解决问题
changeFindClostestByPath: true, // 【未启用】轻度修改findClosestByPath,使得默认按照ignoreCreeps寻找最短
autoVisual: true, // 【未启用】
enableFlee: false // 【未启用】是否添加flee()函数,注意这会在Creep.prototype上添加官方未有键值,flee()用法见最底下module.exports处
}
// 运行时参数
let pathClearDelay = 5000; // 清理相应时间内都未被再次使用的路径,同时清理死亡creep的缓存,设为undefined表示不清除缓存
let hostileCostMatrixClearDelay = 500; // 自动清理相应时间前创建的其他玩家房间的costMatrix
let coreLayoutRange = 3; // 核心布局半径,在离storage这个范围内频繁检查对穿(减少堵路的等待
let avoidRooms = ['E18S8', 'E19S9', 'E21S9', 'E24S8', 'E35N6', 'E25S9',
'E19N2', 'E18N3', 'E29N5', 'E29N3', 'E28N8', 'E33N9', 'E34N8',
'E37N6', 'E41N8', 'E39N11', 'E39N12', 'E39N13', 'E17S9', 'W23S5', 'W25S7'] // 永不踏入这些房间
let avoidExits = {
'E35N7': 'E35N6',
'fromRoom': 'toRoom'
} // 【未启用】单向屏蔽房间的一些出口,永不从fromRoom踏入toRoom
/** @type {{id:string, roomName:string, taskQueue:{path:MyPath, idx:number, roomName:string}[]}[]} */
let observers = ['5e3646219c6dc78024fd7097', '5e55e9b8673548d9468a2d3d', '5e36372d00fab883d281d95e']; // 如果想用ob寻路,把ob的id放这里
/***************************************
* 局部缓存
*/
/** @type {{ [time: number]:{path:MyPath, idx:number, roomName:string}[] }} */
let obTimer = {}; // 【未启用】用于登记ob调用,在相应的tick查看房间对象
let obTick = Game.time;
/** @type {Paths} */
let globalPathCache = {}; // 缓存path
/** @type {MoveTimer} */
let pathCacheTimer = {}; // 用于记录path被使用的时间,清理长期未被使用的path
/** @type {CreepPaths} */
let creepPathCache = {}; // 缓存每个creep使用path的情况
let creepMoveCache = {}; // 缓存每个creep最后一次移动的tick
let emptyCostMatrix = new PathFinder.CostMatrix;
/** @type {CMs} */
let costMatrixCache = {}; // true存ignoreDestructibleStructures==true的,false同理
/** @type {{ [time: number]:{roomName:string, avoids:string[]}[] }} */
let costMatrixCacheTimer = {}; // 用于记录costMatrix的创建时间,清理过期costMatrix
let autoClearTick = Game.time; // 用于避免重复清理缓存
const obstacles = new Set(OBSTACLE_OBJECT_TYPES);
const originMove = Creep.prototype.move;
const originMoveTo = Creep.prototype.moveTo;
const originFindClosestByPath = RoomPosition.prototype.findClosestByPath;
// 统计变量
let startTime;
let endTime;
let startCacheSearch;
let analyzeCPU = { // 统计相关函数总耗时
move: { sum: 0, calls: 0 },
moveTo: { sum: 0, calls: 0 },
findClosestByPath: { sum: 0, calls: 0 }
};
let pathCounter = 0;
let testCacheHits = 0;
let testCacheMiss = 0;
let testNormal = 0;
let testNearStorageCheck = 0;
let testNearStorageSwap = 0;
let testTrySwap = 0;
let testBypass = 0;
let normalLogicalCost = 0;
let cacheHitCost = 0;
let cacheMissCost = 0;
/***************************************
* util functions
*/
let reg1 = /^([WE])([0-9]+)([NS])([0-9]+)$/; // parse得到['E28N7','E','28','N','7']
/**
* 统一到大地图坐标,平均单次开销0.00005
* @param {RoomPosition} pos
*/
function formalize(pos) {
let splited = reg1.exec(pos.roomName);
if (splited && splited.length == 5) {
return { // 如果这里出现类型错误,那么意味着房间名字不是正确格式但通过了parse,小概率事件
x: (splited[1] === 'W' ? -splited[2] : +splited[2] + 1) * 50 + pos.x,
y: (splited[3] === 'N' ? -splited[4] : +splited[4] + 1) * 50 + pos.y
}
} // else 房间名字不是正确格式
return {}
}
function getAdjacents(pos) {
let posArray = [];
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
posArray.push({
x: pos.x + i,
y: pos.y + j
})
}
}
return posArray;
}
/**
* 阉割版isEqualTo,提速
* @param {RoomPosition} pos1
* @param {RoomPosition} pos2
*/
function isEqual(pos1, pos2) {
return pos1.x == pos2.x && pos1.y == pos2.y && pos1.roomName == pos2.roomName;
}
/**
* 兼容房间边界
* 参数具有x和y属性就行
* @param {RoomPosition} pos1
* @param {RoomPosition} pos2
*/
function isNear(pos1, pos2) {
if (pos1.roomName == pos2.roomName) { // undefined == undefined 也成立
return -1 <= pos1.x - pos2.x && pos1.x - pos2.x <= 1 && -1 <= pos1.y - pos2.y && pos1.y - pos2.y <= 1;
} else if (pos1.roomName && pos2.roomName) { // 是完整的RoomPosition
if (pos1.x + pos2.x != 49 && pos1.y + pos2.y != 49) return false; // 肯定不是两个边界点, 0.00003 cpu
// start
let splited1 = reg1.exec(pos1.roomName);
let splited2 = reg1.exec(pos2.roomName);
if (splited1 && splited1.length == 5 && splited2 && splited2.length == 5) {
// 统一到大地图坐标
let formalizedEW = (splited1[1] === 'W' ? -splited1[2] : +splited1[2] + 1) * 50 + pos1.x - (splited2[1] === 'W' ? -splited2[2] : +splited2[2] + 1) * 50 - pos2.x;
let formalizedNS = (splited1[3] === 'N' ? -splited1[4] : +splited1[4] + 1) * 50 + pos1.y - (splited2[3] === 'N' ? -splited2[4] : +splited2[4] + 1) * 50 - pos2.y;
return -1 <= formalizedEW && formalizedEW <= 1 && -1 <= formalizedNS && formalizedNS <= 1;
}
// end - start = 0.00077 cpu
}
return false
}
/**
* @param {RoomPosition} pos1
* @param {RoomPosition} pos2
*/
function inRange(pos1, pos2, range) {
if (pos1.roomName == pos2.roomName) {
return -range <= pos1.x - pos2.x && pos1.x - pos2.x <= range && -range <= pos1.y - pos2.y && pos1.y - pos2.y <= range;
} else {
pos1 = formalize(pos1);
pos2 = formalize(pos2);
return pos1.x && pos2.x && inRange(pos1, pos2);
}
}
/**
* fromPos和toPos是pathFinder寻出的路径上的,只可能是同房相邻点或者跨房边界点
* @param {RoomPosition} fromPos
* @param {RoomPosition} toPos
*/
function getDirection(fromPos, toPos) {
if (fromPos.roomName == toPos.roomName) {
if (toPos.x > fromPos.x) { // 下一步在右边
if (toPos.y > fromPos.y) { // 下一步在下面
return BOTTOM_RIGHT;
} else if (toPos.y == fromPos.y) { // 下一步在正右
return RIGHT;
}
return TOP_RIGHT; // 下一步在上面
} else if (toPos.x == fromPos.x) { // 横向相等
if (toPos.y > fromPos.y) { // 下一步在下面
return BOTTOM;
} else if (toPos.y < fromPos.y) {
return TOP;
}
} else { // 下一步在左边
if (toPos.y > fromPos.y) { // 下一步在下面
return BOTTOM_LEFT;
} else if (toPos.y == fromPos.y) {
return LEFT;
}
return TOP_LEFT;
}
} else { // 房间边界点
if (fromPos.x == 0 || fromPos.x == 49) { // 左右相邻的房间,只需上下移动(左右边界会自动弹过去)
if (toPos.y > fromPos.y) { // 下一步在下面
return BOTTOM;
} else if (toPos.y < fromPos.y) { // 下一步在上
return TOP
} // else 正左正右
return fromPos.x ? RIGHT : LEFT;
} else if (fromPos.y == 0 || fromPos.y == 49) { // 上下相邻的房间,只需左右移动(上下边界会自动弹过去)
if (toPos.x > fromPos.x) { // 下一步在右边
return RIGHT;
} else if (toPos.x < fromPos.x) {
return LEFT;
}// else 正上正下
return fromPos.y ? BOTTOM : TOP;
}
}
}
let reg2 = /^[WE]([0-9]+)[NS]([0-9]+)$/; // parse得到['E28N7','28','7']
let isHighWay = config.地图房号最大数字超过100 ?
(roomName) => {
let splited = reg2.exec(roomName);
return splited[1] % 10 == 0 || splited[2] % 10 == 0;
} :
(roomName) => {
// E0 || E10 || E1S0 || [E10S0|E1S10] || [E10S10] 比正则再除快
return roomName[1] == 0 || roomName[2] == 0 || roomName[3] == 0 || roomName[4] == 0 || roomName[5] == 0;
}
/**
* 缓存的路径和当前moveTo参数相同
* @param {MyPath} path
* @param {*} ops
*/
function isSameOps(path, ops) {
return path.ignoreRoads == !!ops.ignoreRoads &&
path.ignoreSwamps == !!ops.ignoreSwamps &&
path.ignoreStructures == !!ops.ignoreDestructibleStructures;
}
function hasActiveBodypart(body, type) {
if (!body) {
return true;
}
for (var i = body.length - 1; i >= 0; i--) {
if (body[i].hits <= 0)
break;
if (body[i].type === type)
return true;
}
return false;
}
function isClosedRampart(structure) {
return structure.structureType == STRUCTURE_RAMPART && !structure.my && !structure.isPublic;
}
/**
* 查看是否有挡路建筑
* @param {Room} room
* @param {RoomPosition} pos
* @param {boolean} ignoreStructures
*/
function isObstacleStructure(room, pos, ignoreStructures) {
let consSite = room.lookForAt(LOOK_CONSTRUCTION_SITES, pos);
if (0 in consSite && consSite[0].my && obstacles.has(consSite[0].structureType)) { // 工地会挡路
return true;
}
for (let s of room.lookForAt(LOOK_STRUCTURES, pos)) {
if (!s.hits || s.ticksToDeploy) { // 是新手墙或者无敌中的invaderCore
return true;
} else if (!ignoreStructures && (obstacles.has(s.structureType) || isClosedRampart(s))) {
return true
}
}
return false;
// let possibleStructures = room.lookForAt(LOOK_STRUCTURES, pos); // room.lookForAt比pos.lookFor快
// 万一有人把路修在extension上,导致需要每个建筑都判断,最多重叠3个建筑(rap+road+其他)
// return obstacles.has(possibleStructures[0]) || obstacles.has(possibleStructures[1]) || obstacles.has(possibleStructures[2]); // 条件判断平均每次0.00013cpu
}
/**
* 登记ob需求
* @param {MyPath} path
* @param {number} idx
*/
function addObTask(path, idx) {
let roomName = path.posArray[idx].roomName;
//console.log('准备ob ' + roomName);
for (let obData of observers) {
if (Game.map.getRoomLinearDistance(obData.roomName, roomName) <= 10) {
obData.taskQueue.push({ path: path, idx: idx, roomName: roomName });
break;
}
}
}
/**
* 尝试用ob检查路径
*/
function doObTask() {
for (let obData of observers) { // 遍历所有ob
let queue = obData.taskQueue;
while (queue.length) { // 没有task就pass
let task = queue[queue.length - 1];
let roomName = task.roomName;
if (roomName in costMatrixCache) { // 有过视野不用再ob
if (!task.path.directionArray[task.idx]) {
//console.log(roomName + ' 有视野了无需ob');
checkRoom({ name: roomName }, task.path, task.idx - 1);
}
queue.pop();
continue;
}
/** @type {StructureObserver} */
let ob = Game.getObjectById(obData.id);
if (ob) {
//console.log('ob ' + roomName);
ob.observeRoom(roomName);
if (!(Game.time + 1 in obTimer)) {
obTimer[Game.time + 1] = [];
}
obTimer[Game.time + 1].push({ path: task.path, idx: task.idx, roomName: roomName }); // idx位置无direction
} else {
observers.splice(observers.indexOf(obData), 1);
}
break;
}
}
}
/**
* 查看ob得到的房间
*/
function checkObResult() {
for (let tick in obTimer) {
if (tick < Game.time) {
delete obTimer[tick];
continue; // 后面可能还有要检查的
} else if (tick == Game.time) {
for (let result of obTimer[tick]) {
if (result.roomName in Game.rooms) {
//console.log('ob得到 ' + result.roomName);
checkRoom(Game.rooms[result.roomName], result.path, result.idx - 1); // checkRoom要传有direction的idx
}
}
delete obTimer[tick];
} // else 没有要检查的
break; // 检查完了或者没有要检查的
}
}
/**
* 为房间保存costMatrix,ignoreDestructibleStructures这个参数的两种情况各需要一个costMatrix
* 设置costMatrix缓存的过期时间
* @param {Room} room
* @param {RoomPosition} pos
*/
function generateCostMatrix(room, pos) {
let noStructureCostMat = new PathFinder.CostMatrix; // 不考虑可破坏的建筑,但是要考虑墙上资源点和无敌的3种建筑,可能还有其他不能走的?
let structureCostMat = new PathFinder.CostMatrix; // 在noStructrue的基础上加上所有不可行走的建筑
let totalStructures = room.find(FIND_STRUCTURES);
let 修路也没用的墙点 = [].concat(room.find(FIND_SOURCES), room.find(FIND_MINERALS), room.find(FIND_DEPOSITS));
let x, y, noviceWall, deployingCore, centralPortal;
let clearDelay = Infinity;
for (let object of 修路也没用的墙点) {
x = object.pos.x; y = object.pos.y;
noStructureCostMat.set(x, y, 255);
}
if (room.controller && (room.controller.my || room.controller.safeMode)) { // 自己的工地不能踩
for (let consSite of room.find(FIND_CONSTRUCTION_SITES)) {
if (obstacles.has(consSite.structureType)) {
x = consSite.pos.x; y = consSite.pos.y;
noStructureCostMat.set(x, y, 255);
structureCostMat.set(x, y, 255);
}
}
}
for (let s of totalStructures) {
if (s.structureType == STRUCTURE_INVADER_CORE) { // 第1种可能无敌的建筑
if (s.ticksToDeploy) {
deployingCore = true;
clearDelay = clearDelay > s.ticksToDeploy ? s.ticksToDeploy : clearDelay;
noStructureCostMat.set(s.pos.x, s.pos.y, 255);
}
structureCostMat.set(s.pos.x, s.pos.y, 255);
} else if (s.structureType == STRUCTURE_PORTAL) { // 第2种无敌建筑
if (!isHighWay(room.name)) {
centralPortal = true;
clearDelay = clearDelay > s.ticksToDecay ? s.ticksToDecay : clearDelay;
}
x = s.pos.x; y = s.pos.y;
structureCostMat.set(x, y, 255);
noStructureCostMat.set(x, y, 255);
} else if (s.structureType == STRUCTURE_WALL) { // 第3种可能无敌的建筑
if (!s.hits) {
noviceWall = true;
noStructureCostMat.set(s.pos.x, s.pos.y, 255);
}
structureCostMat.set(s.pos.x, s.pos.y, 255);
} else if (s.structureType == STRUCTURE_ROAD) { // 路的移动力损耗是1,此处设置能寻到墙上的路
x = s.pos.x; y = s.pos.y;
if (noStructureCostMat.get(x, y) == 0) { // 不是在3种无敌建筑或墙中资源上
noStructureCostMat.set(x, y, 1);
if (structureCostMat.get(x, y) == 0) { // 不是在不可行走的建筑上
structureCostMat.set(x, y, 1);
}
}
} else if (obstacles.has(s.structureType) || isClosedRampart(s)) { // HELP:有没有遗漏其他应该设置 noStructureCostMat 的点
structureCostMat.set(s.pos.x, s.pos.y, 255);
}
}
costMatrixCache[room.name] = {
roomName: room.name,
true: noStructureCostMat, // 对应 ignoreDestructibleStructures = true
false: structureCostMat // 对应 ignoreDestructibleStructures = false
};
let avoids = [];
if (room.controller && room.controller.owner && !room.controller.my && hostileCostMatrixClearDelay) { // 他人房间,删除costMat才能更新被拆的建筑位置
if (!(Game.time + hostileCostMatrixClearDelay in costMatrixCacheTimer)) {
costMatrixCacheTimer[Game.time + hostileCostMatrixClearDelay] = [];
}
costMatrixCacheTimer[Game.time + hostileCostMatrixClearDelay].push({
roomName: room.name,
avoids: avoids
}); // 记录清理时间
} else if (noviceWall || deployingCore || centralPortal) { // 如果遇到可能消失的挡路建筑,这3种情况下clearDelay才可能被赋值为非Infinity
if (noviceWall) { // 如果看见新手墙
let neighbors = Game.map.describeExits(room.name);
for (let direction in neighbors) {
let status = Game.map.getRoomStatus(neighbors[direction]);
if (status.status == 'closed') {
avoidRooms[neighbors[direction]] = 1;
} else if (status.status != 'normal' && status.timestamp != null) {
let estimateTickToChange = (status.timestamp - new Date().getTime()) / 10000; // 10s per tick
clearDelay = clearDelay > estimateTickToChange ? Math.ceil(estimateTickToChange) : clearDelay;
}
}
if (pos) { // 如果知道自己的pos
for (let direction in neighbors) {
if (!(neighbors[direction] in avoidRooms)) {
let exits = room.find(+direction);
if (PathFinder.search(pos, exits, { maxRooms: 1, roomCallback: () => noStructureCostMat }).incomplete) { // 此路不通
avoidRooms[neighbors[direction]] = 1;
avoids.push(neighbors[direction]);
}
}
}
}
}
//console.log(room.name + ' costMat 设置清理 ' + clearDelay);
if (!(Game.time + clearDelay in costMatrixCacheTimer)) {
costMatrixCacheTimer[Game.time + clearDelay] = [];
}
costMatrixCacheTimer[Game.time + clearDelay].push({
roomName: room.name,
avoids: avoids // 因新手墙导致的avoidRooms需要更新
}); // 记录清理时间
}
//console.log('生成costMat ' + room.name);
}
/**
* 把路径上有视野的位置的正向移动方向拿到,只有在找新路时调用,找新路时会把有视野房间都缓存进costMatrixCache
* @param {MyPath} path
*/
function generateDirectionArray(path) {
let posArray = path.posArray
let directionArray = new Array(posArray.length);
let incomplete = false;
for (let idx = 1; idx in posArray; idx++) {
if (posArray[idx - 1].roomName in costMatrixCache) { // 有costMat,是准确路径,否则需要在有视野时checkRoom()
directionArray[idx] = getDirection(posArray[idx - 1], posArray[idx]);
} else if (!incomplete) { // 记录第一个缺失准确路径的位置
incomplete = idx;
}
}
if (observers.length && incomplete) {
addObTask(path, incomplete); // 这格没有direction
}
path.directionArray = directionArray;
}
/**
* 第一次拿到该room视野,startIdx是新房中唯一有direction的位置
* @param {Room} room
* @param {MyPath} path
* @param {number} startIdx
*/
function checkRoom(room, path, startIdx) {
if (!(room.name in costMatrixCache)) {
generateCostMatrix(room, path.posArray[startIdx]);
}
let thisRoomName = room.name
/** @type {CostMatrix} */
let costMat = costMatrixCache[thisRoomName][path.ignoreStructures];
let posArray = path.posArray;
let directionArray = path.directionArray;
let i;
for (i = startIdx; i + 1 in posArray && posArray[i].roomName == thisRoomName; i++) {
if (costMat.get(posArray[i].x, posArray[i].y) == 255) { // 路上有东西挡路
return false;
}
directionArray[i + 1] = getDirection(posArray[i], posArray[i + 1]);
}
if (observers.length && i + 1 in posArray) {
while (i + 1 in posArray) {
if (!directionArray[i + 1]) {
addObTask(path, i + 1); // 这格没有direction
break;
}
i += 1;
}
}
return true;
}
/**
* 尝试对穿,有2种不可穿情况
* @param {Creep} creep
* @param {RoomPosition} pos
* @param {boolean} bypassHostileCreeps
*/
function trySwap(creep, pos, bypassHostileCreeps, ignoreCreeps) { // ERR_NOT_FOUND开销0.00063,否则开销0.0066
let obstacleCreeps = creep.room.lookForAt(LOOK_CREEPS, pos).concat(creep.room.lookForAt(LOOK_POWER_CREEPS, pos));
if (obstacleCreeps.length) {
if (!ignoreCreeps) {
return ERR_INVALID_TARGET;
}
for (let c of obstacleCreeps) {
if (c.my) {
if (c.memory.dontPullMe) { // 第1种不可穿情况:挡路的creep设置了不对穿
return ERR_INVALID_TARGET;
}
if (creepMoveCache[c.name] != Game.time && originMove.call(c, getDirection(pos, creep.pos)) == ERR_NO_BODYPART && creep.pull) {
creep.pull(c);
originMove.call(c, creep);
}
} else if (bypassHostileCreeps && (!c.room.controller || !c.room.controller.my || !c.room.controller.safeMode)) { // 第二种不可穿情况:希望绕过敌对creep
return ERR_INVALID_TARGET;
}
}
testTrySwap++;
return OK; // 或者全部操作成功
}
return ERR_NOT_FOUND // 没有creep
}
let temporalAvoidFrom, temporalAvoidTo;
function routeCallback(nextRoomName, fromRoomName) { // 避开avoidRooms设置了的
if (nextRoomName in avoidRooms) {
//console.log('Infinity at ' + nextRoomName);
return Infinity;
}
return isHighWay(nextRoomName) ? 1 : 1.15;
}
function bypassRouteCallback(nextRoomName, fromRoomName) {
if (fromRoomName == temporalAvoidFrom && nextRoomName == temporalAvoidTo) {
//console.log(`Infinity from ${fromRoomName} to ${nextRoomName}`);
return Infinity;
}
return routeCallback(nextRoomName, fromRoomName);
}
/**
* 遇到跨房寻路,先以房间为单位寻route,再寻精细的path
* @param {string} fromRoomName
* @param {string} toRoomName
* @param {boolean} bypass
*/
function findRoute(fromRoomName, toRoomName, bypass) { // TODO 以后跨shard寻路也放在这个函数里
//console.log('findRoute', fromRoomName, toRoomName, bypass);
return Game.map.findRoute(fromRoomName, toRoomName, { routeCallback: bypass ? bypassRouteCallback : routeCallback });
}
/**
* @param {RoomPosition} pos
* @param {Room} room
* @param {CostMatrix} costMat
*/
function checkTemporalAvoidExit(pos, room, costMat) { // 用于记录因creep堵路导致的房间出口临时封闭
let neighbors = Game.map.describeExits(room.name);
temporalAvoidFrom = temporalAvoidTo = ''; // 清空旧数据
for (let direction in neighbors) {
if (!(neighbors[direction] in avoidRooms)) {
for (let direction in neighbors) {
let exits = room.find(+direction);
if (PathFinder.search(pos, exits, {
maxRooms: 1,
roomCallback: () => costMat
}).incomplete) { // 此路不通
temporalAvoidFrom = room.name;
temporalAvoidTo = neighbors[direction];
}
}
}
}
}
function routeReduce(temp, item) {
temp[item.room] = 1;
return temp;
}
function bypassHostile(creep) {
return !creep.my || creep.memory.dontPullMe;
}
function bypassMy(creep) {
return creep.my && creep.memory.dontPullMe;
}
let bypassRoomName, bypassCostMat, bypassIgnoreCondition, userCostCallback, costMat, route;
function bypassRoomCallback(roomName) {
if (roomName in avoidRooms) {
return false;
}
if (roomName == bypassRoomName) { // 在findTemporalRoute函数里刚刚建立了costMatrix
costMat = bypassCostMat;
} else {
costMat = roomName in costMatrixCache ? costMatrixCache[roomName][findPathIgnoreCondition] : emptyCostMatrix;
}
if (userCostCallback) {
let resultCostMat = userCostCallback(roomName, roomName in costMatrixCache ? costMat.clone() : new PathFinder.CostMatrix);
if (resultCostMat instanceof PathFinder.CostMatrix) {
costMat = resultCostMat;
}
}
return costMat;
}
function bypassRoomCallbackWithRoute(roomName) {
if (roomName in route) {
if (roomName == bypassRoomName) { // 在findTemporalRoute函数里刚刚建立了costMatrix
costMat = bypassCostMat;
} else {
costMat = roomName in costMatrixCache ? costMatrixCache[roomName][findPathIgnoreCondition] : emptyCostMatrix;
}
if (userCostCallback) {
let resultCostMat = userCostCallback(roomName, roomName in costMatrixCache ? costMat.clone() : new PathFinder.CostMatrix);
if (resultCostMat instanceof PathFinder.CostMatrix) {
costMat = resultCostMat;
}
}
return costMat;
}
return false;
}
/**
* 影响参数:bypassHostileCreeps, ignoreRoads, ignoreDestructibleStructures, ignoreSwamps, costCallback, range, bypassRange
* 及所有PathFinder参数:plainCost, SwampCost, masOps, maxRooms, maxCost, heuristicWeight
* @param {Creep} creep
* @param {RoomPosition} toPos
* @param {MoveToOpts} ops
*/
function findTemporalPath(creep, toPos, ops) {
let nearbyCreeps;
if (ops.ignoreCreeps) { // 有ignoreCreep,只绕过无法对穿的creep
nearbyCreeps = creep.pos.findInRange(FIND_CREEPS, ops.bypassRange, {
filter: ops.bypassHostileCreeps ? bypassHostile : bypassMy
}).concat(creep.pos.findInRange(FIND_POWER_CREEPS, ops.bypassRange, {
filter: ops.bypassHostileCreeps ? bypassHostile : bypassMy
}));
} else { // 绕过所有creep
nearbyCreeps = creep.pos.findInRange(FIND_CREEPS, ops.bypassRange).concat(
creep.pos.findInRange(FIND_POWER_CREEPS, ops.bypassRange)
)
}
if (!(creep.room.name in costMatrixCache)) { // 这个房间的costMatrix已经被删了
generateCostMatrix(creep.room, creep.pos);
}
bypassIgnoreCondition = !!ops.ignoreDestructibleStructures;
/** @type {CostMatrix} */
bypassCostMat = costMatrixCache[creep.room.name][bypassIgnoreCondition].clone();
for (let c of nearbyCreeps) {
bypassCostMat.set(c.pos.x, c.pos.y, 255);
}
bypassRoomName = creep.room.name;
userCostCallback = typeof ops.costCallback == 'function' ? ops.costCallback : undefined;
/**@type {PathFinderOpts} */
let PathFinderOpts = {
maxRooms: ops.maxRooms,
maxCost: ops.maxCost,
heuristicWeight: ops.heuristicWeight || 1.2
}
if (ops.ignoreSwamps) { // HELP 这里有没有什么不增加计算量的简短写法
PathFinderOpts.plainCost = ops.plainCost;
PathFinderOpts.swampCost = ops.swampCost || 1;
} else if (ops.ignoreRoads) {
PathFinderOpts.plainCost = ops.plainCost;
PathFinderOpts.swampCost = ops.swampCost || 5;
} else {
PathFinderOpts.plainCost = ops.plainCost || 2;
PathFinderOpts.swampCost = ops.swampCost || 10;
}
if (creep.pos.roomName != toPos.roomName) { // findRoute会导致非最优path的问题
checkTemporalAvoidExit(creep.pos, creep.room, bypassCostMat); // 因为creep挡路导致的无法通行的出口
route = findRoute(creep.pos.roomName, toPos.roomName, true);
if (route == ERR_NO_PATH) {
return false;
}
PathFinderOpts.maxRooms = PathFinderOpts.maxRooms || route.length + 1;
PathFinderOpts.maxOps = ops.maxOps || 2000 + route.length ** 2 * 100; // 跨10room则有2000+10*10*100=12000
route = route.reduce(routeReduce, { [creep.pos.roomName]: 1 }); // 因为 key in Object 比 Array.includes(value) 快,但不知道值不值得reduce
PathFinderOpts.roomCallback = bypassRoomCallbackWithRoute;
} else {
PathFinderOpts.maxOps = ops.maxOps;
PathFinderOpts.roomCallback = bypassRoomCallback;
}
let result = PathFinder.search(creep.pos, { pos: toPos, range: ops.range }, PathFinderOpts).path;
if (result.length) {
let creepCache = creepPathCache[creep.name];
creepCache.path = { // 弄个新的自己走,不修改公用的缓存路,只会用于正向走所以也不需要start属性,idx属性会在startRoute中设置
end: formalize(result[result.length - 1]),
posArray: result,
ignoreStructures: !!ops.ignoreDestructibleStructures
}
generateDirectionArray(creepCache.path);
return true;
}
return false;
}
let findPathIgnoreCondition;
/**
* @param {{[roomName:string]:1}} temp
* @param {{room:string}} item
* @returns {{[roomName:string]:1}}
*/
function roomCallback(roomName) {
if (roomName in avoidRooms) {
return false;
}
costMat = roomName in costMatrixCache ? costMatrixCache[roomName][findPathIgnoreCondition] : emptyCostMatrix;
if (userCostCallback) {
let resultCostMat = userCostCallback(roomName, roomName in costMatrixCache ? costMat.clone() : new PathFinder.CostMatrix);
if (resultCostMat instanceof PathFinder.CostMatrix) {
costMat = resultCostMat;
}
}
return costMat;
}
function roomCallbackWithRoute(roomName) {
if (roomName in route) {
costMat = roomName in costMatrixCache ? costMatrixCache[roomName][findPathIgnoreCondition] : emptyCostMatrix;
//console.log('in route ' + roomName);
if (userCostCallback) {
let resultCostMat = userCostCallback(roomName, roomName in costMatrixCache ? costMat.clone() : new PathFinder.CostMatrix);
if (resultCostMat instanceof PathFinder.CostMatrix) {
costMat = resultCostMat;
}
}
return costMat;
}
//console.log('out route ' + roomName);
return false; // 不在route上的不搜索
}
/**
* 影响参数:ignoreRoads, ignoreDestructibleStructures, ignoreSwamps, costCallback, range
* 及所有PathFinder参数:plainCost, SwampCost, masOps, maxRooms, maxCost, heuristicWeight
* @param {RoomPosition} fromPos
* @param {RoomPosition} toPos
* @param {MoveToOpts} ops
*/
function findPath(fromPos, toPos, ops) {
if (!(fromPos.roomName in costMatrixCache) && fromPos.roomName in Game.rooms) { // 有视野没costMatrix
generateCostMatrix(Game.rooms[fromPos.roomName], fromPos);
}
findPathIgnoreCondition = !!ops.ignoreDestructibleStructures;
userCostCallback = typeof ops.costCallback == 'function' ? ops.costCallback : undefined;
/**@type {PathFinderOpts} */
let PathFinderOpts = {
maxRooms: ops.maxRooms,
maxCost: ops.maxCost,
heuristicWeight: ops.heuristicWeight || 1.2
}
if (ops.ignoreSwamps) { // HELP 这里有没有什么不增加计算量的简短写法
PathFinderOpts.plainCost = ops.plainCost;
PathFinderOpts.swampCost = ops.swampCost || 1;
} else if (ops.ignoreRoads) {
PathFinderOpts.plainCost = ops.plainCost;
PathFinderOpts.swampCost = ops.swampCost || 5;
} else {
PathFinderOpts.plainCost = ops.plainCost || 2;
PathFinderOpts.swampCost = ops.swampCost || 10;
}
if (fromPos.roomName != toPos.roomName) { // findRoute会导致非最优path的问题
route = findRoute(fromPos.roomName, toPos.roomName);
if (route == ERR_NO_PATH) {
return { path: [] };
}
PathFinderOpts.maxOps = ops.maxOps || 2000 + route.length ** 2 * 100; // 跨10room则有2000+10*10*50=7000
PathFinderOpts.maxRooms = PathFinderOpts.maxRooms || route.length + 1;
route = route.reduce(routeReduce, { [fromPos.roomName]: 1 }); // 因为 key in Object 比 Array.includes(value) 快,但不知道值不值得reduce
//console.log(fromPos + ' using route ' + JSON.stringify(route));
PathFinderOpts.roomCallback = roomCallbackWithRoute;
} else {
PathFinderOpts.maxOps = ops.maxOps;
PathFinderOpts.roomCallback = roomCallback;
}
return PathFinder.search(fromPos, { pos: toPos, range: ops.range }, PathFinderOpts);
}
let combinedX, combinedY;
/**
* @param {MyPath} newPath
*/
function addPathIntoCache(newPath) {
combinedX = newPath.start.x + newPath.start.y;
combinedY = newPath.end.x + newPath.end.y;
if (!(combinedX in globalPathCache)) {
globalPathCache[combinedX] = {
[combinedY]: [] // 数组里放不同ops的及其他start、end与此对称的
};
} else if (!(combinedY in globalPathCache[combinedX])) {
globalPathCache[combinedX][combinedY] = [] // 数组里放不同ops的及其他start、end与此对称的
}
globalPathCache[combinedX][combinedY].push(newPath);
}
function invalidate() {
return 0;
}
/**
* @param {MyPath} path
*/
function deletePath(path) {
if (path.start) { // 有start属性的不是临时路
let pathArray = globalPathCache[path.start.x + path.start.y][path.end.x + path.end.y];
pathArray.splice(pathArray.indexOf(path), 1);
path.posArray = path.posArray.map(invalidate);
}
}
let minX, maxX, minY, maxY;
/**
* 寻找房内缓存路径,起始位置两步限制避免复用非最优路径
* @param {RoomPosition} formalFromPos
* @param {RoomPosition} formalToPos
* @param {RoomPosition} fromPos
* @param {CreepPaths} creepCache
* @param {MoveToOpts} ops
*/
function findShortPathInCache(formalFromPos, formalToPos, fromPos, creepCache, ops) { // ops.range设置越大找的越慢
startCacheSearch = Game.cpu.getUsed();
minX = formalFromPos.x + formalFromPos.y - 2;
maxX = formalFromPos.x + formalFromPos.y + 2;
minY = formalToPos.x + formalToPos.y - 1 - ops.range;
maxY = formalToPos.x + formalToPos.y + 1 + ops.range;
for (combinedX = minX; combinedX <= maxX; combinedX++) {
if (combinedX in globalPathCache) {
for (combinedY = minY; combinedY <= maxY; combinedY++) {
if (combinedY in globalPathCache[combinedX]) {
for (let path of globalPathCache[combinedX][combinedY]) { // 这个数组应该会很短
pathCounter++;
if (isNear(path.start, formalFromPos) && isNear(fromPos, path.posArray[1]) && inRange(path.end, formalToPos, ops.range) && isSameOps(path, ops)) { // 找到路了
creepCache.path = path;
return true;
}
}
}
}
}
}
return false;
}
/**
* 寻找跨房缓存路径,允许起始位置少量的误差
* @param {RoomPosition} formalFromPos
* @param {RoomPosition} formalToPos
* @param {CreepPaths} creepCache
* @param {MoveToOpts} ops
*/
function findLongPathInCache(formalFromPos, formalToPos, creepCache, ops) { // ops.range设置越大找的越慢
startCacheSearch = Game.cpu.getUsed();
minX = formalFromPos.x + formalFromPos.y - 2;
maxX = formalFromPos.x + formalFromPos.y + 2;
minY = formalToPos.x + formalToPos.y - 1 - ops.range;
maxY = formalToPos.x + formalToPos.y + 1 + ops.range;
for (combinedX = minX; combinedX <= maxX; combinedX++) {
if (combinedX in globalPathCache) {
for (combinedY = minY; combinedY <= maxY; combinedY++) {
if (combinedY in globalPathCache[combinedX]) {
for (let path of globalPathCache[combinedX][combinedY]) { // 这个数组应该会很短
pathCounter++;
if (isNear(path.start, formalFromPos) && inRange(path.end, formalToPos, ops.range) && isSameOps(path, ops)) { // 找到路了
creepCache.path = path;
return true;
}
}
}
}
}
}
return false;
}
let startRoomName, endRoomName;
/**
* 起止点都在自己房间的路不清理
* @param {CreepPaths['name']} creepCache
*/
function setPathTimer(creepCache) {
if (pathClearDelay) {
let posArray = creepCache.path.posArray;
startRoomName = posArray[0].roomName;
endRoomName = posArray[posArray.length - 1].roomName;
if (startRoomName != endRoomName || (startRoomName in Game.rooms && Game.rooms[startRoomName].controller && !Game.rooms[startRoomName].controller.my)) { // 跨房路或者敌方房间路
if (!(Game.time + pathClearDelay in pathCacheTimer)) {
pathCacheTimer[Game.time + pathClearDelay] = [];
}
pathCacheTimer[Game.time + pathClearDelay].push(creepCache.path);
creepCache.path.lastTime = Game.time;
}
}
}
/**@type {RoomPosition[]} */
let tempArray = [];
/**
*
* @param {Creep} creep
* @param {RoomPosition} toPos
* @param {RoomPosition[]} posArray
* @param {number} startIdx
* @param {number} idxStep
* @param {PolyStyle} visualStyle
*/
function showVisual(creep, toPos, posArray, startIdx, idxStep, visualStyle) {
tempArray.length = 0;
tempArray.push(creep.pos);
let thisRoomName = creep.room.name;
_.defaults(visualStyle, defaultVisualizePathStyle);
for (let i = startIdx; i in posArray && posArray[i].roomName == thisRoomName; i += idxStep) {
tempArray.push(posArray[i]);
}
if (toPos.roomName == thisRoomName) {
tempArray.push(toPos);