Skip to content
Closed
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
24 changes: 12 additions & 12 deletions src/Rasa.Game/Managers/ManifestationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1955,18 +1955,6 @@
// tracking from these MissionTrack options.
client.CallMethod(SysEntity.ClientMethodId, new CharacterOptionsPacket(player.CharacterOptions));

// Inventory deltas precede LoginOk. Refresh the tray after its controlled actor
// exists so the initial image does not depend on opening the equipment selector.
client.CallMethod(SysEntity.ClientInventoryManagerId,
new Packets.Inventory.Server.InventoryCreatePacket(
InventoryType.WeaponDrawerInventory, player.Inventory.WeaponDrawer.ToList(),
player.Inventory.WeaponDrawer.Count));
client.CallMethod(player.EntityId, new WeaponDrawerSlotPacket(player.ActiveWeapon, false));

// The armed ability too, with its loadout page: not requested, so the client takes it
// as its requested slot as well.
client.CallMethod(player.EntityId, new AbilityDrawerSlotPacket(player.CurrentAbilityDrawer, false));

client.CallMethod(SysEntity.ClientGameMapId, new SetSkyTimePacket { RunningTime = 6666666 }); // ToDo add actual time how long map is running

client.CallMethod(SysEntity.ClientMethodId, new SetCurrentContextIdPacket(client.Player.MapChannel.MapInfo.MapContextId));
Expand All @@ -1985,6 +1973,18 @@
// character options and initial mission state must be sent first.
client.CallMethod(SysEntity.ClientMethodId, new SetControlledActorIdPacket(player.EntityId));

// Inventory deltas precede LoginOk. Refresh the tray after its controlled actor
// exists so the initial image does not depend on opening the equipment selector.
client.CallMethod(SysEntity.ClientInventoryManagerId,
new Packets.Inventory.Server.InventoryCreatePacket(
InventoryType.WeaponDrawerInventory, player.Inventory.WeaponDrawer.ToList(),
player.Inventory.WeaponDrawer.Count));
client.CallMethod(player.EntityId, new WeaponDrawerSlotPacket(player.ActiveWeapon, false));

// The armed ability too, with its loadout page: not requested, so the client takes it
// as its requested slot as well.
client.CallMethod(player.EntityId, new AbilityDrawerSlotPacket(player.CurrentAbilityDrawer, false));

_characterManager.OfferStartingExperienceMission(client);
MissionApplication.Instance.OfferArrivalMissions(client);

Expand Down Expand Up @@ -4279,9 +4279,9 @@
var mindBonus = 0;
var spiritBonus = 0;

var healthBonus = 0;

Check warning on line 4282 in src/Rasa.Game/Managers/ManifestationManager.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'healthBonus' is assigned but its value is never used

Check warning on line 4282 in src/Rasa.Game/Managers/ManifestationManager.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'healthBonus' is assigned but its value is never used
var chiBonus = 0;

Check warning on line 4283 in src/Rasa.Game/Managers/ManifestationManager.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'chiBonus' is assigned but its value is never used

Check warning on line 4283 in src/Rasa.Game/Managers/ManifestationManager.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'chiBonus' is assigned but its value is never used
var regenBonus = 0;

Check warning on line 4284 in src/Rasa.Game/Managers/ManifestationManager.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'regenBonus' is assigned but its value is never used

Check warning on line 4284 in src/Rasa.Game/Managers/ManifestationManager.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'regenBonus' is assigned but its value is never used

float armorBonusPercent = (float)Math.Max(0.0, (totalBody - (2 * (level - 1) + 10)) * 0.667); // every body attribute over the default base attribute gives 0.667% bonus armo;
float logosBonusPercent = (float)Math.Max(0.0, (totalMind - (2 * (level - 1) + 10)) * 0.375); // every mind attribute over the default base attribute gives 0.375% bonus logos damage
Expand Down
2 changes: 1 addition & 1 deletion src/Rasa.Game/Missions/MissionApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3504,7 +3504,7 @@ internal MissionConversationState ClassifyNpcConversation(
/// (overheadwindow.py draws OVERHEAD_MISSION_UNAVAILABLE over the giver).
///
/// A mission asking for anything else is not ahead, it is beside: Bootcamp's retry
/// (2005) wants Calling for Reinforcements failed, and its giver is not to wear the
/// mission wants Calling for Reinforcements failed, and its giver is not to wear the
/// icon for everyone who has not failed it. So a required Failed state, a mission that
/// has to be held at the time, a player flag, a map or a custom requirement keeps the
/// mission out of this list whatever else it asks.
Expand Down
43 changes: 27 additions & 16 deletions src/Rasa.Game/Missions/SceneApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,15 @@ internal void StageExperience(uint characterId, MapChannel map)
Submit(runId, new SceneObservation(SceneEventKind.Signal, _runs[runId].Run.Generation, SequenceId: trigger.SequenceId));
}

