quake2ts 0.0.186 → 0.0.188

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2208,15 +2208,16 @@ function checkTriggers(ent, system) {
2208
2208
  if (ent.movetype === 0 /* None */) {
2209
2209
  return;
2210
2210
  }
2211
- system.forEachEntity((other) => {
2212
- if (other === ent) return;
2213
- if (other.solid !== 1 /* Trigger */) return;
2214
- if (!other.touch) return;
2215
- if (ent.absmax.x < other.absmin.x || ent.absmin.x > other.absmax.x) return;
2216
- if (ent.absmax.y < other.absmin.y || ent.absmin.y > other.absmax.y) return;
2217
- if (ent.absmax.z < other.absmin.z || ent.absmin.z > other.absmax.z) return;
2211
+ const candidates = system.findInBox(ent.absmin, ent.absmax);
2212
+ for (const other of candidates) {
2213
+ if (other === ent) continue;
2214
+ if (other.solid !== 1 /* Trigger */) continue;
2215
+ if (!other.touch) continue;
2216
+ if (ent.absmax.x < other.absmin.x || ent.absmin.x > other.absmax.x) continue;
2217
+ if (ent.absmax.y < other.absmin.y || ent.absmin.y > other.absmax.y) continue;
2218
+ if (ent.absmax.z < other.absmin.z || ent.absmin.z > other.absmax.z) continue;
2218
2219
  other.touch(other, ent);
2219
- });
2220
+ }
2220
2221
  }
2221
2222
 
2222
2223
  // src/physics/movement.ts
@@ -2536,6 +2537,9 @@ var EntityPool = class {
2536
2537
  }
2537
2538
  return count;
2538
2539
  }
