Skip to content

Golden Archer#843

Open
bulgarashi wants to merge 10 commits into
MUnique:masterfrom
bulgarashi:feature/golden-archer
Open

Golden Archer#843
bulgarashi wants to merge 10 commits into
MUnique:masterfrom
bulgarashi:feature/golden-archer

Conversation

@bulgarashi

@bulgarashi bulgarashi commented Jul 23, 2026

Copy link
Copy Markdown

Description

This PR introduces the Golden Archer feature, including full database configuration persistence, Web AdminPanel support, database seeding, and unit test coverage.

Features & Logic Overview

  • Item Registration: Players can register Rena (14, 21), Event Chip (14, 30), or Stone (14, 31) at NPC No. 236 (Golden Archer).
  • Counter Tracking: Tracks both current progress (RegisteredRenas) and lifetime statistics (TotalRegisteredRenas).
  • Configurable Rewards:
    • Zen Reward: Rewards configured amount of Zen upon reaching the required threshold.
    • Item Reward: Drops a random item from the configured RewardItems list according to the specified drop probability.

Web AdminPanel & Persistence Fixes

  • Dedicated Fieldset: Created a dedicated GoldenArcherConfiguration model exposed as a distinct fieldset in AdminPanel.
  • Configurable Properties:
    • RequiredRenas (Default: 1)
    • RewardZen (Default: 5,000,000)
    • ItemDropChance (%) (0.0% to 100.0%)
    • RewardItems list
  • Database Seeding & Migration: Fixed AddGoldenArcherConfigurationPlugIn update plugin and added default seeding in GameConfigurationInitializer to ensure settings persist across server restarts and new database setups.

Unit Tests

Added unit test suite in tests/MUnique.OpenMU.Tests/GoldenArcherTest.cs (4/4 Passed - 100%):

  • RegisterAsync_NoNpcOpened_DoesNothingAsync
  • RegisterAsync_NoRenasInInventory_ShowsMessageAsync
  • RegisterAsync_WithRena_IncreasesRegisteredRenasAsync
  • RegisterAsync_ReachesThreshold_ResetsCounterAndRewardsZenAsync

Small fix in docker-compose.yaml

  • Port fix

How to Test

  1. Compile and run the server.
  2. Open AdminPanel and navigate to Game Configuration -> Golden Archer Configuration.
  3. Adjust Required Renas / Reward Zen / Drop Chance and Save.
  4. Restart the server and verify settings persist.
  5. In-game: Talk to Golden Archer (NPC 236) with Renas in inventory and register them.

Video:
https://github.com/user-attachments/assets/8937b26a-f705-407b-8898-d542931b6246

@bulgarashi bulgarashi changed the title Feature/golden-archer Golden Archer Jul 23, 2026

@sven-n sven-n left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor to a feature plugin, so we don't need all this database changes and be more flexible. The plugin could even be named differently (more generically) when it's configurable which NPC can handle such collect & reward requests.

POSTGRES_USER: postgres
ports:
- "5432"
- "5432:5432"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change, please. We don't want to expose the database on a real deployment.

POSTGRES_USER: postgres
ports:
- "5432"
- "5432:5432"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change, please. We don't want to expose the database on a real deployment.

Comment on lines +31 to +61
var connection = this._player.Connection;
if (connection != null)
{
// Calculate the number of renas in the inventory
int invRenas = this._player.Inventory?.Items.Count(i =>
i.Definition?.Group == 14 && (i.Definition?.Number == 21 || i.Definition?.Number == 30 || i.Definition?.Number == 31)) ?? 0;

int registered = this._player.SelectedCharacter?.RegisteredRenas ?? 0;

int WritePacket()
{
var span = connection.Output.GetSpan(10)[..10];
span[0] = 0xC1; // Packet type (C1)
span[1] = 0x0A; // Length (10 bytes)
span[2] = 0x94; // Packet code for Event Chip/Rena Result
span[3] = 0x00; // Result (0 = Success)

// Registered Count (int / 32-bit) at offset 4
span[4] = (byte)(registered & 0xFF);
span[5] = (byte)((registered >> 8) & 0xFF);
span[6] = (byte)((registered >> 16) & 0xFF);
span[7] = (byte)((registered >> 24) & 0xFF);

// Remaining Inventory Count (short / 16-bit) at offset 8
span[8] = (byte)(invRenas & 0xFF);
span[9] = (byte)((invRenas >> 8) & 0xFF);

return 10;
}

await connection.SendAsync(WritePacket).ConfigureAwait(false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but this is not the way how we define and send packets.
AI falls into this trap all the time, btw ;-)

  1. Extend src\Network\Packets\ServerToClient\ServerToClientPackets.xml
  2. Rebuild the packets project
  3. Now there should be a Send... extension method for the connection, which takes the required values as parameters.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider adding a (feature) plugin for this whole feature? Then you could put this configuration into the plugin configuration. I don't want to add too many specialized configurations to the GameConfiguration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be more configurable to be more generic and not limited to one NPC type?
Like adding these options:

  • Accept item
  • Npc (MonsterDefinition)
    A plugin could contain a list of this configurations.

Comment on lines +32 to +33
player.OpenedNpc = null;
await player.PlayerState.TryAdvanceToAsync(PlayerState.EnteredWorld).ConfigureAwait(false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to use the CloseNpcDialogAction instead:

  1. packet handlers should not have game logic directly
  2. The close action implements additional logging and checks

(i.Definition?.Group == 14 && i.Definition?.Number == 21) || // Rena
(i.Definition?.Group == 14 && i.Definition?.Number == 30) || // Event Chip
(i.Definition?.Group == 14 && i.Definition?.Number == 31) // Stone
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit strange to me. Isn't golden archer just about Rena? Why are other items accepted?
Could they be configured, instead?

Comment on lines +227 to +236
/// <summary>
/// Gets or sets the number of registered renas for the Golden Archer (progress towards next reward).
/// </summary>
public int RegisteredRenas { get; set; }

/// <summary>
/// Gets or sets the total number of registered renas for the Golden Archer (all-time counter for rankings).
/// </summary>
public int TotalRegisteredRenas { get; set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea... instead of adding such 'attributes' here, what about adding two attribute definitions in Stats and adding them as Character.Attributes?
This way they could also be used somehow in custom formulas ;-)

Comment on lines +21 to +35
/// <summary>
/// Gets or sets the zen reward from the Golden Archer.
/// </summary>
public int RewardZen { get; set; } = 5000000;

/// <summary>
/// Gets or sets the chance of dropping an item from the <see cref="RewardItems"/> list. A value between 0.0 and 100.0 (e.g. 50 for 50%).
/// </summary>
[System.ComponentModel.DataAnnotations.Display(Name = "Item Drop Chance (%)")]
public double ItemDropChance { get; set; } = 100.0;

/// <summary>
/// Gets or sets the possible items that can drop. If an item drops, one is picked randomly from this list.
/// </summary>
public virtual ICollection<ItemDefinition> RewardItems { get; protected set; } = new List<ItemDefinition>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be replaced by reference to DropItemGroup?

<ItemGroup>
<PackageVersion Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
<PackageVersion Include="BlazingModal" Version="1.0.8" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Comment on lines +23 to +26
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? It's already added transitive by Microsoft.EntityFrameworkCore.Tools in the right version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants