From b08a2a37156418cb300e3e2b760a0d84ad4ec18d Mon Sep 17 00:00:00 2001 From: Halica Date: Thu, 24 Sep 2026 01:51:02 +0200 Subject: [PATCH] boss token and diary added diary, sprites for projectile and token, attack hitbox changes, added refightability of bosses --- pygame test/README.md | 133 +++ .../__pycache__/pygame_test.cpython-312.pyc | Bin 288134 -> 333006 bytes .../sprites/abilities/Ranged_projectile.png | Bin 0 -> 173 bytes .../data/sprites/tokens/Boss1_token.png | Bin 0 -> 212 bytes pygame test/data/world/rooms/room_01.json | 8 + .../world/rooms/room_boss_refight_01.json | 48 + .../world/rooms/room_town_gloomreach.json | 21 + pygame test/pygame_test.py | 979 ++++++++++++++++-- 8 files changed, 1085 insertions(+), 104 deletions(-) create mode 100644 pygame test/README.md create mode 100644 pygame test/data/sprites/abilities/Ranged_projectile.png create mode 100644 pygame test/data/sprites/tokens/Boss1_token.png create mode 100644 pygame test/data/world/rooms/room_boss_refight_01.json diff --git a/pygame test/README.md b/pygame test/README.md new file mode 100644 index 0000000..3481400 --- /dev/null +++ b/pygame test/README.md @@ -0,0 +1,133 @@ +# Light & Dark — Prototype + +A 2D platformer built with **Pygame**. You switch between two forms — **Light** and **Shade** — each with different movement, combat, and ability behavior, all driven by a single shared resource: **Charge (Flow)**. + +> Note: this is a prototype. All progress (quests, boss tokens, broken walls, defeated bosses) is **in-memory** and resets when you restart the game — intentional, for fast testing. + +--- + +## Running the game + +``` +python pygame_test.py +``` + +Requires Python 3.10+ and `pygame`. Game data (rooms, items, sets, enemies, drops) lives in `data/`. Rooms are edited with the included browser tool: `level_editor_v43.html`. + +--- + +## Controls + +| Action | Key | +|---|---| +| Move | A / D | +| Jump | W | +| Move down / drop | S | +| Heal | Space (hold) | +| Melee attack | E | +| Ranged shot | Q | +| Dash | Tab | +| Shatterstep | R | +| Tether | X | +| Reality Break | C | +| Toggle Light/Shade | Left Shift | +| Interact / Talk | F | +| Inventory | I | +| Stats overlay | H | +| Pause / Settings | Esc | + +--- + +## The two forms + +**Light** — faster movement, higher jump, and charge **refills** over time. You take contact damage from enemies. + +**Shade** — slower but tanky: **immune to enemy contact damage**, deals double melee damage, and can use shade-specific gear bonuses. But being in Shade **drains Charge** (and Shade is forced off when Charge hits 0). Shade also drains Charge **twice as fast while clipping inside an enemy**. + +**Charge (Flow)** — base max 100, refills at 20/s in Light, drains at 40/s in Shade. Standing near a **lantern/light source** doubles Charge regeneration. Attacks, dash (Light), ranged shots, and healing all cost Charge. + +--- + +## Abilities (by form) + +| Ability | Key | Light | Shade | +|---|---|---|---| +| **Melee** | E | 1 damage, costs 1 Charge | **2 damage**, costs 1 Charge | +| **Dash** | Tab | Costs 10 Charge; **stuns** enemies hit | Costs 1 HP; deals **4 damage** to enemies hit | +| **Ranged** | Q | Costs 5 Charge, 0.45s cooldown, 0.5 damage; **breaks green ability walls** | Same, but 1.0 damage | +| **Shatterstep** | R | Spawns a **platform under your feet** while airborne (extra jumps) | On landing, creates a damaging **shockwave AOE** below you | +| **Tether** | X | **Binds** the enemy (3s) and **fears** it (runs away) | **Insta-kills** enemies below a HP threshold (normal ⅓, miniboss 20%, boss 5%); otherwise **slows** them | +| **Reality Break** | C | Doubles drop & coin rates and glows enemies | **Instantly kills all enemies** but halves drop & coin rates | +| **Heal** | Space | Spend Charge to heal over ~2s (slower while healing) | Unavailable | + +--- + +## Combat notes + +- Enemy contact damage only applies in **Light** form. +- Boss **shockwave attacks poison** you for 4 seconds: the health bar turns green and you lose 1 HP per second. +- Attacks and hits use invincibility frames and knockback; your melee hitbox covers ~30% into your body so close/overlapping hits always connect. + +--- + +## Equipment & Armor Sets + +Slots: **helmet, chest, pants, necklace**. Pieces roll stats (move speed, heal, charge, health, etc.). + +**Rarities:** `tempered` (tier 1) → `attuned` (tier 2) → `awakened` (tier 3, unlocks ability slots). The **Upgrader** NPC turns 5 tempered copies of an item into 1 attuned copy. + +**Sets** (3 pieces of the same set, attuned or better): + +| Set | Summary | +|---|---| +| Dawnrunner | +HP; awakened adds Light move speed | +| Gravemark | +max Charge; awakened adds Charge regen | +| Vitalweave | +HP; awakened adds +heal | +| Duskwarrior | +Shade damage; awakened restores Charge on Shade hits | +| Twister | Removes Shade speed penalty; awakened gives move speed after switching forms | +| Paladin | Chance to negate incoming damage; awakened adds +HP | +| Luminal Growth | +Charge regen while standing still in Light; awakened also negates damage | +| Abyss Cleaver | Chance to heal on kill; awakened also gives move speed on kill | +| Umbraflow | Reduces Shade Charge drain | +| Adaptive Traveller | Move speed after switching forms; awakened restores Charge on switch | + +The shade-focused sets (**Duskwarrior, Umbraflow, Abyss Cleaver, Adaptive Traveller**) are intentionally **excluded** from early boss and town-shop loot, so you can always complete the remaining sets. + +--- + +## NPCs (Town 1: Gloomreach) + +- **Vendor** — sells basic armor pieces. +- **Necklace Vendor** — sells necklaces. +- **Upgrader** — attunes 5× tempered → attuned. +- **Combat Master** — upgrades your boss token to unlock the **Hard** boss variant. +- **Boss Diary** — an interactable book: view your collected boss tokens in a grid, slot/unslot them, and start refights. + +--- + +## Rooms & Progression + +- `room_01` — tutorial. Contains a **quest object** (pink): accept the **"First Kills"** quest (kill 4 normal enemies → 5–15 light coins, repeatable). +- `room_02` — first enemies. +- `room_07` — hub, branching to the boss, reward room, and town. +- `room_boss_01` — **Gloom Warden** fight. Defeating him drops his **boss token**. +- `room_reward_01` — reward chest + the **Ranged** ability, locked behind green ability walls. +- `room_town_gloomreach` — Town 1 (NPCs above). +- `room_boss_refight_01` — diary refight room (same arena, no exits until the boss dies or you die). + +--- + +## Boss Diary & Tokens + +1. Beat the world boss → collect his **token** (teal pickup). +2. Bring it to the diary in town and **slot** it (or keep it in your inventory). +3. Slotting unlocks **Fight (Normal)**. Upgrading the token at the Combat Master unlocks **Fight (Hard)** — the boss moves and shoots twice as fast. +4. Dying in a refight returns you to the diary. Removing a token from the diary sends it back to your inventory, disabling the fight until you re-slot it. + +--- + +## HUD & Stats + +- Top-right: health bar (turns **green while poisoned**), Charge bar, coin counts. +- Ability icons with cooldown indicators (top-left). +- **H** toggles a detailed overlay: exact HP/Charge, move speeds (both forms), heal duration, attack rate, Charge regen, the lantern buff, and any stat bonuses from gear. \ No newline at end of file diff --git a/pygame test/__pycache__/pygame_test.cpython-312.pyc b/pygame test/__pycache__/pygame_test.cpython-312.pyc index 1d7229544a760928b1e7494b9c76f56d094b7095..d4d36f84baacea1ca13e0fc9d53b46e5bd33819a 100644 GIT binary patch delta 110860 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%0D(koe9uNiMr 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 Ui*>KJsEp2*4DstNPyj^yU%sva_y7O^ delta 77903 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
j7*QsvY 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 diff --git a/pygame test/data/sprites/abilities/Ranged_projectile.png b/pygame test/data/sprites/abilities/Ranged_projectile.png new file mode 100644 index 0000000000000000000000000000000000000000..3f665995cbfc910e237dd772b52dd855db396f02 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJ5>FS$kcif|m-q5H7)ZE16cWsS zf5>-7RrBeWYHXUq7hj!s*P8a(N5vwaWhn#0kI2whj*BDBPL?*moAYcjbE!p!v<#A X3~i2O6 bool: + """True if an item (base stats or any rarity override) has a shade-only bonus.""" + if not isinstance(base_item, dict): + return False + def _has(stat_dict): + if not isinstance(stat_dict, dict): + return False + return any(stat_dict.get(k) for k in SHADE_STAT_KEYS) + if _has(base_item.get("base_stats")): + return True + for rdata in (base_item.get("rarity_overrides") or {}).values(): + if isinstance(rdata, dict) and _has(rdata.get("extra_stats")): + return True + return False + + +def item_set_has_shade_bonus(set_id: str) -> bool: + """True if ANY piece of the given set carries a shade-only bonus.""" + for it in ITEM_BY_ID.values(): + if isinstance(it, dict) and it.get("set") == set_id: + if item_has_shade_bonus(it): + return True + return False + + +def item_is_shade_restricted(base_item) -> bool: + """Restrict an item if it OR any other piece of its set has a shade-only bonus.""" + if not isinstance(base_item, dict): + return False + if item_has_shade_bonus(base_item): + return True + set_id = base_item.get("set") + return bool(set_id) and item_set_has_shade_bonus(set_id) + + def merge_stat_value(dst: dict, key: str, value): """Numeric stats add, boolean stats OR, everything else overwrites.""" if isinstance(value, bool): @@ -1071,6 +1121,31 @@ TETHER_SPRITE_DARK_RAW = _load_image_or_none(TETHER_SPRITE_DARK_PATH) TETHER_EDGE_LIGHT_RAW = _load_image_or_none(TETHER_EDGE_LIGHT_PATH) TETHER_EDGE_DARK_RAW = _load_image_or_none(TETHER_EDGE_DARK_PATH) + +def _load_first_existing(names: list[Path]): + for p in names: + if p.exists(): + img = _load_image_or_none(p) + if img is not None: + return img + return None + + +# Ranged projectile + boss token sprites (fallback to placeholder shapes if absent) +RANGED_PROJECTILE_SPRITE_RAW = _load_first_existing([ + ABILITY_SPRITES_DIR / "Ranged_projectile.png", + ABILITY_SPRITES_DIR / "ranged_projectile.png", + ABILITY_SPRITES_DIR / "ranged_bolt.png", +]) +TOKENS_DIR = DATA_ROOT / "sprites" / "tokens" +BOSS_TOKEN_SPRITE_RAW = _load_first_existing([ + TOKENS_DIR / "Boss1_token.png", + TOKENS_DIR / "boss_token.png", + OBJECTS_DIR / "Boss1_token.png", + OBJECTS_DIR / "boss_token.png", + OBJECTS_DIR / "Boss_Token.png", +]) + def _scale_icon(img: pygame.Surface | None, size: int) -> pygame.Surface | None: if img is None: return None @@ -1196,6 +1271,17 @@ class Projectile: return self.t > 0 def draw(self, surface, cx, cy): + if RANGED_PROJECTILE_SPRITE_RAW is not None: + img = RANGED_PROJECTILE_SPRITE_RAW + target = 22 + scale = target / max(1, img.get_width(), img.get_height()) + w = max(1, int(img.get_width() * scale)) + h = max(1, int(img.get_height() * scale)) + img = pygame.transform.scale(img, (w, h)) + if self.dir > 0: + img = pygame.transform.flip(img, True, False) + surface.blit(img, (self.rect.centerx - w // 2 - cx, self.rect.centery - h // 2 - cy)) + return # simple readable bolt pygame.draw.rect( surface, @@ -1681,7 +1767,9 @@ class TradeUI: self._try_trade() def _random_item(self, slot: str, rarity: str) -> dict | None: - pool = [it for it in ARMOR_DB if isinstance(it, dict) and it.get("slot") == slot] + # Town shops only sell light/general gear (no shade-only bonuses). + pool = [it for it in ARMOR_DB if isinstance(it, dict) and it.get("slot") == slot + and not item_is_shade_restricted(it)] if not pool: return None base = random.choice(pool) @@ -1689,6 +1777,21 @@ class TradeUI: return build_item_instance(base, use_rarity) def _build_options(self, npc: NPC | None) -> list[dict]: + # Combat Master: offer to upgrade each carried boss token + if npc is not None and npc.type == "combat_master": + options = [] + for it in self.inv.items: + if isinstance(it, dict) and it.get("item_type") == "boss_token": + tier = int(it.get("tier", 0)) + if tier >= 1: + continue + options.append({ + "type": "upgrade_token", + "boss_id": it.get("boss_id"), + "label": f"Upgrade {it.get('name', 'Token')} (Hard)", + }) + return options + offers = npc.offers if npc and npc.offers else [] if not offers: npc_type = npc.type if npc else "vendor" @@ -1792,6 +1895,27 @@ class TradeUI: self.cursor = max(0, min(self.cursor, len(self.options) - 1)) else: self.cursor = 0 + elif choice_type == "upgrade_token": + boss_id = choice.get("boss_id") + idx = None + for i, it in enumerate(self.inv.items): + if (isinstance(it, dict) and it.get("item_type") == "boss_token" + and it.get("boss_id") == boss_id and int(it.get("tier", 0)) == 0): + idx = i + break + if idx is None: + pickup_feed.push_plain("Token not found.") + return + token = dict(self.inv.items.pop(idx)) + token["tier"] = 1 + token["name"] = make_boss_token(boss_id, 1)["name"] + self.inv.add(token) + pickup_feed.push_plain(f"Upgraded to {token['name']}!") + self.options = self._build_options(self.active_npc) + if self.options: + self.cursor = max(0, min(self.cursor, len(self.options) - 1)) + else: + self.cursor = 0 elif choice_type == "sell_specific": item_id = choice.get("item_id") if not item_id: @@ -1889,6 +2013,18 @@ def spawn_drops_from_enemy(enemy) -> list[LootPickup]: elif (not pool) and default_pool == "necklace": pool = list(NECKLACE_BY_ID.keys()) + # Boss (first boss) drops only light/general gear; the miniboss keeps everything. + restrict_shade = (enemy.drop_profile == "boss") + + def _shade_ok(item_id): + base = safe_get_item(item_id) + return base is not None and not item_is_shade_restricted(base) + + def pick_from(source): + if restrict_shade: + source = [i for i in source if _shade_ok(i)] + return random.choice(source) if source else None + # ---- drop placement helper (prevents overlap) ---- spawned = 0 @@ -1919,17 +2055,13 @@ def spawn_drops_from_enemy(enemy) -> list[LootPickup]: pool_kind = g.get("item_pool", None) if not item_id: if pool_kind == "necklace": - if not NECKLACE_BY_ID: - continue - item_id = random.choice(list(NECKLACE_BY_ID.keys())) + item_id = pick_from(list(NECKLACE_BY_ID.keys())) elif pool_kind == "armor": - if not ARMOR_BY_ID: - continue - item_id = random.choice(list(ARMOR_BY_ID.keys())) + item_id = pick_from(list(ARMOR_BY_ID.keys())) else: - if not pool: - continue - item_id = random.choice(pool) + item_id = pick_from(pool) + if not item_id: + continue base = safe_get_item(item_id) if not base: print("DROP DEBUG: missing item ->", item_id) @@ -1953,7 +2085,9 @@ def spawn_drops_from_enemy(enemy) -> list[LootPickup]: if not pool: continue - item_id = random.choice(pool) + item_id = pick_from(pool) + if not item_id: + continue base = safe_get_item(item_id) if not base: print("DROP DEBUG: missing item ->", item_id) @@ -2444,6 +2578,33 @@ class InventoryUI: eq_y += self.line_h + int(_ui(["inventory_ui", "equipped", "line_gap"], 4)) + # --- Quests (below equipped) --- + quests_x = eq_x + quests_y = eq_y + int(_ui(["inventory_ui", "quests", "gap"], 8)) + quest_w = self.w - (quests_x - self.x) - self.pad + self._blit(surface, "Quests:", quests_x, quests_y, (220, 220, 220)) + quests_y += self.line_h + + active_ids = list(quest_manager.active.keys()) + max_q = int(_ui(["inventory_ui", "quests", "max_lines"], 4)) + shown = 0 + for qid in active_ids[:max_q]: + q = quest_manager.get(qid) + if q is None: + continue + st = quest_manager.active[qid] + prog = int(st.get("progress", 0)) + complete = prog >= q.target_count() + color = (150, 220, 150) if complete else (230, 230, 230) + label = self._ellipsize(f"{q.name}: {prog}/{q.target_count()}", max(0, quest_w)) + self._blit(surface, label, quests_x, quests_y, color) + quests_y += self.line_h + shown += 1 + if not active_ids: + self._blit(surface, "(no active quests)", quests_x, quests_y, (160, 160, 160)) + elif len(active_ids) > shown: + self._blit(surface, f"+{len(active_ids) - shown} more", quests_x, quests_y, (160, 160, 160)) + # --- Detail areas --- stats_h = int(_ui(["inventory_ui", "details", "stats_h"], 90)) hint_h = int(_ui(["inventory_ui", "details", "hint_h"], 24)) @@ -2852,6 +3013,7 @@ class Player: self.attacking = False self.attack_timer = 0.0 self.attack_duration = 0.2 + self.attack_cd = 0.0 self.facing_right = True self.damage_applied = False self.dash_cd = 0.0 @@ -2864,6 +3026,9 @@ class Player: self.jump_held_last = False # --- charge regen lockout (prevents instant refill after attacking) --- self.charge_regen_lock = 0.0 + self.charge_drain_mult = 1.0 # set per-frame (e.g. 2x when shade-clipping an enemy) + self.poison_timer = 0.0 + self.poison_acc = 0.0 self.bonus_move_speed_light_pct = 0.0 self.bonus_move_speed_shade_pct = 0.0 @@ -2920,9 +3085,12 @@ class Player: return HEAL_COST_FLAT / dur if dur > 0 else 999999.0 def effective_attack_duration(self) -> float: - # More attack_speed_pct => shorter duration - # clamp so it never becomes 0 or negative - return max(0.05, self.attack_duration / (1.0 + self.bonus_attack_speed_pct)) + # The swing itself is always the same length. Attack speed increases rate, not swing speed. + return float(self.attack_duration) + + def effective_attack_cooldown(self) -> float: + # Recovery between swings: attack_speed_pct reduces it -> faster attack rate. + return ATTACK_COOLDOWN_BASE / (1.0 + self.bonus_attack_speed_pct) def effective_heal_duration(self) -> float: @@ -3135,7 +3303,7 @@ class Player: def attack(self): if self.healing: return - if self.charge >= 1 and not self.attacking: + if self.charge >= 1 and not self.attacking and self.attack_cd <= 0.0: self.attacking = True self.attack_timer = self.effective_attack_duration() self.charge -= 1 @@ -3180,28 +3348,35 @@ class Player: return boxes[0] if boxes else None def get_attack_hitboxes(self): - """Wide-at-player, narrow-outward 'inverse cone' melee hitbox.""" + """Wide-at-player, narrow-outward 'inverse cone' melee hitbox. + + The near segment overlaps ~30% into the player's body so enemies you're + hugging (common in shade) still get hit; the far tip keeps its height so + it reliably connects at max reach. + """ if not self.attacking: return [] + body_overlap = int(self.rect.width * 0.3) # ~30% into the body + # Near (wide) segment w1 = 28 # wider close to player h1 = int(self.rect.height * 0.65) y1 = self.rect.centery - h1 // 2 - # Far (narrow) segment (+50% reach, but slimmer) - w2 = int(round(w1 * 0.5)) # +50% reach - h2 = int(round(h1 * 0.55)) # noticeably narrower + # Far (tip) segment: extends reach, slightly narrower but same height + w2 = 18 + h2 = h1 y2 = self.rect.centery - h2 // 2 if self.facing_right: - x1 = self.rect.right + x1 = self.rect.right - body_overlap x2 = self.rect.right + w1 else: x1 = self.rect.left - w1 x2 = self.rect.left - w1 - w2 - near = pygame.Rect(int(x1), int(y1), int(w1), int(h1)) + near = pygame.Rect(int(x1), int(y1), int(w1 + body_overlap), int(h1)) far = pygame.Rect(int(x2), int(y2), int(w2), int(h2)) return [near, far] @@ -3229,6 +3404,9 @@ class Player: return True + def apply_poison(self, duration: float): + self.poison_timer = max(self.poison_timer, float(duration)) + def apply_knockback(self, source_rect: pygame.Rect, strength_px_per_frame: float = 10.0, duration: float = 0.18, @@ -3266,6 +3444,9 @@ class Player: self.shade = False self.attacking = False self.attack_timer = 0.0 + self.attack_cd = 0.0 + self.poison_timer = 0.0 + self.poison_acc = 0.0 self.invincibility_timer = 0.0 self._cancel_heal() @@ -3466,7 +3647,7 @@ class Player: self._update_heal(keys, dt) if self.shade: - self.charge -= CHARGE_DRAIN_RATE * dt + self.charge -= CHARGE_DRAIN_RATE * getattr(self, "charge_drain_mult", 1.0) * dt if self.charge <= 0: self.charge = 0 self.shade = False @@ -3477,10 +3658,23 @@ class Player: self.charge = min(self.max_charge, self.charge + regen * dt) + if self.attack_cd > 0: + self.attack_cd = max(0.0, self.attack_cd - dt) + if self.attacking: self.attack_timer -= dt if self.attack_timer <= 0: self.attacking = False + self.attack_cd = self.effective_attack_cooldown() + + # Poison: 1 HP per second while active + if self.poison_timer > 0.0: + self.poison_timer = max(0.0, self.poison_timer - dt) + self.poison_acc += dt + if self.poison_acc >= 1.0: + ticks = int(self.poison_acc) + self.poison_acc -= float(ticks) + self.health = max(0, self.health - ticks) self._clamp_resources() @@ -4633,16 +4827,85 @@ ability_doors = spawn_ability_doors(ROOM) loot_drops: list[LootPickup] = [] shockwaves: list[Shockwave] = [] coin_drops: list[CoinPickup] = [] +token_drops: list = [] projectiles: list[Projectile] = [] shatter_platforms: list[ShatterPlatform] = [] shatter_aoes: list[ShatterAOE] = [] tether_shots: list[TetherShot] = [] light_sources: list[LightSource] = [] dust_puffs: list[DustPuff] = [] +class QuestGiver: + """Interactable environment object that hands out a quest.""" + INTERACT_RANGE = 70 + + def __init__(self, x: int, y: int, quest_id: str): + self.rect = pygame.Rect(int(x), int(y), 34, 40) + self.quest_id = str(quest_id) + self.bob_t = random.random() * 10.0 + + def draw(self, surface, cx, cy): + bob_y = int(math.sin(self.bob_t) * 2) + self.bob_t += 0.05 + x = int(self.rect.x - cx) + y = int(self.rect.y - cy + bob_y) + pygame.draw.rect(surface, (255, 130, 180), (x, y, self.rect.width, self.rect.height)) + pygame.draw.rect(surface, (160, 40, 90), (x, y, self.rect.width, self.rect.height), 2) + + +def get_near_quest_giver(player_obj, qg_list: list, range_px: int = QuestGiver.INTERACT_RANGE) -> QuestGiver | None: + best = None + best_d = None + for qg in qg_list: + dx = player_obj.rect.centerx - qg.rect.centerx + dy = player_obj.rect.centery - qg.rect.centery + d = math.hypot(dx, dy) + if d <= range_px: + if best is None or d < best_d: + best = qg + best_d = d + return best + + +def interact_quest_giver(qg: QuestGiver): + """Player pressed F near a quest object. Accept / show progress / turn in.""" + qid = qg.quest_id + q = quest_manager.get(qid) + if q is None: + pickup_feed.push_plain("Quest not found.") + return + + if quest_manager.is_complete(qid): + result = quest_manager.turn_in(qid) + if result is None: + pickup_feed.push_plain(f"{q.name} is not ready to turn in yet.") + return + parts = [f"{q.name} complete!"] + if result.get("coins"): + player.coins_light += int(result["coins"]) + parts.append(f"+{result['coins']} light coins") + if result.get("tokens"): + parts.append(f"+{result['tokens']} token") + if result.get("item"): + inventory.add(result["item"]) + parts.append(f"+{result['item'].get('name', 'Item')}") + pickup_feed.push_center(" ".join(parts), duration=4.0) + return + + if quest_manager.accept(qid): + pickup_feed.push_plain(f"Quest accepted: {q.name} ({q.description})") + return + + st = quest_manager.active.get(qid) + prog = int(st.get("progress", 0)) if st else 0 + pickup_feed.push_plain(f"{q.name}: {prog}/{q.target_count()}") + + def spawn_room_props(room: Room, player_obj: "Player"): reward_chest_obj = None reward_ability_obj = None lights: list[LightSource] = [] + quest_givers: list[QuestGiver] = [] + diary_obj = None props = room.props if not props and room.id == REWARD_ROOM_ID: @@ -4675,11 +4938,15 @@ def spawn_room_props(room: Room, player_obj: "Player"): elif p_type == "light_source": radius = int(p.get("radius", 180)) lights.append(LightSource(x, y, radius)) + elif p_type == "quest_giver": + quest_givers.append(QuestGiver(x, y, str(p.get("quest_id", "")))) + elif p_type == "diary": + diary_obj = DiaryObject(x, y) - return reward_chest_obj, reward_ability_obj, lights + return reward_chest_obj, reward_ability_obj, lights, quest_givers, diary_obj -reward_chest, reward_ability, light_sources = spawn_room_props(ROOM, player) +reward_chest, reward_ability, light_sources, quest_givers, diary_obj = spawn_room_props(ROOM, player) def grant_full_set(set_id: str, rarity: str, equip: bool = True) -> bool: slots = ["helmet", "chest", "pants"] @@ -4729,7 +4996,9 @@ def apply_room(room: Room, set_player_spawn=True): global light_sources global dust_puffs global dust_puffs - global reward_chest, reward_ability + global reward_chest, reward_ability, quest_givers, diary_obj + global token_drops + global ACTIVE_REFIGHT global ROOM_DROP_MULT, ROOM_COIN_MULT @@ -4743,9 +5012,14 @@ def apply_room(room: Room, set_player_spawn=True): if is_town_room(ROOM): town_title.trigger(ROOM.id, town_display_name(ROOM)) + # Leaving a diary refight room ends the refight. + if ACTIVE_REFIGHT is not None and ROOM.id != ACTIVE_REFIGHT.get("room_id"): + ACTIVE_REFIGHT = None + loot_drops = [] shockwaves = [] coin_drops = [] + token_drops = [] projectiles = [] shatter_platforms = [] shatter_aoes = [] @@ -4761,7 +5035,8 @@ def apply_room(room: Room, set_player_spawn=True): reward_chest = None reward_ability = None - reward_chest, reward_ability, light_sources = spawn_room_props(ROOM, player) + diary_obj = None + reward_chest, reward_ability, light_sources, quest_givers, diary_obj = spawn_room_props(ROOM, player) if set_player_spawn: player.start_x = int(ROOM.player_spawn[0]) @@ -4919,7 +5194,17 @@ class Quest: return _build_reward_item(entries[pool_index]) def token_count(self) -> int: - return max(0, int(self.reward.get("token_count", 1 if self.is_repeatable else 0))) + return max(0, int(self.reward.get("token_count", 0))) + + def coin_range(self) -> tuple[int, int] | None: + """Optional [min, max] light-coin reward for a quest.""" + cr = self.reward.get("coin_range") + if isinstance(cr, (list, tuple)) and len(cr) == 2: + return (int(cr[0]), int(cr[1])) + return None + + def target_count(self) -> int: + return max(1, int(self.objective.get("count", 1))) class QuestManager: @@ -4986,13 +5271,40 @@ class QuestManager: else: self.completed_one_time.add(quest_id) + def progress_kill(self, enemy_type: str) -> list[str]: + """Progress every active kill-quest matching this enemy type. + Returns quest ids that just became complete.""" + completed = [] + for qid in list(self.active.keys()): + q = self.quests.get(qid) + if q is None: + continue + obj = q.objective + if obj.get("kind") != "kill" or str(obj.get("target", "")) != enemy_type: + continue + was = self.is_complete(qid) + self.progress(qid, 1) + if not was and self.is_complete(qid): + completed.append(qid) + return completed + def turn_in(self, quest_id: str) -> dict | None: - """Called by the quest giver: completes the quest and returns the reward item.""" + """Completes an active quest. Returns a reward result: + {"item": dict|None, "coins": int, "tokens": int}.""" q = self.quests.get(quest_id) if q is None or not self.is_complete(quest_id): return None self._complete(quest_id) - return q.roll_pool_item() if q.is_repeatable else q.guaranteed_item() + result = {"item": None, "coins": 0, "tokens": 0} + if q.is_repeatable: + result["tokens"] = q.token_count() + result["item"] = q.roll_pool_item() + else: + result["item"] = q.guaranteed_item() + cr = q.coin_range() + if cr: + result["coins"] = random.randint(cr[0], cr[1]) + return result def add_tokens(self, amount: int): self.tokens += max(0, int(amount)) @@ -5043,6 +5355,17 @@ QUEST_DEFS_DEFAULT = [ "guaranteed_item": {"slot": "chest", "rarity": "attuned"}, }, }, + { + "id": "quest_first_kills", + "name": "First Kills", + "description": "Kill 4 normal enemies.", + "type": "repeatable", + "giver": "object:quest_board", + "objective": {"kind": "kill", "target": "enemy", "count": 4}, + "reward": { + "coin_range": [5, 15], + }, + }, ] quest_manager = QuestManager() @@ -5056,75 +5379,371 @@ BOSS_DEFS_DEFAULT = { "boss_01": { "name": "Gloom Warden", "room": "room_boss_01.json", - "trophy_name": "Warden's Heart", + "refight_room": "room_boss_refight_01.json", + "token_name": "Gloom Warden's Token", }, } -class BossTrophy: - def __init__(self, boss_id: str, name: str, tier: int = 0): - self.boss_id = str(boss_id) - self.name = str(name) - self.tier = int(tier) # 0 = base, higher = upgraded (harder) difficulty - - def upgrade(self) -> int: - self.tier += 1 - return self.tier - - -class BossCollection: - def __init__(self): - self.trophies: dict[str, BossTrophy] = {} - self.defeated: set[str] = set() - - def add_trophy(self, boss_id: str, name: str = "") -> BossTrophy: - t = self.trophies.get(boss_id) - if t is None: - t = BossTrophy(boss_id, name or boss_id) - self.trophies[boss_id] = t - self.defeated.add(boss_id) - return t - - def has_trophy(self, boss_id: str) -> bool: - return boss_id in self.trophies - - def get(self, boss_id: str) -> BossTrophy | None: - return self.trophies.get(boss_id) - - def upgrade(self, boss_id: str) -> int | None: - """Combat Master: upgrade a trophy so a harder version can be fought.""" - t = self.trophies.get(boss_id) - return t.upgrade() if t is not None else None - - def refight(self, boss_id: str) -> bool: - """Re-enable this boss for a re-fight. Returns True if it was defeated.""" - if boss_id not in self.defeated: - return False - self.defeated.discard(boss_id) - return True - - def boss_room_id(boss_id: str) -> str | None: bd = BOSS_DEFS_DEFAULT.get(boss_id) return str(bd.get("room")) if isinstance(bd, dict) and bd.get("room") else None +def boss_refight_room_id(boss_id: str) -> str | None: + bd = BOSS_DEFS_DEFAULT.get(boss_id) + return str(bd.get("refight_room")) if isinstance(bd, dict) and bd.get("refight_room") else None + + +def boss_id_for_room(room_id: str) -> str | None: + for bid, bd in BOSS_DEFS_DEFAULT.items(): + if isinstance(bd, dict) and str(bd.get("room")) == room_id: + return bid + return None + + +def make_boss_token(boss_id: str, tier: int = 0) -> dict: + bd = BOSS_DEFS_DEFAULT.get(boss_id, {}) + name = str(bd.get("token_name") or f"{bd.get('name', boss_id)}'s Token") + if int(tier) >= 1: + name = f"{name} (Hard)" + return {"item_type": "boss_token", "boss_id": str(boss_id), "name": name, "tier": int(tier)} + + +def find_carried_boss_token(inv, boss_id: str, tier: int | None = None): + for it in inv.items: + if not isinstance(it, dict): + continue + if it.get("item_type") == "boss_token" and it.get("boss_id") == boss_id: + if tier is None or int(it.get("tier", 0)) == tier: + return it + return None + + +class BossDiary: + """Tracks which boss tokens are slotted into the diary (usable for fights).""" + def __init__(self): + self.slotted: dict[str, int] = {} # boss_id -> token tier + + def tier(self, boss_id: str) -> int: + return self.slotted.get(boss_id, -1) # -1 = not slotted + + def has(self, boss_id: str) -> bool: + return boss_id in self.slotted + + def slot(self, boss_id: str, tier: int): + self.slotted[str(boss_id)] = int(tier) + + def unslot(self, boss_id: str) -> int | None: + return self.slotted.pop(str(boss_id), None) + + +boss_diary = BossDiary() + +# Active diary refight (cleared when leaving the refight room) +ACTIVE_REFIGHT: dict | None = None # {"boss_id": str, "variant": str, "room_id": str} + + def clear_boss_defeated(room_id: str): """Allow a boss room to be re-fought by clearing its runtime defeated flag.""" DEFEATED_BOSSES.discard(room_id) -def refight_boss(boss_id: str) -> bool: - """Re-fight a collected boss: clears its defeated flag so it respawns.""" - if not boss_collection.refight(boss_id): +def start_refight(boss_id: str, variant: str = "normal") -> bool: + """Begin a diary refight: teleport into the boss's refight room.""" + global ACTIVE_REFIGHT + room_id = boss_refight_room_id(boss_id) + if not room_id: return False - room = boss_room_id(boss_id) - if room: - clear_boss_defeated(room) + ACTIVE_REFIGHT = {"boss_id": boss_id, "variant": variant, "room_id": room_id} + DEFEATED_BOSSES.discard(room_id) # refight room must always spawn its boss + room = room_manager.load_room(room_id) + apply_room(room, set_player_spawn=True) + if variant == "hard": + for en in enemies: + if getattr(en, "type_id", "") == "boss": + en.speed *= 2.0 + en.air_speed *= 2.0 + en.shockwave_speed *= 2.0 + en.slam_cooldown_base = max(0.25, en.slam_cooldown_base / 2.0) return True -boss_collection = BossCollection() +class TokenPickup: + """Drops from a world boss fight; grants its boss token when collected.""" + def __init__(self, x, y, boss_id): + self.boss_id = str(boss_id) + self.rect = pygame.Rect(int(x), int(y), 16, 16) + self.vx = random.uniform(-40, 40) + self.vy = random.uniform(-120, -40) + self.on_ground = False + self.bob_t = random.random() * 10.0 + + def update(self, dt, platforms): + self.bob_t += dt * LOOT_BOB_SPEED + if self.on_ground: + self.vx *= LOOT_FRICTION + if abs(self.vx) < 6: + self.vx = 0.0 + self.rect.x += int(self.vx * dt) + return + self.vy = min(LOOT_TERMINAL_VEL, self.vy + LOOT_GRAVITY * dt) + self.rect.x += int(self.vx * dt) + self.rect.y += int(self.vy * dt) + for p in platforms: + if self.rect.colliderect(p): + if self.vy > 0 and self.rect.bottom >= p.top: + self.rect.bottom = p.top + self.vy = -self.vy * LOOT_BOUNCE + if abs(self.vy) < 30: + self.vy = 0.0 + self.on_ground = True + elif self.vy < 0 and self.rect.top <= p.bottom: + self.rect.top = p.bottom + self.vy = 0.0 + + def draw(self, surface, cx, cy): + bob_y = int(math.sin(self.bob_t) * LOOT_BOB_AMP) if self.on_ground else 0 + x = int(self.rect.x - cx) + y = int(self.rect.y - cy + bob_y) + if BOSS_TOKEN_SPRITE_RAW is not None: + img = pygame.transform.scale(BOSS_TOKEN_SPRITE_RAW, (self.rect.width, self.rect.height)) + surface.blit(img, (x, y)) + return + pygame.draw.circle(surface, (60, 200, 190), (x + 8, y + 8), 7) + pygame.draw.circle(surface, (10, 110, 105), (x + 8, y + 8), 7, 2) + + +class DiaryObject: + """Interactable book in town: opens the boss-token diary menu.""" + INTERACT_RANGE = 70 + + def __init__(self, x: int, y: int): + self.rect = pygame.Rect(int(x), int(y), 40, 50) + self.bob_t = random.random() * 10.0 + + def draw(self, surface, cx, cy): + bob_y = int(math.sin(self.bob_t) * 2) + self.bob_t += 0.05 + x = int(self.rect.x - cx) + y = int(self.rect.y - cy + bob_y) + pygame.draw.rect(surface, (150, 110, 70), (x, y, self.rect.width, self.rect.height)) + pygame.draw.rect(surface, (90, 60, 30), (x, y, self.rect.width, self.rect.height), 2) + pygame.draw.line(surface, (90, 60, 30), + (x + self.rect.width // 2, y), + (x + self.rect.width // 2, y + self.rect.height), 2) + + +def get_near_diary(player_obj, diary_obj) -> bool: + if diary_obj is None: + return False + dx = player_obj.rect.centerx - diary_obj.rect.centerx + dy = player_obj.rect.centery - diary_obj.rect.centery + return math.hypot(dx, dy) <= DiaryObject.INTERACT_RANGE + + +def _ellipsize_text(text: str, fnt, max_w: int) -> str: + if fnt.size(text)[0] <= max_w: + return text + while text and fnt.size(text + "...")[0] > max_w: + text = text[:-1] + return text + "..." + + +class DiaryUI: + def __init__(self, inventory, player, font_obj: pygame.font.Font): + self.inv = inventory + self.player = player + self.font = font_obj + self.small_font = pygame.font.SysFont(None, 16) + self.visible = False + self.boss_ids: list[str] = [] + self.grid_cursor = 0 + self.action_open = False + self.actions: list[dict] = [] + self.action_cursor = 0 + self.cols = 4 + + def _rebuild(self): + self.boss_ids = list(BOSS_DEFS_DEFAULT.keys()) + + def toggle(self): + self.visible = not self.visible + self.grid_cursor = 0 + self.action_open = False + self.actions = [] + self.action_cursor = 0 + if self.visible: + self._rebuild() + + def _boss_actions(self, boss_id: str) -> list[dict]: + bd = BOSS_DEFS_DEFAULT.get(boss_id, {}) + name = bd.get("name", boss_id) + tier = boss_diary.tier(boss_id) + carried = find_carried_boss_token(self.inv, boss_id) + actions = [] + if tier >= 0: + actions.append({"action": "fight", "boss_id": boss_id, "variant": "normal", + "label": f"Fight {name} (Normal)"}) + if tier >= 1: + actions.append({"action": "fight", "boss_id": boss_id, "variant": "hard", + "label": f"Fight {name} (Hard)"}) + actions.append({"action": "unslot", "boss_id": boss_id, + "label": f"Remove {bd.get('token_name', name + ' Token')} from diary"}) + elif carried is not None: + actions.append({"action": "slot", "boss_id": boss_id, "tier": int(carried.get("tier", 0)), + "label": f"Use {carried.get('name', 'Token')} (add to diary)"}) + else: + actions.append({"action": "none", "boss_id": boss_id, "label": f"{name}: no token"}) + return actions + + def handle_keydown(self, key): + if not self.visible: + return + if key in (key_for("ui_cancel"), key_for("interact"), key_for("inventory")): + if self.action_open: + self.action_open = False + self.actions = [] + self.action_cursor = 0 + else: + self.visible = False + return + + if self.action_open: + if not self.actions: + return + if key == key_for("ui_up"): + self.action_cursor = (self.action_cursor - 1) % len(self.actions) + elif key == key_for("ui_down"): + self.action_cursor = (self.action_cursor + 1) % len(self.actions) + elif key in (key_for("ui_confirm"), key_for("ui_confirm_alt")): + self._do_action(self.actions[self.action_cursor]) + return + + n = len(self.boss_ids) + if n == 0: + return + row = self.grid_cursor // self.cols + col = self.grid_cursor % self.cols + if key == key_for("ui_left"): + col = (col - 1) % self.cols + elif key == key_for("ui_right"): + col = (col + 1) % self.cols + elif key == key_for("ui_up"): + row = max(0, row - 1) + elif key == key_for("ui_down"): + max_row = max(0, (n - 1) // self.cols) + row = min(max_row, row + 1) + elif key in (key_for("ui_confirm"), key_for("ui_confirm_alt")): + self.actions = self._boss_actions(self.boss_ids[self.grid_cursor]) + self.action_cursor = 0 + self.action_open = True + return + self.grid_cursor = min(n - 1, row * self.cols + col) + + def _do_action(self, choice: dict): + action = choice.get("action") + boss_id = choice.get("boss_id") + if action == "slot": + it = find_carried_boss_token(self.inv, boss_id) + if it is None: + pickup_feed.push_plain("Token not found.") + return + self.inv.items.remove(it) + boss_diary.slot(boss_id, int(choice.get("tier", 0))) + pickup_feed.push_plain(f"{it.get('name')} added to diary.") + elif action == "unslot": + tier = boss_diary.unslot(boss_id) + if tier is None: + return + token = make_boss_token(boss_id, tier) + self.inv.add(token) + pickup_feed.push_plain(f"{token['name']} returned to inventory.") + elif action == "fight": + self.visible = False + start_refight(boss_id, choice.get("variant", "normal")) + return + self.actions = self._boss_actions(boss_id) + self.action_cursor = 0 + + def draw(self, surface: pygame.Surface): + if not self.visible: + return + n = len(self.boss_ids) + if n == 0: + return + + cell_w = 120 + cell_h = 96 + gap = 10 + pad = 14 + cols = self.cols + rows = (n + cols - 1) // cols + panel_w = pad * 2 + cols * cell_w + (cols - 1) * gap + header_h = 36 + action_h = 8 + len(self.actions) * 20 if self.action_open else 0 + panel_h = header_h + rows * cell_h + (rows - 1) * gap + pad + action_h + x = (WIDTH - panel_w) // 2 + y = (HEIGHT - panel_h) // 2 + + panel = pygame.Surface((panel_w, panel_h), pygame.SRCALPHA) + panel.fill((12, 12, 20, 235)) + surface.blit(panel, (x, y)) + pygame.draw.rect(surface, (100, 100, 130), (x, y, panel_w, panel_h), 2) + surface.blit(self.font.render("Boss Diary", True, (235, 230, 215)), (x + pad, y + 10)) + + for i, boss_id in enumerate(self.boss_ids): + bd = BOSS_DEFS_DEFAULT.get(boss_id, {}) + cx = x + pad + (i % cols) * (cell_w + gap) + cy = y + header_h + (i // cols) * (cell_h + gap) + cell = pygame.Rect(cx, cy, cell_w, cell_h) + pygame.draw.rect(surface, (35, 35, 50), cell) + is_sel = (i == self.grid_cursor) + border = (230, 200, 120) if is_sel else (90, 90, 110) + pygame.draw.rect(surface, border, cell, 2 if is_sel else 1) + + tier = boss_diary.tier(boss_id) + carried = find_carried_boss_token(self.inv, boss_id) + + icon_x = cell.centerx - 16 + icon_y = cell.y + 8 + has_token = (tier >= 0 or carried is not None) + if BOSS_TOKEN_SPRITE_RAW is not None and has_token: + icon = BOSS_TOKEN_SPRITE_RAW + if tier < 0: + icon = icon.copy() + icon.set_alpha(120) + surface.blit(icon, (icon_x, icon_y)) + if tier >= 1: + pygame.draw.rect(surface, (200, 50, 50), (icon_x, icon_y, 32, 5)) + htag = self.small_font.render("H", True, (255, 255, 255)) + surface.blit(htag, (icon_x + 13, icon_y - 1)) + else: + pygame.draw.rect(surface, (60, 60, 75), (icon_x, icon_y, 32, 32), 2) + + status = "Ready" if tier >= 0 else ("Carried" if carried is not None else "No token") + status_col = (150, 220, 150) if tier >= 0 else ((200, 200, 160) if carried else (150, 150, 150)) + st = self.small_font.render(status, True, status_col) + surface.blit(st, (cell.centerx - st.get_width() // 2, cell.y + cell_h - 38)) + + label = _ellipsize_text(str(bd.get("name", boss_id)), self.small_font, cell_w - 12) + label_surf = self.small_font.render(label, True, (220, 220, 220)) + surface.blit(label_surf, (cell.centerx - label_surf.get_width() // 2, cell.y + cell_h - 20)) + + if self.action_open and self.actions: + ay = y + panel_h - action_h + 4 + for j, a in enumerate(self.actions): + if j == self.action_cursor: + pygame.draw.rect(surface, (70, 70, 95), (x + 8, ay - 1, panel_w - 16, 18)) + col = (255, 225, 130) if j == self.action_cursor else (230, 230, 230) + if a.get("action") == "none": + col = (150, 150, 150) + label = _ellipsize_text(str(a.get("label", "")), self.small_font, panel_w - 28) + surface.blit(self.small_font.render(label, True, col), (x + 14, ay)) + ay += 20 + + +diary_ui = DiaryUI(inventory, player, font) # ---------------------------- @@ -5196,11 +5815,62 @@ class TownTitleBanner: town_title = TownTitleBanner(font) +def draw_stats_overlay(surface): + if not stats_overlay_visible: + return + light_speed = PLAYER_SPEED * (1.0 + player.bonus_move_speed_pct + player.bonus_move_speed_light_pct) + shade_speed = PLAYER_SHADE_SPEED * (1.0 + player.bonus_move_speed_pct + player.bonus_move_speed_shade_pct) + heal_dur = player.effective_heal_duration() + cycle = player.effective_attack_duration() + player.effective_attack_cooldown() + atk_rate = (1.0 / cycle) if cycle > 0 else 0.0 + regen = CHARGE_REFILL_RATE * (1.0 + player.bonus_charge_regen_pct) * player.charge_regen_mult + lines = [ + f"Health: {player.health} / {player.max_health}", + f"Charge: {player.charge:.1f} / {player.max_charge:.1f}", + f"Light Move Speed: {light_speed:.1f}", + f"Shade Move Speed: {shade_speed:.1f}", + f"Heal Duration: {heal_dur:.2f}s", + f"Attack Rate: {atk_rate:.1f}/s", + f"Charge Regen: {regen:.1f}/s", + ] + bonus = [] + if player.charge_regen_mult > 1.0: + bonus.append(f"Lantern Buff: {player.charge_regen_mult:.0f}x Charge Regen") + if player.bonus_attack_speed_pct: + bonus.append(f"Attack Speed: +{player.bonus_attack_speed_pct:.0%}") + if player.bonus_shade_double_damage_chance: + bonus.append(f"Shade Double Dmg: {player.bonus_shade_double_damage_chance:.0%}") + if player.bonus_negate_damage_chance: + bonus.append(f"Damage Negate: {player.bonus_negate_damage_chance:.0%}") + if player.bonus_shade_damage_flat: + bonus.append(f"Shade Dmg Bonus: +{player.bonus_shade_damage_flat:.0f}") + if player.bonus_jump_height_shade_pct: + bonus.append(f"Shade Jump: +{player.bonus_jump_height_shade_pct:.0%}") + if player.bonus_heal_plus: + bonus.append(f"Heal Plus: +{player.bonus_heal_plus}") + if player.bonus_max_health or player.bonus_max_charge: + bonus.append(f"Bonus HP/Charge: +{player.bonus_max_health}/+{player.bonus_max_charge:.0f}") + lines += bonus + panel_w = 280 + panel_h = 16 + len(lines) * 18 + 10 + x = WIDTH - panel_w - 12 + y = 8 + panel = pygame.Surface((panel_w, panel_h), pygame.SRCALPHA) + panel.fill((15, 15, 20, 210)) + surface.blit(panel, (x, y)) + pygame.draw.rect(surface, (120, 120, 140), (x, y, panel_w, panel_h), 2) + ty = y + 8 + for line in lines: + surface.blit(font.render(line, True, (230, 230, 230)), (x + 10, ty)) + ty += 18 + + # ---------------------------- # Main loop # ---------------------------- running = True paused = False +stats_overlay_visible = False pause_menu = "main" # main | settings pause_menu_index = 0 settings_index = 0 @@ -5245,6 +5915,7 @@ while running: if e.type == pygame.KEYDOWN: was_trade_visible = trade_ui.visible was_inv_visible = inv_ui.visible + was_diary_visible = diary_ui.visible if paused and pause_menu == "settings": if settings_wait_action: @@ -5279,16 +5950,20 @@ while running: paused = False continue - if key_is("ui_cancel", e.key) and (was_inv_visible or was_trade_visible): + if key_is("ui_cancel", e.key) and (was_inv_visible or was_trade_visible or was_diary_visible): inv_ui.visible = False trade_ui.visible = False trade_ui.active_npc = None + diary_ui.visible = False continue - if key_is("inventory", e.key) and not trade_ui.visible: + if key_is("inventory", e.key) and not trade_ui.visible and not diary_ui.visible: inv_ui.toggle() + if key_is("stats_overlay", e.key): + stats_overlay_visible = not stats_overlay_visible inv_ui.handle_keydown(e.key) trade_ui.handle_keydown(e.key) + diary_ui.handle_keydown(e.key) # Debug gear: only in room_01 if ROOM.id == "room_01.json": @@ -5303,16 +5978,23 @@ while running: if inst: pickup_feed.push_center(f"Debug: +5 {inst.get('name', 'Item')} (tempered)", duration=3.0) - if key_is("interact", e.key) and not inv_ui.visible and not was_trade_visible: - near_npc = get_near_npc(player, npcs) - if near_npc and not player.shade: - trade_ui.toggle(near_npc) + if key_is("interact", e.key) and not inv_ui.visible and not was_trade_visible and not diary_ui.visible: + if get_near_diary(player, diary_obj): + diary_ui.toggle() + else: + near_npc = get_near_npc(player, npcs) + if near_npc and not player.shade: + trade_ui.toggle(near_npc) + else: + near_qg = get_near_quest_giver(player, quest_givers) + if near_qg: + interact_quest_giver(near_qg) if key_is("pause", e.key): if paused: paused = False else: - if not inv_ui.visible and not trade_ui.visible: + if not inv_ui.visible and not trade_ui.visible and not diary_ui.visible: paused = True pause_menu = "main" pause_menu_index = 0 @@ -5392,6 +6074,8 @@ while running: drop.draw(screen, camera_x, camera_y) for c in coin_drops: c.draw(screen, camera_x, camera_y) + for t in token_drops: + t.draw(screen, camera_x, camera_y) for sw in shockwaves: sw.draw(screen, camera_x, camera_y) for pr in projectiles: @@ -5426,7 +6110,8 @@ while running: draw_reality_break_icon(screen, player) pygame.draw.rect(screen, HEALTH_BG, (WIDTH - 220, 20, 200, 20)) hp_ratio = (player.health / player.max_health) if player.max_health > 0 else 0 - pygame.draw.rect(screen, HEALTH_FILL, (WIDTH - 220, 20, int(200 * hp_ratio), 20)) + hp_color = (60, 200, 80) if player.poison_timer > 0 else HEALTH_FILL + pygame.draw.rect(screen, hp_color, (WIDTH - 220, 20, int(200 * hp_ratio), 20)) pygame.draw.rect(screen, CHARGE_BG, (WIDTH - 220, 45, 200, 20)) ch_ratio = (player.charge / player.max_charge) if player.max_charge > 0 else 0 @@ -5488,6 +6173,13 @@ while running: break collision_platforms = platforms + [d.rect for d in ability_doors if not d.open] player_collision_platforms = collision_platforms + [p.rect for p in shatter_platforms if p.alive] + + # Shade + clipping an enemy drains charge twice as fast (shade still avoids damage). + if player.shade: + player.charge_drain_mult = 2.0 if any(player.rect.colliderect(en.rect) for en in enemies) else 1.0 + else: + player.charge_drain_mult = 1.0 + player.update(keys, player_collision_platforms, dt) if player.is_shatterstep_active() and (not player.shade) and (not player.on_ground): @@ -5526,6 +6218,9 @@ while running: trade_ui.visible = False trade_ui.active_npc = None + if diary_ui.visible and not get_near_diary(player, diary_obj): + diary_ui.visible = False + room_manager.update(dt) if try_room_transition(keys): camera_x = float(player.rect.centerx - WIDTH // 2) @@ -5534,20 +6229,32 @@ while running: # Respawn on death/void if player.rect.top > KILL_Y or player.health <= 0: - player.respawn(int(ROOM.player_spawn[0]), int(ROOM.player_spawn[1])) - enemies = spawn_enemies_from_room(ROOM) - npcs = spawn_npcs_from_room(ROOM) - loot_drops = [] - shockwaves = [] - coin_drops = [] - projectiles = [] - shatter_platforms = [] - shatter_aoes = [] - dust_puffs = [] - tether_shots = [] - player.shatterstep_landing_pending = False - player.shatterstep_landing_damage = 0.0 - ability_doors = spawn_ability_doors(ROOM) + if ACTIVE_REFIGHT is not None: + # Died in a diary refight -> return to the diary in town. + ACTIVE_REFIGHT = None + room = room_manager.load_room("room_town_gloomreach.json") + diary_spawn = room.entry_spawns.get("entry_diary") or room.player_spawn + room.player_spawn = [int(diary_spawn[0]), int(diary_spawn[1])] + player.respawn(int(diary_spawn[0]), int(diary_spawn[1])) + apply_room(room, set_player_spawn=True) + player.shatterstep_landing_pending = False + player.shatterstep_landing_damage = 0.0 + else: + player.respawn(int(ROOM.player_spawn[0]), int(ROOM.player_spawn[1])) + enemies = spawn_enemies_from_room(ROOM) + npcs = spawn_npcs_from_room(ROOM) + loot_drops = [] + shockwaves = [] + coin_drops = [] + token_drops = [] + projectiles = [] + shatter_platforms = [] + shatter_aoes = [] + dust_puffs = [] + tether_shots = [] + player.shatterstep_landing_pending = False + player.shatterstep_landing_damage = 0.0 + ability_doors = spawn_ability_doors(ROOM) # Update enemies for en in enemies: @@ -5631,6 +6338,7 @@ while running: did_hit = player.take_damage(sw.damage) if did_hit: player.apply_knockback(sw.rect, strength_px_per_frame=14.0, duration=0.22, pop_up_vel=-4.5) + player.apply_poison(BOSS_POISON_DURATION) sw.t = 0 # one-hit @@ -5642,6 +6350,9 @@ while running: for c in coin_drops: c.update(dt, collision_platforms) + for t in token_drops: + t.update(dt, collision_platforms) + if reward_ability: reward_ability.update(dt) @@ -5653,8 +6364,18 @@ while running: alive.append(en) else: loot_drops.extend(spawn_drops_from_enemy(en)) - if getattr(en, "type_id", "") == "boss": + etype = getattr(en, "type_id", "") + if etype == "boss": mark_boss_defeated(ROOM.id) + bid = boss_id_for_room(ROOM.id) + if bid and ACTIVE_REFIGHT is None: + token_drops.append(TokenPickup(en.rect.centerx - 8, en.rect.top - 20, bid)) + + # Quest kill tracking + for qid in quest_manager.progress_kill(etype): + q = quest_manager.get(qid) + if q: + pickup_feed.push_plain(f"Quest fulfilled: {q.name} - return to the quest object!") # --- coin drops --- base_x = en.rect.centerx - 8 @@ -5711,6 +6432,12 @@ while running: else: player.coins_dark += 1 coin_drops.remove(c) + for t in token_drops[:]: + if player.rect.colliderect(t.rect): + token = make_boss_token(t.boss_id) + inventory.add(token) + pickup_feed.push_plain(f"Collected {token['name']}!") + token_drops.remove(t) @@ -5771,12 +6498,24 @@ while running: for c in coin_drops: c.draw(screen, camera_x, camera_y) + # boss tokens + for t in token_drops: + t.draw(screen, camera_x, camera_y) + # reward room props if reward_chest: reward_chest.draw(screen, camera_x, camera_y) if reward_ability: reward_ability.draw(screen, camera_x, camera_y) + # quest giver objects + for qg in quest_givers: + qg.draw(screen, camera_x, camera_y) + + # diary object (boss token diary) + if diary_obj: + diary_obj.draw(screen, camera_x, camera_y) + # shockwaves for sw in shockwaves: @@ -5818,6 +6557,35 @@ while running: hint_text ) + # Quest object hint bubble (shown near the player) + near_qg = get_near_quest_giver(player, quest_givers) + if near_qg: + q = quest_manager.get(near_qg.quest_id) + label = "Press F" + if q is not None: + if quest_manager.is_complete(near_qg.quest_id): + label = "Press F - claim reward" + elif near_qg.quest_id in quest_manager.active: + st = quest_manager.active[near_qg.quest_id] + label = f"Press F - {q.name} ({int(st.get('progress', 0))}/{q.target_count()})" + else: + label = f"Press F - {q.name}" + draw_speech_bubble( + screen, + label, + int(near_qg.rect.centerx - camera_x), + int(near_qg.rect.top - camera_y), + ) + + # Diary hint bubble (shown near the player) + if get_near_diary(player, diary_obj) and not diary_ui.visible: + draw_speech_bubble( + screen, + "Press F - Boss Diary", + int(diary_obj.rect.centerx - camera_x), + int(diary_obj.rect.top - camera_y), + ) + # Boss health bar (if boss exists) boss = next((en for en in enemies if getattr(en, "type_id", "") == "boss"), None) if boss is not None: @@ -5850,7 +6618,8 @@ while running: # Player health pygame.draw.rect(screen, HEALTH_BG, (WIDTH - 220, 20, 200, 20)) hp_ratio = (player.health / player.max_health) if player.max_health > 0 else 0 - pygame.draw.rect(screen, HEALTH_FILL, (WIDTH - 220, 20, int(200 * hp_ratio), 20)) + hp_color = (60, 200, 80) if player.poison_timer > 0 else HEALTH_FILL + pygame.draw.rect(screen, hp_color, (WIDTH - 220, 20, int(200 * hp_ratio), 20)) # Player charge pygame.draw.rect(screen, CHARGE_BG, (WIDTH - 220, 45, 200, 20)) @@ -5884,8 +6653,10 @@ while running: inv_ui.draw(screen) trade_ui.draw(screen) + diary_ui.draw(screen) pickup_feed.draw(screen) town_title.draw(screen) + draw_stats_overlay(screen) pygame.display.flip()