Table of Contents

Interface IPlotService

Namespace
FishMMO.Database.Npgsql.Services.Interfaces
Assembly
FishMMO-DB.dll

Reads and writes ownership of authored plots of land.

public interface IPlotService

Remarks

Every method that changes ownership reports how many rows it affected, and the caller is expected to check it. The counts are the only way to tell a claim that succeeded from one that lost a race, and treating zero as success would sell one plot to two players.

Methods

BackfillTaxDueAsync(long, DateTime, CancellationToken)

Gives a first due date to character-owned plots that have none.

Task<DatabaseResult<int>> BackfillTaxDueAsync(long worldServerID, DateTime firstDueUtc, CancellationToken cancellationToken = default)

Parameters

worldServerID long

The world whose land to backfill.

firstDueUtc DateTime

When those plots should first fall due.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

How many plots were given a due date.

Remarks

Land claimed while tax was switched off is stored with no due date, and the sweep only looks at plots that have one. Turning tax on afterwards would therefore never touch any of it: those plots would be free forever, and the difference between a founding player and a later one would be permanent.

Only rows with no date are touched, so this cannot reset a plot that is already being billed — running it repeatedly is safe, which matters because it runs on every scene resolve.

Character-owned only. Guild land is deferred rather than charged until a treasury exists, so giving it a date would only make every sweep pick it up and push it along again.

ClearTaxDelinquencyAsync(long, CancellationToken)

Clears a plot's delinquency after a successful payment.

Task<DatabaseResult<int>> ClearTaxDelinquencyAsync(long plotID, CancellationToken cancellationToken = default)

Parameters

plotID long
cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

FetchByOwnerCharacterAsync(long, CancellationToken)

Fetches every plot a character owns, across all scenes.

Task<DatabaseResult<List<PlotData>>> FetchByOwnerCharacterAsync(long characterID, CancellationToken cancellationToken = default)

Parameters

characterID long
cancellationToken CancellationToken

Returns

Task<DatabaseResult<List<PlotData>>>

FetchByOwnerGuildAsync(long, CancellationToken)

Fetches every plot a guild owns, across all scenes.

Task<DatabaseResult<List<PlotData>>> FetchByOwnerGuildAsync(long guildID, CancellationToken cancellationToken = default)

Parameters

guildID long
cancellationToken CancellationToken

Returns

Task<DatabaseResult<List<PlotData>>>

FetchBySceneAsync(long, string, CancellationToken)

Fetches every plot in one world server's copy of a scene, owned or not.

Task<DatabaseResult<List<PlotData>>> FetchBySceneAsync(long worldServerID, string sceneName, CancellationToken cancellationToken = default)

Parameters

worldServerID long
sceneName string
cancellationToken CancellationToken

Returns

Task<DatabaseResult<List<PlotData>>>

FetchTaxDueAsync(long, DateTime, int, CancellationToken)

Fetches owned plots whose tax has fallen due.

Task<DatabaseResult<List<PlotData>>> FetchTaxDueAsync(long worldServerID, DateTime asOfUtc, int limit, CancellationToken cancellationToken = default)

Parameters

worldServerID long

The world whose land to sweep.

asOfUtc DateTime

The moment to judge against.

limit int

Most rows to return.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<List<PlotData>>>

Remarks

Bounded, because a server that has been down over a billing period comes back to every plot at once and an unbounded sweep would try to charge all of them in one pass.

MarkTaxDelinquentAsync(long, DateTime, CancellationToken)

Records that a tax payment was missed, without disturbing an earlier miss.

Task<DatabaseResult<int>> MarkTaxDelinquentAsync(long plotID, DateTime delinquentSinceUtc, CancellationToken cancellationToken = default)

Parameters

plotID long

The plot that went unpaid.

delinquentSinceUtc DateTime

When the first missed payment fell due.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

Remarks

Only sets the mark when there is not one already. The grace period is measured from the first miss, so overwriting it on every later failure would restart the clock each period and the plot could never be reclaimed.

RegisterAsync(long, string, IReadOnlyList<string>, CancellationToken)

Ensures a row exists for every foundation authored in a scene.

Task<DatabaseResult<int>> RegisterAsync(long worldServerID, string sceneName, IReadOnlyList<string> plotKeys, CancellationToken cancellationToken = default)

Parameters

worldServerID long

The world server whose land this is.

sceneName string

The scene the foundations belong to.

plotKeys IReadOnlyList<string>

Canonicalised keys of the foundations found in the scene.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

The number of plots that did not previously exist.

Remarks

Idempotent, because every scene server hosting a channel of the scene runs it on load and they all describe the same land. Plots that already exist keep the ownership they have — registration must never disturb it, or a restart would evict whoever lives there.

