Init commit

This commit is contained in:
Nikolai Rodionov 2025-02-06 09:56:39 +01:00
commit 8f61ab6c6b
Signed by: allanger
GPG Key ID: 09F8B434D0FDD99B
48 changed files with 5511 additions and 0 deletions

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
*.png filter=lfs diff=lfs merge=lfs -text
godot/assets/** filter=lfs diff=lfs merge=lfs -text

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Godot 4+ specific ignores
.godot/
rust/target
venv

5
Makefile Normal file
View File

@ -0,0 +1,5 @@
rust_build:
cd rust && cargo build
rust_fmt:
cd rust && cargo fmt

0
godot/.gdextension Normal file
View File

BIN
godot/assets/models/characters/model.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
godot/assets/models/characters/model.glb.import (Stored with Git LFS) Normal file

Binary file not shown.

BIN
godot/assets/models/maps/lowpoly_tdm_1/map.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
godot/assets/models/maps/lowpoly_tdm_1/map.glb.import (Stored with Git LFS) Normal file

Binary file not shown.

1
godot/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

37
godot/icon.svg.import Normal file
View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ds2iifncj650s"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

72
godot/project.godot Normal file
View File

@ -0,0 +1,72 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="Killbox"
config/version="0.1.0"
run/main_scene="res://scenes/game_root/game_root.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"
[autoload]
logger="*res://scenes/utils/logger.gd"
consts="*res://scenes/utils/consts.gd"
GameServerAutoload="*res://scenes/server/server.gd"
[display]
window/size/mode=4
[input]
move_forward={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
move_backward={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
move_left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
move_right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
shoot={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
jump={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
]
}
[layer_names]
3d_render/layer_1="Static world"
3d_render/layer_2="Player node"
3d_render/layer_3="Characters on the server"
3d_physics/layer_1="Static world"
3d_navigation/layer_1="Static world"
3d_physics/layer_2="Player node"
3d_navigation/layer_2="Player Node"
3d_physics/layer_3="Characters on the server"
3d_navigation/layer_3="Characters on the server"

View File

@ -0,0 +1,14 @@
[configuration]
entry_symbol = "gdext_rust_init"
compatibility_minimum = 4.1
reloadable = true
[libraries]
linux.debug.x86_64 = "res://../rust/target/debug/libopen_strike_2.so"
linux.release.x86_64 = "res://../rust/target/release/libopen_strike_2.so"
windows.debug.x86_64 = "res://../rust/target/debug/open_strike_2.dll"
windows.release.x86_64 = "res://../rust/target/release/open_strike_2.dll"
macos.debug = "res://../rust/target/debug/libopen_strike_2.dylib"
macos.release = "res://../rust/target/release/libopen_strike_2.dylib"
macos.debug.arm64 = "res://../rust/target/debug/libopen_strike_2.dylib"
macos.release.arm64 = "res://../rust/target/release/libopen_strike_2.dylib"

View File

@ -0,0 +1,33 @@
extends Node
class_name CharacterWrapper
@export var die_script: GDScript = null
var owner_placeholder: Node3D = null
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# Characters should be always controlled by the server and synced accross client
# The owner should be responsible for the syncronization, since this node is
# just a dummy that is following the controller
set_multiplayer_authority(1)
pass # Replace with function body.
# Set the owner placeholder, so the characters can send the requests to a node
# it depends on
func set_owner_placeholder(owner: Node3D):
owner_placeholder = owner
func die():
push_warning("TODO: Implement ragdoll kind of dying and respawn character as an object")
queue_free()
func _on_area_body_part_hit(damage: int) -> void:
# The owner node should be aware of how to take damage, so we need to
# pass the value.
if owner_placeholder:
if owner_placeholder.has_method("take_damage"):
owner_placeholder.take_damage(damage)
else:
push_warning("Node doesn't know how to take the damage")

View File

@ -0,0 +1,13 @@
extends Area3D
@export var damage_multiplexer: float = 1.0
@export var body_part: String = ""
signal body_part_hit(damage: int)
func _ready() -> void:
add_to_group("target")
func hit(base_damage: int):
var final_damage = round(base_damage * damage_multiplexer)
body_part_hit.emit(final_damage)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,78 @@
extends Node
class_name GameRoot
signal load_map(name: String)
# -- config from args
var config_file: String = ""
const MAIN_MENU_SCENE := "res://scenes/menus/main/main_menu.tscn"
@onready var level_root: Node3D = $LevelLoader/Level
@onready var level_spawner: MultiplayerSpawner = $LevelLoader/MultiplayerSpawner
func _parse_args() -> void:
var args := Array(OS.get_cmdline_args())
var config_arg: String = "--config"
if args.has(config_arg):
var index := args.find(config_arg)
config_file = args[index + 1]
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
_parse_args()
if OS.has_feature("dedicated_server") or DisplayServer.get_name() == "headless":
var err := _start_dedicated_server()
if err != OK:
logger.error("couldn't start the server, err is " + str(err))
else:
var err := _start_game()
if err != OK:
logger.error("couldn't start the game, err is " + str(err))
func _start_dedicated_server() -> Error:
if config_file != "":
logger.info("reading config from the file: " + config_file)
logger.info("starting in the dedicated server mode")
return OK
func _start_game() -> Error:
logger.info("starting in the game mode")
if not ResourceLoader.exists(MAIN_MENU_SCENE):
return ERR_DOES_NOT_EXIST
var scene: PackedScene = ResourceLoader.load(MAIN_MENU_SCENE)
if not scene.can_instantiate():
return ERR_CANT_OPEN
var node: MainMenu = scene.instantiate()
add_child(node)
return OK
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func _on_load_map(map_name: String) -> void:
for c in level_root.get_children():
level_root.remove_child(c)
c.queue_free()
var path_tmpl := "res://scenes/maps/maps/%s"
var path := path_tmpl % map_name
if not ResourceLoader.exists(path):
logger.error("map " + map_name + " doesn't exist")
var scene: PackedScene = ResourceLoader.load(path)
if scene.can_instantiate():
var node: Node3D = scene.instantiate()
logger.info("loading map: " + map_name)
level_root.add_child(node)
else:
logger.error("Can't initialize")

View File

@ -0,0 +1,18 @@
[gd_scene load_steps=2 format=3 uid="uid://d3qjxw4rk4yn4"]
[ext_resource type="Script" path="res://scenes/game_root/game_root.gd" id="1_eb14f"]
[node name="GameRoot" type="Node"]
editor_description = "This node serves a starting point for the game. When the game is running as a dedicated server it's supposed to read the config file and start the server, when the game is running in a default mode, it should render the main menu"
script = ExtResource("1_eb14f")
[node name="LevelLoader" type="Node" parent="."]
editor_description = "This node is supposed to make it possible to sync the server data across all the possible players"
[node name="Level" type="Node3D" parent="LevelLoader"]
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="LevelLoader"]
_spawnable_scenes = PackedStringArray("res://scenes/maps/maps/lowpoly_tdm_1.tscn")
spawn_path = NodePath("../Level")
[connection signal="load_map" from="." to="." method="_on_load_map"]

View File

@ -0,0 +1,21 @@
extends Node
class_name BulletSpawnerController
# This script shoud be able to find the player
func _get_spawner() -> MultiplayerSpawner:
return $MultiplayerSpawner
func _get_root() -> Node3D:
return $Bullets
# -- TODO: Better bullet naming handler
var bullet_amount: int = -2147483647
func spawn_bullet(starting_point: Node3D, speed: int, damage: int):
var node: Node3D = ResourceLoader.load("res://scenes/weapon/bullet.tscn").instantiate()
node.position = starting_point.global_position
node.transform.basis = starting_point.global_transform.basis
node.name = str(bullet_amount)
node.speed = speed
node.damage = damage
bullet_amount += 1
_get_root().add_child(node)

View File

@ -0,0 +1,11 @@
[gd_scene load_steps=2 format=3 uid="uid://dp1lcbwr7vaq1"]
[ext_resource type="Script" path="res://scenes/maps/base/bullet_spawner/bullet_controller.gd" id="1_ohxtg"]
[node name="BulletSpawner" type="Node3D"]
script = ExtResource("1_ohxtg")
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."]
spawn_path = NodePath("../Bullets")
[node name="Bullets" type="Node3D" parent="."]

View File

@ -0,0 +1,92 @@
extends Node
class_name MapController
const PLAYER_SPAWNER: String = "res://scenes/maps/base/player_spawner/player_spawner.tscn"
const BULLET_SPAWNER: String = "res://scenes/maps/base/bullet_spawner/bullet_spawner.tscn"
var player_spawner: PlayerSpawnerController
var bullet_spawner: BulletSpawnerController
var client_node: Node3D
@onready var spawn_locations: SpawnController = $SpawnLocations
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# add player spawner
var err: Error = OK
err = _add_player_spawner()
if err != OK:
push_error("Couldn't load player spawner")
err = _add_bullet_spawner()
if err != OK:
push_error("Couldn't load bullet spawner")
# Adding a node that should be used by the clients
client_node = Node3D.new()
client_node.name = "ClientNode"
add_child(client_node)
# add objects spawner
if not OS.has_feature("dedicated_server"):
_request_spawn.rpc_id(1)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
if multiplayer.is_server():
pass
#var active_players = player_spawner._get_root().get_children()
#for n in active_players:
#if not Server.players.has(n.owner_id):
#_remove_player(n.owner_id)
@rpc("call_local", "reliable", "any_peer")
func _request_spawn():
var id: int = multiplayer.get_remote_sender_id()
_spawn_player(id)
_spawn_player_controller_node.rpc_id(id)
func _spawn_player(id: int):
player_spawner.spawn_players(spawn_locations, id)
@rpc("call_local", "reliable", "any_peer")
func _spawn_player_controller_node():
var path := "res://scenes/player/player_input.tscn"
var scene: PackedScene = ResourceLoader.load(path)
var controller_scene: PlayerPlaceholder = player_spawner.get_player_node(multiplayer.get_unique_id())
var player_node: PlayerInputController = scene.instantiate()
player_node.controlled_node = controller_scene
client_node.add_child(player_node)
func _remove_player(id: int):
player_spawner.remove_player(id)
func _add_player_spawner() -> Error :
if not ResourceLoader.exists(PLAYER_SPAWNER):
return ERR_DOES_NOT_EXIST
var scene: PackedScene = ResourceLoader.load(PLAYER_SPAWNER)
if not scene.can_instantiate():
return ERR_CANT_OPEN
var node: Node3D = scene.instantiate()
add_child(node)
player_spawner = node
return OK
func _add_bullet_spawner() -> Error :
if not ResourceLoader.exists(BULLET_SPAWNER):
return ERR_DOES_NOT_EXIST
var scene: PackedScene = ResourceLoader.load(BULLET_SPAWNER)
if not scene.can_instantiate():
return ERR_CANT_OPEN
var node: BulletSpawnerController = scene.instantiate()
add_child(node)
bullet_spawner = node
return OK
# -- TODO: Better bullet naming handler
var bullet_amount: int = -2147483647
func spawn_bullet(starting_point: Node3D, speed: int, damage: int):
bullet_spawner.spawn_bullet(starting_point, speed, damage)

View File

@ -0,0 +1,38 @@
class_name PlayerSpawnerController extends Node3D
func _get_spawner() -> MultiplayerSpawner:
return $MultiplayerSpawner
func _get_root() -> Node3D:
return $Players
# -- Spawn a player node and sync it across all peers
func spawn_players(spawn_location: SpawnController, id: int) -> Error:
if multiplayer.is_server():
var char : PlayerPlaceholder = null
var player_data: PlayerData = GameServerAutoload.players[id]
char = ResourceLoader.load("res://scenes/player/placeholder.tscn").instantiate()
char.name = "PlayerPlaceholder_" + str(player_data.id)
var new_position: Vector3 = spawn_location.get_spawner(SpawnController.Sides.BLUE)
char.global_position = new_position
char.owner_id = id
_get_root().add_child(char)
return OK
return ERR_UNAUTHORIZED
func remove_player(id: int) -> Error:
if multiplayer.is_server():
var found_childen: Array[Node] =_get_root().get_children()
for found_child in found_childen:
if found_child.owner_id:
if found_child.owner_id == id:
found_child.queue_free()
return OK
return ERR_UNAUTHORIZED
func get_player_node(id: int) -> PlayerPlaceholder:
var nodes: Array[Node] = _get_root().get_children().filter(func(x: PlayerPlaceholder): return x.owner_id == id)
if nodes.size() > 0:
return nodes[0]
return null

View File

@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=3 uid="uid://xh710fr73bid"]
[ext_resource type="Script" path="res://scenes/maps/base/player_spawner/player_spawner.gd" id="1_2hsyd"]
[node name="PlayerSpawner" type="Node3D"]
script = ExtResource("1_2hsyd")
[node name="MultiplayerSpawner" type="MultiplayerSpawner" parent="."]
_spawnable_scenes = PackedStringArray("res://scenes/player/placeholder.tscn")
spawn_path = NodePath("../Players")
spawn_limit = 100
[node name="Players" type="Node3D" parent="."]

View File

@ -0,0 +1,7 @@
extends Node3D
var busy: bool = false
func choose_spawn_location() -> Vector3:
busy = true
return global_position

View File

@ -0,0 +1,49 @@
class_name SpawnController extends Node3D
@export_category("SpawnController")
@onready var blue_spawners: Node3D = $Blue
@onready var red_spawners: Node3D = $Red
const SINGLE_SPAWN_CONTROLLER_PATH = "res://scenes/maps/base/single_spawn_controller.gd"
enum Sides {BLUE, RED, UNDEFINED}
@export var side: Sides = Sides.UNDEFINED
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
var single_spawn_controller: GDScript = ResourceLoader.load(SINGLE_SPAWN_CONTROLLER_PATH)
for spawn: Node3D in blue_spawners.get_children():
spawn.set_script(single_spawn_controller)
for spawn: Node3D in red_spawners.get_children():
spawn.set_script(single_spawn_controller)
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func _get_available_spawn(spawn_set: Node3D) -> Node3D:
if multiplayer.is_server():
var spawns: Array[Node3D] = []
for spawn: Node3D in spawn_set.get_children():
if not spawn.busy:
spawns.push_back(spawn)
var random_index: int = randi_range(0, spawns.size() - 1)
return spawns[random_index]
return null
func get_spawner(team: Sides) -> Vector3:
match team:
Sides.BLUE:
var spawn := _get_available_spawn(blue_spawners)
return spawn.choose_spawn_location()
Sides.RED:
print("red")
return Vector3(0,0,0)
_:
return Vector3(0,0,0)
# Get all spawners for each team

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,65 @@
extends Control
class_name MainMenu
const MAP_DIR := "res://scenes/maps/maps/"
@onready var map_list: ItemList = $CreateServerMenu/MapList
@onready var create_server_panel: VBoxContainer = $CreateServerMenu
@onready var join_server_panel: VBoxContainer = $JoinServerMenu
func _get_game_root() -> GameRoot:
var game_root: GameRoot = find_parent("GameRoot")
return game_root
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
var map_dir := DirAccess.open(MAP_DIR)
if map_dir:
map_dir.list_dir_begin()
var file_name = map_dir.get_next()
while file_name != "":
map_list.add_item(file_name)
file_name = map_dir.get_next()
GameServerAutoload.current_map = map_list.get_item_text(0)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func _on_join_pressed() -> void:
if create_server_panel.visible:
create_server_panel.visible = false
join_server_panel.visible = true
func _on_create_server_pressed() -> void:
if join_server_panel.visible:
join_server_panel.visible = false
create_server_panel.visible = true
func _on_close_join_pressed() -> void:
join_server_panel.visible = false
func _on_close_create_pressed() -> void:
create_server_panel.visible = false
func _on_map_list_item_selected(index: int) -> void:
GameServerAutoload.current_map = map_list.get_item_text(index)
func _on_create_pressed() -> void:
GameServerAutoload.create_server(false)
_get_game_root().load_map.emit(GameServerAutoload.current_map)
visible = false
func _on_join_server_pressed() -> void:
var ip: String = $JoinServerMenu/Host/TextEdit.text
var port: int = $JoinServerMenu/Port/TextEdit.text.to_int()
GameServerAutoload.join_server(ip, port)
visible = false

View File

@ -0,0 +1,126 @@
[gd_scene load_steps=2 format=3 uid="uid://dnqi0ih2hcpym"]
[ext_resource type="Script" path="res://scenes/menus/main/main_menu.gd" id="1_mns85"]
[node name="MainMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4
script = ExtResource("1_mns85")
[node name="Buttons" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_top = -21.5
offset_right = 38.0
offset_bottom = 21.5
grow_vertical = 2
[node name="CreateServer" type="Button" parent="Buttons"]
layout_mode = 2
text = "Create server"
[node name="Join" type="Button" parent="Buttons"]
layout_mode = 2
text = "Join"
[node name="CreateServerMenu" type="VBoxContainer" parent="."]
visible = false
layout_mode = 1
anchors_preset = 11
anchor_left = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 0
grow_vertical = 2
[node name="Port" type="HBoxContainer" parent="CreateServerMenu"]
layout_mode = 2
[node name="Label" type="Label" parent="CreateServerMenu/Port"]
layout_mode = 2
text = "Port"
[node name="TextEdit" type="TextEdit" parent="CreateServerMenu/Port"]
clip_contents = false
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
tooltip_text = "On which port to run the server
"
text = "27015"
[node name="MapList" type="ItemList" parent="CreateServerMenu"]
custom_minimum_size = Vector2(200, 200)
layout_mode = 2
[node name="Create" type="Button" parent="CreateServerMenu"]
layout_mode = 2
text = "Create"
[node name="Close" type="Button" parent="CreateServerMenu"]
layout_mode = 2
text = "Close
"
[node name="JoinServerMenu" type="VBoxContainer" parent="."]
visible = false
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -200.0
offset_bottom = 120.0
grow_horizontal = 0
[node name="Host" type="HBoxContainer" parent="JoinServerMenu"]
layout_mode = 2
[node name="Label" type="Label" parent="JoinServerMenu/Host"]
layout_mode = 2
text = "Host"
[node name="TextEdit" type="TextEdit" parent="JoinServerMenu/Host"]
clip_contents = false
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
tooltip_text = "On which port to run the server
"
text = "127.0.0.1"
[node name="Port" type="HBoxContainer" parent="JoinServerMenu"]
layout_mode = 2
[node name="Label" type="Label" parent="JoinServerMenu/Port"]
layout_mode = 2
text = "Port"
[node name="TextEdit" type="TextEdit" parent="JoinServerMenu/Port"]
clip_contents = false
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
tooltip_text = "On which port to run the server
"
text = "27015"
[node name="Join" type="Button" parent="JoinServerMenu"]
layout_mode = 2
text = "Join"
[node name="Close" type="Button" parent="JoinServerMenu"]
layout_mode = 2
text = "Close
"
[connection signal="pressed" from="Buttons/CreateServer" to="." method="_on_create_server_pressed"]
[connection signal="pressed" from="Buttons/Join" to="." method="_on_join_pressed"]
[connection signal="item_selected" from="CreateServerMenu/MapList" to="." method="_on_map_list_item_selected"]
[connection signal="pressed" from="CreateServerMenu/Create" to="." method="_on_create_pressed"]
[connection signal="pressed" from="CreateServerMenu/Close" to="." method="_on_close_create_pressed"]
[connection signal="pressed" from="JoinServerMenu/Join" to="." method="_on_join_server_pressed"]
[connection signal="pressed" from="JoinServerMenu/Close" to="." method="_on_close_join_pressed"]

View File

@ -0,0 +1,28 @@
extends CharacterBody3D
class_name ServerNode
var jumping := false
var input_direction: Vector2 = null
var owner_id: int = 0
func _ready() -> void:
pass
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity += get_gravity() * delta
if is_on_floor() && jumping:
velocity.y = const .
#if shooting:
jumping = false
var direction := (transform.basis * Vector3(input_direction.x, 0, input_direction.y)).normalized()
if is_on_floor():
if direction:
#first_view_legs_anim.play("Run Forward")
velocity.x = direction.x * character_speed
velocity.z = direction.z * character_speed
else:
velocity.x = move_toward(velocity.x, 0, character_speed)
velocity.z = move_toward(velocity.z, 0, character_speed)

View File

@ -0,0 +1,32 @@
[gd_scene load_steps=5 format=3 uid="uid://3m22sdln5238"]
[ext_resource type="Script" path="res://scenes/player/placeholder.gd" id="1_djt0m"]
[ext_resource type="PackedScene" uid="uid://ddwrs0so7swxn" path="res://scenes/characters/y-bot/character.tscn" id="2_a22jl"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1v0rt"]
height = 1.8
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_43f22"]
properties/0/path = NodePath(".:owner_id")
properties/0/spawn = true
properties/0/replication_mode = 1
properties/1/path = NodePath(".:position")
properties/1/spawn = true
properties/1/replication_mode = 1
[node name="PlayerPlaceholder" type="PlayerPlaceholder"]
collision_layer = 4
collision_mask = 5
script = ExtResource("1_djt0m")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
shape = SubResource("CapsuleShape3D_1v0rt")
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."]
replication_config = SubResource("SceneReplicationConfig_43f22")
[node name="BulletStartingPoint" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.76042, -0.28729)
[node name="Character" parent="." instance=ExtResource("2_a22jl")]

View File

@ -0,0 +1,22 @@
[gd_scene load_steps=3 format=3 uid="uid://dqp551myngaey"]
[ext_resource type="Script" path="res://scenes/player/player_input_controller.gd" id="1_5vm0e"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uacs5"]
[node name="PlayerInput" type="CharacterBody3D"]
collision_layer = 2
collision_mask = 3
script = ExtResource("1_5vm0e")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("CapsuleShape3D_uacs5")
[node name="CameraMount" type="Node3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.70665, -0.197926)
[node name="Camera3D" type="Camera3D" parent="CameraMount"]
[node name="CSGBox3D" type="CSGBox3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.70817, 0)

View File

@ -0,0 +1,72 @@
# ---------------------------------------------------------------------
# This script is supposed to control the node that is rendered on the
# client side, and send the changes to the server node
# ---------------------------------------------------------------------
extends CharacterBody3D
class_name PlayerInputController
var jumping: bool = false
var shooting: bool = false
var moving: bool = false
var look_dir: Vector2
var input_direction := Vector2()
var camera_sens: float = 0.002
const JUMP_VELOCITY = 4.5
var character_speed: int = 5
var controlled_node: PlayerPlaceholder = null
@export var owner_id: int = 0
@onready var camera_mount = $CameraMount
func _ready() -> void:
set_multiplayer_authority(multiplayer.get_unique_id())
global_position = controlled_node.global_position
rotation = controlled_node.rotation
func _input(event):
if multiplayer.get_unique_id() == get_multiplayer_authority():
if Input.is_action_just_pressed("jump"): jump()
if Input.is_action_just_released("shoot"): shooting = false
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
look_dir = event.relative * 1
rotation.y -= look_dir.x * camera_sens * 1.0
camera_mount.rotation.x = clamp(camera_mount.rotation.x - look_dir.y * camera_sens * 1.0, -1.5, 1.5)
controlled_node.set_rotation_x.rpc_id(1, rotation.x)
controlled_node.set_rotation_y.rpc_id(1, rotation.y)
if Input.is_action_pressed("move_left") or Input.is_action_pressed("move_right") or Input.is_action_pressed("move_forward") or Input.is_action_pressed("move_backwards"):
moving = true
else:
moving = false
func jump():
jumping = true
controlled_node.jump.rpc_id(1)
func _physics_process(delta: float) -> void:
if multiplayer.get_unique_id() == get_multiplayer_authority():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
input_direction = Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
controlled_node.set_input_direction.rpc_id(1, input_direction)
if not is_on_floor():
velocity += get_gravity() * delta
if is_on_floor() && jumping:
velocity.y = JUMP_VELOCITY
#if shooting:
jumping = false
var direction := (transform.basis * Vector3(input_direction.x, 0, input_direction.y)).normalized()
if is_on_floor():
if direction:
#first_view_legs_anim.play("Run Forward")
velocity.x = direction.x * character_speed
velocity.z = direction.z * character_speed
else:
velocity.x = move_toward(velocity.x, 0, character_speed)
velocity.z = move_toward(velocity.z, 0, character_speed)
#controlled_node.sync_velocity.rpc_id(1, velocity.x, velocity.y, velocity.z)
func _process(delta: float) -> void:
move_and_slide()

View File

@ -0,0 +1,12 @@
[gd_scene load_steps=3 format=3 uid="uid://cse87uxvcvhee"]
[ext_resource type="Script" path="res://scenes/player/placeholder.gd" id="1_cs2mj"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_82tpl"]
[node name="ServerNode" type="CharacterBody3D"]
script = ExtResource("1_cs2mj")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
shape = SubResource("CapsuleShape3D_82tpl")

View File

@ -0,0 +1,5 @@
extends Resource
class_name PlayerData
@export var id: int = 0
@export var username: String = ""

View File

@ -0,0 +1,69 @@
extends Node
class_name GameServer
@export var port: int = 27015
@export var player_limit: int = 30
@export var current_map: String = "lowpoly_tdm_2"
@export var current_player_data := PlayerData.new()
# -- This variable should store all the active players
var players: Dictionary = {}
func _ready() -> void:
multiplayer.peer_connected.connect(_on_player_connected)
multiplayer.peer_disconnected.connect(_on_player_disconnected)
multiplayer.connected_to_server.connect(_on_connected_to_server)
func _on_player_disconnected(id) -> void:
logger.info("player is disconnected with id: " + str(id))
func _on_player_connected(id) -> void:
if multiplayer.is_server():
logger.info("player is connected with id: " + str(id))
func _on_connected_to_server() -> void:
logger.info("connection is successful, sending info")
register_player.rpc_id(1, multiplayer.get_unique_id(), current_player_data.username)
func create_server(server_only: bool = false) -> Error:
var peer := ENetMultiplayerPeer.new()
logger.info("starting a server the port: " + str(port))
var err := peer.create_server(port, player_limit)
multiplayer.multiplayer_peer = peer
if err:
return err
if !server_only:
var player_data := PlayerData.new()
player_data.id = 1
logger.warning("TODO: player name is not yet implemented")
player_data.username = "dummy"
players[1] = player_data
return OK
func join_server(ip: String, port: int) -> Error:
var peer = ENetMultiplayerPeer.new()
logger.info("trying to connect to: " + ip + ":" + str(port))
var err = peer.create_client(ip, port)
if err != OK:
return err
multiplayer.multiplayer_peer = peer
return OK
@rpc("any_peer", "reliable", "call_remote")
func register_player(id: int, name: String):
if multiplayer.is_server():
logger.info("registering player: " + str(id))
var player_data := PlayerData.new()
player_data.id = id
player_data.username = name
players[multiplayer.get_remote_sender_id()] = player_data
logger.info("player " + str(id) + " is registered")
else:
logger.debug("register player is supposed to be executed on the server")
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass

View File

@ -0,0 +1,4 @@
extends Resource
const DEFAULT_JUMP_VELOCITY: float = 4.5
const DEFAULT_CHARACTER_SPEED: float = 5.0

View File

@ -0,0 +1,50 @@
extends Node
class_name Logger
var thread: Thread = Thread.new()
var mutex: Mutex = Mutex.new()
var log_queue: Array[String] = []
var running: bool = true
func _ready():
thread.start(log_writer)
func info(msg: Variant):
mutex.lock()
log_queue.append("[color=white][b]INFO:[/b] [/color]" + msg)
mutex.unlock()
func debug(msg: Variant):
mutex.lock()
log_queue.append("[color=cyan][b]DEBUG:[/b] [/color]" + msg)
mutex.unlock()
func warning(msg: Variant):
mutex.lock()
log_queue.append("[color=yellow][b]WARN:[/b] [/color]" + msg)
push_warning(msg)
mutex.unlock()
func error(msg: Variant):
mutex.lock()
log_queue.append("[color=red][b]ERROR:[/b] [/color]" + msg)
push_error(msg)
mutex.unlock()
func log_writer():
while running:
mutex.lock()
while log_queue.size() > 0:
var log_message = log_queue.pop_front()
print_rich(log_message)
mutex.unlock()
# Prevent high CPU usage
await get_tree().create_timer(0.1).timeout
func _exit_tree():
running = false
thread.wait_to_finish()

6
renovate.json Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}

265
rust/Cargo.lock generated Normal file
View File

@ -0,0 +1,265 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "gdextension-api"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8707eca996b28193b772a4a9a28a97364bb93c97e3c313542e812f2963fb93"
[[package]]
name = "gensym"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82"
dependencies = [
"proc-macro2",
"quote",
"syn",
"uuid",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glam"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
[[package]]
name = "godot"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6985eb6c22a0370c40d67db1f225f62222a2e04966f76d0beb53245e745744fa"
dependencies = [
"godot-core",
"godot-macros",
]
[[package]]
name = "godot-bindings"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77d85815de344b419513c9854b82c49aad45c7dd9fa4fcc10302aaf6ce6e07c7"
dependencies = [
"gdextension-api",
]
[[package]]
name = "godot-cell"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4750f0da2c2286f8a0f1e2b0aeb5adb2178251086119e1a96186c34cd8857ba1"
[[package]]
name = "godot-codegen"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd9382da869c3e5f4ba9614f43157bb96f5acf3d1f8341bc25147b7aeebd49a"
dependencies = [
"godot-bindings",
"heck",
"nanoserde",
"proc-macro2",
"quote",
"regex",
]
[[package]]
name = "godot-core"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d014ea57227d84955d9db7bba53654e5e9040f3b5a41d6aad370707cad5b7a"
dependencies = [
"glam",
"godot-bindings",
"godot-cell",
"godot-codegen",
"godot-ffi",
]
[[package]]
name = "godot-ffi"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a93bc8ea912fc629cc7c6ad889f919e99b4e7f32cd912cc65b9be2a986dc6eb"
dependencies = [
"gensym",
"godot-bindings",
"godot-codegen",
"libc",
"paste",
]
[[package]]
name = "godot-macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b16a49485735010c107030dc2e9fa8701fb3bf45194e34b14d1305ad5a8b4f"
dependencies = [
"godot-bindings",
"proc-macro2",
"quote",
"venial",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nanoserde"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de9cf844ab1e25a0353525bd74cb889843a6215fa4a0d156fd446f4857a1b99"
dependencies = [
"nanoserde-derive",
]
[[package]]
name = "nanoserde-derive"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e943b2c21337b7e3ec6678500687cdc741b7639ad457f234693352075c082204"
[[package]]
name = "open-strike-2"
version = "0.1.0"
dependencies = [
"godot",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "syn"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]]
name = "uuid"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
dependencies = [
"getrandom",
]
[[package]]
name = "venial"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6816bc32f30bf8dd1b3adb04de8406c7bf187d2f923bd9e4c0b99365d012613f"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

10
rust/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "open-strike-2"
version = "0.1.0"
edition = "2021"
[dependencies]
godot = "0.2.3"
[lib]
crate-type = ["cdylib"]

View File

@ -0,0 +1 @@

2
rust/src/globals.rs Normal file
View File

@ -0,0 +1,2 @@
pub(crate) const DEFAULT_JUMP_VELOCITY: f32 = 4.5;
pub(crate) const DEFAULT_CHARACTER_SPEED: f32 = 5.0;

10
rust/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
use godot::prelude::*;
struct MyExtension;
//mod game_root;
mod player;
//mod server;
mod globals;
#[gdextension]
unsafe impl ExtensionLibrary for MyExtension {}

View File

@ -0,0 +1 @@

2
rust/src/player/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod client_node;
mod server_node;

View File

@ -0,0 +1,119 @@
use godot::classes::{CharacterBody3D, ICharacterBody3D};
use godot::obj::WithBaseField;
use godot::prelude::*;
use crate::globals;
#[derive(GodotClass)]
#[class(base=CharacterBody3D)]
struct PlayerPlaceholder {
base: Base<CharacterBody3D>,
#[export]
owner_id: i32,
jumping: bool,
input_direction: Vector2,
}
#[godot_api]
impl ICharacterBody3D for PlayerPlaceholder {
fn init(base: Base<CharacterBody3D>) -> Self {
Self {
owner_id: 0,
base,
jumping: false,
input_direction: Vector2::new(0.0, 0.0),
}
}
fn ready(&mut self) {
if let Some(mut multiplayer) = self.base().get_multiplayer() {
self.owner_id = multiplayer.get_unique_id();
}
}
fn physics_process(&mut self, delta: f64) {
if !self.base().is_on_floor() {
let new_gravity = self.base().get_gravity() * delta as f32;
let new_velocity = self.base().get_velocity() + new_gravity;
self.base_mut().set_velocity(new_velocity);
}
if self.base().is_on_floor() && self.jumping {
let mut new_velocity = self.base().get_velocity();
new_velocity.y = globals::DEFAULT_JUMP_VELOCITY;
self.base_mut().set_velocity(new_velocity);
}
self.jumping = false;
if self.base().is_on_floor() {
let direction = self.base().get_transform().basis
* Vector3::new(self.input_direction.x, 0.0, self.input_direction.y);
if direction.length() > 0.0 {
let direction = direction.normalized();
let new_velocity = Vector3::new(
direction.x * globals::DEFAULT_CHARACTER_SPEED,
self.base().get_velocity().y,
direction.z * globals::DEFAULT_CHARACTER_SPEED,
);
self.base_mut().set_velocity(new_velocity);
} else {
let moved = self
.base()
.get_velocity()
.clone()
.move_toward(Vector3::new(0.0, 0.0, 0.0), globals::DEFAULT_CHARACTER_SPEED);
let new_velocity = Vector3::new(moved.x, self.base().get_velocity().y, moved.z);
self.base_mut().set_velocity(new_velocity);
}
}
self.base_mut().move_and_slide();
}
}
#[godot_api]
impl PlayerPlaceholder {
#[rpc(any_peer, call_local, unreliable)]
fn jump(&mut self) {
self.jumping = true
}
#[rpc(any_peer, call_local, unreliable)]
fn set_input_direction(&mut self, new_input_direction: Vector2) {
self.input_direction = new_input_direction;
}
#[rpc(any_peer, call_local, unreliable)]
fn set_rotation_y(&mut self, y: f32) {
let mut new_rotation = self.base().get_rotation();
new_rotation.y = y;
self.base_mut().set_rotation(new_rotation);
}
#[rpc(any_peer, call_local, unreliable)]
fn set_rotation_x(&mut self, x: f32) {
let mut camera_mount = self.base().get_node_as::<Node3D>("BulletStartingPoint");
let mut new_rotation = camera_mount.get_rotation();
new_rotation.x = x;
camera_mount.set_rotation(new_rotation);
}
#[rpc(any_peer, call_local, unreliable_ordered)]
fn shoot(&mut self) {
let bullet_starting_poing = match self.base().find_child("BulletStartingPoint") {
Some(node) => node,
None => return,
};
let casted_bullet_node = bullet_starting_poing.cast::<Node3D>();
let mut map = match self.base().find_parent("Map") {
Some(map) => map,
None => return,
};
let args = &[
casted_bullet_node.to_variant(),
200.to_variant(),
50.to_variant(),
];
map.call("spawn_bullet", args);
}
}

View File

@ -0,0 +1,80 @@
use std::collections::HashMap;
use godot::classes::{ENetMultiplayerPeer, ICharacterBody3D, INode, Node};
use godot::meta::ParamType;
use godot::obj::{NewGd, WithBaseField};
use godot::prelude::*;
use crate::globals;
use super::player_data::PlayerData;
#[derive(GodotClass)]
#[class(base=Node)]
struct GameServer {
base: Base<Node>,
#[export]
port: i32,
#[export]
player_limit: i32,
#[export]
current_map: GString,
#[export]
current_player_data: Gd<PlayerData>,
#[export]
pub(crate) players: Dictionary
}
#[godot_api]
impl INode for GameServer {
fn init(base: Base<Node>) -> Self {
let players = Dictionary::new();
Self {
base,
players,
port: 27015,
player_limit: 10,
current_map: "lowpoly_tdm_2".into(),
current_player_data: PlayerData::new_gd(),
}
}
fn ready(&mut self) {
if let Some(multiplayer) = self.base().get_multiplayer() {
let on_connected = multiplayer.callable("peer_connected");
self.base_mut().connect("on_player_connected", &on_connected);
}
}
}
#[godot_api]
impl GameServer {
// Signals
#[func]
fn on_player_connected(&mut self, id: i32) {
godot_print!("test");
}
// Main methods
#[func]
fn create_server(&mut self, server_only: bool) {
let mut peer = ENetMultiplayerPeer::new_gd();
let server = peer.create_server_ex(self.port)
.max_clients(self.player_limit)
.done();
if let Some(mut multiplayer) = self.base().get_multiplayer() {
let id = multiplayer.get_unique_id();
multiplayer.set_multiplayer_peer(&peer);
let player = PlayerData::new_gd();
self.players.set(id, player);
}
}
#[func]
fn join_server(&mut self, ip: GString, port: i32) {
let mut peer = ENetMultiplayerPeer::new_gd();
peer.create_client(&ip, port);
if let Some(mut multiplayer) = self.base().get_multiplayer() {
multiplayer.set_multiplayer_peer(&peer);
}
}
}

2
rust/src/server/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod game_server;
mod player_data;

View File

@ -0,0 +1,21 @@
use godot::{classes::{IResource, Resource}, obj::{Base, NoBase}, prelude::godot_api};
use godot::prelude::*;
#[derive(GodotClass)]
#[class(base=Resource)]
pub(crate) struct PlayerData {
base: Base<Resource>,
#[export]
pub(crate) id: i32,
#[export]
pub(crate) username: GString,
}
#[godot_api]
impl IResource for PlayerData {
fn init(base: Base<Resource>) -> Self {
let id = 0;
let username = "";
Self { base, id, username: username.into() }
}
}