// Under the dispatch gate: it re-attaches runs from their rows, and a commit landing in
// between would leave the resident a revision behind ("Concurrent scene revision").
internal void Resume(Client client)
{
lock (_dispatchGate)
ResumeCore(client);
}

private void ResumeCore(Client client)
{
if (client?.Player?.MapChannel == null || client.State != Data.ClientState.Ingame || client.PendingTransfer != null)
return;
Expand Down Expand Up @@ -1537,22 +1545,25 @@ internal bool Tick(MapChannel map, SceneTickScope scope = SceneTickScope.All)
foreach (var retry in _messageRetries.Where(entry => entry.Value <= _utcNow() &&
_runs.TryGetValue(entry.Key, out var run) && run.Map == map).ToArray())
DrainMessages(retry.Key);
foreach (var due in _due.TakeDue(_utcNow()))
{
if (!_runs.TryGetValue(due.RunId, out var resident))
continue;
if (resident.Map != map)
{ _due.Schedule(due); continue; }
if (scope == SceneTickScope.Deadlines && !due.ObjectiveId.HasValue ||
scope == SceneTickScope.Scripts && due.ObjectiveId.HasValue)
{ _due.Schedule(due); continue; }
if (!Submit(due.RunId, new SceneObservation(due.ObjectiveId.HasValue
? SceneEventKind.ObjectiveDeadlineElapsed : SceneEventKind.TimerElapsed,
due.Generation, due.Name, SequenceId: due.SequenceId)))
_due.Schedule(due with { DueAtUtc = _utcNow().AddSeconds(1) });
else
changed = true;
}
// Taken and submitted under the dispatch gate, as Submit commits: two overlapping ticks
// (the map's, a deadline evaluation) each see a timer either still due or committed.
lock (_dispatchGate)
foreach (var due in _due.TakeDue(_utcNow()))
{
if (!_runs.TryGetValue(due.RunId, out var resident))
continue;
if (resident.Map != map)
{ _due.Schedule(due); continue; }
if (scope == SceneTickScope.Deadlines && !due.ObjectiveId.HasValue ||
scope == SceneTickScope.Scripts && due.ObjectiveId.HasValue)
{ _due.Schedule(due); continue; }
if (!SubmitCore(due.RunId, new SceneObservation(due.ObjectiveId.HasValue
? SceneEventKind.ObjectiveDeadlineElapsed : SceneEventKind.TimerElapsed,
due.Generation, due.Name, SequenceId: due.SequenceId)))
_due.Schedule(due with { DueAtUtc = _utcNow().AddSeconds(1) });
else
changed = true;
}
if (scope != SceneTickScope.Deadlines)
{
_world.Tick(map, _utcNow());
Expand Down
41 changes: 26 additions & 15 deletions src/Rasa.Missions/Scenes/SceneDueQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,45 @@ namespace Rasa.Missions.Scenes
{
public sealed record SceneDueWork(string RunId, uint Generation, string Name, DateTime DueAtUtc, uint SequenceId, uint? ObjectiveId = null);

// Thread-safe: the map thread takes due work while client threads schedule and cancel it.
public sealed class SceneDueQueue
{
private readonly object _gate = new();
private readonly PriorityQueue<SceneDueWork, long> _queue = new();
private readonly Dictionary<(string Run, string Name), SceneDueWork> _current = new();
public void Schedule(SceneDueWork work)
{
_current[(work.RunId, work.Name)] = work;
_queue.Enqueue(work, work.DueAtUtc.Ticks);
lock (_gate)
{
_current[(work.RunId, work.Name)] = work;
_queue.Enqueue(work, work.DueAtUtc.Ticks);
}
}
public void Cancel(string runId, string name)
{
lock (_gate)
_current.Remove((runId, name));
}
public void Cancel(string runId, string name) => _current.Remove((runId, name));
public void Cancel(string runId)
{
foreach (var key in new List<(string Run, string Name)>(_current.Keys))
if (key.Run == runId)
_current.Remove(key);
lock (_gate)
foreach (var key in new List<(string Run, string Name)>(_current.Keys))
if (key.Run == runId)
_current.Remove(key);
}
public IReadOnlyList<SceneDueWork> TakeDue(DateTime utcNow)
{
var due = new List<SceneDueWork>();
while (_queue.TryPeek(out var work, out var ticks) && ticks <= utcNow.Ticks)
{
_queue.Dequeue();
var key = (work.RunId, work.Name);
if (!_current.TryGetValue(key, out var current) || !ReferenceEquals(work, current))
continue;
_current.Remove(key);
due.Add(work);
}
lock (_gate)
while (_queue.TryPeek(out var work, out var ticks) && ticks <= utcNow.Ticks)
{
_queue.Dequeue();
var key = (work.RunId, work.Name);
if (!_current.TryGetValue(key, out var current) || !ReferenceEquals(work, current))
continue;
_current.Remove(key);
due.Add(work);
}
return due;
}
}
Expand Down
15 changes: 6 additions & 9 deletions src/Rasa.Test/Missions/BootcampCaptureTheFlagInteractionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,6 @@ public void YoungbloodArrivesAtTheBaseAndRemainsForTheNextMissionAfterReconnect(
}

[TestMethod]
// Creature timers (buffs, bombs, habits) run on Environment.TickCount64, so how far the
// escort gets in 1,200 simulated ticks depends on how fast the machine runs them: this fails
// on a loaded CI runner, and in class order on Windows. It needs an injectable clock.
[Ignore("Quarantined: depends on wall-clock time; see the linked issue.")]
[GitHubWorkItem("https://github.com/InfiniteRasa/Rasa.NET/issues/132")]
public void ForeanEscortsCanFollowFromTheCaveExitToTheReclaimedBase()
{
using var harness = BootcampRuntimeTestHarness.Create(useWorldContent: true);
Expand All @@ -282,10 +277,12 @@ public void ForeanEscortsCanFollowFromTheCaveExitToTheReclaimedBase()
CellManager.Instance.UpdateVisibility(harness.Client);
harness.Client.Player.Attributes[Attributes.Health] =
new ActorAttributes(Attributes.Health, 100000, 100000, 100000, 0, 0);
// The route, not the fights on it: the Thrax at the base deal real damage (creature
// attacks), and a Forean killed there would lie where it fell.
foreach (var forean in foreans)
forean.Attributes[Attributes.Health] = new ActorAttributes(Attributes.Health, 100000, 100000, 100000, 0, 0);
// The route, not the fights on it: an escort engages the Thrax at the base within 20 of
// its owner, and where that fight has it at the last tick depends on creature timers
// that run on Environment.TickCount64 (windups, habits), so on how fast the machine runs.
foreach (var hostile in Actors(harness).Where(actor =>
foreans.Any(forean => CreatureManager.IsHostileTarget(harness.BootcampMap, forean, actor))).ToArray())
CellManager.Instance.RemoveCreatureFromWorld(harness.BootcampMap, hostile);
var route = harness.BootcampMap.NavMesh.FindPath(exit.Position, destination, out var complete);

AdvanceCombat(harness, 1200);
Expand Down
5 changes: 0 additions & 5 deletions src/Rasa.Test/Missions/MissionDeadlineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,6 @@ public void DeadlineExpiryCompletesAcrossReconnectWithoutReplay()
}

[TestMethod]
// Catches a real race: SceneApplication.Tick takes due timers from SceneDueQueue (a PriorityQueue
// and a Dictionary, unsynchronized) outside _dispatchGate, so two overlapping evaluations can
// each skip the deadline and neither commits it (expected 1, actual 0). Seen on loaded CI runners.
[Ignore("Quarantined: exposes an unsynchronized due-timer queue; see the linked issue.")]
[GitHubWorkItem("https://github.com/InfiniteRasa/Rasa.NET/issues/132")]
public void ConcurrentDeadlineEvaluationCommitsOneCompletion()
{
using var context = MissionTestContext.WithCustomDefinitions(
Expand Down
Loading