-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcreepmover.js
225 lines (190 loc) · 8.55 KB
/
creepmover.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
const PathBuilder = require("pathbuilder");
const RouteFinder = require("routefinder");
const DEFAULT_STUCK_LIMIT = 2;
const MINIMUM_OPS = 2000; // default value for PathFinder
const OPS_PER_ROOM = 1000; // pathing a route of N rooms can take up to N CPU
// TODO:
// - don't cache path on "hot" options (e.g. avoidHostiles)
// - more options (e.g. allow to path over hostile structures only)
module.exports = class CreepMover {
constructor(creep, target, options) {
this.creep = creep;
this.target = target;
this.options = options || {};
this.routeFinder = new RouteFinder(creep.room.name, target.pos.roomName);
this.pathBuilder = this.configurePathBuilder(this.creep, this.options);
}
move() {
if(!this.target) return ERR_INVALID_TARGET;
if(this.creep.fatigue > 0) return ERR_TIRED;
let targetRange = this.options.range || this.rangeByTarget();
let target = { pos: this.target.pos, range: Math.max(1, targetRange) };
if(this.target.shard && this.target.shard !== Game.shard.name) {
this.creep.memory.destinationShard = this.target.shard;
let crossing = CreepMover.nextCrossing(this.creep.room.name);
let pos = new RoomPosition(25, 25, crossing);
if(this.creep.room.name === crossing) {
let portal = this.creep.room.find(FIND_STRUCTURES, {
filter: (s) => s.structureType === STRUCTURE_PORTAL && s.destination.shard === this.target.shard
})[0];
if(portal) pos = portal.pos;
}
target = { pos: pos, range: 0 };
this.routeFinder.destinationRoom = crossing;
}
if(this.creep.pos.getRangeTo(target) <= targetRange) return OK;
let data = this.deserializeData();
if(!CreepMover.samePos(data.target, target.pos)) {
data.path = null;
data.stuck = 0;
} else if(CreepMover.samePos(this.creep.pos, data.lastPos)) {
data.stuck += 1;
} else {
if(data.path) {
let expectedPos = CreepMover.nextCoord(data.lastPos, this.nextDir(data));
if(CreepMover.sameCoord(this.creep.pos, expectedPos)) {
data.path = data.path.substr(1);
if(data.path.length === 0) data.path = null;
} else if(CreepMover.sameCoord(this.creep.pos, CreepMover.coordInDirection(data.lastPos, this.nextDir(data)))) {
// nextCoord and coordInDirection are different:
// creep moved correctly into a room exit but got back
// either due to fatigue or because of a blocking creep on other side
// increase stuck count in case of block, but return immediately,
// waiting to be back on correct side of the exit
if(this.creep.memory.debugPath) {
this.log("Creep moved backwards through exit portal. Waiting another tick.");
}
data.stuck += 1;
return OK;
} else {
this.log("Unexpected movement. Expected: " + expectedPos.x + "|" + expectedPos.y + " Got: " + this.creep.pos.x + "|" + this.creep.pos.y);
data.path = null;
}
}
data.stuck = 0;
}
if(data.stuck >= (this.options.stuckLimit || DEFAULT_STUCK_LIMIT)) {
data.path = null;
this.pathBuilder.avoidCreeps = true;
}
if(!data.path) {
data.target = target.pos;
if(this.creep.pos.isNearTo(target)) {
data.path = this.creep.pos.getDirectionTo(target).toString();
} else {
let allowedRooms = this.routeFinder.findRoute();
this.pathBuilder.allowedRooms = allowedRooms;
let options = {
plainCost: this.pathBuilder.plainCost,
swampCost: this.pathBuilder.swampCost,
roomCallback: this.pathBuilder.getRoomCallback(),
maxOps: Math.max(MINIMUM_OPS, OPS_PER_ROOM * allowedRooms.length)
};
options = Object.assign({}, this.options, options);
let result = PathFinder.search(this.creep.pos, target, options);
var failsafe = 0
//while(result.incomplete || failsafe >= 2){
// result = PathFinder.search(this.creep.pos, target, options);
// failsafe++
//}
if(result.incomplete) {
this.log("Could not find complete path from " + this.creep.pos + " to " + target.pos + ".");
result = PathFinder.search(this.creep.pos, target, options);
}
data.path = CreepMover.serializePath(this.creep.pos, result.path);
}
}
this.serializeData(data);
return this.creep.move(this.nextDir(data));
}
configurePathBuilder(creep, builderOptions) {
let builder = new PathBuilder();
if(creep.memory.debugPath) builder.debugCosts = true;
if(builderOptions.avoidHostiles) builder.avoidHostiles = true;
if(builderOptions.preferRoads === false) builder.preferRoads = false;
return builder;
}
rangeByTarget() {
if(this.target.structureType && OBSTACLE_OBJECT_TYPES.includes(this.target.structureType)) {
return 1;
} else if(Game.map.getRoomTerrain(this.target.pos.roomName).get(this.target.pos.x, this.target.pos.y) === TERRAIN_MASK_WALL) {
return 1;
}
return 0;
}
deserializeData() {
let data = this.creep.memory._goto;
if(!data) {
return {
lastPos: null,
target: null,
stuck: 0,
path: null
};
}
if(data.lastPos) data.lastPos = new RoomPosition(data.lastPos.x, data.lastPos.y, this.creep.room.name);
if(data.target) data.target = new RoomPosition(data.target.x, data.target.y, data.target.roomName);
return data;
}
serializeData(data) {
this.creep.memory._goto = {
lastPos: { x: this.creep.pos.x, y: this.creep.pos.y },
target: { x: data.target.x, y: data.target.y, roomName: data.target.roomName },
stuck: data.stuck,
path: data.path
};
}
nextDir(data) {
return parseInt(data.path[0], 10);
}
log(message) {
console.log("CreepMover (" + this.creep.name + "): " + message);
}
static samePos(posA, posB) {
if(!!posA !== !!posB) return false;
return posA.roomName === posB.roomName && posA.x === posB.x && posA.y === posB.y;
}
static sameCoord(posA, posB) {
if(!!posA !== !!posB) return false;
return posA.x === posB.x && posA.y === posB.y;
}
// returns coordinate in given direction from origin
static coordInDirection(origin, direction) {
let offsetX = [0, 0, 1, 1, 1, 0, -1, -1, -1];
let offsetY = [0, -1, -1, 0, 1, 1, 1, 0, -1];
let x = origin.x + offsetX[direction];
let y = origin.y + offsetY[direction];
return { x: x, y: y };
}
// returns coordinate that creep will be next tick when
// moving into given direction (considering room exits)
static nextCoord(origin, direction) {
let result = CreepMover.coordInDirection(origin, direction);
// correctly predict position when switching room
if(result.x === 0) result.x = 49;
else if(result.x === 49) result.x = 0;
else if(result.y === 0) result.y = 49;
else if(result.y === 49) result.y = 0;
return result;
}
// Taken from Traveler (https://github.com/bonzaiferroni/Traveler)
static serializePath(startPos, path) {
let serializedPath = "";
let lastPosition = startPos;
for (let position of path) {
if (position.roomName === lastPosition.roomName) {
serializedPath += lastPosition.getDirectionTo(position);
}
lastPosition = position;
}
return serializedPath;
}
static nextCrossing(roomName) {
let match = roomName.match(/^([WE])([0-9]+)([NS])([0-9]+)$/);
let longitude = Math.round(parseInt(match[2]) / 10) * 10;
let latitude = Math.round(parseInt(match[4]) / 10) * 10;
return match[1] + longitude + match[3] + latitude;
}
}
const profiler = require("screeps-profiler");
profiler.registerClass(module.exports, 'CreepMover');