slop game oder so

This commit is contained in:
2026-09-24 17:57:54 +02:00
commit 4c997eee21
166 changed files with 14976 additions and 0 deletions
+180
View File
@@ -0,0 +1,180 @@
/**
* Präsident Maximilian Valor – die Spielfigur.
*
* Die Figur ist frei erfunden: Raumrüstung mit geschlossenem Helm (kein Gesicht,
* keine Frisur) und Umhang in den Farben der fiktiven Vereinigten Erde.
* @module entities/Player
*/
import { GAME, PLAYER, WEAPONS } from '../config.js';
import { fromAngle, scale } from '../logic/math.js';
import { isNearlyZero } from '../logic/steering.js';
import { WeaponState } from '../logic/weapon.js';
import { TEXTURE_KEYS } from '../systems/Placeholders.js';
export class Player extends Phaser.Physics.Arcade.Sprite {
/**
* @param {Phaser.Scene} scene
* @param {number} x
* @param {number} y
*/
constructor(scene, x, y) {
super(scene, x, y, TEXTURE_KEYS.player);
scene.add.existing(this);
scene.physics.add.existing(this);
this.setDepth(GAME.depth.player);
this.setCollideWorldBounds(true);
const r = PLAYER.radius;
this.body.setCircle(r, this.width / 2 - r, this.height / 2 - r);
this.maxHp = PLAYER.maxHp;
this.hp = PLAYER.maxHp;
this.alive = true;
this.weapons = PLAYER.weaponSlots.map((id) => new WeaponState(WEAPONS[id]));
this.weaponIndex = 0;
this.aimAngle = 0;
this.dodgeUntil = 0;
this.dodgeReadyAt = 0;
this.dodgeDirection = { x: 1, y: 0 };
this.hitInvulnerableUntil = 0;
}
/** @returns {WeaponState} */
get weapon() {
return this.weapons[this.weaponIndex];
}
/** @param {number} now @returns {boolean} */
isDodging(now) {
return now < this.dodgeUntil;
}
/**
* Während der Rolle und kurz nach einem Treffer nimmt der Präsident keinen Schaden.
* @param {number} now
* @returns {boolean}
*/
isInvulnerable(now) {
return !this.alive || this.isDodging(now) || now < this.hitInvulnerableUntil;
}
/**
* @param {number} now
* @returns {number} Bereitschaft der Rolle, 0..1 (1 = bereit).
*/
dodgeReadiness(now) {
if (now >= this.dodgeReadyAt) return 1;
return 1 - (this.dodgeReadyAt - now) / PLAYER.dodge.cooldownMs;
}
/**
* Startet eine Ausweichrolle in Bewegungsrichtung (ohne Bewegung: in Blickrichtung).
* @param {number} now
* @param {{x: number, y: number}} moveDirection Normalisiert.
* @returns {boolean} true, wenn die Rolle ausgelöst wurde.
*/
tryDodge(now, moveDirection) {
if (!this.alive || now < this.dodgeReadyAt) return false;
this.dodgeDirection = isNearlyZero(moveDirection) ? fromAngle(this.aimAngle) : moveDirection;
this.dodgeUntil = now + PLAYER.dodge.durationMs;
this.dodgeReadyAt = now + PLAYER.dodge.cooldownMs;
return true;
}
/**
* Bewegung für diesen Frame.
* @param {number} now
* @param {{x: number, y: number}} moveDirection Normalisiert.
* @param {boolean} blocked true, solange ein Orbital-Befehl eingegeben wird.
*/
move(now, moveDirection, blocked) {
if (!this.alive) {
this.setVelocity(0, 0);
return;
}
if (this.isDodging(now)) {
const v = scale(this.dodgeDirection, PLAYER.dodge.speed);
this.setVelocity(v.x, v.y);
this.setAlpha(0.55);
return;
}
this.setAlpha(1);
const v = blocked ? { x: 0, y: 0 } : scale(moveDirection, PLAYER.speed);
this.setVelocity(v.x, v.y);
}
/**
* Richtet die Figur auf einen Punkt in Weltkoordinaten aus.
* @param {number} x
* @param {number} y
*/
aimAt(x, y) {
this.aimAngle = Math.atan2(y - this.y, x - this.x);
this.setRotation(this.aimAngle);
}
/** @returns {{x: number, y: number}} Position der Waffenmündung. */
muzzlePosition() {
const reach = PLAYER.radius + 8;
return {
x: this.x + Math.cos(this.aimAngle) * reach,
y: this.y + Math.sin(this.aimAngle) * reach,
};
}
/**
* @param {number} index
* @returns {boolean} true, wenn gewechselt wurde.
*/
switchWeapon(index) {
if (index === this.weaponIndex || !this.weapons[index]) return false;
// Nachladen bricht beim Wechsel ab – sonst könnte man durch Wechseln „heimlich“ laden.
this.weapon.cancelReload();
this.weaponIndex = index;
return true;
}
/**
* @param {1|-1} step
* @returns {boolean}
*/
cycleWeapon(step) {
const count = this.weapons.length;
return this.switchWeapon((this.weaponIndex + step + count) % count);
}
/**
* Fügt Schaden zu, sofern der Präsident nicht gerade unverwundbar ist.
* @param {number} amount
* @param {number} now
* @returns {number} Tatsächlich erlittener Schaden.
*/
takeDamage(amount, now) {
if (this.isInvulnerable(now) || amount <= 0) return 0;
const dealt = Math.min(this.hp, amount);
this.hp -= dealt;
this.hitInvulnerableUntil = now + PLAYER.hitInvulnerabilityMs;
if (this.hp <= 0) {
this.alive = false;
this.setVelocity(0, 0);
}
return dealt;
}
/** @param {number} amount @returns {number} Tatsächlich geheilte Punkte. */
heal(amount) {
const before = this.hp;
this.hp = Math.min(this.maxHp, this.hp + amount);
return this.hp - before;
}
/** Füllt die Reserve aller Waffen auf (Nachschubkapsel). */
refillAmmo() {
for (const weapon of this.weapons) weapon.refill();
}
/** @param {number} now @returns {boolean} true, wenn die aktive Waffe fertig nachgeladen hat. */
updateWeapons(now) {
return this.weapon.update(now);
}
}