mirror of
https://github.com/drwhut/tabletop-club.git
synced 2025-05-05 15:32:56 +00:00
199 lines
6.9 KiB
GDScript
199 lines
6.9 KiB
GDScript
# tabletop-club
|
|
# Copyright (c) 2020-2024 Benjamin 'drwhut' Beddows.
|
|
# Copyright (c) 2021-2024 Tabletop Club contributors (see game/CREDITS.tres).
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
extends Piece
|
|
|
|
class_name PieceContainer
|
|
|
|
signal absorbing_hovered(container, player_id)
|
|
signal releasing_random_piece(container)
|
|
|
|
onready var _pieces = $Pieces
|
|
|
|
var _srv_is_locked: bool = false
|
|
|
|
# Add a piece as a child to the container. Note that the piece cannot already
|
|
# have a parent!
|
|
# piece: The piece to add to the container.
|
|
func add_piece(piece: Piece) -> void:
|
|
# Move the piece out of the way of the table so it is not visible, and make
|
|
# sure it cannot move.
|
|
piece.transform.origin = Vector3(9999, 9999, 9999)
|
|
piece.mode = MODE_STATIC
|
|
|
|
_pieces.add_child(piece)
|
|
mass += piece.mass
|
|
|
|
# Duplicate the given piece and return the duplicate.
|
|
# Returns: A duplicate of the piece with the given name in the container, null
|
|
# if the piece doesn't exist.
|
|
# piece_name: The name of the piece to duplicate.
|
|
func duplicate_piece(piece_name: String) -> Piece:
|
|
if has_piece(piece_name):
|
|
var original: Piece = _pieces.get_node(piece_name)
|
|
var duplicate: Piece = original.duplicate(DUPLICATE_SCRIPTS)
|
|
duplicate.piece_entry = original.piece_entry
|
|
return duplicate
|
|
|
|
return null
|
|
|
|
# Get the number of pieces that the container is holding.
|
|
# Returns: The number of pieces inside the container.
|
|
func get_piece_count() -> int:
|
|
return _pieces.get_child_count()
|
|
|
|
# Get the names of the pieces that the container is holding.
|
|
# Returns: An array of the names of the pieces.
|
|
func get_piece_names() -> Array:
|
|
var out = []
|
|
for piece in _pieces.get_children():
|
|
out.append(piece.name)
|
|
return out
|
|
|
|
# Does the container have the given piece?
|
|
# Returns: If the container has the given piece inside of it.
|
|
# piece_name: The name of the piece to check for.
|
|
func has_piece(piece_name: String) -> bool:
|
|
return _pieces.has_node(piece_name)
|
|
|
|
# Is the piece locked, i.e. unable to move?
|
|
# Returns: If the piece is locked.
|
|
func is_locked() -> bool:
|
|
if _srv_is_locked:
|
|
return true
|
|
return .is_locked()
|
|
|
|
# Called by the server to lock the piece on all clients, with the given
|
|
# transform.
|
|
puppet func lock_client(locked_transform: Transform) -> void:
|
|
if get_tree().get_rpc_sender_id() != 1:
|
|
return
|
|
|
|
if get_tree().is_network_server():
|
|
transform = locked_transform
|
|
_srv_set_locked_container(true)
|
|
else:
|
|
.lock_client(locked_transform)
|
|
|
|
# Recalculate the mass of the container, based on the items inside it.
|
|
# You should use this if you added a piece to the container, but not via the
|
|
# add_piece() function.
|
|
func recalculate_mass() -> void:
|
|
mass = piece_entry["mass"]
|
|
|
|
for piece in _pieces.get_children():
|
|
if piece is Piece:
|
|
mass += piece.mass
|
|
|
|
# Release the given piece from the container, and return it with a new
|
|
# transform such that it is just above the top of the container.
|
|
# Returns: The release piece as an orphan node, null if the piece isn't in the
|
|
# container.
|
|
# piece_name: The name of the piece to release.
|
|
func remove_piece(piece_name: String) -> Piece:
|
|
if has_piece(piece_name):
|
|
var piece: Piece = _pieces.get_node(piece_name)
|
|
mass -= piece.mass
|
|
_pieces.remove_child(piece)
|
|
|
|
# NOTE: For some reason (my theory is the physics interpolation patch:
|
|
# https://github.com/drwhut/godot/commit/5d56dd72b13d261c88602d9b37ada1063c5c4b57
|
|
# ), the basis of the transform may drift slightly while in the
|
|
# container, even if the piece is in static mode. This line is a
|
|
# workaround to prevent that from affecting the physics state.
|
|
# TODO: Maybe this can be removed when using Godot 3.5?
|
|
piece.transform.basis = piece.transform.basis.orthonormalized()
|
|
|
|
# Reverse the modifications done to the piece when it was absorbed.
|
|
# NOTE: Rigidbodies themselves are not scaled, only their collision
|
|
# shapes are.
|
|
var adj_piece_size = Global.rotate_bounding_box(piece.get_size(),
|
|
piece.transform.basis)
|
|
var piece_slice_height = abs(transform.basis.y.dot(adj_piece_size))
|
|
var distance = 0.5 * (get_size().y + piece_slice_height) + 1.0
|
|
var new_origin = transform.origin + distance * transform.basis.y
|
|
piece.transform.origin = new_origin
|
|
|
|
piece.mode = MODE_RIGID
|
|
|
|
return piece
|
|
|
|
return null
|
|
|
|
# Lock the piece server-side.
|
|
func srv_lock() -> void:
|
|
_srv_set_locked_container(true)
|
|
rpc("lock_client", transform)
|
|
|
|
# Called by the server to unlock the piece.
|
|
remotesync func unlock() -> void:
|
|
if get_tree().get_rpc_sender_id() != 1:
|
|
return
|
|
|
|
if get_tree().is_network_server():
|
|
_srv_set_locked_container(false)
|
|
sleeping = false
|
|
else:
|
|
.unlock()
|
|
|
|
func _physics_process(_delta):
|
|
if get_tree().is_network_server():
|
|
var shakable: bool = piece_entry["shakable"]
|
|
if shakable:
|
|
# If the container is upside down, and it is being shaken, then
|
|
# randomly release a piece to simulate what would happen in reality.
|
|
if transform.basis.y.y < -0.9 and is_being_shaked():
|
|
emit_signal("releasing_random_piece", self)
|
|
|
|
func _integrate_forces(state):
|
|
if not _srv_is_locked:
|
|
._integrate_forces(state)
|
|
|
|
# Set if the container is locked on the server.
|
|
# NOTE: Locking a container on the server side is different to every other
|
|
# piece, since it still needs to emit signals when locked.
|
|
# is_locked: If the container will be locked.
|
|
func _srv_set_locked_container(is_locked: bool) -> void:
|
|
_srv_is_locked = is_locked
|
|
|
|
axis_lock_angular_x = is_locked
|
|
axis_lock_angular_y = is_locked
|
|
axis_lock_angular_z = is_locked
|
|
axis_lock_linear_x = is_locked
|
|
axis_lock_linear_y = is_locked
|
|
axis_lock_linear_z = is_locked
|
|
|
|
# Disables in-built physics, as well as physics from Piece.
|
|
custom_integrator = is_locked
|
|
|
|
func _on_body_entered(body) -> void:
|
|
._on_body_entered(body)
|
|
|
|
if get_tree().is_network_server():
|
|
|
|
# If a piece has collided with this container, then figure out if the
|
|
# piece was being hovered by a player. If it is, then we can add it to
|
|
# the container!
|
|
if body is Piece:
|
|
if body.is_hovering():
|
|
emit_signal("absorbing_hovered", self, body.hover_player)
|