smol stuff again
added fairy sprite, unequip inventory, proper death recovery
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 399 B |
+55
-6
@@ -1208,6 +1208,18 @@ BUTTERFLY_RAW = _load_image_or_none(BUTTERFLY_PATH)
|
||||
BUTTERFLY_JAR_PATH = OBJECTS_DIR / "flow_butterfly_jar1.png"
|
||||
BUTTERFLY_JAR_RAW = _load_image_or_none(BUTTERFLY_JAR_PATH)
|
||||
|
||||
# --- Fairy companion (hovers behind the player's head) ---
|
||||
FAIRY_PATH = DATA_ROOT / "sprites" / "npcs" / "fairy1.png"
|
||||
FAIRY_RAW = _load_image_or_none(FAIRY_PATH)
|
||||
FAIRY_SPRITE = None
|
||||
if FAIRY_RAW is not None:
|
||||
try:
|
||||
fs = max(1, int(FAIRY_RAW.get_width() * PLAYER_SPRITE_SCALE * 0.55))
|
||||
FAIRY_SPRITE = pygame.transform.smoothscale(FAIRY_RAW, (fs, fs))
|
||||
except Exception:
|
||||
FAIRY_SPRITE = FAIRY_RAW
|
||||
FAIRY_SPRITE_FLIPPED = pygame.transform.flip(FAIRY_SPRITE, True, False) if FAIRY_SPRITE is not None else None
|
||||
|
||||
|
||||
|
||||
# ----------------------------
|
||||
@@ -2357,6 +2369,8 @@ class InventoryUI:
|
||||
self.equip_idx = (self.equip_idx - 1) % len(self.equip_slots)
|
||||
elif key == key_for("ui_down"):
|
||||
self.equip_idx = (self.equip_idx + 1) % len(self.equip_slots)
|
||||
elif key in (key_for("ui_confirm"), key_for("ui_confirm_alt")):
|
||||
self._unequip_selected()
|
||||
|
||||
def _equip_selected_swap(self):
|
||||
if len(self.inv.items) == 0:
|
||||
@@ -2382,6 +2396,18 @@ class InventoryUI:
|
||||
|
||||
self._clamp_cursor()
|
||||
|
||||
def _unequip_selected(self):
|
||||
"""Move the item in the selected equipment slot back to the inventory."""
|
||||
slot = self.equip_slots[self.equip_idx]
|
||||
item = self.player.equipment.get(slot)
|
||||
if item is None:
|
||||
return
|
||||
self.player.equipment[slot] = None
|
||||
self.inv.items.append(item)
|
||||
self.player.recalc_bonuses()
|
||||
self.player._clamp_resources()
|
||||
self._clamp_cursor()
|
||||
|
||||
def _blit(self, surface, text, x, y, color=(230, 230, 230)):
|
||||
surface.blit(self.font.render(text, True, color), (x, y))
|
||||
|
||||
@@ -2778,7 +2804,7 @@ class InventoryUI:
|
||||
# Hint text (NOW hint_y exists)
|
||||
self._blit(
|
||||
surface,
|
||||
"UP/DOWN move ENTER equip LEFT/RIGHT switch panel",
|
||||
"UP/DOWN move ENTER equip / unequip LEFT/RIGHT switch panel",
|
||||
self.x + self.pad + 6,
|
||||
hint_y + 4,
|
||||
(160, 160, 160),
|
||||
@@ -3213,6 +3239,7 @@ class Player:
|
||||
self.idle_frame = 0
|
||||
self.idle_anim_time = 0.0
|
||||
self.idle_bob_t = 0.0
|
||||
self.fairy_bob_t = random.random() * 10.0
|
||||
self.idle_hold_time = 0.0
|
||||
self.ground_contact_timer = 0.0
|
||||
self.is_idle = False
|
||||
@@ -3661,6 +3688,7 @@ class Player:
|
||||
self.just_jumped = False
|
||||
self.just_landed = False
|
||||
self.just_air_jumped = False
|
||||
self.fairy_bob_t += dt
|
||||
|
||||
if self.invincibility_timer > 0:
|
||||
self.invincibility_timer -= dt
|
||||
@@ -3839,6 +3867,20 @@ class Player:
|
||||
|
||||
self._clamp_resources()
|
||||
|
||||
def _draw_fairy(self, surface, cx, cy, px, py, pw, ph):
|
||||
"""Draw the fairy companion hovering behind/beside the player's head."""
|
||||
img = FAIRY_SPRITE if self.facing_right else FAIRY_SPRITE_FLIPPED
|
||||
if img is None or pw <= 0 or ph <= 0:
|
||||
return
|
||||
fw, fh = img.get_size()
|
||||
# Trail behind the player (opposite the facing direction) at head height.
|
||||
trail = max(1, (pw + fw) // 3)
|
||||
off = -trail if self.facing_right else trail
|
||||
fx = px + (pw - fw) // 2 + off
|
||||
fairy_cy = py + int(ph * 0.18)
|
||||
bob = int(math.sin(self.fairy_bob_t * 2.2) * 3)
|
||||
surface.blit(img, (fx, fairy_cy - fh // 2 + bob))
|
||||
|
||||
def draw(self, surface, cx, cy):
|
||||
if self.healing:
|
||||
dur = self.effective_heal_duration()
|
||||
@@ -3855,6 +3897,7 @@ class Player:
|
||||
if self.sprite_right is None:
|
||||
player_color = PLAYER_HIT_COLOR if self.invincibility_timer > 0 else (PLAYER_DARK if self.shade else PLAYER_LIGHT)
|
||||
pr = pygame.Rect(self.rect.x - cx, self.rect.y - cy, self.rect.width, self.rect.height)
|
||||
self._draw_fairy(surface, cx, cy, self.rect.x - cx, self.rect.y - cy, self.rect.width, self.rect.height)
|
||||
pygame.draw.rect(surface, player_color, pr)
|
||||
else:
|
||||
use_idle = self.is_idle and self.idle_right_frames and (self.idle_hold_time >= PLAYER_IDLE_START_DELAY)
|
||||
@@ -3879,6 +3922,7 @@ class Player:
|
||||
draw_y = self.rect.y - cy - (img.get_height() - self.rect.height) + PLAYER_SPRITE_OFFSET_Y
|
||||
if self.is_idle and (not self.shade or not self.has_shade_sprite):
|
||||
draw_y -= int(math.sin(self.idle_bob_t) * PLAYER_IDLE_BOB_AMP)
|
||||
self._draw_fairy(surface, cx, cy, draw_x, draw_y, img.get_width(), img.get_height())
|
||||
surface.blit(img, (draw_x, draw_y))
|
||||
|
||||
# Debug: show player hitbox
|
||||
@@ -5268,9 +5312,10 @@ def reset_room_combat_state():
|
||||
def apply_death_penalty():
|
||||
"""On death: lose butterfly jars for good and drop coins into a corpse.
|
||||
|
||||
A corpse holds 100% of carried coins on the first death, or 50% on a repeat
|
||||
death (the other 50% is lost forever and the old corpse vanishes). Void deaths
|
||||
(falling out of the world) leave no recoverable corpse.
|
||||
A corpse holds 100% of carried coins on the first death. On a repeat death
|
||||
(while a corpse is still unclaimed) only 50% of the remaining coins stay
|
||||
recoverable — the other 50% is lost forever and the old corpse vanishes.
|
||||
Void deaths (falling out of the world) leave no recoverable corpse.
|
||||
"""
|
||||
global corpse, JAR_DEATH_ROOM
|
||||
player.butterfly_jars = 0
|
||||
@@ -5280,8 +5325,12 @@ def apply_death_penalty():
|
||||
carried_l = player.coins_light
|
||||
carried_d = player.coins_dark
|
||||
if corpse is not None:
|
||||
lost_l = int(carried_l * 0.5)
|
||||
lost_d = int(carried_d * 0.5)
|
||||
# "Remaining coins" = coins still held by the old corpse + anything carried
|
||||
# since then. The new corpse only keeps half of that pool.
|
||||
pool_l = corpse.coins_light + carried_l
|
||||
pool_d = corpse.coins_dark + carried_d
|
||||
lost_l = int(pool_l * 0.5)
|
||||
lost_d = int(pool_d * 0.5)
|
||||
else:
|
||||
lost_l = carried_l
|
||||
lost_d = carried_d
|
||||
|
||||
@@ -1,93 +1,211 @@
|
||||
{
|
||||
"version": 1,
|
||||
"saved_at": "2026-09-28T10:53:55",
|
||||
"saved_at": "2026-09-29T09:39:49",
|
||||
"player": {
|
||||
"room_id": "room_town_gloomreach.json",
|
||||
"x": 120,
|
||||
"x": 230,
|
||||
"y": 360,
|
||||
"health": 9,
|
||||
"charge": 64.71,
|
||||
"health": 10,
|
||||
"charge": 125.0,
|
||||
"shade": false,
|
||||
"coins_light": 84,
|
||||
"coins_dark": 41,
|
||||
"butterfly_jars": 2,
|
||||
"coins_light": 0,
|
||||
"coins_dark": 0,
|
||||
"butterfly_jars": 0,
|
||||
"has_ranged": true,
|
||||
"reward_chest_opened": true,
|
||||
"equipment": {
|
||||
"helmet": null,
|
||||
"chest": null,
|
||||
"pants": null,
|
||||
"necklace": null
|
||||
"helmet": {
|
||||
"id": "adaptive_traveller_helm",
|
||||
"name": "Adaptive Traveller's Helmet",
|
||||
"set": "adaptive_traveller",
|
||||
"slot": "helmet",
|
||||
"base_stats": {},
|
||||
"rarity_overrides": {
|
||||
"tempered": {
|
||||
"extra_stats": {
|
||||
"charge": 10
|
||||
}
|
||||
},
|
||||
"attuned": {
|
||||
"extra_stats": {
|
||||
"charge": 15
|
||||
}
|
||||
},
|
||||
"awakened": {
|
||||
"extra_stats": {
|
||||
"charge": 15,
|
||||
"charge_regen_pct": 0.15
|
||||
}
|
||||
}
|
||||
},
|
||||
"rarity": "tempered",
|
||||
"tier": 1,
|
||||
"stats": {
|
||||
"charge": 10.0
|
||||
},
|
||||
"abilities": []
|
||||
},
|
||||
"chest": {
|
||||
"id": "dawnrunner_chest",
|
||||
"name": "Dawnrunner Chestplate",
|
||||
"set": "dawnrunner",
|
||||
"slot": "chest",
|
||||
"base_stats": {},
|
||||
"rarity_overrides": {
|
||||
"tempered": {
|
||||
"extra_stats": {
|
||||
"charge": 10
|
||||
}
|
||||
},
|
||||
"attuned": {
|
||||
"extra_stats": {
|
||||
"charge": 15,
|
||||
"move_speed_light_pct": 0.05
|
||||
}
|
||||
},
|
||||
"awakened": {
|
||||
"extra_stats": {
|
||||
"charge": 15,
|
||||
"move_speed_light_pct": 0.05,
|
||||
"heal_plus": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"rarity": "attuned",
|
||||
"tier": 2,
|
||||
"stats": {
|
||||
"charge": 15.0,
|
||||
"move_speed_light_pct": 0.05
|
||||
},
|
||||
"abilities": []
|
||||
},
|
||||
"pants": {
|
||||
"id": "dawnrunner_greaves",
|
||||
"name": "Dawnrunner Greaves",
|
||||
"set": "dawnrunner",
|
||||
"slot": "pants",
|
||||
"base_stats": {},
|
||||
"rarity_overrides": {
|
||||
"tempered": {
|
||||
"extra_stats": {
|
||||
"heal_speed_pct": 0.1
|
||||
}
|
||||
},
|
||||
"attuned": {
|
||||
"extra_stats": {
|
||||
"heal_speed_pct": 0.1,
|
||||
"move_speed_light_pct": 0.1
|
||||
}
|
||||
},
|
||||
"awakened": {
|
||||
"extra_stats": {
|
||||
"heal_speed_pct": 0.15,
|
||||
"move_speed_light_pct": 0.15
|
||||
}
|
||||
}
|
||||
},
|
||||
"rarity": "tempered",
|
||||
"tier": 1,
|
||||
"stats": {
|
||||
"heal_speed_pct": 0.1
|
||||
},
|
||||
"abilities": []
|
||||
},
|
||||
"necklace": {
|
||||
"id": "necklace_placeholder",
|
||||
"name": "Plain Necklace",
|
||||
"slot": "necklace",
|
||||
"item_type": "necklace",
|
||||
"base_stats": {
|
||||
"health": 2
|
||||
},
|
||||
"rarity": "",
|
||||
"tier": 0,
|
||||
"abilities": [],
|
||||
"stats": {
|
||||
"health": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"inventory": [
|
||||
{
|
||||
"id": "necklace_placeholder",
|
||||
"name": "Plain Necklace",
|
||||
"slot": "necklace",
|
||||
"item_type": "necklace",
|
||||
"base_stats": {
|
||||
"health": 2
|
||||
},
|
||||
"rarity": "",
|
||||
"tier": 0,
|
||||
"abilities": [],
|
||||
"stats": {
|
||||
"health": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dawnrunner_chest",
|
||||
"name": "Dawnrunner Chestplate",
|
||||
"set": "dawnrunner",
|
||||
"slot": "chest",
|
||||
"id": "twister_helm",
|
||||
"name": "Twister Helmet",
|
||||
"set": "twister",
|
||||
"slot": "helmet",
|
||||
"base_stats": {},
|
||||
"rarity_overrides": {
|
||||
"tempered": {
|
||||
"extra_stats": {
|
||||
"charge": 10
|
||||
"attack_speed_pct": 0.1
|
||||
}
|
||||
},
|
||||
"attuned": {
|
||||
"extra_stats": {
|
||||
"charge": 15,
|
||||
"move_speed_light_pct": 0.05
|
||||
"attack_speed_pct": 0.15
|
||||
}
|
||||
},
|
||||
"awakened": {
|
||||
"extra_stats": {
|
||||
"charge": 15,
|
||||
"move_speed_light_pct": 0.05,
|
||||
"heal_plus": 1
|
||||
"attack_speed_pct": 0.15,
|
||||
"charge_regen_pct": 0.15
|
||||
}
|
||||
}
|
||||
},
|
||||
"rarity": "attuned",
|
||||
"tier": 2,
|
||||
"rarity": "tempered",
|
||||
"tier": 1,
|
||||
"stats": {
|
||||
"charge": 15.0,
|
||||
"move_speed_light_pct": 0.05
|
||||
"attack_speed_pct": 0.1
|
||||
},
|
||||
"abilities": []
|
||||
},
|
||||
{
|
||||
"item_type": "boss_token",
|
||||
"boss_id": "boss_01",
|
||||
"name": "Gloom Warden's Token",
|
||||
"tier": 0
|
||||
"id": "dawnrunner_helm",
|
||||
"name": "Dawnrunner Helmet",
|
||||
"set": "dawnrunner",
|
||||
"slot": "helmet",
|
||||
"base_stats": {},
|
||||
"rarity_overrides": {
|
||||
"tempered": {
|
||||
"extra_stats": {
|
||||
"move_speed_light_pct": 0.1
|
||||
}
|
||||
},
|
||||
"attuned": {
|
||||
"extra_stats": {
|
||||
"move_speed_light_pct": 0.1,
|
||||
"heal_speed_pct": 0.1
|
||||
}
|
||||
},
|
||||
"awakened": {
|
||||
"extra_stats": {
|
||||
"move_speed_light_pct": 0.15,
|
||||
"heal_speed_pct": 0.2
|
||||
}
|
||||
}
|
||||
},
|
||||
"rarity": "tempered",
|
||||
"tier": 1,
|
||||
"stats": {
|
||||
"move_speed_light_pct": 0.1
|
||||
},
|
||||
"abilities": []
|
||||
}
|
||||
],
|
||||
"quests": {
|
||||
"active": {
|
||||
"quest_first_kills": {
|
||||
"progress": 12
|
||||
"progress": 21
|
||||
}
|
||||
},
|
||||
"completed_one_time": [],
|
||||
"tokens": 0
|
||||
},
|
||||
"boss_diary": {
|
||||
"slotted": {}
|
||||
"slotted": {
|
||||
"boss_01": 1
|
||||
}
|
||||
},
|
||||
"defeated_bosses": [
|
||||
"room_boss_01.json"
|
||||
|
||||
Reference in New Issue
Block a user