PageSourceSearch

https://evilmonke.github.io/games/n-gon/js/mob.js

js evilmonke.github.io collected 2026-10-03 09:10:48 UTC 77,456 bytes, 1,470 lines download raw bytes

1//create array of mobs
2let mob = [];
3//method to populate the array above
4const mobs = {
5    loop() {
6        let i = mob.length;
7        while (i--) {
8            if (mob[i].alive) {
9                mob[i].do();
10            } else {
11                mob[i].replace(i); //removing mob and replace with body, this is done here to avoid an array index bug with drawing I think
12            }
13        }
14    },
15    draw() {
16        ctx.lineWidth = 2;
17        let i = mob.length;
18        while (i--) {
19            ctx.beginPath();
20            const vertices = mob[i].vertices;
21            ctx.moveTo(vertices[0].x, vertices[0].y);
22            for (let j = 1, len = vertices.length; j < len; ++j) ctx.lineTo(vertices[j].x, vertices[j].y);
23            ctx.lineTo(vertices[0].x, vertices[0].y);
24            ctx.fillStyle = mob[i].fill;
25            ctx.strokeStyle = mob[i].stroke;
26            ctx.fill();
27            ctx.stroke();
28        }
29    },
30    healthBar() {
31        for (let i = 0, len = mob.length; i < len; i++) {
32            if (mob[i].seePlayer.recall && mob[i].showHealthBar) {
33                const h = mob[i].radius * 0.3;
34                const w = mob[i].radius * 2;
35                const x = mob[i].position.x - w / 2;
36                const y = mob[i].position.y - w * 0.7;
37                ctx.fillStyle = "rgba(100, 100, 100, 0.3)";
38                ctx.fillRect(x, y, w, h);
39                ctx.fillStyle = "rgba(255,0,0,0.7)";
40                ctx.fillRect(x, y, w * mob[i].health, h);
41            }
42        }
43    },
44    statusSlow(who, cycles = 60) {
45        applySlow(who)
46        //look for mobs near the target
47        if (tech.isAoESlow) {
48            const range2 = (200 + 170 * Math.random()) ** 2
49            for (let i = 0, len = mob.length; i < len; i++) {
50                if (who !== mob[i] && Vector.magnitudeSquared(Vector.sub(who.position, mob[i].position)) < range2 + mob[i].radius) applySlow(mob[i])
51            }
52            simulation.drawList.push({
53                x: who.position.x,
54                y: who.position.y,
55                radius: Math.sqrt(range2),
56                color: "rgba(0,100,255,0.05)",
57                time: simulation.drawTime
58            });
59        }
60
61        function applySlow(whom) {
62            if (!whom.shield && !whom.isShielded && whom.alive) {
63                if (tech.isIceMaxHealthLoss && whom.health > 0.65 && whom.damageReduction > 0) whom.health = 0.66
64                if (tech.isIceKill && whom.health < 0.34 && whom.damageReduction > 0 && whom.alive) {
65                    // whom.death();
66                    whom.damage(Infinity);
67                    simulation.drawList.push({
68                        x: whom.position.x,
69                        y: whom.position.y,
70                        radius: whom.radius * 1.2,
71                        color: "rgb(0,100,255)",
72                        time: 8
73                    });
74                    simulation.drawList.push({
75                        x: whom.position.x,
76                        y: whom.position.y,
77                        radius: whom.radius * 0.7,
78                        color: "rgb(0,100,255)",
79                        time: 12
80                    });
81                    simulation.drawList.push({
82                        x: whom.position.x,
83                        y: whom.position.y,
84                        radius: whom.radius * 0.4,
85                        color: "rgb(0,100,255)",
86                        time: 16
87                    });
88                }
89                if (whom.isBoss) cycles = Math.floor(cycles * 0.25)
90                let i = whom.status.length
91                while (i--) {
92                    if (whom.status[i].type === "slow") whom.status.splice(i, 1); //remove other "slow" effects on this mob
93                }
94                whom.isSlowed = true;
95                whom.status.push({
96                    effect() {
97                        if (whom.speed > 1) {
98                            const drag = 0.94
99                            Matter.Body.setVelocity(whom, {
100                                x: whom.velocity.x * drag,
101                                y: whom.velocity.y * drag
102                            });
103                        }
104                        Matter.Body.setAngularVelocity(whom, 0);
105                        ctx.beginPath();
106                        ctx.moveTo(whom.vertices[0].x, whom.vertices[0].y);
107                        for (let j = 1, len = whom.vertices.length; j < len; ++j) {
108                            ctx.lineTo(whom.vertices[j].x, whom.vertices[j].y);
109                        }
110                        ctx.lineTo(whom.vertices[0].x, whom.vertices[0].y);
111                        ctx.strokeStyle = "rgba(0,100,255,0.8)";
112                        ctx.lineWidth = 15;
113                        ctx.stroke();
114                        ctx.fillStyle = whom.fill
115                        ctx.fill();
116                    },
117                    endEffect() {
118                        //check to see if there are not other freeze effects?
119                        whom.isSlowed = false;
120                    },
121                    type: "slow",
122                    endCycle: simulation.cycle + cycles,
123                })
124            }
125        }
126    },
127    statusStun(who, cycles = 180) {
128        if (!who.shield && !who.isShielded) {
129            if (who.speed > 3) {
130                Matter.Body.setVelocity(who, {
131                    x: who.velocity.x * 0.8,
132                    y: who.velocity.y * 0.8
133                });
134            }
135            Matter.Body.setAngularVelocity(who, who.angularVelocity * 0.8);
136            //remove other "stun" effects on this mob
137            let i = who.status.length
138            while (i--) {
139                if (who.status[i].type === "stun") who.status.splice(i, 1);
140            }
141            who.isStunned = true;
142            who.status.push({
143                effect() {
144                    if (who.memory !== Infinity) {
145                        who.seePlayer.yes = false;
146                        who.seePlayer.recall = 0;
147                        who.seePlayer.position = {
148                            x: who.position.x + 100 * (Math.random() - 0.5),
149                            y: who.position.y + 100 * (Math.random() - 0.5)
150                        }
151                    } else {
152                        Matter.Body.setVelocity(who, {
153                            x: who.velocity.x * 0.6,
154                            y: who.velocity.y * 0.6
155                        });
156                    }
157                    if (who.velocity.y < 2) who.force.y += who.mass * 0.0004 //extra gravity
158
159                    //draw health bar
160                    const h = who.radius * 0.3;
161                    const w = who.radius * 2;
162                    const x = who.position.x - w / 2;
163                    const y = who.position.y - w * 0.7;
164                    ctx.fillStyle = "rgba(100, 100, 100, 0.3)";
165                    ctx.fillRect(x, y, w, h);
166                    ctx.fillStyle = `rgba(${Math.floor(255 * Math.random())},${Math.floor(255 * Math.random())},${Math.floor(255 * Math.random())},0.5)`
167                    ctx.fillRect(x, y, w * who.health, h);
168
169                    //draw fill inside mob
170                    ctx.beginPath();
171                    ctx.moveTo(who.vertices[0].x, who.vertices[0].y);
172                    for (let j = 1, len = who.vertices.length; j < len; ++j) {
173                        ctx.lineTo(who.vertices[j].x, who.vertices[j].y);
174                    }
175                    ctx.lineTo(who.vertices[0].x, who.vertices[0].y);
176                    ctx.fill();
177                },
178                endEffect() {
179                    who.isStunned = false
180                },
181                type: "stun",
182                endCycle: simulation.cycle + cycles * (who.isBoss ? 0.2 : 1),
183            })
184        }
185    },
186    statusDoT(who, tickDamage, cycles = 180) {
187        if (!who.isShielded && who.alive && who.damageReduction > 0) {
188            who.status.push({
189                effect() {
190                    if ((simulation.cycle - this.startCycle) % 30 === 0) {
191                        let dmg = m.dmgScale * this.dmg * tech.radioactiveDamage
192                        who.damage(dmg);
193                        if (who.damageReduction) {
194                            simulation.drawList.push({ //add dmg to draw queue
195                                x: who.position.x + (Math.random() - 0.5) * who.radius * 0.5,
196                                y: who.position.y + (Math.random() - 0.5) * who.radius * 0.5,
197                                radius: Math.log(dmg + 1.1) * 40 * who.damageReduction + 3,
198                                color: "rgba(0,80,80,0.9)",
199                                time: simulation.drawTime
200                            });
201                        }
202                    }
203                },
204                endEffect() { },
205                dmg: tickDamage,
206                type: "dot",
207                endCycle: simulation.cycle + cycles,
208                startCycle: simulation.cycle + 29 //makes sure it doesn't tick on first application
209            })
210        }
211    },
212    // statusBurn(who, tickDamage, cycles = 90 + Math.floor(90 * Math.random())) {
213    //   if (!who.isShielded) {
214    //     //remove other "burn" effects on this mob
215    //     let i = who.status.length
216    //     while (i--) {
217    //       if (who.status[i].type === "burn") who.status.splice(i, 1);
218    //     }
219    //     who.status.push({
220    //       effect() {
221    //         if ((simulation.cycle - this.startCycle) % 15 === 0) {
222    //           let dmg = m.dmgScale * tickDamage * 0.5 * (1 + Math.random())
223    //           who.damage(dmg);
224    //           simulation.drawList.push({ //add dmg to draw queue
225    //             x: who.position.x,
226    //             y: who.position.y,
227    //             radius: Math.log(2 * dmg + 1.1) * 40,
228    //             color: `rgba(255,${Math.floor(200*Math.random())},0,0.9)`,
229    //             time: simulation.drawTime
230    //           });
231    //         }
232    //       },
233    //       type: "burn",
234    //       endCycle: simulation.cycle + cycles,
235    //       startCycle: simulation.cycle
236    //     })
237    //   }
238    // },
239    deathCount: 0,
240    mobSpawnWithHealth: 1,
241    setMobSpawnHealth() {
242        mobs.mobSpawnWithHealth = 0.88 ** (tech.mobSpawnWithHealth) //+ (m.fieldMode === 0 || m.fieldMode === 7) * m.coupling
243    },
244    //**********************************************************************************************
245    //**********************************************************************************************
246    spawn(xPos, yPos, sides, radius, color) {
247        let i = mob.length;
248        mob[i] = Matter.Bodies.polygon(xPos, yPos, sides, radius, {
249            //inertia: Infinity, //prevents rotation
250            mob: true,
251            density: 0.001,
252            //friction: 0,
253            frictionAir: 0.005,
254            //frictionStatic: 0,
255            restitution: 0.5,
256            collisionFilter: {
257                group: 0,
258                category: cat.mob,
259                mask: cat.player | cat.map | cat.body | cat.bullet | cat.mob
260            },
261            onHit: undefined,
262            alive: true,
263            index: i,
264            health: mobs.mobSpawnWithHealth,
265            showHealthBar: true,
266            accelMag: 0.001 * simulation.accelScale,
267            cd: 0, //game cycle when cooldown will be over
268            delay: 60, //static: time between cooldowns
269            fill: color,
270            stroke: "#000",
271            seePlayer: {
272                yes: false,
273                recall: 0,
274                position: {
275                    x: xPos,
276                    y: yPos
277                }
278            },
279            radius: radius,
280            spawnPos: {
281                x: xPos,
282                y: yPos
283            },
284            status: [], // [ { effect(), endCycle } ]
285            checkStatus() {
286                let j = this.status.length;
287                while (j--) {
288                    this.status[j].effect();
289                    if (this.status[j].endCycle < simulation.cycle) {
290                        this.status[j].endEffect();
291                        this.status.splice(j, 1);
292                    }
293                }
294            },
295            isSlowed: false,
296            isStunned: false,
297            seeAtDistance2: Infinity, //sqrt(4000000) = 2000 = max seeing range
298            distanceToPlayer() {
299                const dx = this.position.x - player.position.x;
300                const dy = this.position.y - player.position.y;
301                return Math.sqrt(dx * dx + dy * dy);
302            },
303            distanceToPlayer2() {
304                const dx = this.position.x - player.position.x;
305                const dy = this.position.y - player.position.y;
306                return dx * dx + dy * dy;
307            },
308            gravity() {
309                this.force.y += this.mass * this.g;
310            },
311            seePlayerFreq: Math.floor(30 + 30 * Math.random()), //how often NPC checks to see where player is, lower numbers have better vision
312            foundPlayer() {
313                this.locatePlayer();
314                if (!this.seePlayer.yes) {
315                    this.alertNearByMobs();
316                    this.seePlayer.yes = true;
317                }
318            },
319            lostPlayer() {
320                this.seePlayer.yes = false;
321                this.seePlayer.recall -= this.seePlayerFreq;
322                if (this.seePlayer.recall < 0) this.seePlayer.recall = 0;
323            },
324            memory: 120, //default time to remember player's location
325            locatePlayer() { // updates mob's memory of player location
326                this.seePlayer.recall = this.memory + Math.round(this.memory * Math.random()); //cycles before mob falls a sleep
327                this.seePlayer.position.x = player.position.x;
328                this.seePlayer.position.y = player.position.y;
329            },
330            alertNearByMobs() {
331                //this.alertRange2 is set at the very bottom of this mobs, after mob is made
332                for (let i = 0; i < mob.length; i++) {
333                    if (!mob[i].seePlayer.recall && Vector.magnitudeSquared(Vector.sub(this.position, mob[i].position)) < this.alertRange2) {
334                        mob[i].locatePlayer();
335                    }
336                }
337            },
338            alwaysSeePlayer() {
339                if (!m.isCloak) {
340                    this.seePlayer.recall = true;
341                    this.seePlayer.position.x = player.position.x;
342                    this.seePlayer.position.y = player.position.y;
343                }
344            },
345            seePlayerByHistory(depth = 30) { //depth max 60?  limit of history
346                if (!(simulation.cycle % this.seePlayerFreq)) {
347                    if (Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 && !m.isCloak) {
348                        this.foundPlayer();
349                    } else if (this.seePlayer.recall) {
350                        this.lostPlayer();
351                        if (!m.isCloak) {
352                            for (let i = 0; i < depth; i++) { //if lost player lock onto a player location in history
353                                let history = m.history[(m.cycle - 10 * i) % 600]
354                                if (Matter.Query.ray(map, this.position, history.position).length === 0) {
355                                    this.seePlayer.recall = this.memory + Math.round(this.memory * Math.random()); //cycles before mob falls a sleep
356                                    this.seePlayer.position.x = history.position.x;
357                                    this.seePlayer.position.y = history.position.y;
358                                    this.seePlayer.yes = true;
359                                    //draw the history location found for testing purposes
360                                    // ctx.beginPath();
361                                    // ctx.moveTo(this.position.x, this.position.y);
362                                    // ctx.lineTo(history.position.x, history.position.y);
363                                    // ctx.lineWidth = 5;
364                                    // ctx.strokeStyle = "#000";
365                                    // ctx.stroke();
366                                    break
367                                }
368                            }
369                        }
370                    }
371                }
372            },
373            seePlayerCheck() {
374                if (!(simulation.cycle % this.seePlayerFreq)) {
375                    if (
376                        this.distanceToPlayer2() < this.seeAtDistance2 &&
377                        Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 &&
378                        // Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0 &&
379                        !m.isCloak
380                    ) {
381                        this.foundPlayer();
382                    } else if (this.seePlayer.recall) {
383                        this.lostPlayer();
384                    }
385                }
386            },
387            seePlayerCheckByDistance() {
388                if (!(simulation.cycle % this.seePlayerFreq)) {
389                    if (this.distanceToPlayer2() < this.seeAtDistance2 && !m.isCloak) {
390                        this.foundPlayer();
391                    } else if (this.seePlayer.recall) {
392                        this.lostPlayer();
393                    }
394                }
395            },
396            seePlayerByDistOrLOS() {
397                if (!(simulation.cycle % this.seePlayerFreq)) {
398                    if (
399                        (this.distanceToPlayer2() < this.seeAtDistance2 || (Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0)) && //&& Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0
400                        !m.isCloak
401                    ) {
402                        this.foundPlayer();
403                    } else if (this.seePlayer.recall) {
404                        this.lostPlayer();
405                    }
406                }
407            },
408            isLookingAtPlayer(threshold) {
409                const diff = Vector.normalise(Vector.sub(player.position, this.position));
410                //make a vector for the mob's direction of length 1
411                const dir = {
412                    x: Math.cos(this.angle),
413                    y: Math.sin(this.angle)
414                };
415                //the dot product of diff and dir will return how much over lap between the vectors
416                const dot = Vector.dot(dir, diff);
417                // console.log(Math.cos(dot)*180/Math.PI)
418                if (dot > threshold) {
419                    return true;
420                } else {
421                    return false;
422                }
423            },
424            lookRange: 0.2 + Math.random() * 0.2,
425            lookTorque: 0.0000004 * (Math.random() > 0.5 ? -1 : 1),
426            seePlayerByLookingAt() {
427                if (!(simulation.cycle % this.seePlayerFreq) && (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange))) {
428                    if (
429                        this.distanceToPlayer2() < this.seeAtDistance2 &&
430                        Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 &&
431                        // Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0 &&
432                        !m.isCloak
433                    ) {
434                        this.foundPlayer();
435                    } else if (this.seePlayer.recall) {
436                        this.lostPlayer();
437                    }
438                }
439                //if you don't recall player location rotate and draw to show where you are looking
440                if (!this.seePlayer.recall) {
441                    this.torque = this.lookTorque * this.inertia;
442                    //draw
443                    const range = Math.PI * this.lookRange;
444                    ctx.beginPath();
445                    ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range);
446                    ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true);
447                    ctx.fillStyle = "rgba(0,0,0,0.07)";
448                    ctx.fill();
449                }
450            },
451            playerPosRandomY() {
452                return {
453                    x: player.position.x, // + (Math.random() - 0.5) * 50,
454                    y: player.position.y + (Math.random() - 0.5) * 110
455                };
456            },
457            // hacked() { //set this.hackedTarget variable before running this method
458            //   //find a new target
459            //   if (!(simulation.cycle % this.seePlayerFreq)) {
460            //     this.hackedTarget = null
461            //     for (let i = 0, len = mob.length; i < len; i++) {
462            //       if (mob[i] !== this) {
463            //         // const DIST = Vector.magnitude(Vector.sub(this.position, mob[j]));
464            //         if (Matter.Query.ray(map, this.position, mob[i].position).length === 0 &&
465            //           Matter.Query.ray(body, this.position, mob[i].position).length === 0) {
466            //           this.hackedTarget = mob[i]
467            //         }
468            //       }
469            //     }
470            //   }
471            //   //acceleration towards targets
472            //   if (this.hackedTarget) {
473            //     this.force = Vector.mult(Vector.normalise(Vector.sub(this.hackedTarget.position, this.position)), this.mass * 0.0015)
474            //   }
475            // },
476            harmZone() {
477                if (this.seePlayer.yes) {
478                    ctx.setLineDash([125 * Math.random(), 125 * Math.random()]);
479                    // ctx.lineDashOffset = 6*(simulation.cycle % 215);
480                    if (this.distanceToPlayer() < this.laserRange) {
481                        if (m.immuneCycle < m.cycle) {
482                            m.damage(0.0003 * simulation.dmgScale);
483                            if (m.energy > 0.1) m.energy -= 0.003
484                        }
485                        ctx.beginPath();
486                        ctx.moveTo(this.position.x, this.position.y);
487                        ctx.lineTo(m.pos.x, m.pos.y);
488                        ctx.lineTo(m.pos.x + (Math.random() - 0.5) * 3000, m.pos.y + (Math.random() - 0.5) * 3000);
489                        ctx.lineWidth = 2;
490                        ctx.strokeStyle = "rgb(255,0,170)";
491                        ctx.stroke();
492
493                        ctx.beginPath();
494                        ctx.arc(m.pos.x, m.pos.y, 40, 0, 2 * Math.PI);
495                        ctx.fillStyle = "rgba(255,0,170,0.15)";
496                        ctx.fill();
497                    }
498                    ctx.beginPath();
499                    ctx.arc(this.position.x, this.position.y, this.laserRange * 0.9, 0, 2 * Math.PI);
500                    ctx.strokeStyle = "rgba(255,0,170,0.5)";
501                    ctx.lineWidth = 1;
502                    ctx.stroke();
503                    ctx.setLineDash([]);
504                    ctx.fillStyle = "rgba(255,0,170,0.03)";
505                    ctx.fill();
506                }
507            },
508            laser() {
509                const vertexCollision = function (v1, v1End, domain) {
510                    for (let i = 0; i < domain.length; ++i) {
511                        let vertices = domain[i].vertices;
512                        const len = vertices.length - 1;
513                        for (let j = 0; j < len; j++) {
514                            results = simulation.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
515                            if (results.onLine1 && results.onLine2) {
516                                const dx = v1.x - results.x;
517                                const dy = v1.y - results.y;
518                                const dist2 = dx * dx + dy * dy;
519                                if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
520                                    best = {
521                                        x: results.x,
522                                        y: results.y,
523                                        dist2: dist2,
524                                        who: domain[i],
525                                        v1: vertices[j],
526                                        v2: vertices[j + 1]
527                                    };
528                                }
529                            }
530                        }
531                        results = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
532                        if (results.onLine1 && results.onLine2) {
533                            const dx = v1.x - results.x;
534                            const dy = v1.y - results.y;
535                            const dist2 = dx * dx + dy * dy;
536                            if (dist2 < best.dist2) {
537                                best = {
538                                    x: results.x,
539                                    y: results.y,
540                                    dist2: dist2,
541                                    who: domain[i],
542                                    v1: vertices[0],
543                                    v2: vertices[len]
544                                };
545                            }
546                        }
547                    }
548                };
549                if (this.seePlayer.recall && !this.isSlowed) {
550                    const seeRange = 2500;
551                    best = {
552                        x: null,
553                        y: null,
554                        dist2: Infinity,
555                        who: null,
556                        v1: null,
557                        v2: null
558                    };
559                    const look = {
560                        x: this.position.x + seeRange * Math.cos(this.angle),
561                        y: this.position.y + seeRange * Math.sin(this.angle)
562                    };
563                    vertexCollision(this.position, look, map);
564                    vertexCollision(this.position, look, body);
565                    if (!m.isCloak) vertexCollision(this.position, look, [player]);
566                    // hitting player
567                    if (best.who === player) {
568                        if (m.immuneCycle < m.cycle) {
569                            const dmg = 0.0014 * simulation.dmgScale;
570                            m.damage(dmg);
571                            ctx.fillStyle = "#f00"; //draw damage
572                            ctx.beginPath();
573                            ctx.arc(best.x, best.y, dmg * 10000, 0, 2 * Math.PI);
574                            ctx.fill();
575                        }
576                    }
577                    //draw beam
578                    if (best.dist2 === Infinity) {
579                        best = look;
580                    }
581                    ctx.beginPath();
582                    ctx.moveTo(this.position.x, this.position.y);
583                    ctx.lineTo(best.x, best.y);
584                    ctx.strokeStyle = "#f00"; // Purple path
585                    ctx.lineWidth = 1;
586                    ctx.setLineDash([50 + 120 * Math.random(), 50 * Math.random()]);
587                    ctx.stroke(); // Draw it
588                    ctx.setLineDash([]);
589                }
590            },
591            wing(a, radius = 250, ellipticity = 0.4, dmg = 0.0006) {
592                const minorRadius = radius * ellipticity
593                const perp = { x: Math.cos(a), y: Math.sin(a) } //
594                const where = Vector.add(this.position, Vector.mult(perp, radius + 0.8 * this.radius))
595
596                ctx.beginPath();
597                ctx.ellipse(where.x, where.y, radius, minorRadius, a, 0, 2 * Math.PI)
598                ctx.fill();
599
600                //check for wing -> player damage
601                const hitPlayer = Matter.Query.ray([player], this.position, Vector.add(this.position, Vector.mult(perp, radius * 2.05)), minorRadius)
602                if (hitPlayer.length && m.immuneCycle < m.cycle) {
603                    m.damage(dmg * simulation.dmgScale);
604                }
605            },
606            searchSpring() {
607                //draw the two dots on the end of the springs
608                ctx.beginPath();
609                ctx.arc(this.cons.pointA.x, this.cons.pointA.y, 6, 0, 2 * Math.PI);
610                ctx.arc(this.cons2.pointA.x, this.cons2.pointA.y, 6, 0, 2 * Math.PI);
611                ctx.fillStyle = "#222";
612                ctx.fill();
613
614                if (!(simulation.cycle % this.seePlayerFreq)) {
615                    if (
616                        (this.seePlayer.recall || this.isLookingAtPlayer(this.lookRange)) &&
617                        this.distanceToPlayer2() < this.seeAtDistance2 &&
618                        Matter.Query.ray(map, this.position, player.position).length === 0 &&
619                        Matter.Query.ray(body, this.position, player.position).length === 0 &&
620                        !m.isCloak
621                    ) {
622                        this.foundPlayer();
623                    } else if (this.seePlayer.recall) {
624                        this.lostPlayer();
625                    }
626                }
627            },
628            springAttack() {
629                // set new values of the ends of the spring constraints
630                const stepRange = 600
631                if (this.seePlayer.recall && Matter.Query.ray(map, this.position, this.seePlayer.position).length === 0) {
632                    if (!(simulation.cycle % (this.seePlayerFreq * 2))) {
633                        const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position))
634                        const goal = Vector.add(this.position, Vector.mult(unit, stepRange))
635                        this.springTarget.x = goal.x;
636                        this.springTarget.y = goal.y;
637                        // this.springTarget.x = this.seePlayer.position.x;
638                        // this.springTarget.y = this.seePlayer.position.y;
639                        this.cons.length = -200;
640                        this.cons2.length = 100 + 1.5 * this.radius;
641                    } else if (!(simulation.cycle % this.seePlayerFreq)) {
642                        const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position))
643                        const goal = Vector.add(this.position, Vector.mult(unit, stepRange))
644                        this.springTarget2.x = goal.x;
645                        this.springTarget2.y = goal.y;
646                        // this.springTarget2.x = this.seePlayer.position.x;
647                        // this.springTarget2.y = this.seePlayer.position.y;
648                        this.cons.length = 100 + 1.5 * this.radius;
649                        this.cons2.length = -200;
650                    }
651                } else {
652                    this.torque = this.lookTorque * this.inertia;
653                    //draw looking around arcs
654                    const range = Math.PI * this.lookRange;
655                    ctx.beginPath();
656                    ctx.arc(this.position.x, this.position.y, this.radius * 2.5, this.angle - range, this.angle + range);
657                    ctx.arc(this.position.x, this.position.y, this.radius * 1.4, this.angle + range, this.angle - range, true);
658                    ctx.fillStyle = "rgba(0,0,0,0.07)";
659                    ctx.fill();
660                    //spring to random place on map
661                    const vertexCollision = function (v1, v1End, domain) {
662                        for (let i = 0; i < domain.length; ++i) {
663                            let vertices = domain[i].vertices;
664                            const len = vertices.length - 1;
665                            for (let j = 0; j < len; j++) {
666                                results = simulation.checkLineIntersection(v1, v1End, vertices[j], vertices[j + 1]);
667                                if (results.onLine1 && results.onLine2) {
668                                    const dx = v1.x - results.x;
669                                    const dy = v1.y - results.y;
670                                    const dist2 = dx * dx + dy * dy;
671                                    if (dist2 < best.dist2 && (!domain[i].mob || domain[i].alive)) {
672                                        best = {
673                                            x: results.x,
674                                            y: results.y,
675                                            dist2: dist2,
676                                            who: domain[i],
677                                            v1: vertices[j],
678                                            v2: vertices[j + 1]
679                                        };
680                                    }
681                                }
682                            }
683                            results = simulation.checkLineIntersection(v1, v1End, vertices[0], vertices[len]);
684                            if (results.onLine1 && results.onLine2) {
685                                const dx = v1.x - results.x;
686                                const dy = v1.y - results.y;
687                                const dist2 = dx * dx + dy * dy;
688                                if (dist2 < best.dist2) {
689                                    best = {
690                                        x: results.x,
691                                        y: results.y,
692                                        dist2: dist2,
693                                        who: domain[i],
694                                        v1: vertices[0],
695                                        v2: vertices[len]
696                                    };
697                                }
698                            }
699                        }
700                    };
701                    //move to a random location
702                    if (!(simulation.cycle % (this.seePlayerFreq * 4))) {
703                        best = {
704                            x: null,
705                            y: null,
706                            dist2: Infinity,
707                            who: null,
708                            v1: null,
709                            v2: null
710                        };
711                        const seeRange = 3000;
712                        const look = {
713                            x: this.position.x + seeRange * Math.cos(this.angle),
714                            y: this.position.y + seeRange * Math.sin(this.angle)
715                        };
716                        vertexCollision(this.position, look, map);
717                        vertexCollision(this.position, look, body);
718                        if (best.dist2 != Infinity) {
719                            if (Math.random() > 0.5) {
720                                this.springTarget.x = best.x;
721                                this.springTarget.y = best.y;
722                                this.cons.length = 100 + 1.5 * this.radius;
723                                this.cons2.length = 100 + 1.5 * this.radius;
724                            } else {
725                                this.springTarget2.x = best.x;
726                                this.springTarget2.y = best.y;
727                                this.cons.length = 100 + 1.5 * this.radius;
728                                this.cons2.length = 100 + 1.5 * this.radius;
729                            }
730
731
732
733                            // if (!(simulation.cycle % (this.seePlayerFreq * 2))) {
734                            //     const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position))
735                            //     const goal = Vector.add(this.position, Vector.mult(unit, stepRange))
736                            //     this.springTarget.x = goal.x;
737                            //     this.springTarget.y = goal.y;
738                            //     // this.springTarget.x = this.seePlayer.position.x;
739                            //     // this.springTarget.y = this.seePlayer.position.y;
740                            //     this.cons.length = -200;
741                            //     this.cons2.length = 100 + 1.5 * this.radius;
742                            // } else if (!(simulation.cycle % this.seePlayerFreq)) {
743                            //     const unit = Vector.normalise(Vector.sub(this.seePlayer.position, this.position))
744                            //     const goal = Vector.add(this.position, Vector.mult(unit, stepRange))
745                            //     this.springTarget2.x = goal.x;
746                            //     this.springTarget2.y = goal.y;
747                            //     // this.springTarget2.x = this.seePlayer.position.x;
748                            //     // this.springTarget2.y = this.seePlayer.position.y;
749                            //     this.cons.length = 100 + 1.5 * this.radius;
750                            //     this.cons2.length = -200;
751                            // }
752
753                        }
754                    }
755                }
756            },
757            curl(range = 1000, mag = -10) {
758                //cause all mobs, and bodies to rotate in a circle
759                applyCurl = function (center, array, isAntiGravity = true) {
760                    for (let i = 0; i < array.length; ++i) {
761                        if (!array[i].isNotHoldable) {
762                            const sub = Vector.sub(center, array[i].position)
763                            const radius2 = Vector.magnitudeSquared(sub);
764
765                            //if too close, like center mob or shield, don't curl   // if too far don't curl
766                            if (radius2 < range * range && radius2 > 10000) {
767                                const curlVector = Vector.mult(Vector.perp(Vector.normalise(sub)), mag)
768                                //apply curl force
769                                if (array[i].isMobBullet) {
770                                    Matter.Body.setVelocity(array[i], {
771                                        x: array[i].velocity.x * 0.97 + curlVector.x * 0.06,
772                                        y: array[i].velocity.y * 0.97 + curlVector.y * 0.06
773                                    })
774                                } else {
775                                    Matter.Body.setVelocity(array[i], {
776                                        x: array[i].velocity.x * 0.94 + curlVector.x * 0.06,
777                                        y: array[i].velocity.y * 0.94 + curlVector.y * 0.06
778                                    })
779                                }
780                                if (isAntiGravity) array[i].force.y -= 0.8 * simulation.g * array[i].mass
781                                // //draw curl, for debugging
782                                // ctx.beginPath();
783                                // ctx.moveTo(array[i].position.x, array[i].position.y);
784                                // ctx.lineTo(array[i].position.x + curlVector.x * 10, array[i].position.y + curlVector.y * 10);
785                                // ctx.lineWidth = 2;
786                                // ctx.strokeStyle = "#000";
787                                // ctx.stroke();
788                            }
789                        }
790                    }
791                }
792                applyCurl(this.position, mob, false);
793                applyCurl(this.position, body);
794                applyCurl(this.position, powerUp);
795                // applyCurl(this.position, bullet);  // too powerful, just stops all bullets need to write a curl function just for bullets
796                // applyCurl(this.position, [player]);
797
798                //draw limit
799                // ctx.beginPath();
800                // ctx.arc(this.position.x, this.position.y, range, 0, 2 * Math.PI);
801                // ctx.fillStyle = "rgba(55,255,255, 0.1)";
802                // ctx.fill();
803            },
804            pullPlayer() {
805                if (this.seePlayer.yes && Vector.magnitudeSquared(Vector.sub(this.position, player.position)) < 1000000) {
806                    const angle = Math.atan2(player.position.y - this.position.y, player.position.x - this.position.x);
807                    player.force.x -= simulation.accelScale * 0.00113 * player.mass * Math.cos(angle) * (m.onGroun
807d ? 2 : 1);
808                    player.force.y -= simulation.accelScale * 0.00084 * player.mass * Math.sin(angle);
809
810                    ctx.beginPath();
811                    ctx.moveTo(this.position.x, this.position.y);
812                    ctx.lineTo(m.pos.x, m.pos.y);
813                    ctx.lineWidth = Math.min(60, this.radius * 2);
814                    ctx.strokeStyle = "rgba(0,0,0,0.5)";
815                    ctx.stroke();
816                    ctx.beginPath();
817                    ctx.arc(m.pos.x, m.pos.y, 40, 0, 2 * Math.PI);
818                    ctx.fillStyle = "rgba(0,0,0,0.3)";
819                    ctx.fill();
820                }
821            },
822            repelBullets() {
823                // if (this.seePlayer.yes) {
824                // ctx.lineWidth = "8";
825                // ctx.strokeStyle = this.fill;
826                // ctx.beginPath();
827                for (let i = 0, len = bullet.length; i < len; ++i) {
828                    const dx = bullet[i].position.x - this.position.x;
829                    const dy = bullet[i].position.y - this.position.y;
830                    const dist = Math.max(300, Math.sqrt(dx * dx + dy * dy))
831                    if (dist < 700) {
832                        ctx.moveTo(this.position.x, this.position.y);
833                        ctx.lineTo(bullet[i].position.x, bullet[i].position.y);
834                        const angle = Math.atan2(dy, dx);
835                        const mag = (500 * bullet[i].mass * simulation.g) / dist;
836                        bullet[i].force.x += mag * Math.cos(angle);
837                        bullet[i].force.y += mag * Math.sin(angle);
838                    }
839                }
840                // ctx.stroke();
841                // }
842            },
843            attraction() {
844                //accelerate towards the player
845                if (this.seePlayer.recall) {
846                    const force = Vector.mult(Vector.normalise(Vector.sub(this.seePlayer.position, this.position)), this.accelMag * this.mass)
847                    this.force.x += force.x;
848                    this.force.y += force.y;
849                }
850            },
851            repulsionRange: 500000, //squared
852            repulsion() {
853                //accelerate towards the player
854                if (this.seePlayer.recall && this.distanceToPlayer2() < this.repulsionRange) {
855                    // && dx * dx + dy * dy < 2000000) {
856                    const forceMag = this.accelMag * this.mass;
857                    const angle = Math.atan2(this.seePlayer.position.y - this.position.y, this.seePlayer.position.x - this.position.x);
858                    this.force.x -= 2 * forceMag * Math.cos(angle);
859                    this.force.y -= 2 * forceMag * Math.sin(angle); // - 0.0007 * this.mass; //antigravity
860                }
861            },
862            hoverOverPlayer() {
863                if (this.seePlayer.recall) {
864                    // vertical positioning
865                    const rangeY = 250;
866                    if (this.position.y > this.seePlayer.position.y - this.hoverElevation + rangeY) {
867                        this.force.y -= this.accelMag * this.mass;
868                    } else if (this.position.y < this.seePlayer.position.y - this.hoverElevation - rangeY) {
869                        this.force.y += this.accelMag * this.mass;
870                    }
871                    // horizontal positioning
872                    const rangeX = 150;
873                    if (this.position.x > this.seePlayer.position.x + this.hoverXOff + rangeX) {
874                        this.force.x -= this.accelMag * this.mass;
875                    } else if (this.position.x < this.seePlayer.position.x + this.hoverXOff - rangeX) {
876                        this.force.x += this.accelMag * this.mass;
877                    }
878                }
879            },
880            // invulnerability() {
881            //     if (this.isInvulnerable) {
882            //         if (this.invulnerabilityCountDown > 0) {
883            //             this.invulnerabilityCountDown--
884            //             //graphics //draw a super shield?
885            //             ctx.beginPath();
886            //             let vertices = this.vertices;
887            //             ctx.moveTo(vertices[0].x, vertices[0].y);
888            //             for (let j = 1; j < vertices.length; j++) ctx.lineTo(vertices[j].x, vertices[j].y);
889            //             ctx.lineTo(vertices[0].x, vertices[0].y);
890            //             ctx.lineWidth = 20;
891            //             // ctx.fillStyle = `rgba(${Math.floor(255 * Math.random())},${Math.floor(255 * Math.random())},${Math.floor(255 * Math.random())},0.5)`
892            //             // ctx.fill();
893            //             ctx.strokeStyle = "rgba(255,255,255,0.4)";
894            //             ctx.stroke();
895            //         } else {
896            //             this.isInvulnerable = false
897            //             this.damageReduction = this.startingDamageReduction
898            //         }
899            //     }
900            // },
901            grow() {
902                if (this.seePlayer.recall) {
903                    if (this.radius < 80) {
904                        const scale = 1.01;
905                        Matter.Body.scale(this, scale, scale);
906                        this.radius *= scale;
907                        // this.torque = -0.00002 * this.inertia;
908                        this.fill = `hsl(144, ${this.radius}%, 50%)`;
909                        if (this.isShielded) { //remove shield if shielded when growing
910                            this.isShielded = false;
911                            this.removeConsBB();
912                        }
913                    }
914                } else {
915                    if (this.radius > 15) {
916                        const scale = 0.99;
917                        Matter.Body.scale(this, scale, scale);
918                        this.radius *= scale;
919                        this.fill = `hsl(144, ${this.radius}%, 50%)`;
920                    }
921                }
922            },
923            search() {
924                //be sure to declare searchTarget in mob spawn
925                //accelerate towards the searchTarget
926                if (!this.seePlayer.recall) {
927                    const newTarget = function (that) {
928                        if (Math.random() < 0.0007) {
929                            that.searchTarget = player.position; //chance to target player
930                        } else {
931                            //target random body
932                            that.searchTarget = map[Math.floor(Math.random() * (map.length - 1))].position;
933                        }
934                    };
935
936                    const sub = Vector.sub(this.searchTarget, this.position);
937                    if (Vector.magnitude(sub) > this.radius * 2) {
938                        // ctx.beginPath();
939                        // ctx.strokeStyle = "#aaa";
940                        // ctx.moveTo(this.position.x, this.position.y);
941                        // ctx.lineTo(this.searchTarget.x,this.searchTarget.y);
942                        // ctx.stroke();
943                        //accelerate at 0.1 of normal acceleration
944                        this.force = Vector.mult(Vector.normalise(sub), this.accelMag * this.mass * 0.2);
945                    } else {
946                        //after reaching random target switch to new target
947                        newTarget(this);
948                    }
949                    //switch to a new target after a while
950                    if (!(simulation.cycle % (this.seePlayerFreq * 15))) {
951                        newTarget(this);
952                    }
953                }
954            },
955            blink() {
956                //teleport towards player as a way to move
957                if (this.seePlayer.recall && !(simulation.cycle % this.blinkRate)) {
958                    ctx.beginPath();
959                    ctx.moveTo(this.position.x, this.position.y);
960                    const dist = Vector.sub(this.seePlayer.position, this.position);
961                    const distMag = Vector.magnitude(dist);
962                    const unitVector = Vector.normalise(dist);
963                    const rando = (Math.random() - 0.5) * 50;
964                    if (distMag < this.blinkLength) {
965                        Matter.Body.translate(this, Vector.mult(unitVector, distMag + rando));
966                    } else {
967                        Matter.Body.translate(this, Vector.mult(unitVector, this.blinkLength + rando));
968                    }
969                    ctx.lineTo(this.position.x, this.position.y);
970                    ctx.lineWidth = radius * 2;
971                    ctx.strokeStyle = this.stroke; //"rgba(0,0,0,0.5)"; //'#000'
972                    ctx.stroke();
973                }
974            },
975            drift() {
976                //teleport towards player as a way to move
977                if (this.seePlayer.recall && !(simulation.cycle % this.blinkRate)) {
978                    // && !m.lookingAtMob(this,0.5)){
979                    ctx.beginPath();
980                    ctx.moveTo(this.position.x, this.position.y);
981                    const dist = Vector.sub(this.seePlayer.position, this.position);
982                    const distMag = Vector.magnitude(dist);
983                    const vector = Vector.mult(Vector.normalise(dist), this.blinkLength);
984                    if (distMag < this.blinkLength) {
985                        Matter.Body.setPosition(this, this.seePlayer.position);
986                        Matter.Body.translate(this, {
987                            x: (Math.random() - 0.5) * 50,
988                            y: (Math.random() - 0.5) * 50
989                        });
990                    } else {
991                        vector.x += (Math.random() - 0.5) * 200;
992                        vector.y += (Math.random() - 0.5) * 200;
993                        Matter.Body.translate(this, vector);
994                    }
995                    ctx.lineTo(this.position.x, this.position.y);
996                    ctx.lineWidth = radius * 2;
997                    ctx.strokeStyle = this.stroke;
998                    ctx.stroke();
999                }
1000            },
1001            bomb() {
1002                //throw a mob/bullet at player
1003                if (
1004                    !(simulation.cycle % this.fireFreq) &&
1005                    Math.abs(this.position.x - this.seePlayer.position.x) < 400 && //above player
1006                    Matter.Query.ray(map, this.position, this.playerPosRandomY()).length === 0 && //see player
1007                    Matter.Query.ray(body, this.position, this.playerPosRandomY()).length === 0
1008                ) {
1009                    spawn.bomb(this.position.x, this.position.y + this.radius * 0.7, 9 + Math.ceil(this.radius / 15), 5);
1010                    //add spin and speed
1011                    Matter.Body.setAngularVelocity(mob[mob.length - 1], (Math.random() - 0.5) * 0.5);
1012                    Matter.Body.setVelocity(mob[mob.length - 1], {
1013                        x: this.velocity.x,
1014                        y: this.velocity.y
1015                    });
1016                    //spin for mob as well
1017                    Matter.Body.setAngularVelocity(this, (Math.random() - 0.5) * 0.25);
1018                }
1019            },
1020            fire() {
1021                const setNoseShape = () => {
1022                    const mag = this.radius + this.radius * this.noseLength;
1023                    this.vertices[1].x = this.position.x + Math.cos(this.angle) * mag;
1024                    this.vertices[1].y = this.position.y + Math.sin(this.angle) * mag;
1025                };
1026                //throw a mob/bullet at player
1027                if (this.seePlayer.recall) {
1028                    //set direction to turn to fire
1029                    if (!(simulation.cycle % this.seePlayerFreq)) {
1030                        this.fireDir = Vector.normalise(Vector.sub(this.seePlayer.position, this.position));
1031                        this.fireDir.y -= Math.abs(this.seePlayer.position.x - this.position.x) / 2500; //gives the bullet an arc  //was    / 1600
1032                    }
1033                    //rotate towards fireAngle
1034                    const angle = this.angle + Math.PI / 2;
1035                    const dot = Vector.dot({
1036                        x: Math.cos(angle),
1037                        y: Math.sin(angle)
1038                    }, this.fireDir)
1039                    // c = Math.cos(angle) * this.fireDir.x + Math.sin(angle) * this.fireDir.y;
1040                    const threshold = 0.1;
1041                    if (dot > threshold) {
1042                        this.torque += 0.000004 * this.inertia;
1043                    } else if (dot < -threshold) {
1044                        this.torque -= 0.000004 * this.inertia;
1045                    } else if (this.noseLength > 1.5 && dot > -0.2 && dot < 0.2) {
1046                        //fire
1047                        spawn.bullet(this.vertices[1].x, this.vertices[1].y, 9 + Math.ceil(this.radius / 15));
1048                        const v = 15;
1049                        Matter.Body.setVelocity(mob[mob.length - 1], {
1050                            x: this.velocity.x + this.fireDir.x * v + 3 * Math.random(),
1051                            y: this.velocity.y + this.fireDir.y * v + 3 * Math.random()
1052                        });
1053                        this.noseLength = 0;
1054                        // recoil
1055                        this.force.x -= 0.005 * this.fireDir.x * this.mass;
1056                        this.force.y -= 0.005 * this.fireDir.y * this.mass;
1057                    }
1058                    if (this.noseLength < 1.5) this.noseLength += this.fireFreq;
1059                    setNoseShape();
1060                } else if (this.noseLength > 0.1) {
1061                    this.noseLength -= this.fireFreq / 2;
1062                    setNoseShape();
1063                }
1064                // else if (this.noseLength < -0.1) {
1065                //   this.noseLength += this.fireFreq / 4;
1066                //   setNoseShape();
1067                // }
1068            },
1069            // launch() {
1070            //     if (this.seePlayer.recall) {
1071            //       //fire
1072            //       spawn.seeker(this.vertices[1].x, this.vertices[1].y, 5 + Math.
1072ceil(this.radius / 15), 5);
1073            //       const v = 15;
1074            //       Matter.Body.setVelocity(mob[mob.length - 1], {
1075            //         x: this.velocity.x + this.fireDir.x * v + Math.random(),
1076            //         y: this.velocity.y + this.fireDir.y * v + Math.random()
1077            //       });
1078            //       // recoil
1079            //       this.force.x -= 0.005 * this.fireDir.x * this.mass;
1080            //       this.force.y -= 0.005 * this.fireDir.y * this.mass;
1081            //     }
1082            // },
1083            turnToFacePlayer() {
1084                //turn to face player
1085                const dx = player.position.x - this.position.x;
1086                const dy = -player.position.y + this.position.y;
1087                const dist = this.distanceToPlayer();
1088                const angle = this.angle + Math.PI / 2;
1089                c = Math.cos(angle) * dx - Math.sin(angle) * dy;
1090                // if (c > 0.04) {
1091                //   Matter.Body.rotate(this, 0.01);
1092                // } else if (c < 0.04) {
1093                //   Matter.Body.rotate(this, -0.01);
1094                // }
1095                if (c > 0.04 * dist) {
1096                    this.torque += 0.002 * this.mass;
1097                } else if (c < 0.04) {
1098                    this.torque -= 0.002 * this.mass;
1099                }
1100            },
1101            facePlayer() {
1102                const unitVector = Vector.normalise(Vector.sub(this.seePlayer.position, this.position));
1103                const angle = Math.atan2(unitVector.y, unitVector.x);
1104                Matter.Body.setAngle(this, angle - Math.PI);
1105            },
1106            explode(mass = this.mass) {
1107                if (m.immuneCycle < m.cycle) {
1108                    m.damage(Math.min(Math.max(0.02 * Math.sqrt(mass), 0.01), 0.35) * simulation.dmgScale);
1109                    this.isDropPowerUp = false;
1110                    this.death(); //death with no power up or body
1111                }
1112            },
1113            timeLimit() {
1114                this.timeLeft--;
1115                if (this.timeLeft < 0) {
1116                    this.isDropPowerUp = false;
1117                    this.death(); //death with no power up
1118                }
1119            },
1120            healthBar() { //draw health by mob //most health bars are drawn in mobs.healthbar();
1121                if (this.seePlayer.recall) {
1122                    const h = this.radius * 0.3;
1123                    const w = this.radius * 2;
1124                    const x = this.position.x - w / 2;
1125                    const y = this.position.y - w * 0.7;
1126                    ctx.fillStyle = "rgba(100, 100, 100, 0.3)";
1127                    ctx.fillRect(x, y, w, h);
1128                    ctx.fillStyle = "rgba(255,0,0,0.7)";
1129                    ctx.fillRect(x, y, w * this.health, h);
1130                }
1131            },
1132            damage(dmg, isBypassShield = false) {
1133                if ((!this.isShielded || isBypassShield) && this.alive) {
1134                    if (dmg !== Infinity) {
1135                        dmg *= tech.damageFromTech()
1136                        //mobs specific damage changes
1137                        if (tech.isFarAwayDmg) dmg *= 1 + Math.sqrt(Math.max(500, Math.min(3000, this.distanceToPlayer())) - 500) * 0.0067 //up to 33% dmg at max range of 3000
1138                        dmg *= this.damageReduction
1139                        //energy and heal drain should be calculated after damage boosts
1140                        if (tech.energySiphon && dmg !== Infinity && this.isDropPowerUp && m.immuneCycle < m.cycle) m.energy += Math.min(this.health, dmg) * tech.energySiphon
1141                        if (tech.healthDrain && dmg !== Infinity && this.isDropPowerUp && Math.random() < tech.healthDrain * Math.min(this.health, dmg)) {
1142                            powerUps.spawn(m.pos.x + 20 * (Math.random() - 0.5), m.pos.y + 20 * (Math.random() - 0.5), "heal");
1143                        }
1144                        dmg /= Math.sqrt(this.mass)
1145                    }
1146                    this.health -= dmg
1147                    //this.fill = this.color + this.health + ')';
1148                    this.onDamage(dmg); //custom damage effects
1149                    if ((this.health < 0.01 || isNaN(this.health)) && this.alive) this.death();
1150                }
1151            },
1152            onDamage() {
1153                // a placeholder for custom effects on mob damage
1154                //to use declare custom method in mob spawn
1155            },
1156            onDeath() {
1157                // a placeholder for custom effects on mob death
1158                // to use declare custom method in mob spawn
1159            },
1160            damageReduction: 1,
1161            // damageReductionGoal: 0.001, //must add this to boss set up:   me.damageReduction = 0.25
1162            // damageReductionScale: 0.004, //for bosses in this.onDamage  determines the impact of dmg on damageReductionGoal
1163            // armor() { //slowly reduce damage reduction, for bosses
1164            //     if (this.seePlayer.recall) {
1165            //         if (this.damageReductionGoal > 0.24) {
1166            //             this.damageReductionGoal = 0.25
1167            //         } else {
1168            //             this.damageReductionGoal = this.damageReductionGoal * 0.999 + 0.001 * 0.25 //smooth the goal towards 0.25 damage reduction
1169            //         }
1170            //         this.damageReduction = this.damageReduction * 0.995 + 0.005 * this.damageReductionGoal //smooth damage reduction towards the goal
1171            //         // console.log(`damageReduction = ${this.damageReduction.toFixed(4)}`, `damageReductionGoal = ${this.damageReductionGoal.toFixed(4)}`)
1172            //     }
1173            //     //draw armor
1174            //     //draw body
1175            //     ctx.beginPath();
1176            //     const vertices = this.vertices;
1177            //     ctx.moveTo(vertices[0].x, vertices[0].y);
1178            //     for (let j = 1, len = vertices.length; j < len; ++j) {
1179            //         ctx.lineTo(vertices[j].x, vertices[j].y);
1180            //     }
1181            //     ctx.lineTo(vertices[0].x, vertices[0].y);
1182            //     console.log(this.damageReduction, this.damageReductionGoal)
1183            //     ctx.lineWidth = 3 //60 * (0.25 - this.damageReductionGoal)
1184            //     ctx.strokeStyle = `rgba(255,255,255,${4.1*(0.25 - this.damageReductionGoal)})` //"rgba(150,150,225,0.5)";
1185            //     ctx.stroke();
1186            // },
1187            leaveBody: true,
1188            isDropPowerUp: true,
1189            death() {
1190                if (tech.collidePowerUps && Math.random() < tech.collidePowerUps && this.isDropPowerUp) powerUps.randomize(this.position) //needs to run before onDeath spawns power ups
1191                this.onDeath(this); //custom death effects
1192                this.removeConsBB();
1193                this.alive = false; //triggers mob removal in mob[i].replace(i)
1194
1195                if (this.isDropPowerUp) {
1196                    if (this.isSoonZombie) { //spawn zombie on death
1197                        this.leaveBody = false;
1198                        let count = 5 //delay spawn cycles
1199                        let cycle = () => {
1200                            if (count > 0) {
1201                                if (m.alive) requestAnimationFrame(cycle);
1202                                if (!simulation.paused && !simulation.isChoosing) {
1203                                    count--
1204                                }
1205                            } else {
1206                                spawn.zombie(this.position.x, this.position.y, this.radius, this.vertices.length, this.fill) // zombie(x, y, radius, sides, color)
1207                            }
1208                        }
1209                        requestAnimationFrame(cycle);
1210                    }
1211                    if (tech.iceIXOnDeath && this.isSlowed) {
1212                        for (let i = 0, len = 2 * Math.sqrt(Math.min(this.mass, 25)) * tech.iceIXOnDeath; i < len; i++) b.iceIX(3, Math.random() * 2 * Math.PI, this.position)
1213                    }
1214                    if (tech.deathSpawnsFromBoss || tech.deathSpawns) {
1215                        const spawns = tech.deathSpawns + tech.deathSpawnsFromBoss
1216                        const len = Math.min(12, spawns * Math.ceil(Math.random() * simulation.difficulty * spawns))
1217                        for (let i = 0; i < len; i++) {
1218                            spawn.spawns(this.position.x + (Math.random() - 0.5) * radius * 2.5, this.position.y + (Math.random() - 0.5) * radius * 2.5);
1219                            Matter.Body.setVelocity(mob[mob.length - 1], {
1220                                x: this.velocity.x + (Math.random() - 0.5) * 10,
1221                                y: this.velocity.x + (Math.random() - 0.5) * 10
1222                            });
1223                        }
1224                    }
1225
1226                    if (tech.deathSkipTime && !m.isBodiesAsleep) {
1227                        requestAnimationFrame(() => {
1228                            simulation.timePlayerSkip((this.isBoss ? 45 : 25) * tech.deathSkipTime)
1229                            simulation.loop(); //ending with a wipe and normal loop fixes some very minor graphical issues where things are draw in the wrong locations
1230                        }); //wrapping in animation frame prevents errors, probably
1231
1232                        // if (tech.isFlipFlopOn) {
1233                        //     m.rewind(this.isBoss ? 45 : 25)
1234                        // } else {
1235                        //     requestAnimationFrame(() => {
1236                        //         simulation.timePlayerSkip(this.isBoss ? 45 : 25)
1237                        //         simulation.loop(); //ending with a wipe and normal loop fixes some very minor graphical issues where things are draw in the wrong locations
1238                        //     }); //wrapping in animation frame prevents errors, probably
1239                        // }
1240                    }
1241                    if (tech.isEnergyLoss) m.energy *= 0.8;
1242                    powerUps.spawnRandomPowerUp(this.position.x, this.position.y);
1243                    m.lastKillCycle = m.cycle; //tracks the last time a kill was made, mostly used in simulation.checks()
1244                    mobs.mobDeaths++
1245
1246                    if (Math.random() < tech.sporesOnDeath) {
1247                        const amount = Math.min(25, Math.floor(2 + this.mass * (0.5 + 0.5 * Math.random())))
1248                        if (tech.isSporeFlea) {
1249                            const len = amount / 2
1250                            for (let i = 0; i < len; i++) {
1251                                const speed = 10 + 5 * Math.random()
1252                                const angle = 2 * Math.PI * Math.random()
1253                                b.flea(this.position, { x: speed * Math.cos(angle), y: speed * Math.sin(angle) })
1254                            }
1255                        } else if (tech.isSporeWorm) {
1256                            const len = amount / 2
1257                            for (let i = 0; i < len; i++) b.worm(this.position)
1258                        } else {
1259                            for (let i = 0; i < amount; i++) b.spore(this.position)
1260                        }
1261                    } else if (tech.isExplodeMob) {
1262                        b.explosion(this.position, Math.min(700, Math.sqrt(this.mass + 6) * (30 + 60 * Math.random())))
1263                    } else if (tech.nailsDeathMob) {
1264                        b.targetedNail(this.position, tech.nailsDeathMob, 39 + 6 * Math.random())
1265                    }
1266                    if (tech.isBotSpawnerReset) {
1267                        for (let i = 0, len = bullet.length; i < len; i++) {
1268                            if (bullet[i].botType && bullet[i].endCycle !== Infinity) bullet[i].endCycle = simulation.cycle + 780 //13 seconds
1269                        }
1270                    }
1271                    if (Math.random() < tech.botSpawner) {
1272                        b.randomBot(this.position, false)
1273                        bullet[bullet.length - 1].endCycle = simulation.cycle + 780 //13 seconds
1274                        this.leaveBody = false; // no body since it turned into the bot
1275                    }
1276                    if (tech.isAddRemoveMaxHealth) {
1277                        if (this.isBoss && this.isDropPowerUp) {
1278                            powerUps.spawn(this.position.x + 20, this.position.y, "tech", false)
1279                            powerUps.spawn(this.position.x - 20, this.position.y, "ammo", false)
1280                            powerUps.spawn(this.position.x, this.position.y + 20, "research", false)
1281                            powerUps.spawn(this.position.x, this.position.y - 20, "heal", false)
1282                            powerUps.spawn(this.position.x - 40, this.position.y, "ammo", false)
1283                            powerUps.spawn(this.position.x, this.position.y + 40, "research", false)
1284                            powerUps.spawn(this.position.x, this.position.y - 40, "heal", false)
1285                        } else {
1286                            const amount = 0.005
1287                            if (tech.isEnergyHealth) {
1288                                if (m.maxEnergy > amount) {
1289                                    tech.healMaxEnergyBonus -= amount
1290                                    m.setMaxEnergy();
1291                                }
1292                            } else if (m.maxHealth > amount) {
1293                                tech.extraMaxHealth -= amount //decrease max health
1294                                m.setMaxHealth();
1295                            }
1296                        }
1297                    }
1298                    if (tech.cloakDuplication && !this.isBoss) {
1299                        tech.cloakDuplication -= 0.02
1300                        powerUps.setPowerUpMode(); //needed after adjusting duplication chance
1301                    }
1302                } else if (tech.isShieldAmmo && this.shield && !this.isExtraShield) {
1303                    let type = tech.isEnergyNoAmmo ? "heal" : "ammo"
1304                    if (Math.random() < 0.4) {
1305                        type = "heal"
1306                    } else if (Math.random() < 0.3 && !tech.isSuperDeterminism) {
1307                        type = "research"
1308                    }
1309                    for (let i = 0, len = Math.ceil(2 * Math.random()); i < len; i++) {
1310                        powerUps.spawn(this.position.x, this.position.y, type);
1311                    }
1312                }
1313                if (tech.isRadioactive) {
1314                    //look for dots and spread them
1315                    let dmgTotal = 0
1316                    for (let i = 0, len = this.status.length; i < len; i++) {
1317                        if (this.status[i].type === "dot") dmgTotal += this.status[i].dmg * (this.status[i].endCycle - simulation.cycle)
1318                    }
1319                    if (dmgTotal > 0) { //look for closest mob
1320                        let closestRadius = 500;
1321                        let closestIndex = null;
1322                        for (let i = 0, len = mob.length; i < len; ++i) {
1323                            const radius = Vector.magnitude(Vector.sub(this.position, mob[i].position))
1324                            if (mob[i].alive && !mob[i].isShielded && radius < closestRadius) {
1325                                closestRadius = radius
1326                                closestIndex = i
1327                            }
1328                        }
1329                        if (closestIndex) {
1330                            mobs.statusDoT(mob[closestIndex], dmgTotal / 180, 180)
1331                            ctx.beginPath();
1332                            ctx.moveTo(this.position.x, this.position.y);
1333                            ctx.lineTo(mob[closestIndex].position.x, mob[closestIndex].position.y);
1334                            ctx.lineWidth = this.radius;
1335                            ctx.strokeStyle = "rgba(0,80,80,1)";
1336                            ctx.stroke();
1337                        }
1338                        //draw AOE
1339                        // simulation.drawList.push({ //add dmg to draw queue
1340                        //     x: this.position.x,
1341                        //     y: this.position.y,
1342                        //     radius: radius,
1343                        //     color: "rgba(0,80,80,0.03)",
1344                        //     time: 15
1345                        // });
1346                    }
1347                }
1348            },
1349            removeConsBB() {
1350                for (let i = 0, len = consBB.length; i < len; ++i) {
1351                    if (consBB[i].bodyA === this) {
1352                        if (consBB[i].bodyB.shield) { //&& !this.shield
1353                            consBB[i].bodyB.do = function () { this.death() }
1354                        }
1355                        consBB[i].bodyA = consBB[i].bodyB;
1356                        consBB.splice(i, 1);
1357                        this.removeConsBB();
1358                        break;
1359                    } else if (consBB[i].bodyB === this) {
1360                        if (consBB[i].bodyA.shield) {
1361                            consBB[i].bodyA.do = function () { this.death() }
1362                        }
1363                        consBB[i].bodyB = consBB[i].bodyA;
1364                        consBB.splice(i, 1);
1365                        this.removeConsBB();
1366                        break;
1367                    }
1368                }
1369            },
1370            removeCons() {
1371                for (let i = 0, len = cons.length; i < len; ++i) {
1372                    if (cons[i].bodyA === this) {
1373                        cons[i].bodyA = cons[i].bodyB;
1374                        cons.splice(i, 1);
1375                        this.removeCons();
1376                        break;
1377                    } else if (cons[i].bodyB === this) {
1378                        cons[i].bodyB = cons[i].bodyA;
1379                        cons.splice(i, 1);
1380                        this.removeCons();
1381                        break;
1382                    }
1383                }
1384            },
1385            //replace dead mob with a regular body
1386            replace(i) {
1387                //if there are too many bodies don't turn into blocks to help performance
1388                if (this.leaveBody && body.length < 40 && this.mass < 200 && this.radius > 18) {
1389                    let v = Matter.Vertices.hull(Matter.Vertices.clockwiseSort(this.vertices)) //might help with vertex collision issue, not sure
1390                    if (v.length > 5 && body.length < 35 && Math.random() < 0.25) {
1391                        const cutPoint = 3 + Math.floor((v.length - 6) * Math.random()) //Math.floor(v.length / 2)
1392                        const v2 = v.slice(0, cutPoint + 1)
1393                        v = v.slice(cutPoint - 1)
1394                        const len = body.length;
1395                        body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, v2);
1396                        Matter.Body.setVelocity(body[len], Vector.mult(this.velocity, 0.5));
1397                        Matter.Body.setAngularVelocity(body[len], this.angularVelocity);
1398                        body[len].collisionFilter.category = cat.body;
1399                        body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet;
1400                        body[len].classType = "body";
1401                        Composite.add(engine.world, body[len]); //add to world
1402
1403                        const len2 = body.length;
1404                        body[len2] = Matter.Bodies.fromVertices(this.position.x, this.position.y, v);
1405                        Matter.Body.setVelocity(body[len2], Vector.mult(this.velocity, 0.5));
1406                        Matter.Body.setAngularVelocity(body[len2], this.angularVelocity);
1407                        body[len2].collisionFilter.category = cat.body;
1408                        body[len2].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet;
1409                        // if (body[len].mass > 10 || 45 + 10 * Math.random() < body.length) {
1410                        //   body[len].collisionFilter.mask = cat.player | cat.bullet | cat.mob | cat.mobBullet;
1411                        // }
1412                        body[len2].classType = "body";
1413                        Composite.add(engine.world, body[len2]); //add to world
1414
1415                        //large mobs shrink so they don't block paths
1416                        if (body[len].mass + body[len2].mass > 16) {
1417                            const massLimit = 8 + 6 * Math.random()
1418                            const shrink = function (that1, that2) {
1419                                if (that1.mass + that2.mass > massLimit) {
1420                                    const scale = 0.95;
1421                                    Matter.Body.scale(that1, scale, scale);
1422                                    Matter.Body.scale(that2, scale, scale);
1423                                    setTimeout(shrink, 20, that1, that2);
1424                                }
1425                            };
1426                            shrink(body[len], body[len2])
1427                        }
1428                    } else {
1429                        const len = body.length;
1430                        body[len] = Matter.Bodies.fromVertices(this.position.x, this.position.y, v);
1431                        Matter.Body.setVelocity(body[len], Vector.mult(this.velocity, 0.5));
1432                        Matter.Body.setAngularVelocity(body[len], this.angularVelocity);
1433                        body[len].collisionFilter.category = cat.body;
1434                        body[len].collisionFilter.mask = cat.player | cat.map | cat.body | cat.bullet | cat.mob | cat.mobBullet;
1435                        body[len].classType = "body";
1436                        Composite.add(engine.world, body[len]); //add to world
1437
1438                        //large mobs shrink so they don't block paths
1439                        if (body[len].mass > 9) {
1440                            const massLimit = 7 + 4 * Math.random()
1441                            const shrink = function (that) {
1442                                if (that.mass > massLimit) {
1443                                    const scale = 0.95;
1444                                    Matter.Body.scale(that, scale, scale);
1445                                    setTimeout(shrink, 20, that);
1446                                }
1447                            };
1448                            shrink(body[len])
1449                        }
1450                    }
1451                    Matter.Composite.remove(engine.world, this);
1452                    mob.splice(i, 1);
1453                    if (tech.isMobBlockFling) {
1454                        const who = body[body.length - 1]
1455                        if (!who.isNotHoldable) {
1456                            b.targetedBlock(who)
1457                            Matter.Body.setAngularVelocity(who, (0.5 + 0.2 * Math.random()) * (Math.random() < 0.5 ? -1 : 1));
1458                            // who.torque += who.inertia * 0.002 * (Math.random() - 0.5)
1459                        }
1460                    }
1461                } else {
1462                    Matter.Composite.remove(engine.world, this);
1463                    mob.splice(i, 1);
1464                }
1465            }
1466        });
1467        mob[i].alertRange2 = Math.pow(mob[i].radius * 3 + 550, 2);
1468        Composite.add(engine.world, mob[i]); //add to world
1469    }
1470};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.