Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Game.Shared.Services;
using NSubstitute;
using NUnit.Framework;
using R3;
using UnityEditor;
using UnityEngine;
using UnityEngine.TestTools;
Expand Down Expand Up @@ -692,5 +693,188 @@ public async Task GetEquippableWeaponMasters_AppendsEquippedNotInSlots()
Assert.That(masters[0].Id, Is.EqualTo(5));
Assert.That(masters[1].Id, Is.EqualTo(7), "装備中は末尾に合流する");
}

[Test]
public async Task TryEquip_Success_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
SetupRealDatabase(5);
_mockInventory.HasObject(ObjectCategory.Weapon, 5).Returns(true);
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

var ok = _service.TryEquip(ObjectCategory.Weapon, 5);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task TryEquip_NotPossessed_DoesNotEmitEquipmentChanged()
{
await LoadDefaultData();
_mockInventory.HasObject(ObjectCategory.Weapon, 5).Returns(false);
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

var ok = _service.TryEquip(ObjectCategory.Weapon, 5);

Assert.That(ok, Is.False);
Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task Unequip_WhenEquipped_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
SetupRealDatabase(5);
_repository.Data.Equipment.ObjectCategory = ObjectCategory.Weapon;
_repository.Data.Equipment.Id = 5;
_service.ResolveEquippedWeaponMaster();
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

_service.Unequip();

Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task Unequip_WhenNotEquipped_DoesNotEmitEquipmentChanged()
{
await LoadDefaultData();
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

_service.Unequip();

Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task ResolveEquippedWeaponMaster_MasterNotFound_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
SetupRealDatabase(7); // 記録された Id=5 はテーブル未登録 → 未装備へ戻す修復パス
_repository.Data.Equipment.ObjectCategory = ObjectCategory.Weapon;
_repository.Data.Equipment.Id = 5;
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

LogAssert.Expect(LogType.Error, "装備中の武器マスターが見つかりません Id=5。未装備へ戻します");
_service.ResolveEquippedWeaponMaster();

Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task ResolveEquippedWeaponMaster_Unequipped_DoesNotEmitEquipmentChanged()
{
await LoadDefaultData();
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

_service.ResolveEquippedWeaponMaster();

Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task TrySetSlot_Success_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

var ok = _service.TrySetSlot(1, ObjectCategory.Weapon, 5);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task TryAssignSlot_MoveBetweenSlots_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
_service.TrySetSlot(0, ObjectCategory.Weapon, 5);
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

// 2 スロット(移動元・移動先)が変わっても発行は操作単位で 1 回
var ok = _service.TryAssignSlot(1, ObjectCategory.Weapon, 5);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task TryAssignSlot_SameSlot_DoesNotEmitEquipmentChanged()
{
await LoadDefaultData();
_service.TrySetSlot(2, ObjectCategory.Weapon, 5);
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

var ok = _service.TryAssignSlot(2, ObjectCategory.Weapon, 5);

Assert.That(ok, Is.False);
Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task TryAutoAssignSlot_Success_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

// TrySetSlot 経由の登録でも二重発行しない
var ok = _service.TryAutoAssignSlot(ObjectCategory.Weapon, 5);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task ClearSlot_Success_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
_service.TrySetSlot(2, ObjectCategory.Item, 3);
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

var ok = _service.ClearSlot(2);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task TryClearSlotOf_Registered_EmitsEquipmentChangedOnce()
{
await LoadDefaultData();
_service.TrySetSlot(1, ObjectCategory.Weapon, 2);
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

var ok = _service.TryClearSlotOf(ObjectCategory.Weapon, 2);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task SetMagazineCount_Always_DoesNotEmitEquipmentChanged()
{
await LoadDefaultData();
int emitted = 0;
using var sub = _service.EquipmentChanged.Subscribe(_ => emitted++);

// 弾倉残弾は発行対象外(発砲ごとに変わるため、装備変更の購読者に通知しない)
_service.SetMagazineCount(5, 12);

Assert.That(emitted, Is.EqualTo(0));
Assert.That(_repository.IsDirty, Is.True, "Dirty にはなる(保存対象ではある)");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Game.Shared.Services;
using NSubstitute;
using NUnit.Framework;
using R3;

namespace Game.Tests.MVC.Horror
{
Expand Down Expand Up @@ -720,5 +721,132 @@ public async Task CanAddAfterConsume_NoConsumption_JudgesCapacityOnly()
_service.CanAddAfterConsume(null, ObjectCategory.Item, 4, 1, 1),
Is.True, "空きが生まれた");
}

[Test]
public async Task TryAdd_Success_EmitsSlotsChangedOnce()
{
await LoadDefaultData();
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

// 分割配置(3 スロットに跨る)でも発行は操作単位で 1 回
var ok = _service.TryAdd(ObjectCategory.Item, 3, 25, 10);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task TryAdd_InsufficientCapacity_DoesNotEmitSlotsChanged()
{
await LoadDefaultData();
FillSlotsWithDummies(HorrorInventoryConstants.MaxSlotCount);
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

var ok = _service.TryAdd(ObjectCategory.Item, 3, 1, 10);

Assert.That(ok, Is.False);
Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task TryConsume_Success_EmitsSlotsChangedOnce()
{
await LoadDefaultData();
AddSlotDirect(ObjectCategory.Item, 3, 10, 0);
AddSlotDirect(ObjectCategory.Item, 3, 5, 1);
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

// 複数スロットに跨る消費でも発行は操作単位で 1 回
var ok = _service.TryConsume(ObjectCategory.Item, 3, 12);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task TryConsume_InsufficientCount_DoesNotEmitSlotsChanged()
{
await LoadDefaultData();
AddSlotDirect(ObjectCategory.Item, 3, 2, 0);
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

var ok = _service.TryConsume(ObjectCategory.Item, 3, 5);

Assert.That(ok, Is.False);
Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task TryConsumeAt_Success_EmitsSlotsChangedOnce()
{
await LoadDefaultData();
AddSlotDirect(ObjectCategory.Item, 3, 5, 0);
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

var ok = _service.TryConsumeAt(ObjectCategory.Item, 3, 0, 1);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task TryConsumeAt_EmptyPosition_DoesNotEmitSlotsChanged()
{
await LoadDefaultData();
AddSlotDirect(ObjectCategory.Item, 3, 5, 0);
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

var ok = _service.TryConsumeAt(ObjectCategory.Item, 3, 1, 1);

Assert.That(ok, Is.False);
Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task DiscardSlot_Success_EmitsSlotsChangedOnce()
{
await LoadDefaultData();
AddSlotDirect(ObjectCategory.Item, 3, 1);
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

var ok = _service.DiscardSlot(0);

Assert.That(ok, Is.True);
Assert.That(emitted, Is.EqualTo(1));
}

[Test]
public async Task DiscardSlot_EmptyPosition_DoesNotEmitSlotsChanged()
{
await LoadDefaultData();
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

var ok = _service.DiscardSlot(0);

Assert.That(ok, Is.False);
Assert.That(emitted, Is.EqualTo(0));
}

[Test]
public async Task CanAddAfterConsume_DoesNotEmitSlotsChanged()
{
await LoadDefaultData();
AddSlotDirect(ObjectCategory.Item, 3, 2, 0);
int emitted = 0;
using var sub = _service.SlotsChanged.Subscribe(_ => emitted++);

// 判定系はインベントリを変更しないため発行しない
_service.CanAddAfterConsume(Amounts((3, 2)), ObjectCategory.Item, 4, 1, 1);

Assert.That(emitted, Is.EqualTo(0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Game.Horror.Services.Interfaces;
using Game.MVC.Core.Scenes;
using Game.Shared.Interfaces;
using Game.Shared.Services;
using R3;
using UnityEngine;

Expand Down Expand Up @@ -47,22 +46,22 @@ public void Initialize(IObjectInfo target)
// 初期フォーカスを先頭スロットへ
if (_slots.Length > 0)
_inputService.SetSelectedGameObject(_slots[0].gameObject);

_equipmentService.EquipmentChanged
.Subscribe(_ => RefreshAllSlots())
.AddTo(Disposables);
}

/// <summary>現在選択中スロットの登録を外す(Dialog の Remove 入力から呼ぶ)。</summary>
/// <summary>現在選択中スロットの登録を外す(Dialog の Remove 入力から呼ぶ)。表示は EquipmentChanged の購読で追従する。</summary>
public void RemoveCurrent()
{
if (_equipmentService.ClearSlot(_currentIndex))
_slots[_currentIndex].SetEmpty();
}
=> _equipmentService.ClearSlot(_currentIndex);

// 対象アイテムを指定スロットへ登録し、表示を更新する。
// Assign は既登録なら移動/入替、未登録なら上書きするため、影響が2スロットに及ぶ。全スロット再表示する
// 対象アイテムを指定スロットへ登録する。Assign は既登録なら移動/入替、未登録なら上書きで
// 影響が2スロットに及ぶが、表示は EquipmentChanged の購読(全スロット再表示)で追従する
private void Register(int index)
{
if (_target == null) return;
if (_equipmentService.TryAssignSlot(index, _target.ObjectCategory, _target.ObjectId))
RefreshAllSlots();
_equipmentService.TryAssignSlot(index, _target.ObjectCategory, _target.ObjectId);
}

private void RefreshAllSlots()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,12 @@ public override UniTask Startup()
if (!result) return;

_inventoryService.DiscardSlot(ctx.SlotView.SlotIndex);
SceneComponent.ApplySlots();
break;
case ContextActionType.Equip:
_result = new HorrorInventoryResult { EquipCategory = info.ObjectCategory, EquipId = info.ObjectId };
break;
case ContextActionType.Shortcut:
await HorrorEquipmentShortcutDialog.RunAsync(info);
SceneComponent.RefreshSlots();
break;
}
})
Expand Down
Loading
Loading