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
58 changes: 49 additions & 9 deletions ClientLibrary/ConnectionManager.ClientToServerFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,46 @@ public static void SendPlayerShopCloseOther(int handle, ushort @playerId, IntPtr
}
}

/// <summary>
/// Sends a <see cref="PlayerShopSetItemPriceExtended" /> to this connection.
/// </summary>
/// <param name="handle">The handle of the connection.</param>
/// <param name="moneyPrice">The price in zen, or in account coins when PriceItemType is one of the account coin values. Encoded like the price of PlayerShopItemExtended.</param>
/// <param name="priceItemType">The item (group in the highest 4 bits, number in the remaining ones) with which the price is paid, when RequiredItemAmount is greater than 0. Otherwise, 0xFFF0 = WCoin (C), 0xFFF1 = WCoin (P) and 0xFFF2 = Goblin Points define an account coin price; any other value a zen price. Encoded like the price of PlayerShopItemExtended.</param>
/// <param name="requiredItemAmount">The number of pieces of the item which is defined by PriceItemType; 0, if the price is not paid with items. Encoded like the price of PlayerShopItemExtended.</param>
/// <param name="itemSlot">The item slot.</param>
/// <remarks>
/// Is sent by the client when: The player wants to set a price of an item which is inside his personal item shop, in zen or in one of the price types which are allowed by the server (see PlayerShopPriceTypesExtended).
/// Causes reaction on server side: The price is set for the specified item, if its price type is allowed and the amount is within the configured limits. Works only if the shop is currently closed. The server responds with a PlayerShopSetItemPriceResponse.
/// </remarks>
[UnmanagedCallersOnly(EntryPoint = "SendPlayerShopSetItemPriceExtended")]
public static void SendPlayerShopSetItemPriceExtended(int handle, uint @moneyPrice, ushort @priceItemType, ushort @requiredItemAmount, byte @itemSlot)
{
if (!Connections.TryGetValue(handle, out var connection))
{
return;
}

try
{
connection.CreateAndSend(pipeWriter =>
{
var length = PlayerShopSetItemPriceExtendedRef.Length;
var packet = new PlayerShopSetItemPriceExtendedRef(pipeWriter.GetSpan(length)[..length]);
packet.MoneyPrice = @moneyPrice;
packet.PriceItemType = @priceItemType;
packet.RequiredItemAmount = @requiredItemAmount;
packet.ItemSlot = @itemSlot;

return length;
});
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}

/// <summary>
/// Sends a <see cref="PickupItemRequest" /> to this connection.
/// </summary>
Expand Down Expand Up @@ -1570,14 +1610,14 @@ public static void SendCastleSiegeTaxInfoRequest(int handle)
/// Sends a <see cref="CastleSiegeTaxChangeRequest" /> to this connection.
/// </summary>
/// <param name="handle">The handle of the connection.</param>
/// <param name="taxType">0=Undefined, 1=ChaosMachine, 2 = Normal, 3 = EntranceFeeLandOfTrials</param>
/// <param name="taxRate">The tax rate.</param>
/// <param name="taxType">The tax type.</param>
/// <param name="taxValue">The percentage rate for shop and Chaos Machine taxes, or the entrance fee amount for the hunting zone.</param>
/// <remarks>
/// Is sent by the client when: The guild master wants to change the tax rate in the castle npc.
/// Causes reaction on server side: The server changes the tax rates accordingly.
/// </remarks>
[UnmanagedCallersOnly(EntryPoint = "SendCastleSiegeTaxChangeRequest")]
public static void SendCastleSiegeTaxChangeRequest(int handle, byte @taxType, uint @taxRate)
public static void SendCastleSiegeTaxChangeRequest(int handle, CastleSiegeTaxType @taxType, uint @taxValue)
{
if (!Connections.TryGetValue(handle, out var connection))
{
Expand All @@ -1591,7 +1631,7 @@ public static void SendCastleSiegeTaxChangeRequest(int handle, byte @taxType, ui
var length = CastleSiegeTaxChangeRequestRef.Length;
var packet = new CastleSiegeTaxChangeRequestRef(pipeWriter.GetSpan(length)[..length]);
packet.TaxType = @taxType;
packet.TaxRate = @taxRate;
packet.TaxValue = @taxValue;

return length;
});
Expand Down Expand Up @@ -1640,14 +1680,14 @@ public static void SendCastleSiegeTaxMoneyWithdraw(int handle, uint @amount)
/// Sends a <see cref="ToggleCastleGateRequest" /> to this connection.
/// </summary>
/// <param name="handle">The handle of the connection.</param>
/// <param name="closeState">The close state.</param>
/// <param name="isOpen">The is open.</param>
/// <param name="gateId">The gate id.</param>
/// <remarks>
/// Is sent by the client when: The guild member of the castle owner wants to toggle the gate switch.
/// Causes reaction on server side: The castle gate is getting opened or closed.
/// </remarks>
[UnmanagedCallersOnly(EntryPoint = "SendToggleCastleGateRequest")]
public static void SendToggleCastleGateRequest(int handle, byte @closeState, ushort @gateId)
public static void SendToggleCastleGateRequest(int handle, byte @isOpen, ushort @gateId)
{
if (!Connections.TryGetValue(handle, out var connection))
{
Expand All @@ -1660,7 +1700,7 @@ public static void SendToggleCastleGateRequest(int handle, byte @closeState, ush
{
var length = ToggleCastleGateRequestRef.Length;
var packet = new ToggleCastleGateRequestRef(pipeWriter.GetSpan(length)[..length]);
packet.CloseState = @closeState == 1;
packet.IsOpen = @isOpen == 1;
packet.GateId = @gateId;

return length;
Expand All @@ -1679,13 +1719,13 @@ public static void SendToggleCastleGateRequest(int handle, byte @closeState, ush
/// <param name="team">Team Number 0 to 7.</param>
/// <param name="positionX">The position x.</param>
/// <param name="positionY">The position y.</param>
/// <param name="command">0 = Attack, 1 = Defend, 2 = Wait</param>
/// <param name="command">The command.</param>
/// <remarks>
/// Is sent by the client when: The guild master sent a command to his guild during the castle siege event.
/// Causes reaction on server side: The command is shown on the mini map of the guild members.
/// </remarks>
[UnmanagedCallersOnly(EntryPoint = "SendCastleGuildCommand")]
public static void SendCastleGuildCommand(int handle, byte @team, byte @positionX, byte @positionY, byte @command)
public static void SendCastleGuildCommand(int handle, byte @team, byte @positionX, byte @positionY, CastleSiegeGuildCommandType @command)
{
if (!Connections.TryGetValue(handle, out var connection))
{
Expand Down
2 changes: 1 addition & 1 deletion ClientLibrary/MUnique.Client.Library.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<WarningsAsErrors>nullable;CS4014;VSTHRD110;VSTHRD100</WarningsAsErrors>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<!-- The version of the packet definitions this client is built against. -->
<OpenMuPacketsVersion>0.9.10</OpenMuPacketsVersion>
<OpenMuPacketsVersion>0.9.11</OpenMuPacketsVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
7 changes: 6 additions & 1 deletion docs/control-socket.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@ The shops: `npc_shop` is the open NPC shop (`repair_shop`, `tax_rate`,
included) or `null`; `my_shop` is the player's own personal shop (`open`, the
`title` in its title field, its goods with `price`), `purchase_shop` the
personal shop the player looks into (`seller`, `title`, goods with `price`) or
`null`. `repair_mode` says whether a click repairs instead of picking up.
`null`. The goods of the personal shops whose price isn't in zen also carry the
`price_item_type` of their price, as the server encodes it: the item (group in
the highest 4 bits, number in the others) or the account coin (`0xFFF0` WCoin
(C), `0xFFF1` WCoin (P), `0xFFF2` Goblin Points); see
[personal-shop.md](personal-shop.md). `repair_mode` says whether a click
repairs instead of picking up.

For the quests and their rewards: `class_name` (e.g. `Blade Knight`; `class`
is the client's number, e.g. 1 Dark Knight, 8 Blade Knight, 12 Blade Master),
Expand Down
49 changes: 49 additions & 0 deletions docs/personal-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Personal shop prices in items and account coins

A personal shop sells its items for zen. When the server allows it (the
OpenMU plugin *Player shop currencies*), the price of each item can also be
set in:

- items which the server configured as price items, by default the Jewels of
Bless, Soul, Chaos, Creation and Life;
- the account coins WCoin (C), WCoin (P) and Goblin Points, if the server
allows them.

The server tells the client which price items and coins it allows when the
character enters the game. Without that, the shop works with zen only, as in
the original client.

## Setting a price

Right-click an item in the own shop (or drop one into it) to enter its price.
When the server allows other prices, the price dialog names the currency of the
price in its own line ("Currency: Jewel of Bless"), and its "Currency" button
opens a menu of the allowed currencies:

- the common ones first: zen, the account coins, and the jewels (Bless, Soul,
Chaos, Life, Creation, Guardian, Harmony);
- the other items the server allows are listed in a second menu, behind an
"Other items" button, in the order the server sent them.

A long menu takes two columns. Choosing a currency, or closing the menu, returns
to the price dialog and keeps the typed amount. Zen prices below the item's value
still ask for a confirmation; the other currencies don't.

Which items can be prices is the server's choice: OpenMU offers the five jewels
Bless, Soul, Chaos, Creation and Life by default, and its administrators can add
any other item.

The server checks every price: a price item or coin that isn't allowed, or an
amount above the server's maximum, is refused with a message.

## Buying

The tooltip of an item in a shop shows its price with the currency, e.g.
`Selling Price: 12 Jewel of Bless`. The "short of zen" warning is only shown
for zen prices.

Item prices are paid with the price items of the inventory: a single item
counts as one piece, and the jewel bundles of the jewel mix as their number of
pieces. The server doesn't split bundles and gives no change, so the buyer
needs items which match the price exactly. When the items or coins are not
enough, the server says which amount is missing.
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ file(MAKE_DIRECTORY "${MU_NUGET_CACHE_DIR}")
# same package the .NET ClientLibrary consumes (see MUnique.Client.Library.csproj).
# Sourcing from NuGet means a single versioned dependency instead of vendoring
# the entire OpenMU repository as a submodule.
set(OPENMU_PACKETS_VERSION "0.9.10" CACHE STRING
set(OPENMU_PACKETS_VERSION "0.9.11" CACHE STRING
"Version of MUnique.OpenMU.Network.Packets to source packet XML from")
set(OPENMU_PACKETS_XML
"${MU_NUGET_CACHE_DIR}/munique.openmu.network.packets/${OPENMU_PACKETS_VERSION}/contentFiles/any/net10.0/ServerToClient/ServerToClientPackets.xml")
Expand Down
15 changes: 15 additions & 0 deletions src/Localization/Game.de.resx
Original file line number Diff line number Diff line change
Expand Up @@ -13114,4 +13114,19 @@
<data name="QuestRequirementBuff" xml:space="preserve">
<value>Bonus: %ls</value>
</data>
<data name="W Coin(C)" xml:space="preserve">
<value>W Coin(C)</value>
</data>
<data name="Goblin Points" xml:space="preserve">
<value>Goblin-Punkte</value>
</data>
<data name="Currency" xml:space="preserve">
<value>Währung</value>
</data>
<data name="Currency: %ls" xml:space="preserve">
<value>Währung: %ls</value>
</data>
<data name="Other items" xml:space="preserve">
<value>Andere Gegenstände</value>
</data>
</root>
15 changes: 15 additions & 0 deletions src/Localization/Game.en.resx
Original file line number Diff line number Diff line change
Expand Up @@ -13177,4 +13177,19 @@
<data name="QuestRequirementBuff" xml:space="preserve">
<value>Bonus: %ls</value>
</data>
<data name="W Coin(C)" xml:space="preserve">
<value>W Coin(C)</value>
</data>
<data name="Goblin Points" xml:space="preserve">
<value>Goblin Points</value>
</data>
<data name="Currency" xml:space="preserve">
<value>Currency</value>
</data>
<data name="Currency: %ls" xml:space="preserve">
<value>Currency: %ls</value>
</data>
<data name="Other items" xml:space="preserve">
<value>Other items</value>
</data>
</root>
8 changes: 6 additions & 2 deletions src/source/App/Control/ControlState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,12 @@ json PersonalShopItems(mu::ui::window::CInventoryCtrl* grid, int priceTable)
}
const int slot = grid->GetIndexByItem(item);
json described = DescribeItem(*item, slot);
int price = 0;
described["price"] = GetPersonalItemPrice(slot, price, priceTable) ? json(price) : json(nullptr);
GameLogic::PersonalShop::ItemPrice price;
const bool hasPrice = GetPersonalItemPrice(slot, price, priceTable);
described["price"] = hasPrice ? json(price.Amount) : json(nullptr);
described["price_item_type"] = hasPrice && price.Kind != GameLogic::PersonalShop::PriceKind::Zen
? json(price.PriceItemType)
: json(nullptr);
items.push_back(std::move(described));
}
return items;
Expand Down
6 changes: 4 additions & 2 deletions src/source/Dotnet/PacketBindings_ClientToServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ typedef void(CORECLR_DELEGATE_CALLTYPE* SendPlayerShopItemBuyRequest)(int32_t, u

typedef void(CORECLR_DELEGATE_CALLTYPE* SendPlayerShopCloseOther)(int32_t, uint16_t , const char16_t* );

typedef void(CORECLR_DELEGATE_CALLTYPE* SendPlayerShopSetItemPriceExtended)(int32_t, uint32_t , uint16_t , uint16_t , BYTE );

typedef void(CORECLR_DELEGATE_CALLTYPE* SendPickupItemRequest)(int32_t, uint16_t );

typedef void(CORECLR_DELEGATE_CALLTYPE* SendPickupItemRequest075)(int32_t, uint16_t );
Expand Down Expand Up @@ -104,13 +106,13 @@ typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleSiegeDefenseUpgradeRequest)(in

typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleSiegeTaxInfoRequest)(int32_t);

typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleSiegeTaxChangeRequest)(int32_t, BYTE , uint32_t );
typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleSiegeTaxChangeRequest)(int32_t, CastleSiegeTaxType , uint32_t );

typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleSiegeTaxMoneyWithdraw)(int32_t, uint32_t );

typedef void(CORECLR_DELEGATE_CALLTYPE* SendToggleCastleGateRequest)(int32_t, BYTE , uint16_t );

typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleGuildCommand)(int32_t, BYTE , BYTE , BYTE , BYTE );
typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleGuildCommand)(int32_t, BYTE , BYTE , BYTE , CastleSiegeGuildCommandType );

typedef void(CORECLR_DELEGATE_CALLTYPE* SendCastleSiegeHuntingZoneEntranceSetting)(int32_t, BYTE );

Expand Down
21 changes: 16 additions & 5 deletions src/source/Dotnet/PacketFunctions_ClientToServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,17 @@ void PacketFunctions_ClientToServer::SendPlayerShopCloseOther(uint16_t playerId,
dotnet_SendPlayerShopCloseOther(this->GetHandle(), playerId, playerName);
}

void PacketFunctions_ClientToServer::SendPlayerShopSetItemPriceExtended(uint32_t moneyPrice, uint16_t priceItemType, uint16_t requiredItemAmount, BYTE itemSlot)
{
static const auto dotnet_SendPlayerShopSetItemPriceExtended = LoadManagedSymbol<::SendPlayerShopSetItemPriceExtended>("SendPlayerShopSetItemPriceExtended");
if (!dotnet_SendPlayerShopSetItemPriceExtended)
{
return;
}

dotnet_SendPlayerShopSetItemPriceExtended(this->GetHandle(), moneyPrice, priceItemType, requiredItemAmount, itemSlot);
}

void PacketFunctions_ClientToServer::SendPickupItemRequest(uint16_t itemId)
{
static const auto dotnet_SendPickupItemRequest = LoadManagedSymbol<::SendPickupItemRequest>("SendPickupItemRequest");
Expand Down Expand Up @@ -480,15 +491,15 @@ void PacketFunctions_ClientToServer::SendCastleSiegeTaxInfoRequest()
dotnet_SendCastleSiegeTaxInfoRequest(this->GetHandle());
}

void PacketFunctions_ClientToServer::SendCastleSiegeTaxChangeRequest(BYTE taxType, uint32_t taxRate)
void PacketFunctions_ClientToServer::SendCastleSiegeTaxChangeRequest(CastleSiegeTaxType taxType, uint32_t taxValue)
{
static const auto dotnet_SendCastleSiegeTaxChangeRequest = LoadManagedSymbol<::SendCastleSiegeTaxChangeRequest>("SendCastleSiegeTaxChangeRequest");
if (!dotnet_SendCastleSiegeTaxChangeRequest)
{
return;
}

dotnet_SendCastleSiegeTaxChangeRequest(this->GetHandle(), taxType, taxRate);
dotnet_SendCastleSiegeTaxChangeRequest(this->GetHandle(), taxType, taxValue);
}

void PacketFunctions_ClientToServer::SendCastleSiegeTaxMoneyWithdraw(uint32_t amount)
Expand All @@ -502,18 +513,18 @@ void PacketFunctions_ClientToServer::SendCastleSiegeTaxMoneyWithdraw(uint32_t am
dotnet_SendCastleSiegeTaxMoneyWithdraw(this->GetHandle(), amount);
}

void PacketFunctions_ClientToServer::SendToggleCastleGateRequest(BYTE closeState, uint16_t gateId)
void PacketFunctions_ClientToServer::SendToggleCastleGateRequest(BYTE isOpen, uint16_t gateId)
{
static const auto dotnet_SendToggleCastleGateRequest = LoadManagedSymbol<::SendToggleCastleGateRequest>("SendToggleCastleGateRequest");
if (!dotnet_SendToggleCastleGateRequest)
{
return;
}

dotnet_SendToggleCastleGateRequest(this->GetHandle(), closeState, gateId);
dotnet_SendToggleCastleGateRequest(this->GetHandle(), isOpen, gateId);
}

void PacketFunctions_ClientToServer::SendCastleGuildCommand(BYTE team, BYTE positionX, BYTE positionY, BYTE command)
void PacketFunctions_ClientToServer::SendCastleGuildCommand(BYTE team, BYTE positionX, BYTE positionY, CastleSiegeGuildCommandType command)
{
static const auto dotnet_SendCastleGuildCommand = LoadManagedSymbol<::SendCastleGuildCommand>("SendCastleGuildCommand");
if (!dotnet_SendCastleGuildCommand)
Expand Down
Loading
Loading