slop game oder so
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Waffenzustand: Magazin, Reserve, Feuerrate, Nachladen (ohne Phaser).
|
||||
* @module logic/weapon
|
||||
*/
|
||||
|
||||
import { degToRad } from './math.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} WeaponDef
|
||||
* @property {string} id
|
||||
* @property {string} name
|
||||
* @property {number} magazineSize
|
||||
* @property {number|null} reserveStart null = unbegrenzt
|
||||
* @property {number|null} reserveMax null = unbegrenzt
|
||||
* @property {number} fireIntervalMs
|
||||
* @property {number} reloadMs
|
||||
* @property {number} damage
|
||||
* @property {number} pellets
|
||||
* @property {number} spreadDeg
|
||||
* @property {number} bulletSpeed
|
||||
* @property {number} rangePx
|
||||
* @property {boolean} automatic
|
||||
*/
|
||||
|
||||
export class WeaponState {
|
||||
/** @param {WeaponDef} def */
|
||||
constructor(def) {
|
||||
this.def = def;
|
||||
this.magazine = def.magazineSize;
|
||||
/** @type {number} Bei unbegrenzter Reserve Infinity. */
|
||||
this.reserve = def.reserveStart === null ? Infinity : def.reserveStart;
|
||||
/** Spielzeit, zu der das Nachladen fertig ist; 0 = lädt nicht nach. */
|
||||
this.reloadingUntil = 0;
|
||||
this.reloadStartedAt = 0;
|
||||
this.lastShotAt = -Infinity;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
get hasInfiniteReserve() {
|
||||
return this.def.reserveMax === null;
|
||||
}
|
||||
|
||||
/** @returns {boolean} Weder Magazin noch Reserve – Waffe ist leer. */
|
||||
get isEmpty() {
|
||||
return this.magazine === 0 && this.reserve === 0;
|
||||
}
|
||||
|
||||
/** @returns {boolean} */
|
||||
get isReloading() {
|
||||
return this.reloadingUntil > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schließt ein fälliges Nachladen ab. Muss jeden Frame aufgerufen werden.
|
||||
* @param {number} now
|
||||
* @returns {boolean} true, wenn in diesem Aufruf fertig nachgeladen wurde.
|
||||
*/
|
||||
update(now) {
|
||||
if (this.isReloading && now >= this.reloadingUntil) {
|
||||
const needed = this.def.magazineSize - this.magazine;
|
||||
const taken = Math.min(needed, this.reserve);
|
||||
this.magazine += taken;
|
||||
this.reserve -= taken;
|
||||
this.reloadingUntil = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} now
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canFire(now) {
|
||||
return !this.isReloading && this.magazine > 0 && now - this.lastShotAt >= this.def.fireIntervalMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feuert einen Schuss, falls möglich.
|
||||
* @param {number} now
|
||||
* @param {() => number} rng Zufall für die Streuung.
|
||||
* @returns {number[]|null} Winkelabweichungen der Projektile (Bogenmaß) oder null.
|
||||
*/
|
||||
fire(now, rng) {
|
||||
if (!this.canFire(now)) return null;
|
||||
this.magazine -= 1;
|
||||
this.lastShotAt = now;
|
||||
const offsets = pelletOffsets(this.def.pellets, this.def.spreadDeg, rng);
|
||||
// Leeres Magazin lädt automatisch nach – niemand will im Gefecht R suchen.
|
||||
if (this.magazine === 0) this.startReload(now);
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beginnt das Nachladen.
|
||||
* @param {number} now
|
||||
* @returns {boolean} false, wenn es nichts nachzuladen gibt.
|
||||
*/
|
||||
startReload(now) {
|
||||
if (this.isReloading || this.magazine >= this.def.magazineSize || this.reserve <= 0) {
|
||||
return false;
|
||||
}
|
||||
this.reloadStartedAt = now;
|
||||
this.reloadingUntil = now + this.def.reloadMs;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Bricht das Nachladen ab (z. B. beim Waffenwechsel); nichts wird verbraucht. */
|
||||
cancelReload() {
|
||||
this.reloadingUntil = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} now
|
||||
* @returns {number} 0..1, 1 wenn nicht nachgeladen wird.
|
||||
*/
|
||||
reloadProgress(now) {
|
||||
if (!this.isReloading) return 1;
|
||||
return Math.min(1, (now - this.reloadStartedAt) / this.def.reloadMs);
|
||||
}
|
||||
|
||||
/** Füllt die Reserve bis zum Maximum auf (Nachschubkapsel). */
|
||||
refill() {
|
||||
if (!this.hasInfiniteReserve) {
|
||||
this.reserve = /** @type {number} */ (this.def.reserveMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Streuung der Projektile eines Schusses. Mehrere Projektile (Schrot) werden
|
||||
* gleichmäßig über den Fächer verteilt und leicht verwackelt, damit sich das Muster
|
||||
* nicht wiederholt; ein Einzelprojektil streut zufällig innerhalb des Fächers.
|
||||
* @param {number} pellets
|
||||
* @param {number} spreadDeg
|
||||
* @param {() => number} rng
|
||||
* @returns {number[]}
|
||||
*/
|
||||
export function pelletOffsets(pellets, spreadDeg, rng) {
|
||||
const spread = degToRad(spreadDeg);
|
||||
if (pellets <= 1) {
|
||||
return [(rng() - 0.5) * spread];
|
||||
}
|
||||
const step = spread / (pellets - 1);
|
||||
return Array.from({ length: pellets }, (_, i) => -spread / 2 + i * step + (rng() - 0.5) * step * 0.5);
|
||||
}
|
||||
Reference in New Issue
Block a user