158 lines
4.9 KiB
GDScript
158 lines
4.9 KiB
GDScript
extends TestCase
|
|
|
|
## Covers GameServer's order handling and setup: only a peer's own units and
|
|
## cities may be commanded, and only the first configuration is applied.
|
|
|
|
const LAND := "Land"
|
|
const SEED := 12345
|
|
|
|
|
|
func _map_config() -> MapGenerationConfig:
|
|
var config := MapGenerationConfig.new()
|
|
config.map_size = Vector2i(24, 16)
|
|
config.terrain_seed = SEED
|
|
config.min_capital_landmass = 1
|
|
config.min_capital_distance = 2.0
|
|
config.cities_per_civ = 2
|
|
config.city_spacing = 2
|
|
return config
|
|
|
|
|
|
func _server() -> GameServer:
|
|
var server := GameServer.new()
|
|
server.map_config = _map_config()
|
|
server.configure_game({
|
|
"seed": SEED,
|
|
"player_civ": 0,
|
|
"civilisations": [
|
|
"res://data/civilisations/civ_france.tres",
|
|
"res://data/civilisations/civ_britain.tres",
|
|
],
|
|
})
|
|
return server
|
|
|
|
|
|
func _adjacent_land(state: GameState, unit: Dictionary) -> Vector2i:
|
|
for neighbour in state._neighbours(unit["coords"]):
|
|
if str((state.tiles.get(neighbour, {}) as Dictionary).get("terrain_class")) == LAND:
|
|
return neighbour
|
|
return Vector2i(9999, 9999)
|
|
|
|
|
|
func test_configure_game_builds_a_state() -> void:
|
|
var server := _server()
|
|
assert_true(server.is_configured())
|
|
assert_not_null(server.state)
|
|
assert_eq(server.host_civ, 0)
|
|
assert_eq(server.state.units.size(), 2)
|
|
assert_eq(server.state.civilisations.size(), 2)
|
|
server.free()
|
|
|
|
|
|
func test_configure_game_ignores_a_second_call() -> void:
|
|
var server := _server()
|
|
var first_seed := server.state.seed
|
|
server.configure_game({"seed": 999, "player_civ": 1, "civilisations": []})
|
|
assert_eq(server.state.seed, first_seed)
|
|
server.free()
|
|
|
|
|
|
func test_move_order_for_own_unit_is_accepted() -> void:
|
|
var server := _server()
|
|
server.peer_civ[1] = 0
|
|
var unit := server.state.units[0]
|
|
var goal := _adjacent_land(server.state, unit)
|
|
assert_true(server.handle_order(1, {"type": "move", "unit": unit["id"], "coords": [goal.x, goal.y]}))
|
|
server.free()
|
|
|
|
|
|
## The server must advance ordered units every frame, not only on the hour, or
|
|
## the client's prediction could never be confirmed.
|
|
func test_server_process_advances_movement() -> void:
|
|
var server := _server()
|
|
server.peer_civ[1] = 0
|
|
var unit := server.state.units[0]
|
|
var goal := _adjacent_land(server.state, unit)
|
|
assert_true(server.handle_order(1, {"type": "move", "unit": unit["id"], "coords": [goal.x, goal.y]}))
|
|
for _i in 30:
|
|
server._process(1.0 / 60.0)
|
|
assert_true(float(unit["progress_hours"]) > 0.0, "movement advanced between hourly ticks")
|
|
assert_eq(server.state.total_hours, 0, "_process does not tick the clock")
|
|
server.free()
|
|
|
|
|
|
## State changes only mark the server dirty; the broadcast is deferred to the
|
|
## next frame so a burst of orders cannot trigger several full snapshots.
|
|
func test_state_changes_coalesce_into_one_broadcast() -> void:
|
|
var server := _server()
|
|
server._on_state_changed()
|
|
server._on_state_changed()
|
|
assert_true(server._state_dirty)
|
|
server._process(1.0 / 60.0)
|
|
assert_false(server._state_dirty, "the pending change was flushed")
|
|
server.free()
|
|
|
|
|
|
func test_move_order_for_enemy_unit_is_rejected() -> void:
|
|
var server := _server()
|
|
server.peer_civ[1] = 0
|
|
var enemy: Dictionary = {}
|
|
for candidate in server.state.units:
|
|
if int(candidate["civ"]) != 0:
|
|
enemy = candidate
|
|
var goal := _adjacent_land(server.state, enemy)
|
|
assert_false(server.handle_order(1, {"type": "move", "unit": enemy["id"], "coords": [goal.x, goal.y]}))
|
|
server.free()
|
|
|
|
|
|
func test_train_order_for_own_city_is_accepted_once() -> void:
|
|
var server := _server()
|
|
server.peer_civ[1] = 0
|
|
var city: Dictionary = server.state.cities[0]
|
|
var order := {"type": "train", "city": city["id"], "proto": 0}
|
|
assert_true(server.handle_order(1, order))
|
|
assert_false(server.handle_order(1, order), "city is already training")
|
|
server.free()
|
|
|
|
|
|
func test_train_order_for_enemy_city_is_rejected() -> void:
|
|
var server := _server()
|
|
server.peer_civ[1] = 0
|
|
var enemy: Dictionary = {}
|
|
for city in server.state.cities:
|
|
if int(city["civ"]) != 0:
|
|
enemy = city
|
|
break
|
|
if enemy.is_empty():
|
|
server.free()
|
|
return
|
|
assert_false(server.handle_order(1, {"type": "train", "city": enemy["id"], "proto": 0}))
|
|
server.free()
|
|
|
|
|
|
func test_orders_from_unknown_peers_are_rejected() -> void:
|
|
var server := _server()
|
|
assert_false(server.handle_order(42, {"type": "move", "unit": 1, "coords": [0, 0]}))
|
|
server.free()
|
|
|
|
|
|
func test_unknown_order_types_are_ignored() -> void:
|
|
var server := _server()
|
|
server.peer_civ[1] = 0
|
|
assert_false(server.handle_order(1, {"type": "dance"}))
|
|
server.free()
|
|
|
|
|
|
func test_orders_before_configuration_are_ignored() -> void:
|
|
var server := GameServer.new()
|
|
assert_false(server.handle_order(1, {"type": "move", "unit": 1, "coords": [0, 0]}))
|
|
server.free()
|
|
|
|
|
|
func test_empty_civilisation_list_falls_back_to_the_project() -> void:
|
|
var server := GameServer.new()
|
|
server.map_config = _map_config()
|
|
server.configure_game({"seed": SEED, "player_civ": 0, "civilisations": []})
|
|
assert_eq(server.state.civilisations.size(), server._default_civ_paths().size())
|
|
server.free()
|