271 lines
9.7 KiB
Python
271 lines
9.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
|
|
def test_non_fog_card_keeps_custom_opening_scene(room_manager, persistence):
|
|
card = persistence.get_game_card('sunken_arcade')
|
|
assert card is not None
|
|
|
|
created = room_manager.create_room('Host', game_card=card)
|
|
room_id = created['room_id']
|
|
host_id = created['player_id']
|
|
|
|
room_manager.start_game(room_id, host_id)
|
|
snapshot = room_manager.get_snapshot(room_id)
|
|
|
|
assert snapshot['game_card']['card_id'] == 'sunken_arcade'
|
|
assert snapshot['game_state']['scene_id'] == 'opening'
|
|
assert '沉没游乐城' in snapshot['game_state']['global_flags']['game_card_title']
|
|
assert isinstance(snapshot['game_state']['global_flags'].get('card_content_pack'), dict)
|
|
|
|
|
|
def test_game_ended_blocks_chat(room_manager):
|
|
created = room_manager.create_room('Host')
|
|
room_id = created['room_id']
|
|
host_id = created['player_id']
|
|
|
|
room_manager.start_game(room_id, host_id)
|
|
room = room_manager.persistence.get_room(room_id)
|
|
state = room['game_state']
|
|
state['game_phase'] = 'ended'
|
|
room_manager.state_manager.set_room_state(room_id, 'ended', state)
|
|
|
|
with pytest.raises(ValueError, match='game_ended'):
|
|
room_manager.submit_chat(room_id, host_id, 'hello')
|
|
|
|
|
|
def test_custom_card_end_conditions_can_end_game(room_manager, persistence):
|
|
custom_card = persistence.create_game_card(
|
|
title='战斗测试卡',
|
|
subtitle='',
|
|
summary='用于验证自定义结束条件',
|
|
cover_url='/static/img/cards/fogharbor.svg',
|
|
opening_text='战斗即将开始。',
|
|
starter_goals=['提交一次战斗行动'],
|
|
state_schema={
|
|
'metrics': [
|
|
{'key': 'hp', 'label': '队伍生命', 'min': 0, 'max': 100, 'initial': 50},
|
|
{'key': 'rage', 'label': '敌方怒气', 'min': 0, 'max': 100, 'initial': 10},
|
|
],
|
|
'ui': {
|
|
'status_tracks': [
|
|
{'key': 'hp', 'label': '队伍生命', 'min': 0, 'max': 100, 'color': '#ff5a4d'},
|
|
{'key': 'rage', 'label': '敌方怒气', 'min': 0, 'max': 100, 'color': '#6aa3ff'},
|
|
]
|
|
},
|
|
},
|
|
content_pack={
|
|
'end_conditions': [
|
|
{
|
|
'id': 'team_wiped',
|
|
'metric': 'hp',
|
|
'op': '<=',
|
|
'value': 0,
|
|
'ending_state': 'defeat',
|
|
'ending_summary': '生命归零,队伍倒下。',
|
|
}
|
|
]
|
|
},
|
|
creator_user_id='test-user',
|
|
)
|
|
|
|
created = room_manager.create_room('Host', game_card=custom_card)
|
|
room_id = created['room_id']
|
|
host_id = created['player_id']
|
|
room_manager.start_game(room_id, host_id)
|
|
|
|
room_manager.deepseek_client.chat_json = lambda *args, **kwargs: {
|
|
'ok': True,
|
|
'content': json.dumps(
|
|
{
|
|
'narration': '敌方重击命中,队伍承受严重伤害。',
|
|
'npc_dialogues': [],
|
|
'state_updates': {
|
|
'danger_delta': 0,
|
|
'add_clues': [],
|
|
'set_flags': {'hp_delta': -80},
|
|
'npc_changes': {},
|
|
'inventory_add': {},
|
|
},
|
|
'private_player_updates': [],
|
|
'choices': ['紧急撤离'],
|
|
'should_end_scene': False,
|
|
'should_end_game': False,
|
|
'ending_summary': None,
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
}
|
|
|
|
room_manager.submit_action(room_id, host_id, '发起正面进攻')
|
|
room_manager.resolve_round(room_id, host_id)
|
|
|
|
snapshot = room_manager.get_snapshot(room_id)
|
|
state = snapshot['game_state']
|
|
assert state['game_phase'] == 'ended'
|
|
assert state['ending_state'] == 'defeat'
|
|
assert state['global_flags'].get('ending_summary') == '生命归零,队伍倒下。'
|
|
|
|
|
|
def test_custom_card_scope_supports_per_player_metrics_and_shared_inventory(room_manager, persistence):
|
|
card = persistence.create_game_card(
|
|
title='作用域测试卡',
|
|
subtitle='',
|
|
summary='验证 shared/per_player 作用域',
|
|
cover_url='/static/img/cards/fogharbor.svg',
|
|
opening_text='你们踏入测试场。',
|
|
starter_goals=['提交行动'],
|
|
state_schema={
|
|
'metrics': [
|
|
{'key': 'hp', 'label': '生命', 'min': 0, 'max': 100, 'initial': 50},
|
|
],
|
|
'data_scope': {
|
|
'metrics': 'per_player',
|
|
'inventory': 'shared',
|
|
'clues': 'shared',
|
|
},
|
|
'visibility': {
|
|
'show_all_player_metrics': True,
|
|
'show_all_player_inventory': True,
|
|
},
|
|
'ui': {
|
|
'status_tracks': [
|
|
{'key': 'hp', 'label': '生命', 'min': 0, 'max': 100, 'color': '#ff5a4d'},
|
|
]
|
|
},
|
|
},
|
|
content_pack={},
|
|
creator_user_id='scope-user',
|
|
)
|
|
created = room_manager.create_room('Host', game_card=card)
|
|
room_id = created['room_id']
|
|
host_id = created['player_id']
|
|
|
|
joined, _ = room_manager.join_room(room_id, 'Mate')
|
|
mate_id = joined['player_id']
|
|
room_manager.set_ready(room_id, mate_id, True)
|
|
room_manager.start_game(room_id, host_id)
|
|
|
|
room_manager.deepseek_client.chat_json = lambda *args, **kwargs: {
|
|
'ok': True,
|
|
'content': json.dumps(
|
|
{
|
|
'narration': '敌人命中主攻位,队伍拿到补给。',
|
|
'npc_dialogues': [],
|
|
'state_updates': {
|
|
'danger_delta': 0,
|
|
'add_clues': [],
|
|
'set_flags': {'hp_delta': -10},
|
|
'npc_changes': {},
|
|
'inventory_add': {host_id: ['repair_kit']},
|
|
},
|
|
'private_player_updates': [],
|
|
'choices': ['继续推进'],
|
|
'should_end_scene': False,
|
|
'should_end_game': False,
|
|
'ending_summary': None,
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
}
|
|
|
|
room_manager.submit_action(room_id, host_id, '吸引火力')
|
|
room_manager.resolve_round(room_id, host_id)
|
|
snapshot = room_manager.get_snapshot(room_id)
|
|
game_state = snapshot['game_state']
|
|
|
|
by_player = game_state['global_flags'].get('metric_values_by_player', {})
|
|
assert by_player[host_id]['hp'] == 40
|
|
assert by_player[mate_id]['hp'] == 50
|
|
assert 'repair_kit' in game_state['global_flags'].get('shared_inventory', [])
|
|
|
|
|
|
def test_inventory_remove_consumes_used_item(room_manager):
|
|
created = room_manager.create_room('Host')
|
|
room_id = created['room_id']
|
|
host_id = created['player_id']
|
|
room_manager.start_game(room_id, host_id)
|
|
|
|
room = room_manager.persistence.get_room(room_id)
|
|
state = room['game_state']
|
|
state['inventory_by_player'][host_id] = ['silver_bell', 'torn_manifest']
|
|
room_manager.state_manager.set_room_state(room_id, 'running', state)
|
|
|
|
room_manager.deepseek_client.chat_json = lambda *args, **kwargs: {
|
|
'ok': True,
|
|
'content': json.dumps(
|
|
{
|
|
'narration': '你敲响银铃吸引注意,铃体裂开彻底损毁。',
|
|
'npc_dialogues': [],
|
|
'state_updates': {
|
|
'danger_delta': 0,
|
|
'add_clues': [],
|
|
'set_flags': {},
|
|
'npc_changes': {},
|
|
'inventory_add': {},
|
|
'inventory_remove': {host_id: ['silver_bell']},
|
|
},
|
|
'private_player_updates': [],
|
|
'choices': ['继续调查'],
|
|
'should_end_scene': False,
|
|
'should_end_game': False,
|
|
'ending_summary': None,
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
}
|
|
|
|
room_manager.submit_action(room_id, host_id, '使用银铃引开旅店老板')
|
|
room_manager.resolve_round(room_id, host_id)
|
|
|
|
snapshot = room_manager.get_snapshot(room_id)
|
|
inventory = snapshot['game_state']['inventory_by_player'][host_id]
|
|
assert 'silver_bell' not in inventory
|
|
assert 'torn_manifest' in inventory
|
|
|
|
|
|
def test_inventory_kept_when_ai_does_not_emit_remove(room_manager):
|
|
created = room_manager.create_room('Host')
|
|
room_id = created['room_id']
|
|
host_id = created['player_id']
|
|
room_manager.start_game(room_id, host_id)
|
|
|
|
room = room_manager.persistence.get_room(room_id)
|
|
state = room['game_state']
|
|
state['inventory_by_player'][host_id] = ['silver_bell']
|
|
room_manager.state_manager.set_room_state(room_id, 'running', state)
|
|
|
|
room_manager.deepseek_client.chat_json = lambda *args, **kwargs: {
|
|
'ok': True,
|
|
'content': json.dumps(
|
|
{
|
|
'narration': '你使用银铃制造声响,守卫被成功引开。',
|
|
'npc_dialogues': [],
|
|
'state_updates': {
|
|
'danger_delta': 0,
|
|
'add_clues': [],
|
|
'set_flags': {},
|
|
'npc_changes': {},
|
|
'inventory_add': {},
|
|
'inventory_remove': {},
|
|
},
|
|
'private_player_updates': [],
|
|
'choices': ['继续潜入'],
|
|
'should_end_scene': False,
|
|
'should_end_game': False,
|
|
'ending_summary': None,
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
}
|
|
|
|
room_manager.submit_action(room_id, host_id, '使用银铃吸引注意')
|
|
room_manager.resolve_round(room_id, host_id)
|
|
|
|
snapshot = room_manager.get_snapshot(room_id)
|
|
inventory = snapshot['game_state']['inventory_by_player'][host_id]
|
|
assert 'silver_bell' in inventory
|