|
|
1.1 root 1: /*
2: ==============================================================================
3:
4: boss2
5:
6: ==============================================================================
7: */
8:
9: #include "g_local.h"
10: #include "m_boss2.h"
11:
12: void BossExplode (edict_t *self);
13:
14: qboolean infront (edict_t *self, edict_t *other);
15:
16: static int sound_pain1;
17: static int sound_pain2;
18: static int sound_pain3;
19: static int sound_death;
20: static int sound_search1;
21:
22: void boss2_search (edict_t *self)
23: {
24: if (random() < 0.5)
25: gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
26: }
27:
28: void boss2_run (edict_t *self);
29: void boss2_stand (edict_t *self);
30: void boss2_dead (edict_t *self);
31: void boss2_attack (edict_t *self);
32: void boss2_attack_mg (edict_t *self);
33: void boss2_reattack_mg (edict_t *self);
34: void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
35:
36: void Boss2Rocket (edict_t *self)
37: {
38: vec3_t forward, right;
39: vec3_t start;
40: vec3_t dir;
41: vec3_t vec;
42:
43: AngleVectors (self->s.angles, forward, right, NULL);
44:
45: //1
46: G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start);
47: VectorCopy (self->enemy->s.origin, vec);
48: vec[2] += self->enemy->viewheight;
49: VectorSubtract (vec, start, dir);
50: VectorNormalize (dir);
51: monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1);
52:
53: //2
54: G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start);
55: VectorCopy (self->enemy->s.origin, vec);
56: vec[2] += self->enemy->viewheight;
57: VectorSubtract (vec, start, dir);
58: VectorNormalize (dir);
59: monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
60:
61: //3
62: G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start);
63: VectorCopy (self->enemy->s.origin, vec);
64: vec[2] += self->enemy->viewheight;
65: VectorSubtract (vec, start, dir);
66: VectorNormalize (dir);
67: monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3);
68:
69: //4
70: G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
71: VectorCopy (self->enemy->s.origin, vec);
72: vec[2] += self->enemy->viewheight;
73: VectorSubtract (vec, start, dir);
74: VectorNormalize (dir);
75: monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4);
76: }
77:
78: void boss2_firebullet_right (edict_t *self)
79: {
80: vec3_t forward, right, target;
81: vec3_t start;
82:
83: AngleVectors (self->s.angles, forward, right, NULL);
84: G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right, start);
85:
86: VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
87: target[2] += self->enemy->viewheight;
88: VectorSubtract (target, start, forward);
89: VectorNormalize (forward);
90:
91: monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1);
92: }
93:
94: void boss2_firebullet_left (edict_t *self)
95: {
96: vec3_t forward, right, target;
97: vec3_t start;
98:
99: AngleVectors (self->s.angles, forward, right, NULL);
100: G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right, start);
101:
102: VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
103:
104: target[2] += self->enemy->viewheight;
105: VectorSubtract (target, start, forward);
106: VectorNormalize (forward);
107:
108: monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1);
109: }
110:
111: void Boss2MachineGun (edict_t *self)
112: {
113: /* vec3_t forward, right;
114: vec3_t start;
115: vec3_t dir;
116: vec3_t vec;
117: int flash_number;
118:
119: AngleVectors (self->s.angles, forward, right, NULL);
120:
121: flash_number = MZ2_BOSS2_MACHINEGUN_1 + (self->s.frame - FRAME_attack10);
122: G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
123:
124: VectorCopy (self->enemy->s.origin, vec);
125: vec[2] += self->enemy->viewheight;
126: VectorSubtract (vec, start, dir);
127: VectorNormalize (dir);
128: monster_fire_bullet (self, start, dir, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
129: */
130: boss2_firebullet_left(self);
131: boss2_firebullet_right(self);
132: }
133:
134:
135: mframe_t boss2_frames_stand [] =
136: {
137: ai_stand, 0, NULL,
138: ai_stand, 0, NULL,
139: ai_stand, 0, NULL,
140: ai_stand, 0, NULL,
141: ai_stand, 0, NULL,
142: ai_stand, 0, NULL,
143: ai_stand, 0, NULL,
144: ai_stand, 0, NULL,
145: ai_stand, 0, NULL,
146: ai_stand, 0, NULL,
147: ai_stand, 0, NULL,
148: ai_stand, 0, NULL,
149: ai_stand, 0, NULL,
150: ai_stand, 0, NULL,
151: ai_stand, 0, NULL,
152: ai_stand, 0, NULL,
153: ai_stand, 0, NULL,
154: ai_stand, 0, NULL,
155: ai_stand, 0, NULL,
156: ai_stand, 0, NULL,
157: ai_stand, 0, NULL
158: };
159: mmove_t boss2_move_stand = {FRAME_stand30, FRAME_stand50, boss2_frames_stand, NULL};
160:
161: mframe_t boss2_frames_fidget [] =
162: {
163: ai_stand, 0, NULL,
164: ai_stand, 0, NULL,
165: ai_stand, 0, NULL,
166: ai_stand, 0, NULL,
167: ai_stand, 0, NULL,
168: ai_stand, 0, NULL,
169: ai_stand, 0, NULL,
170: ai_stand, 0, NULL,
171: ai_stand, 0, NULL,
172: ai_stand, 0, NULL,
173: ai_stand, 0, NULL,
174: ai_stand, 0, NULL,
175: ai_stand, 0, NULL,
176: ai_stand, 0, NULL,
177: ai_stand, 0, NULL,
178: ai_stand, 0, NULL,
179: ai_stand, 0, NULL,
180: ai_stand, 0, NULL,
181: ai_stand, 0, NULL,
182: ai_stand, 0, NULL,
183: ai_stand, 0, NULL,
184: ai_stand, 0, NULL,
185: ai_stand, 0, NULL,
186: ai_stand, 0, NULL,
187: ai_stand, 0, NULL,
188: ai_stand, 0, NULL,
189: ai_stand, 0, NULL,
190: ai_stand, 0, NULL,
191: ai_stand, 0, NULL,
192: ai_stand, 0, NULL
193: };
194: mmove_t boss2_move_fidget = {FRAME_stand1, FRAME_stand30, boss2_frames_fidget, NULL};
195:
196: mframe_t boss2_frames_walk [] =
197: {
198: ai_walk, 8, NULL,
199: ai_walk, 8, NULL,
200: ai_walk, 8, NULL,
201: ai_walk, 8, NULL,
202: ai_walk, 8, NULL,
203: ai_walk, 8, NULL,
204: ai_walk, 8, NULL,
205: ai_walk, 8, NULL,
206: ai_walk, 8, NULL,
207: ai_walk, 8, NULL,
208: ai_walk, 8, NULL,
209: ai_walk, 8, NULL,
210: ai_walk, 8, NULL,
211: ai_walk, 8, NULL,
212: ai_walk, 8, NULL,
213: ai_walk, 8, NULL,
214: ai_walk, 8, NULL,
215: ai_walk, 8, NULL,
216: ai_walk, 8, NULL,
217: ai_walk, 8, NULL
218: };
219: mmove_t boss2_move_walk = {FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL};
220:
221:
222: mframe_t boss2_frames_run [] =
223: {
224: ai_run, 8, NULL,
225: ai_run, 8, NULL,
226: ai_run, 8, NULL,
227: ai_run, 8, NULL,
228: ai_run, 8, NULL,
229: ai_run, 8, NULL,
230: ai_run, 8, NULL,
231: ai_run, 8, NULL,
232: ai_run, 8, NULL,
233: ai_run, 8, NULL,
234: ai_run, 8, NULL,
235: ai_run, 8, NULL,
236: ai_run, 8, NULL,
237: ai_run, 8, NULL,
238: ai_run, 8, NULL,
239: ai_run, 8, NULL,
240: ai_run, 8, NULL,
241: ai_run, 8, NULL,
242: ai_run, 8, NULL,
243: ai_run, 8, NULL
244: };
245: mmove_t boss2_move_run = {FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL};
246:
247: mframe_t boss2_frames_attack_pre_mg [] =
248: {
249: ai_charge, 1, NULL,
250: ai_charge, 1, NULL,
251: ai_charge, 1, NULL,
252: ai_charge, 1, NULL,
253: ai_charge, 1, NULL,
254: ai_charge, 1, NULL,
255: ai_charge, 1, NULL,
256: ai_charge, 1, NULL,
257: ai_charge, 1, boss2_attack_mg
258: };
259: mmove_t boss2_move_attack_pre_mg = {FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, NULL};
260:
261:
262: // Loop this
263: mframe_t boss2_frames_attack_mg [] =
264: {
265: ai_charge, 1, Boss2MachineGun,
266: ai_charge, 1, Boss2MachineGun,
267: ai_charge, 1, Boss2MachineGun,
268: ai_charge, 1, Boss2MachineGun,
269: ai_charge, 1, Boss2MachineGun,
270: ai_charge, 1, boss2_reattack_mg
271: };
272: mmove_t boss2_move_attack_mg = {FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, NULL};
273:
274: mframe_t boss2_frames_attack_post_mg [] =
275: {
276: ai_charge, 1, NULL,
277: ai_charge, 1, NULL,
278: ai_charge, 1, NULL,
279: ai_charge, 1, NULL
280: };
281: mmove_t boss2_move_attack_post_mg = {FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run};
282:
283: mframe_t boss2_frames_attack_rocket [] =
284: {
285: ai_charge, 1, NULL,
286: ai_charge, 1, NULL,
287: ai_charge, 1, NULL,
288: ai_charge, 1, NULL,
289: ai_charge, 1, NULL,
290: ai_charge, 1, NULL,
291: ai_charge, 1, NULL,
292: ai_charge, 1, NULL,
293: ai_charge, 1, NULL,
294: ai_charge, 1, NULL,
295: ai_charge, 1, NULL,
296: ai_charge, 1, NULL,
297: ai_move, -20, Boss2Rocket,
298: ai_charge, 1, NULL,
299: ai_charge, 1, NULL,
300: ai_charge, 1, NULL,
301: ai_charge, 1, NULL,
302: ai_charge, 1, NULL,
303: ai_charge, 1, NULL,
304: ai_charge, 1, NULL,
305: ai_charge, 1, NULL
306: };
307: mmove_t boss2_move_attack_rocket = {FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run};
308:
309: mframe_t boss2_frames_pain_heavy [] =
310: {
311: ai_move, 0, NULL,
312: ai_move, 0, NULL,
313: ai_move, 0, NULL,
314: ai_move, 0, NULL,
315: ai_move, 0, NULL,
316: ai_move, 0, NULL,
317: ai_move, 0, NULL,
318: ai_move, 0, NULL,
319: ai_move, 0, NULL,
320: ai_move, 0, NULL,
321: ai_move, 0, NULL,
322: ai_move, 0, NULL,
323: ai_move, 0, NULL,
324: ai_move, 0, NULL,
325: ai_move, 0, NULL,
326: ai_move, 0, NULL,
327: ai_move, 0, NULL,
328: ai_move, 0, NULL
329: };
330: mmove_t boss2_move_pain_heavy = {FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run};
331:
332: mframe_t boss2_frames_pain_light [] =
333: {
334: ai_move, 0, NULL,
335: ai_move, 0, NULL,
336: ai_move, 0, NULL,
337: ai_move, 0, NULL
338: };
339: mmove_t boss2_move_pain_light = {FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run};
340:
341: mframe_t boss2_frames_death [] =
342: {
343: ai_move, 0, NULL,
344: ai_move, 0, NULL,
345: ai_move, 0, NULL,
346: ai_move, 0, NULL,
347: ai_move, 0, NULL,
348: ai_move, 0, NULL,
349: ai_move, 0, NULL,
350: ai_move, 0, NULL,
351: ai_move, 0, NULL,
352: ai_move, 0, NULL,
353: ai_move, 0, NULL,
354: ai_move, 0, NULL,
355: ai_move, 0, NULL,
356: ai_move, 0, NULL,
357: ai_move, 0, NULL,
358: ai_move, 0, NULL,
359: ai_move, 0, NULL,
360: ai_move, 0, NULL,
361: ai_move, 0, NULL,
362: ai_move, 0, NULL,
363: ai_move, 0, NULL,
364: ai_move, 0, NULL,
365: ai_move, 0, NULL,
366: ai_move, 0, NULL,
367: ai_move, 0, NULL,
368: ai_move, 0, NULL,
369: ai_move, 0, NULL,
370: ai_move, 0, NULL,
371: ai_move, 0, NULL,
372: ai_move, 0, NULL,
373: ai_move, 0, NULL,
374: ai_move, 0, NULL,
375: ai_move, 0, NULL,
376: ai_move, 0, NULL,
377: ai_move, 0, NULL,
378: ai_move, 0, NULL,
379: ai_move, 0, NULL,
380: ai_move, 0, NULL,
381: ai_move, 0, NULL,
382: ai_move, 0, NULL,
383: ai_move, 0, NULL,
384: ai_move, 0, NULL,
385: ai_move, 0, NULL,
386: ai_move, 0, NULL,
387: ai_move, 0, NULL,
388: ai_move, 0, NULL,
389: ai_move, 0, NULL,
390: ai_move, 0, NULL,
391: ai_move, 0, BossExplode
392: };
393: mmove_t boss2_move_death = {FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead};
394:
395: void boss2_stand (edict_t *self)
396: {
397: self->monsterinfo.currentmove = &boss2_move_stand;
398: }
399:
400: void boss2_run (edict_t *self)
401: {
402: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
403: self->monsterinfo.currentmove = &boss2_move_stand;
404: else
405: self->monsterinfo.currentmove = &boss2_move_run;
406: }
407:
408: void boss2_walk (edict_t *self)
409: {
410: self->monsterinfo.currentmove = &boss2_move_walk;
411: }
412:
413: void boss2_attack (edict_t *self)
414: {
415: vec3_t vec;
416: float range;
417:
418: VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
419: range = VectorLength (vec);
420:
421: if (range <= 125)
422: {
423: self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
424: }
425: else
426: {
427: if (random() <= 0.6)
428: self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
429: else
430: self->monsterinfo.currentmove = &boss2_move_attack_rocket;
431: }
432: }
433:
434: void boss2_attack_mg (edict_t *self)
435: {
436: self->monsterinfo.currentmove = &boss2_move_attack_mg;
437: }
438:
439: void boss2_reattack_mg (edict_t *self)
440: {
441: if ( infront(self, self->enemy) )
442: if (random() <= 0.7)
443: self->monsterinfo.currentmove = &boss2_move_attack_mg;
444: else
445: self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
446: else
447: self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
448: }
449:
450:
451: void boss2_pain (edict_t *self, edict_t *other, float kick, int damage)
452: {
453: if (self->health < (self->max_health / 2))
454: self->s.skinnum = 1;
455:
456: if (level.time < self->pain_debounce_time)
457: return;
458:
459: self->pain_debounce_time = level.time + 3;
460: // American wanted these at no attenuation
461: if (damage < 10)
462: {
463: gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
464: self->monsterinfo.currentmove = &boss2_move_pain_light;
465: }
466: else if (damage < 30)
467: {
468: gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
469: self->monsterinfo.currentmove = &boss2_move_pain_light;
470: }
471: else
472: {
473: gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
474: self->monsterinfo.currentmove = &boss2_move_pain_heavy;
475: }
476: }
477:
478: void boss2_dead (edict_t *self)
479: {
480: VectorSet (self->mins, -56, -56, 0);
481: VectorSet (self->maxs, 56, 56, 80);
482: self->movetype = MOVETYPE_TOSS;
483: self->svflags |= SVF_DEADMONSTER;
484: self->nextthink = 0;
485: gi.linkentity (self);
486: }
487:
488: void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
489: {
490: gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
491: self->deadflag = DEAD_DEAD;
492: self->takedamage = DAMAGE_NO;
493: self->count = 0;
494: self->monsterinfo.currentmove = &boss2_move_death;
495: #if 0
496: int n;
497:
498: self->s.sound = 0;
499: // check for gib
500: if (self->health <= self->gib_health)
501: {
502: gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
503: for (n= 0; n < 2; n++)
504: ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
505: for (n= 0; n < 4; n++)
506: ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
507: ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
508: self->deadflag = DEAD_DEAD;
509: return;
510: }
511:
512: if (self->deadflag == DEAD_DEAD)
513: return;
514:
515: self->deadflag = DEAD_DEAD;
516: self->takedamage = DAMAGE_YES;
517: self->monsterinfo.currentmove = &boss2_move_death;
518: #endif
519: }
520:
521: qboolean Boss2_CheckAttack (edict_t *self)
522: {
523: vec3_t spot1, spot2;
524: vec3_t temp;
525: float chance;
526: trace_t tr;
527: qboolean enemy_infront;
528: int enemy_range;
529: float enemy_yaw;
530:
531: if (self->enemy->health > 0)
532: {
533: // see if any entities are in the way of the shot
534: VectorCopy (self->s.origin, spot1);
535: spot1[2] += self->viewheight;
536: VectorCopy (self->enemy->s.origin, spot2);
537: spot2[2] += self->enemy->viewheight;
538:
539: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
540:
541: // do we have a clear shot?
542: if (tr.ent != self->enemy)
543: return false;
544: }
545:
546: enemy_infront = infront(self, self->enemy);
547: enemy_range = range(self, self->enemy);
548: VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
549: enemy_yaw = vectoyaw(temp);
550:
551: self->ideal_yaw = enemy_yaw;
552:
553:
554: // melee attack
555: if (enemy_range == RANGE_MELEE)
556: {
557: if (self->monsterinfo.melee)
558: self->monsterinfo.attack_state = AS_MELEE;
559: else
560: self->monsterinfo.attack_state = AS_MISSILE;
561: return true;
562: }
563:
564: // missile attack
565: if (!self->monsterinfo.attack)
566: return false;
567:
568: if (level.time < self->monsterinfo.attack_finished)
569: return false;
570:
571: if (enemy_range == RANGE_FAR)
572: return false;
573:
574: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
575: {
576: chance = 0.4;
577: }
578: else if (enemy_range == RANGE_MELEE)
579: {
580: chance = 0.8;
581: }
582: else if (enemy_range == RANGE_NEAR)
583: {
584: chance = 0.8;
585: }
586: else if (enemy_range == RANGE_MID)
587: {
588: chance = 0.8;
589: }
590: else
591: {
592: return false;
593: }
594:
595: if (random () < chance)
596: {
597: self->monsterinfo.attack_state = AS_MISSILE;
598: self->monsterinfo.attack_finished = level.time + 2*random();
599: return true;
600: }
601:
602: if (self->flags & FL_FLY)
603: {
604: if (random() < 0.3)
605: self->monsterinfo.attack_state = AS_SLIDING;
606: else
607: self->monsterinfo.attack_state = AS_STRAIGHT;
608: }
609:
610: return false;
611: }
612:
613:
614:
615: /*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight
616: */
617: void SP_monster_boss2 (edict_t *self)
618: {
619: if (deathmatch->value)
620: {
621: G_FreeEdict (self);
622: return;
623: }
624:
625: sound_pain1 = gi.soundindex ("bosshovr/bhvpain1.wav");
626: sound_pain2 = gi.soundindex ("bosshovr/bhvpain2.wav");
627: sound_pain3 = gi.soundindex ("bosshovr/bhvpain3.wav");
628: sound_death = gi.soundindex ("bosshovr/bhvdeth1.wav");
629: sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
630:
631: self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
632:
633: self->movetype = MOVETYPE_STEP;
634: self->solid = SOLID_BBOX;
635: self->s.modelindex = gi.modelindex ("models/monsters/boss2/tris.md2");
636: VectorSet (self->mins, -56, -56, 0);
637: VectorSet (self->maxs, 56, 56, 80);
638:
639: self->health = 2000;
640: self->gib_health = -200;
641: self->mass = 1000;
642:
643: self->flags |= FL_IMMUNE_LASER;
644:
645: self->pain = boss2_pain;
646: self->die = boss2_die;
647:
648: self->monsterinfo.stand = boss2_stand;
649: self->monsterinfo.walk = boss2_walk;
650: self->monsterinfo.run = boss2_run;
651: self->monsterinfo.attack = boss2_attack;
652: self->monsterinfo.search = boss2_search;
653: self->monsterinfo.checkattack = Boss2_CheckAttack;
654: gi.linkentity (self);
655:
656: self->monsterinfo.currentmove = &boss2_move_stand;
657: self->monsterinfo.scale = MODEL_SCALE;
658:
659: flymonster_start (self);
660: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.