From 8c5c8b8fe0a3b4751006cb34b3eb163cc5c19b1e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 24 Aug 2026 23:10:30 +0200 Subject: [PATCH] feat(sim): projectiles, items and inventory --- game/sim/inventory.py | 31 +++++++++++++++++++++++++ game/sim/items.py | 51 ++++++++++++++++++++++++++++++++++++++++++ game/sim/projectile.py | 32 ++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 game/sim/inventory.py create mode 100644 game/sim/items.py create mode 100644 game/sim/projectile.py diff --git a/game/sim/inventory.py b/game/sim/inventory.py new file mode 100644 index 0000000..73131c6 --- /dev/null +++ b/game/sim/inventory.py @@ -0,0 +1,31 @@ +class Inventory: + def __init__(self): + self.counts = {} + self.keys = 0 + + def add(self, kind, amount=1): + self.counts[kind] = self.counts.get(kind, 0) + amount + if kind == "key": + self.keys += amount + return self.counts[kind] + + def count(self, kind): + return self.counts.get(kind, 0) + + def has(self, kind, amount=1): + return self.count(kind) >= amount + + def consume(self, kind, amount=1): + if self.count(kind) < amount: + return False + self.counts[kind] -= amount + if kind == "key": + self.keys -= amount + return True + + def asDict(self): + return {"counts": dict(self.counts), "keys": self.keys} + + def loadDict(self, record): + self.counts = dict(record.get("counts", {})) + self.keys = record.get("keys", 0) diff --git a/game/sim/items.py b/game/sim/items.py new file mode 100644 index 0000000..34ad313 --- /dev/null +++ b/game/sim/items.py @@ -0,0 +1,51 @@ +from ..constants import tileSize +from .geometry import Aabb + +itemDefs = { + "coin": {"score": 100, "size": 12, "bob": True}, + "gem": {"score": 500, "size": 12, "bob": True}, + "key": {"score": 50, "size": 12, "bob": False}, + "heart": {"score": 0, "size": 14, "bob": True, "heal": 2}, +} + + +class Collectible: + def __init__(self, kind, x, y): + self.kind = kind + self.x = float(x) + self.y = float(y) + self.collected = False + definition = itemDefs.get(kind, itemDefs["coin"]) + self.size = definition["size"] + self.score = definition["score"] + self.heal = definition.get("heal", 0) + self.bob = definition.get("bob", False) + self.bobPhase = 0.0 + + @property + def aabb(self): + pad = (tileSize - self.size) * 0.5 + return Aabb(self.x + pad, self.y + pad, self.size, self.size) + + def update(self): + if self.bob: + self.bobPhase = (self.bobPhase + 0.15) % 6.2831853 + + def collect(self, player, world): + if self.collected: + return + self.collected = True + player.score += self.score + world.score += self.score + if self.kind == "coin": + player.coins += 1 + world.coins += 1 + elif self.heal: + player.heal(self.heal) + player.inventory.add(self.kind) + world.playSound("pickup", self.x) + world.spawnBurst("pickup", self.x + tileSize * 0.5, self.y + tileSize * 0.5, 5) + + @classmethod + def fromSpawn(cls, spawn): + return cls(spawn.kind, spawn.x, spawn.y) diff --git a/game/sim/projectile.py b/game/sim/projectile.py new file mode 100644 index 0000000..f73a161 --- /dev/null +++ b/game/sim/projectile.py @@ -0,0 +1,32 @@ +from .. import constants +from .entity import Actor +from . import physics + + +class Projectile(Actor): + def __init__(self, x, y, vx, vy, owner="enemy", damage=1): + super().__init__(x, y, 6, 6) + self.vx = vx + self.vy = vy + self.owner = owner + self.damage = damage + self.life = constants.projectileLife + self.gravityScale = 0.0 + + def update(self, world): + self.life -= 1 + if self.life <= 0: + self.alive = False + return + if self.gravityScale: + self.vy += constants.gravity * self.gravityScale + self.x += self.vx + self.y += self.vy + box = self.aabb + for tileBox in physics.solidTilesOverlapping(world.level, box): + if box.intersects(tileBox): + self.alive = False + world.spawnBurst("spark", self.centerX, self.centerY, 4) + return + if self.centerX < -32 or self.centerX > world.level.pixelWidth() + 32: + self.alive = False