"""
Player entity management and physics controller.
This module defines the `Player` class, which extends the base `Camera` to add
AABB (Axis-Aligned Bounding Box) collision detection, gravity, fluid dynamics,
inventory state, and survival statistics (health, hunger, oxygen). It acts as
the primary interface between user input and the 3D voxel world.
"""
import math
from typing import Any, List, Optional, Tuple
import pygame as pg
from pyglm import glm
import noise
from camera import Camera
from profiler import global_profiler
from settings import (
ASPECT_RATIO,
BAREHAND_MINING_PENALTY,
BLOCK_HARDNESS,
CENTER_XZ,
COBBELSTONE,
CREATIVE,
FALL_DAMAGE_THRESHOLD,
FAR,
GRASS,
GRAVITY,
HOTBAR_SIZE,
HUNGER_DRAIN_SPRINT,
HUNGER_DRAIN_WALK,
INTERACTION_DELAY,
INVENTORY_SIZE,
JUMP_VELOCITY,
MAX_HEALTH,
MAX_HUNGER,
MAX_OXYGEN,
NEAR,
OXYGEN_GAIN_TIMER,
OXYGEN_LOSE_TIMER,
PICKAXE_MINING_MULTIPLIER,
PLAYER_DOLPHIN_LEAP_MULTIPLIER,
PLAYER_EYE_HEIGHT,
PLAYER_HALF_W,
PLAYER_HEIGHT,
PLAYER_POS,
PLAYER_SPEED,
PLAYER_SPRINT_MULTIPLIER,
PLAYER_UNDERWATER_GRAVITY_MULTIPLIER,
PLAYER_VERTICAL_WATER_DRAG,
PLAYER_WATER_DRAG_MULTIPLIER,
SPAWN_SEARCH_RADIUS,
SPRINT_FOV_BOOST,
SPRINT_FOV_LERP_SPEED,
STONE,
SURVIVAL,
VIEW_BOBBING_AMPLITUDE,
VIEW_BOBBING_STEP_FREQUENCY,
VOID_DAMAGE,
VOID_DAMAGE_INTERVAL,
VOID_DEATH_Y,
WATER,
WATER_LINE,
WOODEN_PICKAXE,
)
from terrain_gen import get_height
[docs]
class Player(Camera):
"""
Represents the player entity in the world.
Handles movement physics, collision detection, block interaction,
inventory management, and survival statistics like health and hunger.
Args:
app (Any): The main application context.
position (Optional[Any]): The initial spawn coordinates. If None, it automatically calculates a safe spawn.
yaw (float): Initial horizontal rotation in degrees.
pitch (float): Initial vertical rotation in degrees.
"""
@global_profiler.profile_func('Player_Init')
def __init__(self, app: Any, position: Optional[Any] = None, yaw: float = -90.0, pitch: float = 0.0) -> None:
"""
Initializes the player's attributes, camera orientation, physics state,
inventory system, and survival stats.
"""
# Variable assignments
self.app: Any = app
# Conditional logic
if position is None:
# Variable assignments
position = self.find_spawn_position()
# Execute expressions
super().__init__(position, yaw, pitch)
# Variable assignments
self.velocity: Any = glm.vec3(0)
self.on_ground: bool = False
self.in_water: bool = False
self.head_in_water: bool = False
self.feet_pos: Any = glm.vec3(position)
self.game_mode: int = SURVIVAL
self.step_counter: float = 0.0
self.interaction_timer: int = 0
self.interaction_delay: int = INTERACTION_DELAY
self.last_step_time: int = 0
self.target_voxel_pos: Optional[Any] = None
self.mining_time: float = 0.0
self.mining_duration: float = 0.0 # updated dynamically
self.inventory: List[int] = [0] * INVENTORY_SIZE # 0 means empty slot
self.inventory_counts: List[int] = [0] * INVENTORY_SIZE
self.hotbar_index: int = 0
self.fov: float = float(glm.radians(self.app.config['fov']))
self.is_sprinting: bool = False
self.max_health: int = MAX_HEALTH
self.health: float = float(self.max_health)
self.max_hunger: int = MAX_HUNGER
self.hunger: float = float(self.max_hunger)
self.max_oxygen: int = MAX_OXYGEN
self.oxygen: float = float(self.max_oxygen)
self.highest_y: float = position.y
self.oxygen_timer: int = 0
self.last_damage_time: int = 0
self.spawn_immunity: bool = True
[docs]
@global_profiler.profile_func('Player_FindSpawnPosition')
def find_spawn_position(self) -> Any:
"""
Locates a valid surface spawn position near the center of the world
by scanning outward iteratively until a solid block above water is found.
"""
# Variable assignments
center_x: int = int(CENTER_XZ)
center_z: int = int(CENTER_XZ)
# Loop processing
for radius in range(0, SPAWN_SEARCH_RADIUS):
# Loop processing
for dx in range(-radius, radius + 1):
# Loop processing
for dz in range(-radius, radius + 1):
# Conditional logic
if abs(dx) == radius or abs(dz) == radius:
# Variable assignments
x: int = center_x + dx
z: int = center_z + dz
y: int = get_height(x, z, noise.perm)
# Conditional logic
if y > WATER_LINE:
# Return result
return glm.vec3(x + 0.5, y, z + 0.5)
# Return result
return glm.vec3(PLAYER_POS)
[docs]
@global_profiler.profile_func('Player_Update')
def update(self) -> None:
"""
Called every frame. Updates player inputs, physics logic, view bobbing,
dynamic FOV for sprinting, fluid dynamics, and core survival metric drains.
"""
# Conditional logic
if self.app.game_state == 'IN_GAME':
# Execute expressions
self.mouse_control()
if self.game_mode == CREATIVE:
# Conditional logic
if self.app.game_state == 'IN_GAME':
# Execute expressions
self.keyboard_control()
else:
# Variable assignments
self.velocity = glm.vec3(0)
# Variable assignments
head_pos: Any = glm.ivec3(
glm.floor(self.position.x), glm.floor(self.position.y), glm.floor(self.position.z)
)
voxel_head, *_ = self.app.scene.world.voxel_handler.get_voxel_id(head_pos)
self.head_in_water = voxel_head == WATER
else:
# Variable assignments
was_on_ground: bool = self.on_ground
# Conditional logic
if self.app.game_state == 'IN_GAME':
# Execute expressions
self.keyboard_control()
else:
# Variable assignments
self.velocity.x = self.velocity.z = 0.0
self.is_sprinting = False
# Execute expressions
self.apply_gravity()
# Variable assignments
self.on_ground = False
prev_feet_pos: Any = glm.vec3(self.feet_pos)
# Execute expressions
self.move_and_collide()
# Variable assignments
block_under_pos: Any = glm.ivec3(
glm.floor(self.feet_pos.x), glm.floor(self.feet_pos.y - 0.05), glm.floor(self.feet_pos.z)
)
voxel_under, *_ = self.app.scene.world.voxel_handler.get_voxel_id(block_under_pos)
# Conditional logic
if not voxel_under:
# Variable assignments
voxel_under = GRASS
# Variable assignments
body_pos: Any = glm.ivec3(
glm.floor(self.position.x),
glm.floor(self.position.y - PLAYER_EYE_HEIGHT * 0.5),
glm.floor(self.position.z),
)
voxel_body, *_ = self.app.scene.world.voxel_handler.get_voxel_id(body_pos)
feet_block_pos: Any = glm.ivec3(
glm.floor(self.feet_pos.x), glm.floor(self.feet_pos.y), glm.floor(self.feet_pos.z)
)
voxel_feet, *_ = self.app.scene.world.voxel_handler.get_voxel_id(feet_block_pos)
self.in_water = (voxel_body == WATER) or (voxel_feet == WATER)
head_pos = glm.ivec3(glm.floor(self.position.x), glm.floor(self.position.y), glm.floor(self.position.z))
voxel_head, *_ = self.app.scene.world.voxel_handler.get_voxel_id(head_pos)
self.head_in_water = voxel_head == WATER
# Conditional logic
if self.on_ground and not was_on_ground:
# Execute expressions
self.app.sounds.play_jump(voxel_under)
# Variable assignments
fall_dist: float = self.highest_y - self.position.y
# Conditional logic
if self.spawn_immunity:
# Variable assignments
self.spawn_immunity = False
else:
# Conditional logic
if fall_dist > FALL_DAMAGE_THRESHOLD and not self.in_water:
# Variable assignments
damage: int = int(math.floor(fall_dist - FALL_DAMAGE_THRESHOLD))
# Conditional logic
if damage > 0:
# Execute expressions
self.take_damage(damage)
# Variable assignments
self.highest_y = self.position.y
if self.velocity.y > 0 or self.in_water or self.position.y > self.highest_y:
# Variable assignments
self.highest_y = self.position.y
# Variable assignments
bob_offset: float = 0.0
actual_move_dist: float = float(
glm.length(glm.vec2(self.feet_pos.x - prev_feet_pos.x, self.feet_pos.z - prev_feet_pos.z))
)
is_walking: bool = self.on_ground and actual_move_dist > 0.0001
# Conditional logic
if is_walking:
# Variable assignments
self.step_counter += actual_move_dist * VIEW_BOBBING_STEP_FREQUENCY
bob_offset = float(glm.sin(self.step_counter) * VIEW_BOBBING_AMPLITUDE)
hunger_drain: float = (
HUNGER_DRAIN_SPRINT if self.is_sprinting else HUNGER_DRAIN_WALK
) * self.app.delta_time
self.hunger = max(0.0, self.hunger - hunger_drain)
current_time: int = pg.time.get_ticks()
# Conditional logic
if current_time - self.last_step_time > 400: # ms between steps
# Execute expressions
self.app.sounds.play_walk(voxel_under)
# Variable assignments
self.last_step_time = current_time
# Variable assignments
base_fov: float = float(glm.radians(self.app.config['fov']))
is_moving_horizontally: bool = actual_move_dist > 0.0001
target_fov: float = (
base_fov + float(glm.radians(SPRINT_FOV_BOOST))
if self.is_sprinting and is_moving_horizontally
else base_fov
)
self.fov += (target_fov - self.fov) * min(1.0, SPRINT_FOV_LERP_SPEED * self.app.delta_time)
self.m_proj = glm.perspective(self.fov, ASPECT_RATIO, NEAR, FAR)
horizontal_fov: float = 2 * math.atan(math.tan(self.fov * 0.5) * ASPECT_RATIO)
# Execute expressions
self.frustum.update_factors(self.fov, horizontal_fov)
# Variable assignments
self.position = self.feet_pos + glm.vec3(0, PLAYER_EYE_HEIGHT + bob_offset, 0)
# Conditional logic
if self.app.game_state == 'IN_GAME':
# Execute expressions
self.handle_interaction()
# Variable assignments
current_time = pg.time.get_ticks()
# Conditional logic
if self.head_in_water:
# Conditional logic
if current_time - self.oxygen_timer > OXYGEN_LOSE_TIMER:
# Variable assignments
self.oxygen -= 1
self.oxygen_timer = current_time
# Conditional logic
if self.oxygen < 0:
# Variable assignments
self.oxygen = 0
# Execute expressions
self.take_damage(1)
else:
# Conditional logic
if self.oxygen < self.max_oxygen and current_time - self.oxygen_timer > OXYGEN_GAIN_TIMER:
# Variable assignments
self.oxygen += 1
self.oxygen_timer = current_time
if self.position.y < VOID_DEATH_Y:
# Conditional logic
if current_time - self.last_damage_time > VOID_DAMAGE_INTERVAL: # Take damage every half second
# Execute expressions
self.take_damage(VOID_DAMAGE)
# Variable assignments
self.last_damage_time = current_time
# Execute expressions
super().update()
[docs]
@global_profiler.profile_func('Player_HandleEvent')
def handle_event(self, event: Any) -> None:
"""
Processes discrete user inputs such as key presses (mode toggling, hotbar selection)
and mouse clicks (block mining and placing).
"""
# Conditional logic
if event.type == pg.KEYDOWN:
# Conditional logic
if event.key == pg.K_f:
# Variable assignments
self.game_mode = SURVIVAL if self.game_mode == CREATIVE else CREATIVE
# Conditional logic
if self.game_mode == CREATIVE:
# Variable assignments
self.feet_pos = glm.vec3(self.position)
self.velocity = glm.vec3(0)
else:
# Variable assignments
self.feet_pos = glm.vec3(self.position)
self.velocity = glm.vec3(0)
# Variable assignments
self.highest_y = self.position.y
if pg.K_1 <= event.key <= pg.K_1 + HOTBAR_SIZE - 1:
# Variable assignments
self.hotbar_index = event.key - pg.K_1
if event.type == pg.MOUSEBUTTONDOWN:
# Variable assignments
voxel_handler: Any = self.app.scene.world.voxel_handler
# Conditional logic
if event.button == 3: # Right click to place
# Execute expressions
voxel_handler.set_voxel(mode='add')
# Variable assignments
self.interaction_timer = pg.time.get_ticks()
if event.button == 4: # Scroll Up
# Variable assignments
self.hotbar_index = (self.hotbar_index - 1) % HOTBAR_SIZE
if event.button == 5: # Scroll Down
# Variable assignments
self.hotbar_index = (self.hotbar_index + 1) % HOTBAR_SIZE
[docs]
@global_profiler.profile_func('Player_MouseControl')
def mouse_control(self) -> None:
"""
Retrieves relative mouse movement and adjusts the camera's yaw and pitch
based on the configured sensitivity.
"""
# Variable assignments
mouse_dx: int
mouse_dy: int
mouse_dx, mouse_dy = pg.mouse.get_rel()
sens: float = self.app.config['sensitivity']
# Conditional logic
if mouse_dx:
# Execute expressions
self.rotate_yaw(delta_x=mouse_dx * sens)
if mouse_dy:
# Execute expressions
self.rotate_pitch(delta_y=mouse_dy * sens)
[docs]
@global_profiler.profile_func('Player_HandleInteraction')
def handle_interaction(self) -> None:
"""
Processes continuous block interactions (mining and placing).
Factoring in tool requirements, block hardness, and interaction delays.
"""
# Variable assignments
mouse_pressed: Tuple[bool, bool, bool] = pg.mouse.get_pressed()
voxel_handler: Any = self.app.scene.world.voxel_handler
current_time: int = pg.time.get_ticks()
# Conditional logic
if mouse_pressed[0] and voxel_handler.voxel_id:
# Conditional logic
if self.target_voxel_pos == voxel_handler.voxel_world_pos:
# Variable assignments
self.mining_time += self.app.delta_time
# Execute expressions
self.app.sounds.play_breaking(voxel_handler.voxel_id, self.mining_time, self.mining_duration)
# Conditional logic
if (
self.mining_time >= self.mining_duration
and current_time - self.interaction_timer > self.interaction_delay
):
# Execute expressions
voxel_handler.set_voxel(mode='remove')
# Variable assignments
self.mining_time = 0.0
self.interaction_timer = current_time
else:
# Variable assignments
self.target_voxel_pos = voxel_handler.voxel_world_pos
self.mining_time = 0.0
hardness: float = float(BLOCK_HARDNESS.get(voxel_handler.voxel_id, 600.0))
held_id: int = self.inventory[self.hotbar_index]
# Conditional logic
if voxel_handler.voxel_id in (STONE, COBBELSTONE):
# Conditional logic
if held_id == WOODEN_PICKAXE:
# Variable assignments
hardness /= PICKAXE_MINING_MULTIPLIER # 5x faster WITH a pickaxe!
else:
# Variable assignments
hardness *= BAREHAND_MINING_PENALTY # 5x slower without a pickaxe!
# Variable assignments
self.mining_duration = 0.0 if self.game_mode == CREATIVE else hardness
# Execute expressions
self.app.sounds.play_breaking(voxel_handler.voxel_id, self.mining_time, self.mining_duration)
# Conditional logic
if (
self.mining_time >= self.mining_duration
and current_time - self.interaction_timer > self.interaction_delay
):
# Execute expressions
voxel_handler.set_voxel(mode='remove')
# Variable assignments
self.mining_time = 0.0
self.interaction_timer = current_time
else:
# Variable assignments
self.mining_time = 0.0
if mouse_pressed[2]:
# Conditional logic
if current_time - self.interaction_timer > self.interaction_delay:
# Execute expressions
voxel_handler.set_voxel(mode='add')
# Variable assignments
self.interaction_timer = current_time
[docs]
@global_profiler.profile_func('Player_KeyboardControl')
def keyboard_control(self) -> None:
"""
Calculates movement vectors based on keyboard input. Adapts movement
physics depending on whether the player is in Creative or Survival mode.
"""
# Conditional logic
if self.game_mode == CREATIVE:
# Variable assignments
key_state: Any = pg.key.get_pressed()
vel: float = PLAYER_SPEED * 5 * self.app.delta_time
# Conditional logic
if key_state[pg.K_w]:
# Execute expressions
self.move_forward(vel)
if key_state[pg.K_s]:
# Execute expressions
self.move_back(vel)
if key_state[pg.K_d]:
# Execute expressions
self.move_right(vel)
if key_state[pg.K_a]:
# Execute expressions
self.move_left(vel)
if key_state[pg.K_SPACE]:
# Execute expressions
self.move_up(vel)
if key_state[pg.K_LSHIFT]:
# Execute expressions
self.move_down(vel)
else:
# Variable assignments
keys: Any = pg.key.get_pressed()
speed: float = PLAYER_SPEED
self.is_sprinting = False
move_dir: Any = glm.vec3(0)
flat_forward: Any = glm.vec3(self.forward.x, 0, self.forward.z)
# Conditional logic
if glm.length(flat_forward) > 0:
# Variable assignments
flat_forward = glm.normalize(flat_forward)
if keys[pg.K_w]:
# Variable assignments
move_dir += flat_forward
if keys[pg.K_s]:
# Variable assignments
move_dir -= flat_forward
if keys[pg.K_d]:
# Variable assignments
move_dir += self.right
if keys[pg.K_a]:
# Variable assignments
move_dir -= self.right
if glm.length(move_dir):
# Variable assignments
move_dir = glm.normalize(move_dir)
if keys[pg.K_LSHIFT]:
# Variable assignments
speed *= PLAYER_SPRINT_MULTIPLIER
self.is_sprinting = True
# Variable assignments
self.velocity.x = move_dir.x * speed
self.velocity.z = move_dir.z * speed
# Conditional logic
if self.in_water:
# Variable assignments
self.velocity.x *= PLAYER_WATER_DRAG_MULTIPLIER # Water drag
self.velocity.z *= PLAYER_WATER_DRAG_MULTIPLIER
# Conditional logic
if self.on_ground and keys[pg.K_SPACE]:
# Variable assignments
self.velocity.y = JUMP_VELOCITY
self.on_ground = False
# Conditional logic
elif keys[pg.K_SPACE]:
# Conditional logic
if not getattr(self, 'head_in_water', False):
# Variable assignments
self.velocity.y = max(self.velocity.y, JUMP_VELOCITY * PLAYER_DOLPHIN_LEAP_MULTIPLIER)
else:
# Variable assignments
self.velocity.y = max(self.velocity.y, JUMP_VELOCITY * 0.8) # Swim up
else:
# Conditional logic
if self.on_ground and keys[pg.K_SPACE]:
# Variable assignments
self.velocity.y = JUMP_VELOCITY
self.on_ground = False
[docs]
@global_profiler.profile_func('Player_ApplyGravity')
def apply_gravity(self) -> None:
"""
Applies downward gravitational acceleration to the player's vertical velocity,
accounting for drag if the player is swimming in water.
"""
# Conditional logic
if self.in_water:
# Variable assignments
self.velocity.y += GRAVITY * PLAYER_UNDERWATER_GRAVITY_MULTIPLIER * self.app.delta_time
self.velocity.y *= max(0.0, 1.0 - PLAYER_VERTICAL_WATER_DRAG * self.app.delta_time) # vertical water drag
else:
# Variable assignments
self.velocity.y += GRAVITY * self.app.delta_time
[docs]
@global_profiler.profile_func('Player_MoveAndCollide')
def move_and_collide(self) -> None:
"""
Moves the player incrementally along the X, Y, and Z axes,
resolving collision clipping individually for each axis.
"""
# Variable assignments
self.feet_pos.x += self.velocity.x * self.app.delta_time
# Execute expressions
self.resolve_axis('x')
# Variable assignments
self.feet_pos.y += self.velocity.y * self.app.delta_time
# Execute expressions
self.resolve_axis('y')
# Variable assignments
self.feet_pos.z += self.velocity.z * self.app.delta_time
# Execute expressions
self.resolve_axis('z')
[docs]
@global_profiler.profile_func('Player_ResolveAxis')
def resolve_axis(self, axis: str) -> None:
"""
Checks for intersection between the player's AABB and the surrounding
solid voxels. Stops the player's velocity along the tested axis if
a collision is detected to prevent clipping.
"""
# Conditional logic
if getattr(self.velocity, axis) == 0:
# Return result
return
# Variable assignments
aabb_min: Any
aabb_max: Any
aabb_min, aabb_max = self.get_aabb()
min_x: int = int(glm.floor(aabb_min.x))
max_x: int = int(glm.floor(aabb_max.x))
min_y: int = int(glm.floor(aabb_min.y))
max_y: int = int(glm.floor(aabb_max.y))
min_z: int = int(glm.floor(aabb_min.z))
max_z: int = int(glm.floor(aabb_max.z))
# Conditional logic
if axis == 'x':
# Conditional logic
if self.velocity.x > 0:
# Variable assignments
min_x = max_x
else:
# Variable assignments
max_x = min_x
# Conditional logic
elif axis == 'y':
# Conditional logic
if self.velocity.y > 0:
# Variable assignments
min_y = max_y
else:
# Variable assignments
max_y = min_y
# Conditional logic
elif axis == 'z':
# Conditional logic
if self.velocity.z > 0:
# Variable assignments
min_z = max_z
else:
# Variable assignments
max_z = min_z
# Variable assignments
world: Any = self.app.scene.world
# Loop processing
for x in range(min_x, max_x + 1):
# Loop processing
for y in range(min_y, max_y + 1):
# Loop processing
for z in range(min_z, max_z + 1):
# Variable assignments
voxel_id: int
voxel_id, *_ = world.voxel_handler.get_voxel_id(glm.ivec3(x, y, z))
# Conditional logic
if not voxel_id or voxel_id == WATER:
# Loop control
continue
# Variable assignments
voxel_min: Any = glm.vec3(x, y, z)
voxel_max: Any = voxel_min + 1
# Conditional logic
if self.aabb_intersect(aabb_min, aabb_max, voxel_min, voxel_max):
# Conditional logic
if axis == 'x':
# Conditional logic
if self.velocity.x > 0:
# Variable assignments
self.feet_pos.x = voxel_min.x - PLAYER_HALF_W
else:
# Variable assignments
self.feet_pos.x = voxel_max.x + PLAYER_HALF_W
# Variable assignments
self.velocity.x = 0
# Conditional logic
elif axis == 'y':
# Conditional logic
if self.velocity.y > 0:
# Variable assignments
self.feet_pos.y = voxel_min.y - PLAYER_HEIGHT
else:
# Variable assignments
self.feet_pos.y = voxel_max.y
self.on_ground = True
# Variable assignments
self.velocity.y = 0
# Conditional logic
elif axis == 'z':
# Conditional logic
if self.velocity.z > 0:
# Variable assignments
self.feet_pos.z = voxel_min.z - PLAYER_HALF_W
else:
# Variable assignments
self.feet_pos.z = voxel_max.z + PLAYER_HALF_W
# Variable assignments
self.velocity.z = 0
# Variable assignments
aabb_min, aabb_max = self.get_aabb()
[docs]
@global_profiler.profile_func('Player_GetAABB')
def get_aabb(self) -> Tuple[Any, Any]:
"""
Calculates and returns the minimum and maximum boundaries of the
Axis-Aligned Bounding Box representing the player's physical volume.
"""
# Variable assignments
min_v: Any = glm.vec3(
self.feet_pos.x - PLAYER_HALF_W,
self.feet_pos.y,
self.feet_pos.z - PLAYER_HALF_W,
)
max_v: Any = glm.vec3(
self.feet_pos.x + PLAYER_HALF_W,
self.feet_pos.y + PLAYER_HEIGHT,
self.feet_pos.z + PLAYER_HALF_W,
)
# Return result
return min_v, max_v
[docs]
@staticmethod
def aabb_intersect(a_min: Any, a_max: Any, b_min: Any, b_max: Any) -> bool:
"""
Static helper that determines if two given 3D Axis-Aligned Bounding
Boxes overlap with each other.
"""
# Return result
return bool(
a_min.x < b_max.x
and a_max.x > b_min.x
and a_min.y < b_max.y
and a_max.y > b_min.y
and a_min.z < b_max.z
and a_max.z > b_min.z
)
[docs]
@global_profiler.profile_func('Player_AddItem')
def add_item(self, voxel_id: int) -> bool:
"""
Attempts to add an item to the player's inventory by stacking onto
existing slots or finding an empty one. Returns True if successful.
"""
# Loop processing
for i in range(36): # Only check the main 36 storage slots
# Conditional logic
if self.inventory[i] == voxel_id and self.inventory_counts[i] < 64:
# Variable assignments
self.inventory_counts[i] += 1
# Return result
return True
for i in range(36):
# Conditional logic
if self.inventory[i] == 0:
# Variable assignments
self.inventory[i] = voxel_id
self.inventory_counts[i] = 1
# Return result
return True
# Return result
return False # Inventory is full!
[docs]
@global_profiler.profile_func('Player_TakeDamage')
def take_damage(self, amount: int) -> None:
"""
Reduces player health by the specified amount (if in Survival mode),
triggering a respawn sequence if health is completely depleted.
"""
# Conditional logic
if self.game_mode == CREATIVE:
# Return result
return
# Variable assignments
self.health -= amount
# Execute expressions
self.app.sounds.play_walk(GRASS) # Generic damage sound
# Conditional logic
if self.health <= 0:
# Execute expressions
self.respawn()
[docs]
@global_profiler.profile_func('Player_Respawn')
def respawn(self) -> None:
"""
Resets survival statistics and teleports the player back to a
safe, auto-calculated spawn position on the surface.
"""
# Variable assignments
self.health = self.max_health
self.hunger = self.max_hunger
self.oxygen = self.max_oxygen
self.feet_pos = self.find_spawn_position()
self.position = self.feet_pos + glm.vec3(0, PLAYER_EYE_HEIGHT, 0)
self.velocity = glm.vec3(0)
self.highest_y = self.position.y
self.spawn_immunity = True