Class PlotService
Reads and writes ownership of authored plots of land.
public sealed class PlotService : BaseService<PlotEntity>, IPlotService
- Inheritance
-
PlotService
- Implements
- Inherited Members
Constructors
PlotService(INpgsqlDbContextFactory)
public PlotService(INpgsqlDbContextFactory dbContextFactory)
Parameters
dbContextFactoryINpgsqlDbContextFactory
Methods
BackfillTaxDueAsync(long, DateTime, CancellationToken)
Gives a first due date to character-owned plots that have none.
public Task<DatabaseResult<int>> BackfillTaxDueAsync(long worldServerID, DateTime firstDueUtc, CancellationToken cancellationToken = default)
Parameters
worldServerIDlongThe world whose land to backfill.
firstDueUtcDateTimeWhen those plots should first fall due.
cancellationTokenCancellationToken
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.
public Task<DatabaseResult<int>> ClearTaxDelinquencyAsync(long plotID, CancellationToken cancellationToken = default)
Parameters
plotIDlongcancellationTokenCancellationToken
Returns
FetchByOwnerCharacterAsync(long, CancellationToken)
Fetches every plot a character owns, across all scenes.
public Task<DatabaseResult<List<PlotData>>> FetchByOwnerCharacterAsync(long characterID, CancellationToken cancellationToken = default)
Parameters
characterIDlongcancellationTokenCancellationToken
Returns
FetchByOwnerGuildAsync(long, CancellationToken)
Fetches every plot a guild owns, across all scenes.
public Task<DatabaseResult<List<PlotData>>> FetchByOwnerGuildAsync(long guildID, CancellationToken cancellationToken = default)
Parameters
guildIDlongcancellationTokenCancellationToken
Returns
FetchBySceneAsync(long, string, CancellationToken)
Fetches every plot in one world server's copy of a scene, owned or not.
public Task<DatabaseResult<List<PlotData>>> FetchBySceneAsync(long worldServerID, string sceneName, CancellationToken cancellationToken = default)
Parameters
worldServerIDlongsceneNamestringcancellationTokenCancellationToken
Returns
FetchTaxDueAsync(long, DateTime, int, CancellationToken)
Fetches owned plots whose tax has fallen due.
public Task<DatabaseResult<List<PlotData>>> FetchTaxDueAsync(long worldServerID, DateTime asOfUtc, int limit, CancellationToken cancellationToken = default)
Parameters
worldServerIDlongThe world whose land to sweep.
asOfUtcDateTimeThe moment to judge against.
limitintMost rows to return.
cancellationTokenCancellationToken
Returns
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.
public Task<DatabaseResult<int>> MarkTaxDelinquentAsync(long plotID, DateTime delinquentSinceUtc, CancellationToken cancellationToken = default)
Parameters
plotIDlongThe plot that went unpaid.
delinquentSinceUtcDateTimeWhen the first missed payment fell due.
cancellationTokenCancellationToken
Returns
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.
public Task<DatabaseResult<int>> RegisterAsync(long worldServerID, string sceneName, IReadOnlyList<string> plotKeys, CancellationToken cancellationToken = default)
Parameters
worldServerIDlongThe world server whose land this is.
sceneNamestringThe scene the foundations belong to.
plotKeysIReadOnlyList<string>Canonicalised keys of the foundations found in the scene.
cancellationTokenCancellationToken
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.
public Task<DatabaseResult<int>> ReleaseAllForGuildAsync(long guildID, CancellationToken cancellationToken = default)
Parameters
guildIDlongcancellationTokenCancellationToken
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.
public Task<DatabaseResult<int>> ReleaseAsync(long plotID, long expectedOwnerCharacterID, long expectedOwnerGuildID, int releasedState = 0, CancellationToken cancellationToken = default)
Parameters
plotIDlongThe plot to release.
expectedOwnerCharacterIDlongThe character believed to own it, or zero.
expectedOwnerGuildIDlongThe guild believed to own it, or zero.
releasedStateintThe
FishMMO.Shared.PlotStatethe plot is left in, as its underlying integer. Defaults toEmpty; reclamation passesAbandoned.cancellationTokenCancellationToken
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.
public Task<DatabaseResult<int>> TryAdvanceTaxAsync(long plotID, DateTime expectedDueUtc, DateTime nextDueUtc, CancellationToken cancellationToken = default)
Parameters
plotIDlongThe plot being charged.
expectedDueUtcDateTimeThe due date the caller read.
nextDueUtcDateTimeThe date the next payment falls due.
cancellationTokenCancellationToken
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.
public Task<DatabaseResult<int>> TryClaimAsync(long plotID, long ownerCharacterID, long ownerGuildID, DateTime? taxDueUtc, int claimedState = 1, CancellationToken cancellationToken = default)
Parameters
plotIDlongThe plot to claim.
ownerCharacterIDlongThe claiming character, or zero for a guild claim.
ownerGuildIDlongThe claiming guild, or zero for a character claim.
taxDueUtcDateTime?When the first tax payment falls due, or null for untaxed land.
claimedStateintThe
FishMMO.Shared.PlotStatethe plot enters, as its underlying integer. Defaults toBuilding.cancellationTokenCancellationToken
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.
public Task<DatabaseResult<int>> TrySetStateAsync(long plotID, int expectedState, int newState, long expectedOwnerCharacterID, long expectedOwnerGuildID, CancellationToken cancellationToken = default)
Parameters
plotIDlongThe plot to move.
expectedStateintThe state it must currently be in.
newStateintThe state to move it to. Must differ from the expected one.
expectedOwnerCharacterIDlongThe character believed to own it, or zero.
expectedOwnerGuildIDlongThe guild believed to own it, or zero.
cancellationTokenCancellationToken
Returns
- Task<DatabaseResult<int>>
1 when the plot moved, 0 when it had already moved or changed hands.