Files
space_game_server/core/server.rb
T

176 lines
3.6 KiB
Ruby

# Nathan Hinton 20 Dec 2025
# The main game server. This will handle the clients and the game in one go
require 'async'
require 'async/websocket'
require 'json'
require 'yaml'
require './game'
class GameServer
def initialize(conf_path)
@clients = {}
File.open(conf_path, 'r') do |fi|
world_config = YAML.safe_load(fi.read)
end
puts world_config
@world = World.new(
width: world_config['width'],
height: world_config['height'],
planets: world_config['planets'],
game_id: world_config['game_id'],
name: world_config['name']
)
end
def start(endpoint)
Async do |task|
Async::WebSocket::Server.open(endpoint) do |connection|
task.async do
handle_connection(connection)
end
end
task.async {simulation_loop}
end
end
def handle_connection(ws)
player = nil
while message == ws.read
data = JSON.parse(message)
response = nil
case data['type']
when 'hello'
player = handle_hello(ws, data)
when 'querey'
response = handle_query(ws, data)
when 'command'
response = handle_command(ws, data)
else
response = send_error('UNKNOWN_TYPE', 'Unknown message type')
end
ws.write(JSON.dump(response)) if response
end
rescue => e
puts "Connection error: #{e.message}"
ensure
disconnect(player)
end
def handle_hello(ws, data)
token = data.dig("payload", "auth_token")
player = authenticate(token)
@clients[player.id] = ws
ws.write(
JSON.dump(
{
type: "update",
domain: "all",
payload: {
player_id: player_id,
server_time: @world.time,
game_speed: @world.game_speed
player: player.snapshot
}
}
)
)
return player
end
def handle_query(player, data)
domain = data["domain"]
payload =
case domain
when "player"
player.snapshot
when "ship"
ship_id = data.dig("payload", "ship_id")
@world.ship(ship_id)&.snapshot
else
return error("UNKNOWN_DOMAIN", "Unknown query domain")
end
{
type: "update",
domain: domain,
payload: payload
}
end
def handle_command(player, data)
domain = data["domain"]
payload = data["payload"]
case domain
when "ship"
validate_and_queue_ship_command(player, payload)
else
return error("UNKNOWN_DOMAIN", "Unknown command domain")
end
{
type: "update",
domain: "command",
payload: { status: "accepted" }
}
rescue ValidationError => e
error("INVALID_COMMAND", e.message)
end
def validate_and_queue_ship_command(player, payload)
ship = @world.ship(payload["ship_id"])
raise ValidationError, "Ship not found" unless ship
raise ValidationError, "Not your ship" unless ship.owner == player
ship.set_target(payload["target"])
end
def broadcast_event(type, domain, payload)
message = JSON.dump({
type: type,
domain: domain,
payload: payload
})
@clients.values.each do |ws|
ws.write(message)
end
end
def simulation_loop
last = Time.now
loop do
now = Time.now
delta_real = now - last
delta_sim = delta_real * @world.game_speed
@world.advance(delta_sim)
last = now
sleep 0.05 # 20 tps
end
end
def error(code, message)
{
type: "error",
domain: "system",
payload: {
code: code,
message: message
}
}
end
end