update holiday
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
# Nakama Local Development Setup Checklist
|
||||
|
||||
## Prerequisites
|
||||
- [ ] Docker Desktop installed (Windows/Mac)
|
||||
- [ ] Docker Desktop running
|
||||
- [ ] SSH client available (for localhost.run tunneling)
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### 1. Create docker-compose.yaml
|
||||
- [ ] Create `server/docker-compose.yaml` in project root
|
||||
- [ ] Configure Nakama service (port 7350)
|
||||
- [ ] Configure PostgreSQL service (port 5432)
|
||||
- [ ] Set up persistent volumes for PostgreSQL data
|
||||
|
||||
### 2. Start Nakama Locally
|
||||
- [ ] Open terminal in `server/` directory
|
||||
- [ ] Run `docker-compose up -d`
|
||||
- [ ] Verify Nakama console at http://localhost:7351
|
||||
- [ ] Test API endpoint at http://localhost:7350
|
||||
|
||||
### 3. Tunnel for Remote Testing
|
||||
- [ ] Run: `ssh -R 80:localhost:7350 nokey@localhost.run`
|
||||
- [ ] Copy the generated public URL (e.g., `https://xxxxx.lhr.life`)
|
||||
- [ ] Update `nakama_manager.gd` with tunnel URL for testing
|
||||
|
||||
### 4. Configure Godot Client
|
||||
- [ ] Update `NAKAMA_HOST` in `nakama_manager.gd`
|
||||
- [ ] For local: `localhost`, port `7350`, scheme `http`
|
||||
- [ ] For tunnel: use tunnel hostname, port `443`, scheme `https`
|
||||
|
||||
### 5. PostgreSQL Learning Notes
|
||||
- [ ] Database: `nakama` (created by Nakama on startup)
|
||||
- [ ] Access via: `docker exec -it nakama-postgres psql -U postgres -d nakama`
|
||||
- [ ] Key tables: `users`, `user_edge`, `storage`, `leaderboard`, `wallet_ledger`
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Start services
|
||||
docker-compose up -d
|
||||
|
||||
# Stop services
|
||||
docker-compose down
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f nakama
|
||||
|
||||
# Access PostgreSQL
|
||||
docker exec -it nakama-postgres psql -U postgres -d nakama
|
||||
|
||||
# Tunnel port 7350
|
||||
ssh -R 80:localhost:7350 nokey@localhost.run
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
[ ADT's Report ]
|
||||
|
||||
Updated the `tekton-enet` ( Armageddon Multiplayer ) on branch `launcher`
|
||||
|
||||
**Network Animation Synchronization**
|
||||
|
||||
✅ **Animation RPC Functions** - Added `sync_walk_animation()`, `sync_pickup_animation()`, `sync_put_animation()`, and `sync_special_animation()` RPC functions to `player.gd` for network-synced animations.
|
||||
|
||||
✅ **Manager Updates** - Updated `player_movement_manager.gd`, `playerboard_manager.gd`, and `powerup_manager.gd` to call RPC animation sync functions instead of local-only animation methods. Now all players see each other's walking, grab, put, and power-up animations.
|
||||
|
||||
**Username Synchronization**
|
||||
|
||||
✅ **Display Name Variable** - Added `display_name` variable to `player.gd` that stores the player's actual username from their profile.
|
||||
|
||||
✅ **Lobby Name Sync** - Updated `lobby_manager.gd` to send the client's username and character to the host when requesting room info via `request_room_info()`. Host now correctly displays client usernames instead of "Player 12345".
|
||||
|
||||
✅ **Auto-Use Profile Name** - Updated `lobby.gd` to hide the name input field for logged-in users and automatically use `UserProfileManager.get_display_name()`. Only guest users see and can edit the name input.
|
||||
|
||||
✅ **In-Game Name Display** - Updated `player.gd` `_ready()` to look up display name from `LobbyManager.get_players()` and sync via `sync_display_name()` RPC. Player name labels and message bar now show actual usernames.
|
||||
|
||||
**Leaderboard Name Fix**
|
||||
|
||||
✅ **Display Name Usage** - Updated `main.gd` to use `player.display_name` instead of `player.name` (peer ID) in 6 locations: `_on_leaderboard_updated()`, `_show_game_over_panel()`, `request_leaderboard_sync()`, `sync_leaderboard_data()`, and `_update_leaderboard_display()`.
|
||||
|
||||
**Player Rotation Sync**
|
||||
|
||||
✅ **Rotation RPC** - Updated `player_movement_manager.gd` to call `rpc("sync_rotation", target_rotation)` when player rotates toward a target. Other clients now see correct facing direction.
|
||||
|
||||
✅ **Smooth Interpolation** - Updated `sync_rotation()` RPC in `player.gd` to also set `movement_manager.target_rotation` for smooth rotation interpolation on remote clients.
|
||||
|
||||
---
|
||||
|
||||
**Nakama Local Development Checklist** (Next Steps)
|
||||
|
||||
⬜ **Docker Compose Setup** - Create `server/docker-compose.yaml` with Nakama + PostgreSQL services for local development on Docker Desktop (Windows/Mac).
|
||||
|
||||
⬜ **Run Locally** - Start Nakama on localhost:7350 via `docker-compose up -d`.
|
||||
|
||||
⬜ **Tunnel with localhost.run** - Use `ssh -R 80:localhost:7350 nokey@localhost.run` to expose port 7350 for remote device testing.
|
||||
|
||||
⬜ **PostgreSQL Learning** - Study the Nakama PostgreSQL schema and flow for managing user data, storage, and leaderboards. Goal: Make the online dedicated server more flexible with PostgreSQL.
|
||||
+31
-8
@@ -74,9 +74,24 @@ func _ready():
|
||||
# Get player slot references
|
||||
_setup_player_slots()
|
||||
|
||||
# Set player name from profile
|
||||
# Set player name from profile and configure input visibility
|
||||
if player_name_input:
|
||||
player_name_input.text = UserProfileManager.get_display_name()
|
||||
# Get the parent container for the input to hide/show properly
|
||||
var input_section = player_name_input.get_parent()
|
||||
|
||||
if AuthManager.is_guest:
|
||||
# Guest user - show name input and let them enter a name
|
||||
if input_section:
|
||||
input_section.visible = true
|
||||
player_name_input.text = "Guest"
|
||||
player_name_input.editable = true
|
||||
else:
|
||||
# Logged-in user - hide name input and use profile name automatically
|
||||
if input_section:
|
||||
input_section.visible = false
|
||||
player_name_input.text = UserProfileManager.get_display_name()
|
||||
# Also set the LobbyManager name immediately
|
||||
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
||||
|
||||
# Connect button signals - Main Menu
|
||||
create_room_btn.pressed.connect(_on_create_room_pressed)
|
||||
@@ -168,9 +183,13 @@ func _show_panel(panel_name: String) -> void:
|
||||
# =============================================================================
|
||||
|
||||
func _on_create_room_pressed() -> void:
|
||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
||||
if LobbyManager.local_player_name.is_empty():
|
||||
LobbyManager.local_player_name = "Host"
|
||||
# Use profile name for logged-in users, or input name for guests
|
||||
if AuthManager.is_guest:
|
||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
||||
if LobbyManager.local_player_name.is_empty():
|
||||
LobbyManager.local_player_name = "Guest"
|
||||
else:
|
||||
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
||||
|
||||
connection_status.text = "Creating room..."
|
||||
LobbyManager.create_room("Room %d" % randi_range(1000, 9999))
|
||||
@@ -206,9 +225,13 @@ func _on_join_pressed() -> void:
|
||||
connection_status.text = "No room selected"
|
||||
return
|
||||
|
||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
||||
if LobbyManager.local_player_name.is_empty():
|
||||
LobbyManager.local_player_name = "Player"
|
||||
# Use profile name for logged-in users, or input name for guests
|
||||
if AuthManager.is_guest:
|
||||
LobbyManager.local_player_name = player_name_input.text.strip_edges()
|
||||
if LobbyManager.local_player_name.is_empty():
|
||||
LobbyManager.local_player_name = "Guest"
|
||||
else:
|
||||
LobbyManager.local_player_name = UserProfileManager.get_display_name()
|
||||
|
||||
connection_status.text = "Joining room..."
|
||||
LobbyManager.join_room(match_id)
|
||||
|
||||
+6
-6
@@ -957,7 +957,7 @@ func _on_leaderboard_updated(sorted_scores: Array):
|
||||
for p in get_tree().get_nodes_in_group("Players"):
|
||||
player_data.append({
|
||||
"peer_id": p.get_multiplayer_authority(),
|
||||
"name": p.name,
|
||||
"name": p.display_name if not p.display_name.is_empty() else str(p.name),
|
||||
"score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0
|
||||
})
|
||||
rpc("sync_leaderboard_data", player_data)
|
||||
@@ -1049,7 +1049,7 @@ func _show_game_over_panel():
|
||||
var player_scores = []
|
||||
for p in get_tree().get_nodes_in_group("Players"):
|
||||
player_scores.append({
|
||||
"name": p.name,
|
||||
"name": p.display_name if not p.display_name.is_empty() else str(p.name),
|
||||
"score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0
|
||||
})
|
||||
player_scores.sort_custom(func(a, b): return a.score > b.score)
|
||||
@@ -1068,7 +1068,7 @@ func _show_game_over_panel():
|
||||
entry.add_child(rank_label)
|
||||
|
||||
var name_label = Label.new()
|
||||
name_label.text = "Player %s" % player_scores[i].name
|
||||
name_label.text = player_scores[i].name
|
||||
name_label.add_theme_font_size_override("font_size", 28)
|
||||
name_label.add_theme_color_override("font_color", rank_colors[i])
|
||||
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
@@ -1149,7 +1149,7 @@ func request_leaderboard_sync():
|
||||
for p in get_tree().get_nodes_in_group("Players"):
|
||||
player_data.append({
|
||||
"peer_id": p.get_multiplayer_authority(),
|
||||
"name": p.name,
|
||||
"name": p.display_name if not p.display_name.is_empty() else str(p.name),
|
||||
"score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0
|
||||
})
|
||||
rpc_id(sender_id, "sync_leaderboard_data", player_data)
|
||||
@@ -1183,7 +1183,7 @@ func sync_leaderboard_data(player_data: Array):
|
||||
if rank_label:
|
||||
rank_label.text = _get_ordinal(i + 1)
|
||||
if name_label:
|
||||
name_label.text = "Player " + str(data.name)
|
||||
name_label.text = str(data.name)
|
||||
if score_label:
|
||||
score_label.text = str(data.score)
|
||||
|
||||
@@ -1211,7 +1211,7 @@ func _update_leaderboard_display():
|
||||
for p in all_players:
|
||||
var peer_id = p.get_multiplayer_authority()
|
||||
var score = goals_cycle_manager.get_player_score(peer_id) if goals_cycle_manager else 0
|
||||
player_data.append({"peer_id": peer_id, "name": p.name, "score": score})
|
||||
player_data.append({"peer_id": peer_id, "name": p.display_name if not p.display_name.is_empty() else str(p.name), "score": score})
|
||||
|
||||
# Sort by score descending
|
||||
player_data.sort_custom(func(a, b): return a.score > b.score)
|
||||
|
||||
+54
-6
@@ -12,6 +12,9 @@ var powerup_manager
|
||||
# Score tracking
|
||||
var score: int = 0
|
||||
|
||||
# Display name (synced across network)
|
||||
var display_name: String = ""
|
||||
|
||||
# Special effect states
|
||||
var is_frozen: bool = false
|
||||
var is_invisible: bool = false
|
||||
@@ -115,9 +118,22 @@ const AVAILABLE_CHARACTERS: Array[String] = ["Bob", "Masbro", "Gatot", "Oldpop"]
|
||||
# Delegated to RaceManager
|
||||
|
||||
func _ready():
|
||||
# Ensure name is set first
|
||||
# Ensure name is set first (node name = authority ID for multiplayer identification)
|
||||
name = str(get_multiplayer_authority())
|
||||
$Name.text = str(name)
|
||||
|
||||
# Look up player's display name from LobbyManager
|
||||
var my_id = get_multiplayer_authority()
|
||||
for player_data in LobbyManager.get_players():
|
||||
if player_data.get("id") == my_id:
|
||||
display_name = player_data.get("name", "Player")
|
||||
break
|
||||
if display_name.is_empty():
|
||||
display_name = "Player %d" % my_id
|
||||
$Name.text = display_name
|
||||
|
||||
# Sync name to other peers if this is our local player
|
||||
if is_multiplayer_authority():
|
||||
rpc("sync_display_name", display_name)
|
||||
|
||||
# Wait briefly to ensure proper scene setup
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
@@ -354,6 +370,30 @@ func play_idle_animation() -> void:
|
||||
if anim_player and anim_player.has_animation("animation-pack/idle"):
|
||||
anim_player.play("animation-pack/idle")
|
||||
|
||||
# =============================================================================
|
||||
# Network-Synced Animation Functions
|
||||
# =============================================================================
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_walk_animation() -> void:
|
||||
"""Sync walk animation across network."""
|
||||
play_walk_animation()
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_pickup_animation() -> void:
|
||||
"""Sync pickup/grab animation across network."""
|
||||
play_pickup_animation()
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_put_animation() -> void:
|
||||
"""Sync put/drop animation across network."""
|
||||
play_put_animation()
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_special_animation() -> void:
|
||||
"""Sync special ability animation across network."""
|
||||
play_special_animation()
|
||||
|
||||
# =============================================================================
|
||||
# Screen Shake
|
||||
# =============================================================================
|
||||
@@ -486,10 +526,16 @@ func sync_bot_status(is_bot_status: bool):
|
||||
behavior_tree.set_physics_process(false)
|
||||
behavior_tree.set_process(false)
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_display_name(new_name: String) -> void:
|
||||
"""Sync display name across network."""
|
||||
display_name = new_name
|
||||
$Name.text = display_name
|
||||
|
||||
func _process(delta):
|
||||
if is_multiplayer_authority():
|
||||
# Visual debugging - show connection status in name label
|
||||
$Name.text = str(name) + "\n(Auth: " + str(get_multiplayer_authority()) + ")"
|
||||
# Visual debugging - show display name with connection status
|
||||
$Name.text = display_name if not display_name.is_empty() else str(name)
|
||||
|
||||
# Periodically verify our existence to others
|
||||
_verify_timer += delta
|
||||
@@ -819,8 +865,8 @@ func display_message(message, type: int = 0):
|
||||
# Send message to the main scene's message bar instead of player bubble
|
||||
var main = get_tree().get_root().get_node_or_null("Main")
|
||||
if main and main.has_method("add_message_to_bar"):
|
||||
var player_name = $Name.text.split("\n")[0] if $Name else str(name)
|
||||
main.add_message_to_bar(player_name, message, type)
|
||||
var player_name_str = display_name if not display_name.is_empty() else str(name)
|
||||
main.add_message_to_bar(player_name_str, message, type)
|
||||
|
||||
func initialize_random_goals(_size: int, min_value: int, max_value: int, null_count: float) -> Array[int]:
|
||||
goals.clear()
|
||||
@@ -1171,6 +1217,8 @@ func _highlight_adjacent_playerboard_slots():
|
||||
func sync_rotation(new_rotation: float):
|
||||
if not is_multiplayer_authority():
|
||||
rotation.y = new_rotation
|
||||
if movement_manager:
|
||||
movement_manager.target_rotation = new_rotation
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func sync_grid_item(x: int, y: int, z: int, item: int):
|
||||
|
||||
@@ -309,13 +309,24 @@ func _on_match_joined(match_id: String) -> void:
|
||||
# Client will request room info when peer connection is established
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func request_room_info(requester_id: int) -> void:
|
||||
"""Client requests room info from host."""
|
||||
func request_room_info(requester_id: int, requester_name: String, requester_character: String) -> void:
|
||||
"""Client requests room info from host, sending their name and character."""
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
# Update the player's name and character in the list
|
||||
for player in players_in_room:
|
||||
if player["id"] == requester_id:
|
||||
player["name"] = requester_name
|
||||
player["character"] = requester_character
|
||||
break
|
||||
|
||||
# Send room data to requester
|
||||
rpc_id(requester_id, "receive_room_info", current_room, players_in_room)
|
||||
|
||||
# Also sync updated player list to all other clients
|
||||
rpc("sync_player_list", players_in_room)
|
||||
emit_signal("player_list_changed")
|
||||
|
||||
@rpc("reliable")
|
||||
func receive_room_info(room_data: Dictionary, player_list: Array) -> void:
|
||||
@@ -346,7 +357,8 @@ func _on_peer_connected(peer_id: int) -> void:
|
||||
if peer_id == 1 and not is_host:
|
||||
# Wait a frame to ensure connection is stable
|
||||
await get_tree().process_frame
|
||||
rpc_id(1, "request_room_info", multiplayer.get_unique_id())
|
||||
# Send our actual name and character to the host
|
||||
rpc_id(1, "request_room_info", multiplayer.get_unique_id(), local_player_name, available_characters[local_character_index])
|
||||
|
||||
func _on_peer_disconnected(peer_id: int) -> void:
|
||||
"""Called when peer disconnects."""
|
||||
|
||||
@@ -32,6 +32,9 @@ func rotate_towards_target(target_pos: Vector2i):
|
||||
var direction = Vector3(target_pos.x - player.current_position.x, 0, target_pos.y - player.current_position.y)
|
||||
if direction != Vector3.ZERO:
|
||||
target_rotation = atan2(direction.x, direction.z)
|
||||
# Sync rotation to other clients
|
||||
if player.is_multiplayer_authority():
|
||||
player.rpc("sync_rotation", target_rotation)
|
||||
|
||||
func simple_move_to(grid_position: Vector2i) -> bool:
|
||||
if not player.is_multiplayer_authority() or is_moving:
|
||||
@@ -70,9 +73,9 @@ func simple_move_to(grid_position: Vector2i) -> bool:
|
||||
# All checks passed, perform move
|
||||
rotate_towards_target(grid_position)
|
||||
|
||||
# Play walk animation
|
||||
if player.has_method("play_walk_animation"):
|
||||
player.play_walk_animation()
|
||||
# Play walk animation (synced across network)
|
||||
if player.is_multiplayer_authority() and player.has_method("sync_walk_animation"):
|
||||
player.rpc("sync_walk_animation")
|
||||
|
||||
var path = [Vector2(player.current_position.x, player.current_position.y), Vector2(grid_position.x, grid_position.y)]
|
||||
path.pop_front()
|
||||
|
||||
@@ -54,9 +54,9 @@ func grab_item(grid_position: Vector2i) -> bool:
|
||||
if not player.is_multiplayer_authority():
|
||||
return false
|
||||
|
||||
# Play pickup animation
|
||||
if player.has_method("play_pickup_animation"):
|
||||
player.play_pickup_animation()
|
||||
# Play pickup animation (synced across network)
|
||||
if player.is_multiplayer_authority() and player.has_method("sync_pickup_animation"):
|
||||
player.rpc("sync_pickup_animation")
|
||||
|
||||
# === Optimistic Local Update (immediate visual feedback) ===
|
||||
# Apply changes locally first, server will validate/sync
|
||||
@@ -316,9 +316,9 @@ func auto_put_item() -> bool:
|
||||
var cell = Vector3i(target_pos.x, 1, target_pos.y)
|
||||
|
||||
if player.is_multiplayer_authority():
|
||||
# Play put animation
|
||||
if player.has_method("play_put_animation"):
|
||||
player.play_put_animation()
|
||||
# Play put animation (synced across network)
|
||||
if player.is_multiplayer_authority() and player.has_method("sync_put_animation"):
|
||||
player.rpc("sync_put_animation")
|
||||
|
||||
# === Optimistic Local Update (immediate visual feedback) ===
|
||||
enhanced_gridmap.set_cell_item(cell, item) # Add item to grid visually immediately
|
||||
|
||||
@@ -100,9 +100,9 @@ func use_special_effect():
|
||||
# Start cooldown
|
||||
special_cooldown_timer = SPECIAL_COOLDOWN
|
||||
|
||||
# Play special animation (backflip)
|
||||
if player.has_method("play_special_animation"):
|
||||
player.play_special_animation()
|
||||
# Play special animation (backflip) - synced across network
|
||||
if player.is_multiplayer_authority() and player.has_method("sync_special_animation"):
|
||||
player.rpc("sync_special_animation")
|
||||
|
||||
# Trigger random special effect via SpecialTilesManager
|
||||
var special_tiles_manager = player.get_node_or_null("SpecialTilesManager")
|
||||
|
||||
@@ -375,7 +375,11 @@ func initialize_leaderboard_with_players(players: Array):
|
||||
var score_label = entry.get_node_or_null("ScoreLabel")
|
||||
|
||||
if name_label:
|
||||
name_label.text = str(player.name) if player else "Player " + str(i + 1)
|
||||
# Use display_name if available, otherwise fallback to node name
|
||||
var player_display_name = player.display_name if player and player.get("display_name") else ""
|
||||
if player_display_name.is_empty():
|
||||
player_display_name = str(player.name) if player else "Player " + str(i + 1)
|
||||
name_label.text = player_display_name
|
||||
if score_label:
|
||||
score_label.text = str(player.score) if player and player.get("score") else "0"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ extends Node
|
||||
|
||||
# Standard Nakama Configuration
|
||||
const NAKAMA_SERVER_KEY = "defaultkey"
|
||||
const NAKAMA_HOST = "77.237.232.232"
|
||||
const NAKAMA_HOST = "localhost"
|
||||
const NAKAMA_PORT = 7350
|
||||
const NAKAMA_SCHEME = "http"
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
container_name: nakama-postgres
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
- POSTGRES_DB=nakama
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=localdb
|
||||
volumes:
|
||||
- nakama-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "nakama"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
nakama:
|
||||
container_name: nakama
|
||||
image: heroiclabs/nakama:3.24.2
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
entrypoint:
|
||||
- "/bin/sh"
|
||||
- "-ecx"
|
||||
- >
|
||||
/nakama/nakama migrate up --database.address postgres:localdb@postgres:5432/nakama &&
|
||||
exec /nakama/nakama --name nakama
|
||||
--database.address postgres:localdb@postgres:5432/nakama
|
||||
--logger.level DEBUG
|
||||
--session.token_expiry_sec 7200
|
||||
--console.username admin
|
||||
--console.password password
|
||||
ports:
|
||||
- "7349:7349" # gRPC API
|
||||
- "7350:7350" # HTTP API (main client port)
|
||||
- "7351:7351" # Console
|
||||
healthcheck:
|
||||
test: ["CMD", "/nakama/nakama", "healthcheck"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
nakama-data:
|
||||
Reference in New Issue
Block a user