2540
+ getByIndex(index) {
2541
+ return this.entities[index];
2542
+ }
2539
2543
  [Symbol.iterator]() {
2540
2544
  let current = this.activeHead;
2541
2545
  return {
@@ -2797,7 +2801,7 @@ var EntitySystem = class {
2797
2801
  this.thinkScheduler = new ThinkScheduler();
2798
2802
  this.engine = engine;
2799
2803
  this.deathmatch = deathmatch ?? false;
2800
- this.imports = imports || {
2804
+ const defaultImports = {
2801
2805
  trace: () => ({
2802
2806
  allsolid: false,
2803
2807
  startsolid: false,
@@ -2821,11 +2825,14 @@ var EntitySystem = class {
2821
2825
  z: ent.origin.z + ent.maxs.z
2822
2826
  };
2823
2827
  },
2828
+ areaEdicts: () => null,
2829
+ // Default to null to signal fallback
2824
2830
  multicast: () => {
2825
2831
  },
2826
2832
  unicast: () => {
2827
2833
  }
2828
2834
  };
2835
+ this.imports = { ...defaultImports, ...imports };
2829
2836
  this.gravity = gravity || { x: 0, y: 0, z: 0 };
2830
2837
  this.callbackToName = /* @__PURE__ */ new Map();
2831
2838
  if (callbackRegistry) {
@@ -2943,14 +2950,37 @@ var EntitySystem = class {
2943
2950
  }
2944
2951
  return Array.from(matches).filter((entity) => entity.inUse && !entity.freePending);
2945
2952
  }
2953
+ findInBox(mins, maxs) {
2954
+ const indices = this.imports.areaEdicts(mins, maxs);
2955
+ if (indices === null) {
2956
+ const results2 = [];
2957
+ const bounds = { min: mins, max: maxs };
2958
+ for (const entity of this.pool) {
2959
+ if (!entity.inUse || entity.freePending || entity.solid === 0 /* Not */) continue;
2960
+ if (boundsIntersect(bounds, computeBounds(entity))) {
2961
+ results2.push(entity);
2962
+ }
2963
+ }
2964
+ return results2;
2965
+ }
2966
+ const results = [];
2967
+ for (const index of indices) {
2968
+ const entity = this.pool.getByIndex(index);
2969
+ if (entity && entity.inUse && !entity.freePending) {
2970
+ results.push(entity);
2971
+ }
2972
+ }
2973
+ return results;
2974
+ }
2946
2975
  findByRadius(origin, radius) {
2976
+ const mins = { x: origin.x - radius, y: origin.y - radius, z: origin.z - radius };
2977
+ const maxs = { x: origin.x + radius, y: origin.y + radius, z: origin.z + radius };
2978
+ const candidates = this.findInBox(mins, maxs);
2947
2979
  const matches = [];
2948
- for (const entity of this.pool) {
2949
- if (entity.inUse && !entity.freePending) {
2950
- const distance2 = lengthVec3(subtractVec3(origin, entity.origin));
2951
- if (distance2 <= radius) {
2952
- matches.push(entity);
2953
- }
2980
+ for (const entity of candidates) {
2981
+ const distance2 = lengthVec3(subtractVec3(origin, entity.origin));
2982
+ if (distance2 <= radius) {
2983
+ matches.push(entity);
2954
2984
  }
2955
2985
  }
2956
2986
  return matches;
@@ -3160,35 +3190,19 @@ var EntitySystem = class {
3160
3190
  const world = this.pool.world;
3161
3191
  const activeEntities = [];
3162
3192
  for (const entity of this.pool) {
3163
- if (entity === world) {
3164
- continue;
3165
- }
3166
- if (!entity.inUse || entity.freePending || entity.solid === 0 /* Not */) {
3167
- continue;
3168
- }
3193
+ if (entity === world) continue;
3194
+ if (!entity.inUse || entity.freePending || entity.solid === 0 /* Not */) continue;
3169
3195
  activeEntities.push(entity);
3170
3196
  }
3171
- for (let i = 0; i < activeEntities.length; i += 1) {
3172
- const first = activeEntities[i];
3173
- let firstBounds = null;
3174
- for (let j = i + 1; j < activeEntities.length; j += 1) {
3175
- const second = activeEntities[j];
3176
- if (!first.touch && !second.touch) {
3177
- continue;
3178
- }
3179
- if (!firstBounds) {
3180
- firstBounds = computeBounds(first);
3181
- }
3197
+ for (const first of activeEntities) {
3198
+ const candidates = this.findInBox(first.absmin, first.absmax);
3199
+ const firstBounds = computeBounds(first);
3200
+ for (const second of candidates) {
3201
+ if (first === second) continue;
3202
+ if (!first.touch) continue;
3182
3203
  const secondBounds = computeBounds(second);
3183
- if (!boundsIntersect(firstBounds, secondBounds)) {
3184
- continue;
3185
- }
3186
- if (first.touch) {
3187
- first.touch(first, second);
3188
- }
3189
- if (second.touch) {
3190
- second.touch(second, first);
3191
- }
3204
+ if (!boundsIntersect(firstBounds, secondBounds)) continue;
3205
+ first.touch(first, second);
3192
3206
  }
3193
3207
  }
3194
3208
  }
@@ -9017,9 +9031,11 @@ function registerMakronSpawns(registry) {
9017
9031
  // src/entities/monsters/medic.ts
9018
9032
  var MONSTER_TICK15 = 0.1;
9019
9033
  function monster_ai_stand15(self, dist, context) {
9020
- if (medic_find_dead(self, context)) {
9021
- self.monsterinfo.current_move = run_move13;
9022
- return;
9034
+ if (self.classname === "monster_medic") {
9035
+ if (medic_find_dead(self, context)) {
9036
+ self.monsterinfo.current_move = run_move13;
9037
+ return;
9038
+ }
9023
9039
  }
9024
9040
  ai_stand(self, MONSTER_TICK15, context);
9025
9041
  }
@@ -9027,8 +9043,12 @@ function monster_ai_walk15(self, dist, context) {
9027
9043
  ai_walk(self, dist, MONSTER_TICK15, context);
9028
9044
  }
9029
9045
  function monster_ai_run14(self, dist, context) {
9030
- if (medic_find_dead(self, context)) {
9031
- self.monsterinfo.current_move = run_move13;
9046
+ if (self.classname === "monster_medic") {
9047
+ if (medic_find_dead(self, context)) {
9048
+ self.monsterinfo.current_move = run_move13;
9049
+ } else {
9050
+ ai_run(self, dist, MONSTER_TICK15, context);
9051
+ }
9032
9052
  } else {
9033
9053
  ai_run(self, dist, MONSTER_TICK15, context);
9034
9054
  }
@@ -9044,6 +9064,7 @@ var walk_move13;
9044
9064
  var run_move13;
9045
9065
  var attack_hyper_move;
9046
9066
  var attack_cable_move;
9067
+ var spawn_move;
9047
9068
  var pain_move8;
9048
9069
  var death_move12;
9049
9070
  function medic_stand(self) {
@@ -9053,11 +9074,13 @@ function medic_walk(self) {
9053
9074
  self.monsterinfo.current_move = walk_move13;
9054
9075
  }
9055
9076
  function medic_run(self) {
9056
- if (self.enemy && self.enemy.deadflag === 2 /* Dead */) {
9057
- const dist = rangeTo(self, self.enemy);
9058
- if (dist < 80) {
9059
- self.monsterinfo.current_move = attack_cable_move;
9060
- return;
9077
+ if (self.classname === "monster_medic") {
9078
+ if (self.enemy && self.enemy.deadflag === 2 /* Dead */) {
9079
+ const dist = rangeTo(self, self.enemy);
9080
+ if (dist < 80) {
9081
+ self.monsterinfo.current_move = attack_cable_move;
9082
+ return;
9083
+ }
9061
9084
  }
9062
9085
  }
9063
9086
  if (self.enemy && self.enemy.health > 0) {
@@ -9067,10 +9090,16 @@ function medic_run(self) {
9067
9090
  }
9068
9091
  }
9069
9092
  function medic_attack(self) {
9070
- if (self.enemy && self.enemy.deadflag === 2 /* Dead */) {
9093
+ if (self.classname === "monster_medic" && self.enemy && self.enemy.deadflag === 2 /* Dead */) {
9071
9094
  self.monsterinfo.current_move = attack_cable_move;
9072
9095
  return;
9073
9096
  }
9097
+ if (self.classname === "monster_medic_commander") {
9098
+ if (Math.random() < 0.2) {
9099
+ self.monsterinfo.current_move = spawn_move;
9100
+ return;
9101
+ }
9102
+ }
9074
9103
  self.monsterinfo.current_move = attack_hyper_move;
9075
9104
  }
9076
9105
  function medic_fire_blaster(self, context) {
@@ -9109,7 +9138,6 @@ function medic_cable_attack(self, context) {
9109
9138
  te: TempEntity.MEDIC_CABLE_ATTACK,
9110
9139
  entId: self.index,
9111
9140
  targetId: self.enemy.index,
9112
- // Assuming targetId for the beam target
9113
9141
  start,
9114
9142
  end
9115
9143
  });
@@ -9137,14 +9165,13 @@ function medic_hook_retract(self, context) {
9137
9165
  if (ent.monsterinfo && ent.monsterinfo.stand) {
9138
9166
  ent.monsterinfo.stand(ent, context);
9139
9167
  }
9168
+ ent.bad_medic = self;
9140
9169
  } else {
9141
9170
  const spawnContext = {
9142
9171
  entities: context,
9143
9172
  keyValues: { classname: ent.classname },
9144
- // Minimal keyvalues
9145
9173
  warn: (msg) => {
9146
9174
  },
9147
- // Suppress warnings
9148
9175
  free: (e) => context.free(e)
9149
9176
  };
9150
9177
  const origin = { ...ent.origin };
@@ -9192,6 +9219,44 @@ function medic_find_dead(self, context) {
9192
9219
  }
9193
9220
  return false;
9194
9221
  }
9222
+ function medic_call_reinforcements(self, context) {
9223
+ const r = Math.random();
9224
+ let chosenClass = "monster_soldier_light";
9225
+ if (r > 0.8) {
9226
+ chosenClass = "monster_soldier_ssg";
9227
+ } else if (r > 0.5) {
9228
+ chosenClass = "monster_soldier";
9229
+ }
9230
+ const spawnFunc = context.getSpawnFunction(chosenClass);
9231
+ if (spawnFunc) {
9232
+ const vectors = angleVectors(self.angles);
9233
+ const forwardDist = scaleVec3(vectors.forward, 64);
9234
+ const spawnOrigin = addVec3(self.origin, forwardDist);
9235
+ const adjustedOrigin = { ...spawnOrigin, z: spawnOrigin.z + 8 };
9236
+ const tr = context.trace(self.origin, { x: -16, y: -16, z: -24 }, { x: 16, y: 16, z: 32 }, adjustedOrigin, self, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER);
9237
+ if (tr.fraction < 1 || tr.startsolid || tr.allsolid) {
9238
+ return;
9239
+ }
9240
+ const ent = context.spawn();
9241
+ ent.origin = adjustedOrigin;
9242
+ ent.angles = { ...self.angles };
9243
+ const spawnContext = {
9244
+ entities: context,
9245
+ keyValues: { classname: chosenClass },
9246
+ warn: () => {
9247
+ },
9248
+ free: (e) => context.free(e)
9249
+ };
9250
+ spawnFunc(ent, spawnContext);
9251
+ ent.enemy = self.enemy;
9252
+ context.multicast(adjustedOrigin, 1 /* Pvs */, ServerCommand.muzzleflash, {
9253
+ entId: ent.index,
9254
+ flash_number: 0
9255
+ // generic flash
9256
+ });
9257
+ context.engine.sound?.(self, 0, "medic/medatck2.wav", 1, 1, 0);
9258
+ }
9259
+ }
9195
9260
  function medic_die(self) {
9196
9261
  self.monsterinfo.current_move = death_move12;
9197
9262
  }
@@ -9253,8 +9318,6 @@ var attack_cable_frames = [
9253
9318
  ];
9254
9319
  attack_cable_move = {
9255
9320
  firstframe: 106,
9256
- // FRAME_attack42 (starts at 0+30+40+20+16 = 106) ... wait, check original counts.
9257
- // stand=30, walk=40, run=20, hyper=16. 30+40+20+16 = 106. Correct.
9258
9321
  lastframe: 114,
9259
9322
  frames: attack_cable_frames,
9260
9323
  endfunc: medic_run
@@ -9269,13 +9332,25 @@ pain_move8 = {
9269
9332
  frames: pain_frames8,
9270
9333
  endfunc: medic_run
9271
9334
  };
9272
- var death_frames12 = Array.from({ length: 10 }, () => ({
9335
+ var spawn_frames = Array.from({ length: 23 }, (_, i) => ({
9336
+ ai: monster_ai_move15,
9337
+ dist: 0,
9338
+ think: i === 11 ? medic_call_reinforcements : null
9339
+ // Trigger halfway
9340
+ }));
9341
+ spawn_move = {
9342
+ firstframe: 122,
9343
+ lastframe: 144,
9344
+ frames: spawn_frames,
9345
+ endfunc: medic_run
9346
+ };
9347
+ var death_frames12 = Array.from({ length: 30 }, () => ({
9273
9348
  ai: monster_ai_move15,
9274
9349
  dist: 0
9275
9350
  }));
9276
9351
  death_move12 = {
9277
- firstframe: 122,
9278
- lastframe: 131,
9352
+ firstframe: 161,
9353
+ lastframe: 190,
9279
9354
  frames: death_frames12,
9280
9355
  endfunc: medic_dead
9281
9356
  };
@@ -9293,6 +9368,10 @@ function SP_monster_medic(self, context) {
9293
9368
  self.pain = (self2, other, kick, damage) => {
9294
9369
  if (self2.health < self2.max_health / 2) {
9295
9370
  self2.monsterinfo.current_move = pain_move8;
9371
+ if (Math.random() < 0.5) {
9372
+ const sound = self2.classname === "monster_medic_commander" ? "medic/medpain2.wav" : "medic/medpain1.wav";
9373
+ context.entities.sound?.(self2, 0, sound, 1, 1, 0);
9374
+ }
9296
9375
  }
9297
9376
  };
9298
9377
  self.die = (self2, inflictor, attacker, damage, point) => {
@@ -9313,8 +9392,16 @@ function SP_monster_medic(self, context) {
9313
9392
  medic_stand(self);
9314
9393
  self.nextthink = self.timestamp + MONSTER_TICK15;
9315
9394
  }
9395
+ function SP_monster_medic_commander(self, context) {
9396
+ SP_monster_medic(self, context);
9397
+ self.classname = "monster_medic_commander";
9398
+ self.health = 600;
9399
+ self.max_health = 600;
9400
+ self.skin = 1;
9401
+ }
9316
9402
  function registerMedicSpawns(registry) {
9317
9403
  registry.register("monster_medic", SP_monster_medic);
9404
+ registry.register("monster_medic_commander", SP_monster_medic_commander);
9318
9405
  }
9319
9406
 
9320
9407
  // src/entities/monsters/mutant.ts
@@ -12780,13 +12867,29 @@ var WEAPONS = {
12780
12867
 
12781
12868
  // src/index.ts
12782
12869
  var ZERO_VEC38 = { x: 0, y: 0, z: 0 };
12783
- function createGame({ trace, pointcontents, multicast, unicast }, engine, options) {
12870
+ function createGame(imports, engine, options) {
12784
12871
  const gravity = options.gravity;
12785
12872
  const deathmatch = options.deathmatch ?? false;
12786
12873
  const levelClock = new LevelClock();
12787
12874
  const frameLoop = new GameFrameLoop();
12788
12875
  const rng = options.random ?? new RandomGenerator();
12789
- const linkentity = (ent) => {
12876
+ const trace = imports.trace || (() => ({
12877
+ allsolid: false,
12878
+ startsolid: false,
12879
+ fraction: 1,
12880
+ endpos: { x: 0, y: 0, z: 0 },
12881
+ plane: null,
12882
+ surfaceFlags: 0,
12883
+ contents: 0,
12884
+ ent: null
12885
+ }));
12886
+ const pointcontents = imports.pointcontents || (() => 0);
12887
+ const multicast = imports.multicast || (() => {
12888
+ });
12889
+ const unicast = imports.unicast || (() => {
12890
+ });
12891
+ const linkentity = imports.linkentity;
12892
+ const wrappedLinkEntity = (ent) => {
12790
12893
  ent.absmin = {
12791
12894
  x: ent.origin.x + ent.mins.x,
12792
12895
  y: ent.origin.y + ent.mins.y,
@@ -12797,8 +12900,19 @@ function createGame({ trace, pointcontents, multicast, unicast }, engine, option
12797
12900
  y: ent.origin.y + ent.maxs.y,
12798
12901
  z: ent.origin.z + ent.maxs.z
12799
12902
  };
12903
+ if (linkentity) {
12904
+ linkentity(ent);
12905
+ }
12906
+ };
12907
+ const systemImports = {
12908
+ ...imports,
12909
+ trace,
12910
+ pointcontents,
12911
+ linkentity: wrappedLinkEntity,
12912
+ multicast,
12913
+ unicast
12800
12914
  };
12801
- const entities = new EntitySystem(engine, { trace, pointcontents, linkentity, multicast, unicast }, gravity, void 0, void 0, deathmatch);
12915
+ const entities = new EntitySystem(engine, systemImports, gravity, void 0, void 0, deathmatch);
12802
12916
  frameLoop.addStage("prep", (context) => {
12803
12917
  levelClock.tick(context);
12804
12918
  entities.beginFrame(levelClock.current.timeSeconds);