1+ /*
2+ * A set of functions that makes creeps tell other creeps to get out of the way using creep memory
3+ *
4+ * call so creep reacts to being nudged
5+ * Creep.giveWay() - swaps places with creep that nudged it
6+ * Creep.giveWay(true) - moves into random available spot
7+ * Creep.giveWay({pos: controller.pos, range: 3 }) - moves into random available spot in range of target, if none are avaiable fallbacks to random spot
8+ */
9+
10+ /*
11+ * if alwaysNudge false you have to call Creep.move with additional argument -
12+ * creep.move(direction, true); - for creep to nudge other creeps,
13+ * so it's not compatible with creep.moveTo
14+ *
15+ * if alwaysNudge is true then creeps... always nudge creeps in front of them
16+ */
17+ const alwaysNudge = true ;
18+
19+ /*
20+ * some utils that I'm using
21+ */
22+ const offsetX = [ 0 , 0 , 1 , 1 , 1 , 0 , - 1 , - 1 , - 1 ] ;
23+ const offsetY = [ 0 , - 1 , - 1 , 0 , 1 , 1 , 1 , 0 , - 1 ] ;
24+ function getRandomDir ( ) {
25+ return ( Math . floor ( Math . random ( ) * 8 ) + 1 ) ;
26+ }
27+ function getOppositeDir ( dir ) {
28+ return ( ( dir + 3 ) % 8 + 1 ) ;
29+ }
30+
31+ /**
32+ * returns a weighted random direction from given position
33+ * prefers empty tiles over ones with creeps
34+ * never picks a direction that would result in hitting a wall or an obstacle structure
35+ *
36+ * @param {RoomPosition } pos
37+ */
38+ function getNudgeDirection_Random ( pos ) {
39+ const room = Game . rooms [ pos . roomName ] ;
40+ const terrain = Game . map . getRoomTerrain ( pos . roomName ) ;
41+ let totalWeight = 0 ;
42+ let dirCandidates = new Uint8Array ( 9 ) ;
43+ for ( let dir = TOP ; dir <= TOP_LEFT ; ++ dir ) {
44+ let posX = pos . x + offsetX [ dir ] ;
45+ let posY = pos . y + offsetY [ dir ] ;
46+ if ( posX < 1 || posX > 48 || posY < 1 || posY > 48 )
47+ continue ;
48+ if ( ( terrain . get ( posX , posY ) & TERRAIN_MASK_WALL ) > 0 )
49+ continue ;
50+ if ( room . lookForAt ( LOOK_STRUCTURES , posX , posY ) . find ( s => OBSTACLE_OBJECT_TYPES . includes ( s . structureType ) ) )
51+ continue ;
52+
53+ const hasCreeps = room . lookForAt ( LOOK_CREEPS , posX , posY ) . length > 0 ;
54+ const addWeight = hasCreeps ? 1 : 2 ;
55+ dirCandidates [ dir ] += addWeight ;
56+ totalWeight += dirCandidates [ dir ] ;
57+ }
58+
59+ let sum = 0 ;
60+ let rnd = _ . random ( 1 , totalWeight , false ) ;
61+ for ( let dir = TOP ; dir <= TOP_LEFT ; ++ dir ) {
62+ if ( dirCandidates [ dir ] > 0 ) {
63+ sum += dirCandidates [ dir ] ;
64+ if ( rnd <= sum ) {
65+ return dir ;
66+ }
67+ }
68+ }
69+
70+ // this should never happen, unless creep is spawned into a corner
71+ // or structure is built next to it and seals the only path out
72+ return getRandomDir ( ) ;
73+ }
74+
75+ /**
76+ * returns a weighted random direction from given position
77+ * tries to stay in targets range, if it's impossible then fallbacks to random direction
78+ * prefers empty tiles over ones with creeps
79+ * never picks a direction that would result in hitting a wall or an obstacle structure
80+ *
81+ * @param {RoomPosition } pos
82+ * @param {Object } target
83+ * @param {RoomPosition } target.pos
84+ * @param {number } target.range
85+ */
86+ function getNudgeDirection_KeepRange ( pos , target ) {
87+ const room = Game . rooms [ pos . roomName ] ;
88+ const terrain = Game . map . getRoomTerrain ( pos . roomName ) ;
89+ let keepRangeTotalWeight = 0 ;
90+ let keepRangeDirCandidates = new Uint8Array ( 9 ) ;
91+ let randomTotalWeight = 0 ;
92+ let randomDirCandidates = new Uint8Array ( 9 ) ;
93+ for ( let dir = TOP ; dir <= TOP_LEFT ; ++ dir ) {
94+ let posX = pos . x + offsetX [ dir ] ;
95+ let posY = pos . y + offsetY [ dir ] ;
96+ if ( posX < 1 || posX > 48 || posY < 1 || posY > 48 )
97+ continue ;
98+ if ( ( terrain . get ( posX , posY ) & TERRAIN_MASK_WALL ) > 0 )
99+ continue ;
100+ if ( room . lookForAt ( LOOK_STRUCTURES , posX , posY ) . find ( s => OBSTACLE_OBJECT_TYPES . includes ( s . structureType ) ) )
101+ continue ;
102+
103+ const hasCreeps = room . lookForAt ( LOOK_CREEPS , posX , posY ) . length > 0 ;
104+ const addWeight = hasCreeps ? 1 : 2 ;
105+ randomDirCandidates [ dir ] += addWeight ;
106+ if ( target . pos . inRangeTo ( posX , posY , target . range ) )
107+ keepRangeDirCandidates [ dir ] += addWeight ;
108+ keepRangeTotalWeight += keepRangeDirCandidates [ dir ] ;
109+ randomTotalWeight += randomDirCandidates [ dir ] ;
110+ }
111+
112+ const dirCandidates = keepRangeTotalWeight > 0 ? keepRangeDirCandidates : randomDirCandidates ;
113+ const totalWeight = keepRangeTotalWeight > 0 ? keepRangeTotalWeight : randomTotalWeight ;
114+ let sum = 0 ;
115+ if ( totalWeight > 0 ) {
116+ let rnd = _ . random ( 1 , totalWeight , false ) ;
117+ for ( let dir = TOP ; dir <= TOP_LEFT ; ++ dir ) {
118+ if ( dirCandidates [ dir ] > 0 ) {
119+ sum += dirCandidates [ dir ] ;
120+ if ( rnd <= sum ) {
121+ return dir ;
122+ }
123+ }
124+ }
125+ }
126+
127+ // this should never happen, unless creep is spawned into a corner
128+ // or structure is built next to it and seals the only path out
129+ return getRandomDir ( ) ;
130+ }
131+
132+ /**
133+ * a nudge
134+ *
135+ * @param {RoomPosition } pos - a nudge origin point
136+ * @param {DirectionConstant } direction
137+ */
138+ function excuseMe ( pos , direction ) {
139+ const nextX = pos . x + offsetX [ direction ] ;
140+ const nextY = pos . y + offsetY [ direction ] ;
141+ if ( nextX > 49 || nextX < 0 || nextY > 49 || nextY < 0 )
142+ return ;
143+
144+ const room = Game . rooms [ pos . roomName ] ;
145+ const creeps = room . lookForAt ( LOOK_CREEPS , nextX , nextY ) ;
146+ if ( creeps . length > 0 && creeps [ 0 ] . my )
147+ creeps [ 0 ] . memory . excuseMe = getOppositeDir ( direction ) ;
148+ const powerCreeps = room . lookForAt ( LOOK_POWER_CREEPS , nextX , nextY ) ;
149+ if ( powerCreeps . length > 0 && powerCreeps [ 0 ] . my )
150+ powerCreeps [ 0 ] . memory . excuseMe = getOppositeDir ( direction ) ;
151+ }
152+
153+ /*
154+ *
155+ */
156+ let creepsThatTriedToMove = { } ;
157+ const move = Creep . prototype . move ;
158+ Creep . prototype . move = function ( direction , nudge ) {
159+ if ( ( alwaysNudge || nudge ) && _ . isNumber ( direction ) )
160+ excuseMe ( this . pos , direction ) ;
161+ creepsThatTriedToMove [ this . name ] = this . pos ;
162+ return move . call ( this , direction ) ;
163+ } ;
164+
165+ /*
166+ * call this on creeps that should react to being nudged
167+ */
168+ function giveWay ( creep , arg ) {
169+ if ( creep . memory . excuseMe ) {
170+ if ( ! arg )
171+ creep . move ( creep . memory . excuseMe , true ) ;
172+ else if ( typeof arg === 'object' )
173+ creep . move ( getNudgeDirection_KeepRange ( creep . pos , arg ) , true ) ;
174+ else
175+ creep . move ( getNudgeDirection_Random ( creep . pos ) , true ) ;
176+ }
177+ }
178+ Creep . prototype . giveWay = function ( arg ) {
179+ giveWay ( this , arg ) ;
180+ } ;
181+ PowerCreep . prototype . giveWay = function ( arg ) {
182+ giveWay ( this , arg ) ;
183+ } ;
184+
185+ /*
186+ * clears nudges from memory of creeps that moved
187+ * call on tick start
188+ */
189+ function clearNudges ( ) {
190+ for ( let creepName in creepsThatTriedToMove ) {
191+ const creep = Game . creeps [ creepName ] ;
192+ const powerCreep = Game . powerCreeps [ creepName ] ;
193+ const prevPos = creepsThatTriedToMove [ creepName ] ;
194+ if ( ( ! creep || ! creep . pos . isEqualTo ( prevPos ) ) && ( ! powerCreep || ! powerCreep . pos . isEqualTo ( prevPos ) ) ) {
195+ const creepMemory = Memory . creeps [ creepName ] ;
196+ if ( creepMemory )
197+ creepMemory . excuseMe = undefined ;
198+ const powerCreepMemory = Memory . powerCreeps [ creepName ] ;
199+ if ( powerCreepMemory )
200+ powerCreepMemory . excuseMe = undefined ;
201+ delete creepsThatTriedToMove [ creepName ] ;
202+ }
203+ }
204+ }
205+
206+ module . exports = {
207+ clearNudges : clearNudges
208+ } ;
0 commit comments