Scoped to one world server. The same scene runs on every world, and registering it unscoped would give them all a single shared row per plot — one player's house appearing as already-owned land to everybody on every other world.

ReleaseAllForGuildAsync(long, CancellationToken)

Releases every plot a guild owns.

Task<DatabaseResult<int>> ReleaseAllForGuildAsync(long guildID, CancellationToken cancellationToken = default)

Parameters

guildID long
cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

The number of plots released.

Remarks

Guilds are hard-deleted, and the owner column carries no foreign key to clean up behind them, so land held by a disbanded guild would otherwise stay claimed by an identifier that no longer resolves to anything — unclaimable, and unbuildable, permanently.

ReleaseAsync(long, long, long, int, CancellationToken)

Releases a plot back to the unowned pool, if the expected owner still holds it.

Task<DatabaseResult<int>> ReleaseAsync(long plotID, long expectedOwnerCharacterID, long expectedOwnerGuildID, int releasedState = 0, CancellationToken cancellationToken = default)

Parameters

plotID long

The plot to release.

expectedOwnerCharacterID long

The character believed to own it, or zero.

expectedOwnerGuildID long

The guild believed to own it, or zero.

releasedState int

The FishMMO.Shared.PlotState the plot is left in, as its underlying integer. Defaults to Empty; reclamation passes Abandoned.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

1 when released, 0 when somebody else owns it or it was already unowned.

Remarks

The state the plot lands in is the caller's to choose, because releasing is two different events wearing one name. An owner handing the deed back leaves a bare lot; a sweep taking land off somebody who stopped paying leaves a house standing empty, which renders differently and tells a passer-by a different story.

TryAdvanceTaxAsync(long, DateTime, DateTime, CancellationToken)

Moves a plot's tax date forward, if it still holds the date the caller charged against.

Task<DatabaseResult<int>> TryAdvanceTaxAsync(long plotID, DateTime expectedDueUtc, DateTime nextDueUtc, CancellationToken cancellationToken = default)

Parameters

plotID long

The plot being charged.

expectedDueUtc DateTime

The due date the caller read.

nextDueUtc DateTime

The date the next payment falls due.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

1 when this call advanced it, 0 when somebody else already did.

Remarks

The pin is what makes the tax safe to sweep from every scene server at once. Several may host the same world's scenes, they all see the same plot come due, and only the one whose expected date still matches may charge it — so a period produces one payment rather than one per server.

Called before the money is taken, for the same reason a plot is claimed before it is paid for: winning the right to charge is the contended step, and a caller that loses it must not have taken anything.

TryClaimAsync(long, long, long, DateTime?, int, CancellationToken)

Claims an unowned plot.

Task<DatabaseResult<int>> TryClaimAsync(long plotID, long ownerCharacterID, long ownerGuildID, DateTime? taxDueUtc, int claimedState = 1, CancellationToken cancellationToken = default)

Parameters

plotID long

The plot to claim.

ownerCharacterID long

The claiming character, or zero for a guild claim.

ownerGuildID long

The claiming guild, or zero for a character claim.

taxDueUtc DateTime?

When the first tax payment falls due, or null for untaxed land.

claimedState int

The FishMMO.Shared.PlotState the plot enters, as its underlying integer. Defaults to Building.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

1 when this call took the plot; 0 when it was already owned, does not exist, or the claiming character already owns one.

Remarks

Exactly one of the two identifiers may be non-zero.

The first tax date and the new state are set by the same statement that takes the plot, so there is no moment where land is owned but untaxed, or owned but still showing as an empty lot. Set separately, a crash in the gap would leave a plot nobody ever has to pay for, or one that every channel draws as available.

A character may hold only one plot. That is asked here and enforced by a partial unique index, because this clause alone cannot stop two simultaneous claims on two scene servers. A caller that gets UNIQUE_VIOLATION back rather than a row count lost that race and should report it the same way it reports zero.

TrySetStateAsync(long, int, int, long, long, CancellationToken)

Moves a plot from one lifecycle state to another, if it is still where and whose the caller thinks it is.

Task<DatabaseResult<int>> TrySetStateAsync(long plotID, int expectedState, int newState, long expectedOwnerCharacterID, long expectedOwnerGuildID, CancellationToken cancellationToken = default)

Parameters

plotID long

The plot to move.

expectedState int

The state it must currently be in.

newState int

The state to move it to. Must differ from the expected one.

expectedOwnerCharacterID long

The character believed to own it, or zero.

expectedOwnerGuildID long

The guild believed to own it, or zero.

cancellationToken CancellationToken

Returns

Task<DatabaseResult<int>>

1 when the plot moved, 0 when it had already moved or changed hands.