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_PATH = OBJECTS_DIR / "flow_butterfly_jar1.png"
|
||||||
BUTTERFLY_JAR_RAW = _load_image_or_none(BUTTERFLY_JAR_PATH)
|
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)
|
self.equip_idx = (self.equip_idx - 1) % len(self.equip_slots)
|
||||||
elif key == key_for("ui_down"):
|
elif key == key_for("ui_down"):
|
||||||
self.equip_idx = (self.equip_idx + 1) % len(self.equip_slots)
|
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):
|
def _equip_selected_swap(self):
|
||||||
if len(self.inv.items) == 0:
|
if len(self.inv.items) == 0:
|
||||||
@@ -2382,6 +2396,18 @@ class InventoryUI:
|
|||||||
|
|
||||||
self._clamp_cursor()
|
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)):
|
def _blit(self, surface, text, x, y, color=(230, 230, 230)):
|
||||||
surface.blit(self.font.render(text, True, color), (x, y))
|
surface.blit(self.font.render(text, True, color), (x, y))
|
||||||
|
|
||||||
@@ -2778,7 +2804,7 @@ class InventoryUI:
|
|||||||
# Hint text (NOW hint_y exists)
|
# Hint text (NOW hint_y exists)
|
||||||
self._blit(
|
self._blit(
|
||||||
surface,
|
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,
|
self.x + self.pad + 6,
|
||||||
hint_y + 4,
|
hint_y + 4,
|
||||||
(160, 160, 160),
|
(160, 160, 160),
|
||||||
@@ -3213,6 +3239,7 @@ class Player:
|
|||||||
self.idle_frame = 0
|
self.idle_frame = 0
|
||||||
self.idle_anim_time = 0.0
|
self.idle_anim_time = 0.0
|
||||||
self.idle_bob_t = 0.0
|
self.idle_bob_t = 0.0
|
||||||
|
self.fairy_bob_t = random.random() * 10.0
|
||||||
self.idle_hold_time = 0.0
|
self.idle_hold_time = 0.0
|
||||||
self.ground_contact_timer = 0.0
|
self.ground_contact_timer = 0.0
|
||||||
self.is_idle = False
|
self.is_idle = False
|
||||||
@@ -3661,6 +3688,7 @@ class Player:
|
|||||||
self.just_jumped = False
|
self.just_jumped = False
|
||||||
self.just_landed = False
|
self.just_landed = False
|
||||||
self.just_air_jumped = False
|
self.just_air_jumped = False
|
||||||
|
self.fairy_bob_t += dt
|
||||||
|
|
||||||
if self.invincibility_timer > 0:
|
if self.invincibility_timer > 0:
|
||||||
self.invincibility_timer -= dt
|
self.invincibility_timer -= dt
|
||||||
@@ -3839,6 +3867,20 @@ class Player:
|
|||||||
|
|
||||||
self._clamp_resources()
|
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):
|
def draw(self, surface, cx, cy):
|
||||||
if self.healing:
|
if self.healing:
|
||||||
dur = self.effective_heal_duration()
|
dur = self.effective_heal_duration()
|
||||||
@@ -3855,6 +3897,7 @@ class Player:
|
|||||||
if self.sprite_right is None:
|
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)
|
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)
|
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)
|
pygame.draw.rect(surface, player_color, pr)
|
||||||
else:
|
else:
|
||||||
use_idle = self.is_idle and self.idle_right_frames and (self.idle_hold_time >= PLAYER_IDLE_START_DELAY)
|
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
|
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):
|
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)
|
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))
|
surface.blit(img, (draw_x, draw_y))
|
||||||
|
|
||||||
# Debug: show player hitbox
|
# Debug: show player hitbox
|
||||||
@@ -5268,9 +5312,10 @@ def reset_room_combat_state():
|
|||||||
def apply_death_penalty():
|
def apply_death_penalty():
|
||||||
"""On death: lose butterfly jars for good and drop coins into a corpse.
|
"""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
|
A corpse holds 100% of carried coins on the first death. On a repeat death
|
||||||
death (the other 50% is lost forever and the old corpse vanishes). Void deaths
|
(while a corpse is still unclaimed) only 50% of the remaining coins stay
|
||||||
(falling out of the world) leave no recoverable corpse.
|
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
|
global corpse, JAR_DEATH_ROOM
|
||||||
player.butterfly_jars = 0
|
player.butterfly_jars = 0
|
||||||
@@ -5280,8 +5325,12 @@ def apply_death_penalty():
|
|||||||
carried_l = player.coins_light
|
carried_l = player.coins_light
|
||||||
carried_d = player.coins_dark
|
carried_d = player.coins_dark
|
||||||
if corpse is not None:
|
if corpse is not None:
|
||||||
lost_l = int(carried_l * 0.5)
|
# "Remaining coins" = coins still held by the old corpse + anything carried
|
||||||
lost_d = int(carried_d * 0.5)
|
# 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:
|
else:
|
||||||
lost_l = carried_l
|
lost_l = carried_l
|
||||||
lost_d = carried_d
|
lost_d = carried_d
|
||||||
|
|||||||
@@ -1,42 +1,51 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"saved_at": "2026-09-28T10:53:55",
|
"saved_at": "2026-09-29T09:39:49",
|
||||||
"player": {
|
"player": {
|
||||||
"room_id": "room_town_gloomreach.json",
|
"room_id": "room_town_gloomreach.json",
|
||||||
"x": 120,
|
"x": 230,
|
||||||
"y": 360,
|
"y": 360,
|
||||||
"health": 9,
|
"health": 10,
|
||||||
"charge": 64.71,
|
"charge": 125.0,
|
||||||
"shade": false,
|
"shade": false,
|
||||||
"coins_light": 84,
|
"coins_light": 0,
|
||||||
"coins_dark": 41,
|
"coins_dark": 0,
|
||||||
"butterfly_jars": 2,
|
"butterfly_jars": 0,
|
||||||
"has_ranged": true,
|
"has_ranged": true,
|
||||||
"reward_chest_opened": true,
|
"reward_chest_opened": true,
|
||||||
"equipment": {
|
"equipment": {
|
||||||
"helmet": null,
|
"helmet": {
|
||||||
"chest": null,
|
"id": "adaptive_traveller_helm",
|
||||||
"pants": null,
|
"name": "Adaptive Traveller's Helmet",
|
||||||
"necklace": null
|
"set": "adaptive_traveller",
|
||||||
|
"slot": "helmet",
|
||||||
|
"base_stats": {},
|
||||||
|
"rarity_overrides": {
|
||||||
|
"tempered": {
|
||||||
|
"extra_stats": {
|
||||||
|
"charge": 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inventory": [
|
"attuned": {
|
||||||
{
|
"extra_stats": {
|
||||||
"id": "necklace_placeholder",
|
"charge": 15
|
||||||
"name": "Plain Necklace",
|
}
|
||||||
"slot": "necklace",
|
|
||||||
"item_type": "necklace",
|
|
||||||
"base_stats": {
|
|
||||||
"health": 2
|
|
||||||
},
|
},
|
||||||
"rarity": "",
|
"awakened": {
|
||||||
"tier": 0,
|
"extra_stats": {
|
||||||
"abilities": [],
|
"charge": 15,
|
||||||
|
"charge_regen_pct": 0.15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rarity": "tempered",
|
||||||
|
"tier": 1,
|
||||||
"stats": {
|
"stats": {
|
||||||
"health": 2
|
"charge": 10.0
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"abilities": []
|
||||||
|
},
|
||||||
|
"chest": {
|
||||||
"id": "dawnrunner_chest",
|
"id": "dawnrunner_chest",
|
||||||
"name": "Dawnrunner Chestplate",
|
"name": "Dawnrunner Chestplate",
|
||||||
"set": "dawnrunner",
|
"set": "dawnrunner",
|
||||||
@@ -70,24 +79,133 @@
|
|||||||
},
|
},
|
||||||
"abilities": []
|
"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": [
|
||||||
{
|
{
|
||||||
"item_type": "boss_token",
|
"id": "twister_helm",
|
||||||
"boss_id": "boss_01",
|
"name": "Twister Helmet",
|
||||||
"name": "Gloom Warden's Token",
|
"set": "twister",
|
||||||
"tier": 0
|
"slot": "helmet",
|
||||||
|
"base_stats": {},
|
||||||
|
"rarity_overrides": {
|
||||||
|
"tempered": {
|
||||||
|
"extra_stats": {
|
||||||
|
"attack_speed_pct": 0.1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attuned": {
|
||||||
|
"extra_stats": {
|
||||||
|
"attack_speed_pct": 0.15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"awakened": {
|
||||||
|
"extra_stats": {
|
||||||
|
"attack_speed_pct": 0.15,
|
||||||
|
"charge_regen_pct": 0.15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rarity": "tempered",
|
||||||
|
"tier": 1,
|
||||||
|
"stats": {
|
||||||
|
"attack_speed_pct": 0.1
|
||||||
|
},
|
||||||
|
"abilities": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": {
|
"quests": {
|
||||||
"active": {
|
"active": {
|
||||||
"quest_first_kills": {
|
"quest_first_kills": {
|
||||||
"progress": 12
|
"progress": 21
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"completed_one_time": [],
|
"completed_one_time": [],
|
||||||
"tokens": 0
|
"tokens": 0
|
||||||
},
|
},
|
||||||
"boss_diary": {
|
"boss_diary": {
|
||||||
"slotted": {}
|
"slotted": {
|
||||||
|
"boss_01": 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"defeated_bosses": [
|
"defeated_bosses": [
|
||||||
"room_boss_01.json"
|
"room_boss_01.json"
|
||||||
|
|||||||
Reference in New Issue
Block a user