798 lines
29 KiB
Python
Executable File
798 lines
29 KiB
Python
Executable File
#!/bin/python3
|
|
|
|
"""
|
|
________ ______ ______ __
|
|
| \ / \ / \ | \
|
|
\$$$$$$$$______ ______ ______ ______ | $$$$$$\ ______ ______ | $$$$$$\ _| $$_
|
|
| $$ / \ / \ / \ | \ | $$ \$$ / \ | \ | $$_ \$$| $$ \
|
|
| $$ | $$$$$$\| $$$$$$\| $$$$$$\ \$$$$$$\| $$ | $$$$$$\ \$$$$$$\| $$ \ \$$$$$$
|
|
| $$ | $$ $$| $$ \$$| $$ \$$/ $$| $$ __ | $$ \$$/ $$| $$$$ | $$ __
|
|
| $$ | $$$$$$$$| $$ | $$ | $$$$$$$| $$__/ \| $$ | $$$$$$$| $$ | $$| \
|
|
| $$ \$$ \| $$ | $$ \$$ $$ \$$ $$| $$ \$$ $$| $$ \$$ $$
|
|
\$$ \$$$$$$$ \$$ \$$ \$$$$$$$ \$$$$$$ \$$ \$$$$$$$ \$$ \$$$$
|
|
|
|
|
|
Copyright (C) 2013 Michael Fogleman
|
|
Copyright (C) 2018/2019 Stefano Peris <xenonlab.develop@gmail.com>
|
|
|
|
Github repository: <https://github.com/XenonLab-Studio/TerraCraft>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
import time
|
|
|
|
from collections import deque
|
|
|
|
from pyglet.gl import *
|
|
from pyglet.media import Player
|
|
from pyglet.window import key, mouse
|
|
from pyglet.sprite import Sprite
|
|
from pyglet.graphics import OrderedGroup
|
|
|
|
from .blocks import *
|
|
from .utilities import *
|
|
from .graphics import BlockGroup
|
|
from .genworld import WorldGenerator
|
|
from .world import Model
|
|
|
|
|
|
class AudioEngine:
|
|
"""A high level audio engine for easily playing SFX and Music."""
|
|
|
|
def __init__(self, channels=5):
|
|
self.sfx_players = deque([Player() for _ in range(channels)], maxlen=channels)
|
|
self.music_player = Player()
|
|
|
|
def set_volume(self, percentage):
|
|
"""Set the audio volume, as a percentage of 1 to 100.
|
|
|
|
:param percentage: int: The volume, as a percentage.
|
|
"""
|
|
volume = max(min(1, percentage / 100), 0)
|
|
for player in self.sfx_players:
|
|
player.volume = volume
|
|
self.music_player.volume = volume
|
|
|
|
def play(self, source, position=(0, 0, 0)):
|
|
"""Play a sound effect on the next available channel
|
|
|
|
:param source: A pyglet audio Source
|
|
:param position: Optional spacial position for the sound.
|
|
"""
|
|
player = self.sfx_players[0]
|
|
player.position = position
|
|
player.queue(source=source)
|
|
if not player.playing:
|
|
player.play()
|
|
else:
|
|
player.next_source()
|
|
self.sfx_players.rotate()
|
|
|
|
def play_music(self, source):
|
|
"""Play a music track, or switch to a new one.
|
|
|
|
:param source: A pyglet audio Source
|
|
"""
|
|
self.music_player.queue(source=source)
|
|
if not self.music_player.playing:
|
|
self.music_player.play()
|
|
else:
|
|
self.music_player.next_source()
|
|
|
|
|
|
class Scene:
|
|
"""A base class for all Scenes to inherit from.
|
|
|
|
All Scenes must contain an `update` method. In addition,
|
|
you can also define event handlers for any of the events
|
|
dispatched by the `Window`. Any Scene methods that match
|
|
the Window event names will be automatically set when
|
|
changing to the Scene.
|
|
"""
|
|
|
|
scene_manager = None # This is assigned when adding the Scene
|
|
audio = AudioEngine() # All Scenes share the same AudioEngine
|
|
|
|
def update(self, dt):
|
|
raise NotImplementedError
|
|
|
|
|
|
class MenuScene(Scene):
|
|
def __init__(self, window):
|
|
self.window = window
|
|
self.batch = pyglet.graphics.Batch()
|
|
|
|
# Create a
|
|
title_image = pyglet.resource.image('TerraCraft.png')
|
|
title_image.anchor_x = title_image.width // 2
|
|
title_image.anchor_y = title_image.height + 10
|
|
position = self.window.width // 2, self.window.height
|
|
self.title_graphic = Sprite(img=title_image, x=position[0], y=position[1], batch=self.batch)
|
|
|
|
self.start_label = pyglet.text.Label('Select save & press Enter to start', font_size=25,
|
|
x=self.window.width // 2, y=self.window.height // 2,
|
|
anchor_x='center', anchor_y='center', batch=self.batch)
|
|
|
|
# Create labels for three save slots:
|
|
self.save_slot_labels = []
|
|
for save_slot in [1, 2, 3]:
|
|
self.scene_manager.save.save_slot = save_slot
|
|
# indicate if an existing save exists
|
|
if self.scene_manager.save.has_save_game():
|
|
label_text = f"{save_slot}: load"
|
|
else:
|
|
label_text = f"{save_slot}: new game"
|
|
y_pos = 190 - 50 * save_slot
|
|
label = pyglet.text.Label(
|
|
label_text, font_size=20, x=40, y=y_pos, batch=self.batch)
|
|
self.save_slot_labels.append(label)
|
|
|
|
# Highlight the default save slot
|
|
self.scene_manager.save.save_slot = 1
|
|
self._highlight_save_slot()
|
|
|
|
def update(self, dt):
|
|
pass
|
|
|
|
def _highlight_save_slot(self):
|
|
# First reset all labels to white
|
|
for label in self.save_slot_labels:
|
|
label.color = 255, 255, 255, 255
|
|
# Highlight the selected slot
|
|
index = self.scene_manager.save.save_slot - 1
|
|
self.save_slot_labels[index].color = 50, 50, 50, 255
|
|
|
|
def on_key_press(self, symbol, modifiers):
|
|
"""Event handler for the Window.on_key_press event."""
|
|
if symbol == key.ENTER:
|
|
self.scene_manager.change_scene('GameScene')
|
|
elif symbol == key.ESCAPE:
|
|
self.window.set_exclusive_mouse(False)
|
|
return pyglet.event.EVENT_HANDLED
|
|
|
|
if symbol in (key._1, key._2, key._3):
|
|
if symbol == key._1:
|
|
self.scene_manager.save.save_slot = 1
|
|
elif symbol == key._2:
|
|
self.scene_manager.save.save_slot = 2
|
|
elif symbol == key._3:
|
|
self.scene_manager.save.save_slot = 3
|
|
self._highlight_save_slot()
|
|
|
|
def on_mouse_press(self, x, y, button, modifiers):
|
|
"""Event handler for the Window.on_resize event."""
|
|
self.window.set_exclusive_mouse(True)
|
|
|
|
def on_resize(self, width, height):
|
|
"""Event handler for the Window.on_resize event."""
|
|
# Keep the graphics centered on resize
|
|
self.title_graphic.position = width//2, height
|
|
self.start_label.x = width // 2
|
|
self.start_label.y = height // 2
|
|
|
|
def on_draw(self):
|
|
"""Event handler for the Window.on_draw event."""
|
|
self.window.clear()
|
|
self.batch.draw()
|
|
|
|
|
|
class GameScene(Scene):
|
|
def __init__(self, window):
|
|
self.window = window
|
|
|
|
# A Batch is a collection of vertex lists for batched rendering.
|
|
self.batch = pyglet.graphics.Batch()
|
|
|
|
# pyglet Groups manages setting/unsetting OpenGL state.
|
|
self.block_group = BlockGroup(
|
|
self.window, pyglet.resource.texture('textures.png'), order=0)
|
|
self.hud_group = OrderedGroup(order=1)
|
|
|
|
# Whether or not the window exclusively captures the mouse.
|
|
self.exclusive = False
|
|
|
|
# When flying gravity has no effect and speed is increased.
|
|
self.flying = FLYING
|
|
|
|
# Determine if player is running. If false, then player is walking.
|
|
self.running = RUNNING
|
|
|
|
# Wether or not all gui elements are drawn.
|
|
self.toggleGui = TOGGLE_GUI
|
|
|
|
# Wether or not the fps counter and player coordinates are drawn.
|
|
self.toggleLabel = TOGGLE_INFO_LABEL
|
|
|
|
# Strafing is moving lateral to the direction you are facing,
|
|
# e.g. moving to the left or right while continuing to face forward.
|
|
#
|
|
# First element is -1 when moving forward, 1 when moving back, and 0
|
|
# otherwise. The second element is -1 when moving left, 1 when moving
|
|
# right, and 0 otherwise.
|
|
self.strafe = [0, 0]
|
|
|
|
# Current (x, y, z) position in the world, specified with floats. Note
|
|
# that, perhaps unlike in math class, the y-axis is the vertical axis.
|
|
self.position = (SECTOR_SIZE // 2, 6, SECTOR_SIZE // 2)
|
|
|
|
# First element is rotation of the player in the x-z plane (ground
|
|
# plane) measured from the z-axis down. The second is the rotation
|
|
# angle from the ground plane up. Rotation is in degrees.
|
|
#
|
|
# The vertical plane rotation ranges from -90 (looking straight down) to
|
|
# 90 (looking straight up). The horizontal rotation range is unbounded.
|
|
self.rotation = (0, 0)
|
|
|
|
# Which sector the player is currently in.
|
|
self.sector = None
|
|
|
|
# True if the location of the camera have changed between an update
|
|
self.frustum_updated = False
|
|
|
|
# Velocity in the y (upward) direction.
|
|
self.dy = 0
|
|
|
|
# A list of blocks the player can place. Hit num keys to cycle.
|
|
self.inventory = [DIRT, DIRT_WITH_GRASS, SAND, SNOW, COBBLESTONE,
|
|
BRICK_COBBLESTONE, BRICK, TREE, LEAVES, WOODEN_PLANKS]
|
|
|
|
# The current block the user can place. Hit num keys to cycle.
|
|
self.block = self.inventory[0]
|
|
|
|
# Convenience list of num keys.
|
|
self.num_keys = [key._1, key._2, key._3, key._4, key._5,
|
|
key._6, key._7, key._8, key._9, key._0]
|
|
|
|
# Instance of the model that handles the world.
|
|
self.model = Model(batch=self.batch, group=self.block_group)
|
|
|
|
# The crosshairs at the center of the screen.
|
|
self.reticle = self.batch.add(4, GL_LINES, self.hud_group, 'v2i', ('c3B', [0]*12))
|
|
|
|
# The highlight around focused block.
|
|
indices = [0, 1, 1, 2, 2, 3, 3, 0, 4, 7, 7, 6, 6, 5, 5, 4, 0, 4, 1, 7, 2, 6, 3, 5]
|
|
self.highlight = self.batch.add_indexed(24, GL_LINES, self.block_group, indices,
|
|
'v3f/dynamic', ('c3B', [0]*72))
|
|
|
|
# The label that is displayed in the top left of the canvas.
|
|
self.info_label = pyglet.text.Label('', font_name='Arial', font_size=INFO_LABEL_FONTSIZE,
|
|
x=10, y=self.window.height - 10, anchor_x='left',
|
|
anchor_y='top', color=(0, 0, 0, 255))
|
|
|
|
# Boolean whether to display loading screen.
|
|
self.initialized = False
|
|
|
|
# Some environmental SFX to preload:
|
|
self.jump_sfx = pyglet.resource.media('jump.wav', streaming=False)
|
|
self.destroy_sfx = pyglet.resource.media('dirt.wav', streaming=False)
|
|
|
|
self.on_resize(*self.window.get_size())
|
|
|
|
def set_exclusive_mouse(self, exclusive):
|
|
""" If `exclusive` is True, the game will capture the mouse, if False
|
|
the game will ignore the mouse.
|
|
|
|
"""
|
|
self.window.set_exclusive_mouse(exclusive)
|
|
self.exclusive = exclusive
|
|
|
|
def get_sight_vector(self):
|
|
""" Returns the current line of sight vector indicating the direction
|
|
the player is looking.
|
|
|
|
"""
|
|
x, y = self.rotation
|
|
# y ranges from -90 to 90, or -pi/2 to pi/2, so m ranges from 0 to 1 and
|
|
# is 1 when looking ahead parallel to the ground and 0 when looking
|
|
# straight up or down.
|
|
m = math.cos(math.radians(y))
|
|
# dy ranges from -1 to 1 and is -1 when looking straight down and 1 when
|
|
# looking straight up.
|
|
dy = math.sin(math.radians(y))
|
|
dx = math.cos(math.radians(x - 90)) * m
|
|
dz = math.sin(math.radians(x - 90)) * m
|
|
return dx, dy, dz
|
|
|
|
def get_motion_vector(self):
|
|
""" Returns the current motion vector indicating the velocity of the
|
|
player.
|
|
|
|
Returns
|
|
-------
|
|
vector : tuple of len 3
|
|
Tuple containing the velocity in x, y, and z respectively.
|
|
|
|
"""
|
|
if any(self.strafe):
|
|
x, y = self.rotation
|
|
strafe = math.degrees(math.atan2(*self.strafe))
|
|
y_angle = math.radians(y)
|
|
x_angle = math.radians(x + strafe)
|
|
if self.flying:
|
|
m = math.cos(y_angle)
|
|
dy = math.sin(y_angle)
|
|
if self.strafe[1]:
|
|
# Moving left or right.
|
|
dy = 0.0
|
|
m = 1
|
|
if self.strafe[0] > 0:
|
|
# Moving backwards.
|
|
dy *= -1
|
|
# When you are flying up or down, you have less left and right
|
|
# motion.
|
|
dx = math.cos(x_angle) * m
|
|
dz = math.sin(x_angle) * m
|
|
else:
|
|
dy = 0.0
|
|
dx = math.cos(x_angle)
|
|
dz = math.sin(x_angle)
|
|
elif self.flying and not self.dy == 0:
|
|
dx = 0.0
|
|
dy = self.dy
|
|
dz = 0.0
|
|
else:
|
|
dy = 0.0
|
|
dx = 0.0
|
|
dz = 0.0
|
|
return dx, dy, dz
|
|
|
|
def init_player_on_summit(self):
|
|
"""Make sure the sector containing the actor is loaded and the player is on top of it.
|
|
"""
|
|
generator = self.model.generator
|
|
x, y, z = self.position
|
|
free_height = 0
|
|
limit = 100
|
|
while free_height < PLAYER_HEIGHT and limit:
|
|
pos = x , y, z
|
|
sector_position = sectorize(pos)
|
|
if sector_position not in self.model.sectors:
|
|
sector = generator.generate(sector_position)
|
|
self.model.register_sector(sector)
|
|
if self.model.empty(pos):
|
|
free_height += 1
|
|
else:
|
|
free_height = 0
|
|
y = y + 1
|
|
limit -= 1
|
|
|
|
position = x, y - PLAYER_HEIGHT + 1, z
|
|
if self.position != position:
|
|
self.position = position
|
|
self.frustum_updated = True
|
|
|
|
def update(self, dt):
|
|
""" This method is scheduled to be called repeatedly by the pyglet
|
|
clock.
|
|
|
|
Parameters
|
|
----------
|
|
dt : float
|
|
The change in time since the last call.
|
|
|
|
"""
|
|
if not self.initialized:
|
|
self.set_exclusive_mouse(True)
|
|
|
|
has_save = False
|
|
if self.scene_manager.save.has_save_game():
|
|
# Returns False if unable to load the save
|
|
has_save = self.scene_manager.save.load_world(self.model)
|
|
|
|
if not has_save:
|
|
generator = WorldGenerator()
|
|
generator.y = self.position[1]
|
|
generator.hills_enabled = HILLS_ON
|
|
self.model.generator = generator
|
|
self.init_player_on_summit()
|
|
|
|
self.initialized = True
|
|
|
|
self.model.process_queue()
|
|
|
|
if self.frustum_updated:
|
|
sector = sectorize(self.position)
|
|
self.update_shown_sectors(self.position, self.rotation)
|
|
self.sector = sector
|
|
self.frustum_updated = False
|
|
|
|
m = 8
|
|
dt = min(dt, 0.2)
|
|
for _ in range(m):
|
|
self._update(dt / m)
|
|
|
|
def _update(self, dt):
|
|
""" Private implementation of the `update()` method. This is where most
|
|
of the motion logic lives, along with gravity and collision detection.
|
|
|
|
Parameters
|
|
----------
|
|
dt : float
|
|
The change in time since the last call.
|
|
|
|
"""
|
|
# walking
|
|
speed = FLYING_SPEED if self.flying else RUNNING_SPEED if self.running else WALKING_SPEED
|
|
d = dt * speed # distance covered this tick.
|
|
dx, dy, dz = self.get_motion_vector()
|
|
# New position in space, before accounting for gravity.
|
|
dx, dy, dz = dx * d, dy * d, dz * d
|
|
# gravity
|
|
if not self.flying:
|
|
# Update your vertical speed: if you are falling, speed up until you
|
|
# hit terminal velocity; if you are jumping, slow down until you
|
|
# start falling.
|
|
self.dy -= dt * GRAVITY
|
|
self.dy = max(self.dy, -TERMINAL_VELOCITY)
|
|
dy += self.dy * dt
|
|
# collisions
|
|
x, y, z = self.position
|
|
x, y, z = self.collide((x + dx, y + dy, z + dz), PLAYER_HEIGHT)
|
|
|
|
position = (x, y, z)
|
|
if self.position != position:
|
|
self.position = position
|
|
self.frustum_updated = True
|
|
|
|
def collide(self, position, height):
|
|
""" Checks to see if the player at the given `position` and `height`
|
|
is colliding with any blocks in the world.
|
|
|
|
Parameters
|
|
----------
|
|
position : tuple of len 3
|
|
The (x, y, z) position to check for collisions at.
|
|
height : int or float
|
|
The height of the player.
|
|
|
|
Returns
|
|
-------
|
|
position : tuple of len 3
|
|
The new position of the player taking into account collisions.
|
|
|
|
"""
|
|
# How much overlap with a dimension of a surrounding block you need to
|
|
# have to count as a collision. If 0, touching terrain at all counts as
|
|
# a collision. If .49, you sink into the ground, as if walking through
|
|
# tall DIRT_WITH_GRASS. If >= .5, you'll fall through the ground.
|
|
pad = 0.25
|
|
p = list(position)
|
|
np = normalize(position)
|
|
for face in FACES: # check all surrounding blocks
|
|
for i in range(3): # check each dimension independently
|
|
if not face[i]:
|
|
continue
|
|
# How much overlap you have with this dimension.
|
|
d = (p[i] - np[i]) * face[i]
|
|
if d < pad:
|
|
continue
|
|
for dy in range(height): # check each height
|
|
op = list(np)
|
|
op[1] -= dy
|
|
op[i] += face[i]
|
|
if self.model.empty(tuple(op), must_be_loaded=True):
|
|
continue
|
|
p[i] -= (d - pad) * face[i]
|
|
if face == (0, -1, 0) or face == (0, 1, 0):
|
|
# You are colliding with the ground or ceiling, so stop
|
|
# falling / rising.
|
|
self.dy = 0
|
|
break
|
|
|
|
generator = self.model.generator
|
|
if generator is None:
|
|
# colliding with the virtual floor
|
|
# to avoid to fall infinitely.
|
|
p[1] = max(-1.25, p[1])
|
|
else:
|
|
if generator.enclosure:
|
|
# Force the player inside the enclosure
|
|
s = generator.enclosure_size
|
|
if p[0] < -s:
|
|
p[0] = -s
|
|
elif p[0] > s:
|
|
p[0] = s
|
|
if p[2] < -s:
|
|
p[2] = -s
|
|
elif p[2] > s:
|
|
p[2] = s
|
|
|
|
return tuple(p)
|
|
|
|
def update_shown_sectors(self, position, rotation):
|
|
"""Update shown sectors according to the actual frustum.
|
|
|
|
A sector is a contiguous x, y sub-region of world. Sectors are
|
|
used to speed up world rendering.
|
|
"""
|
|
sector = sectorize(position)
|
|
if self.sector == sector:
|
|
# The following computation is based on the actual sector
|
|
# So if there is no changes on the sector, it have to display
|
|
# The exact same thing
|
|
return
|
|
|
|
sectors_to_show = []
|
|
pad = int(FOG_END) // SECTOR_SIZE
|
|
for dx in range(-pad, pad + 1):
|
|
for dy in range(-pad, pad + 1):
|
|
for dz in range(-pad, pad + 1):
|
|
# Manathan distance
|
|
dist = abs(dx) + abs(dy) + abs(dz)
|
|
if dist > pad + pad // 2:
|
|
# Skip sectors outside of the sphere of radius pad+1
|
|
continue
|
|
x, y, z = sector
|
|
sectors_to_show.append((dist, x + dx, y + dy, z + dz))
|
|
|
|
# Sort by distance to the player in order to
|
|
# displayed closest sectors first
|
|
sectors_to_show = sorted(sectors_to_show)
|
|
sectors_to_show = [s[1:] for s in sectors_to_show]
|
|
self.model.show_only_sectors(sectors_to_show)
|
|
|
|
def on_mouse_press(self, x, y, button, modifiers):
|
|
"""Event handler for the Window.on_mouse_press event.
|
|
|
|
Called when a mouse button is pressed. See pyglet docs for button
|
|
amd modifier mappings.
|
|
|
|
Parameters
|
|
----------
|
|
x, y : int
|
|
The coordinates of the mouse click. Always center of the screen if
|
|
the mouse is captured.
|
|
button : int
|
|
Number representing mouse button that was clicked. 1 = left button,
|
|
4 = right button.
|
|
modifiers : int
|
|
Number representing any modifying keys that were pressed when the
|
|
mouse button was clicked.
|
|
|
|
"""
|
|
if self.exclusive:
|
|
vector = self.get_sight_vector()
|
|
block, previous = self.model.hit_test(self.position, vector)
|
|
if button == mouse.RIGHT or (button == mouse.LEFT and modifiers & key.MOD_CTRL):
|
|
# ON OSX, control + left click = right click.
|
|
if previous:
|
|
self.model.add_block(previous, self.block)
|
|
elif button == pyglet.window.mouse.LEFT and block:
|
|
texture = self.model.get_block(block)
|
|
if texture != BEDSTONE:
|
|
self.model.remove_block(block)
|
|
self.audio.play(self.destroy_sfx)
|
|
else:
|
|
self.set_exclusive_mouse(True)
|
|
|
|
def on_mouse_motion(self, x, y, dx, dy):
|
|
"""Event handler for the Window.on_mouse_motion event.
|
|
|
|
Called when the player moves the mouse.
|
|
|
|
Parameters
|
|
----------
|
|
x, y : int
|
|
The coordinates of the mouse click. Always center of the screen if
|
|
the mouse is captured.
|
|
dx, dy : float
|
|
The movement of the mouse.
|
|
|
|
"""
|
|
if self.exclusive:
|
|
x, y = self.rotation
|
|
x, y = x + dx * LOOK_SPEED_X, y + dy * LOOK_SPEED_Y
|
|
y = max(-90, min(90, y))
|
|
rotation = (x, y)
|
|
if self.rotation != rotation:
|
|
self.rotation = rotation
|
|
self.frustum_updated = True
|
|
|
|
def on_key_press(self, symbol, modifiers):
|
|
"""Event handler for the Window.on_key_press event.
|
|
|
|
Called when the player presses a key. See pyglet docs for key
|
|
mappings.
|
|
|
|
Parameters
|
|
----------
|
|
symbol : int
|
|
Number representing the key that was pressed.
|
|
modifiers : int
|
|
Number representing any modifying keys that were pressed.
|
|
|
|
"""
|
|
if symbol == key.W:
|
|
self.strafe[0] -= 1
|
|
elif symbol == key.S:
|
|
self.strafe[0] += 1
|
|
elif symbol == key.A:
|
|
self.strafe[1] -= 1
|
|
elif symbol == key.D:
|
|
self.strafe[1] += 1
|
|
elif symbol == key.SPACE:
|
|
if self.flying:
|
|
# Reduces vertical flying speed
|
|
# 0.1 positive value that allows vertical flight up.
|
|
self.dy = 0.1 * JUMP_SPEED
|
|
elif self.dy == 0:
|
|
self.dy = JUMP_SPEED
|
|
self.audio.play(self.jump_sfx)
|
|
elif symbol == key.LCTRL:
|
|
self.running = True
|
|
elif symbol == key.LSHIFT:
|
|
if self.flying:
|
|
# Reduces vertical flying speed
|
|
# -0.1 negative value that allows vertical flight down.
|
|
self.dy = -0.1 * JUMP_SPEED
|
|
elif self.dy == 0:
|
|
self.dy = JUMP_SPEED
|
|
elif symbol == key.ESCAPE:
|
|
self.set_exclusive_mouse(False)
|
|
return pyglet.event.EVENT_HANDLED
|
|
elif symbol == key.TAB:
|
|
self.flying = not self.flying
|
|
elif symbol == key.F1:
|
|
self.scene_manager.change_scene("HelpScene")
|
|
elif symbol == key.F2:
|
|
self.toggleGui = not self.toggleGui
|
|
elif symbol == key.F3:
|
|
self.toggleLabel = not self.toggleLabel
|
|
elif symbol == key.F5:
|
|
self.scene_manager.save.save_world(self.model)
|
|
elif symbol == key.F12:
|
|
pyglet.image.get_buffer_manager().get_color_buffer().save('screenshot.png')
|
|
elif symbol in self.num_keys:
|
|
index = (symbol - self.num_keys[0]) % len(self.inventory)
|
|
self.block = self.inventory[index]
|
|
elif symbol == key.ENTER:
|
|
self.scene_manager.change_scene('MenuScene')
|
|
|
|
def on_key_release(self, symbol, modifiers):
|
|
"""Event handler for the Window.on_key_release event.
|
|
|
|
Called when the player releases a key. See pyglet docs for key
|
|
mappings.
|
|
|
|
Parameters
|
|
----------
|
|
symbol : int
|
|
Number representing the key that was pressed.
|
|
modifiers : int
|
|
Number representing any modifying keys that were pressed.
|
|
|
|
"""
|
|
if symbol == key.W:
|
|
self.strafe[0] += 1
|
|
elif symbol == key.S:
|
|
self.strafe[0] -= 1
|
|
elif symbol == key.A:
|
|
self.strafe[1] += 1
|
|
elif symbol == key.D:
|
|
self.strafe[1] -= 1
|
|
elif symbol == key.SPACE:
|
|
self.dy = 0
|
|
elif symbol == key.LCTRL:
|
|
self.running = False
|
|
elif symbol == key.LSHIFT:
|
|
self.dy = 0
|
|
elif symbol == key.P:
|
|
breakpoint()
|
|
|
|
def on_resize(self, width, height):
|
|
"""Event handler for the Window.on_resize event.
|
|
|
|
Called when the window is resized to a new `width` and `height`.
|
|
"""
|
|
# Reset the info label and reticle positions.
|
|
self.info_label.y = height - 10
|
|
x, y = width // 2, height // 2
|
|
n = 10
|
|
self.reticle.vertices[:] = (x - n, y, x + n, y, x, y - n, x, y + n)
|
|
|
|
def on_draw(self):
|
|
"""Event handler for the Window.on_draw event.
|
|
|
|
Called by pyglet to draw the canvas.
|
|
"""
|
|
self.window.clear()
|
|
# Set the current position/rotation before drawing
|
|
self.block_group.position = self.position
|
|
self.block_group.rotation = self.rotation
|
|
# Draw everything in the batch
|
|
self.batch.draw()
|
|
|
|
# Optionally draw some things
|
|
if self.toggleGui:
|
|
self.draw_focused_block()
|
|
if self.toggleLabel:
|
|
self.draw_label()
|
|
|
|
def get_focus_block(self):
|
|
vector = self.get_sight_vector()
|
|
block = self.model.hit_test(self.position, vector)[0]
|
|
return block
|
|
|
|
def draw_focused_block(self):
|
|
""" Draw black edges around the block that is currently under the
|
|
crosshairs.
|
|
|
|
"""
|
|
block = self.get_focus_block()
|
|
if block:
|
|
x, y, z = block
|
|
self.highlight.vertices[:] = cube_vertices(x, y, z, 0.51)
|
|
else:
|
|
# Make invisible by setting all vertices to 0
|
|
self.highlight.vertices[:] = [0] * 72
|
|
|
|
def draw_label(self):
|
|
""" Draw the label in the top left of the screen.
|
|
|
|
"""
|
|
x, y, z = self.position
|
|
elements = []
|
|
elements.append("FPS = [%02d]" % pyglet.clock.get_fps())
|
|
elements.append("COORDS = [%.2f, %.2f, %.2f]" % (x, y, z))
|
|
elements.append("SECTORS = %d [+%d]" % (len(self.model.sectors), len(self.model.requested)))
|
|
elements.append("BLOCKS = %d" % self.model.count_blocks())
|
|
self.info_label.text = ' : '.join(elements)
|
|
self.info_label.draw()
|
|
|
|
|
|
class HelpScene(Scene):
|
|
def __init__(self, window):
|
|
self.window = window
|
|
self.batch = pyglet.graphics.Batch()
|
|
|
|
self.labels = []
|
|
self.text_strings = [" GAME OPTIONS",
|
|
"* Left click mouse to destroy block",
|
|
"* Right click mouse to create block",
|
|
"* Press keys 1 through 0 to choose block type",
|
|
"* Press F2 key to hide block selection",
|
|
"* Press F3 key to hide debug stats"]
|
|
|
|
self.return_label = pyglet.text.Label("Press any key to return to game", font_size=25,
|
|
x=self.window.width // 2, y=20, anchor_x='center',
|
|
color=(0, 50, 50, 255), batch=self.batch)
|
|
|
|
self.spacing = 60
|
|
y_position = self.window.height - self.spacing
|
|
|
|
for string in self.text_strings:
|
|
self.labels.append(pyglet.text.Label(string, font_size=22, x=40, y=y_position,
|
|
color=(0, 50, 50, 255), batch=self.batch))
|
|
y_position -= self.spacing
|
|
|
|
self.on_resize(*self.window.get_size())
|
|
|
|
def on_resize(self, width, height):
|
|
y_position = height - self.spacing
|
|
for label in self.labels:
|
|
label.y = y_position
|
|
y_position -= self.spacing
|
|
|
|
def update(self, dt):
|
|
pass
|
|
|
|
def on_key_press(self, symbol, modifiers):
|
|
self.scene_manager.change_scene("GameScene")
|
|
return pyglet.event.EVENT_HANDLED
|
|
|
|
def on_draw(self):
|
|
self.window.clear()
|
|
self.batch.draw()
|