Barter. Closes #23.

This was SVN commit r10588.
This commit is contained in:
fcxSanya 2011-11-24 15:43:32 +00:00
parent d81516defd
commit 8cbab40137
19 changed files with 300 additions and 4 deletions

View file

@ -179,6 +179,7 @@ hotkey.session.kill = Delete ; Destroy selected units
hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building
hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing
hotkey.session.batchtrain = Shift ; Modifier to train units in batches
hotkey.session.massbarter = Shift ; Modifier to barter bunch of resources
hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting
hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise
hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1740836d5cff952e983725dc598ffacb6ff7a675fd6fb0eea16f4de42301d87
size 8058

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4ba89588689c34ed997e33d54fc81438a3c96e3bbddd0528fa42392249cd5d0a
size 12337

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9a71ea32e213cd8a176a4c1d8b1d1ec82c29ffcf05f7ef58dcd04dfd02749f30
size 8170

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c95c090f576894162665dd8890b9d6c90649f2cfb016e693aaa118b924728247
size 11839

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1039395a9129f05fdf8da7ec85222f5f440a8a70aca3bd1c3e507ee538fedb4
size 7976

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe94df8df4ead787a4f4141489adec6cfc847a7213964ce2a350cfd9fa58a894
size 11973

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e43be7f72e0a7d162d463d97d62d1f5c4cd4419dfe85198feb7889e976ae6a8f
size 9654

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da6b1c590428dca431ec028ac3855080b092439553c1dbce4d2f92e2ba0b2702
size 14070

View file

@ -1008,6 +1008,13 @@ function startBuildingPlacement(buildEntType)
inputState = INPUT_BUILDING_PLACEMENT;
}
// Called by GUI when user clicks exchange resources button
function exchangeResources(command)
{
Engine.PostNetworkCommand({"type": "barter", "sell": command.sell, "buy": command.buy, "amount": command.amount});
}
// Batch training:
// When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING
// When the user releases shift, or clicks on a different training button, we create the batched units

View file

@ -524,6 +524,31 @@
</object>
</object>
<object name="unitBarterPanel" hidden="true"
size="5 5 100% 100%"
>
<object ghost="true" style="resourceText" type="text" size="0 0 100% 18">Exchange resources:</object>
<object size="0 18 100% 64">
<repeat count="4">
<object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
<object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
</object>
</repeat>
</object>
<object size="0 64 100% 110">
<repeat count="4">
<object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
<object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
</object>
</repeat>
</object>
<object name="PerformDealButton" type="button" style="StoneButton" size="2 112 100%-7 142">
<object ghost="true" style="statsText" type="text" size="0 0 100% 100%">Barter</object>
</object>
</object>
<!-- Stance Selection -->
<object name="unitStancePanel"
style="TranslucentPanel"

View file

@ -13,11 +13,21 @@ const COMMANDS_PANEL_WIDTH = 228;
const UNIT_PANEL_BASE = -52; // QUEUE: The offset above the main panel (will often be negative)
const UNIT_PANEL_HEIGHT = 44; // QUEUE: The height needed for a row of buttons
// Barter constants
const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
const BARTER_BUNCH_MULTIPLIER = 5;
const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
const BARTER_ACTIONS = ["Sell", "Buy"];
// The number of currently visible buttons (used to optimise showing/hiding)
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Construction": 0, "Command": 0, "Stance": 0};
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, "Command": 0, "Stance": 0};
// Unit panels are panels with row(s) of buttons
var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Construction", "Research", "Stance", "Command"];
var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Research", "Stance", "Command"];
// Indexes of resources to sell and buy on barter panel
var g_barterSell = 0;
var g_barterBuy = 1;
// Lay out a row of centered buttons (does not work inside a loop like the other function)
function layoutButtonRowCentered(rowNumber, guiName, startIndex, endIndex, width)
@ -108,6 +118,16 @@ function layoutButtonRow(rowNumber, guiName, buttonSideLength, buttonSpacer, sta
}
}
function selectBarterResourceToSell(resourceIndex)
{
g_barterSell = resourceIndex;
// g_barterBuy should be set to different value in case if it is the same as g_barterSell
// (it is no make sense to exchange resource to the same one).
// We change it cyclic to next value.
if (g_barterBuy == g_barterSell)
g_barterBuy = (g_barterBuy + 1) % BARTER_RESOURCES.length;
}
// Sets up "unit panels" - the panels with rows of icons (Helper function for updateUnitDisplay)
function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
{
@ -368,6 +388,65 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
g_unitPanelButtons[guiName] = numButtons;
}
// Sets up "unit barter panel" - special case for setupUnitPanel
function setupUnitBarterPanel(unitEntState)
{
// Amount of player's resource to exchange
var amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
if (Engine.HotkeyIsPressed("session.massbarter"))
amountToSell *= BARTER_BUNCH_MULTIPLIER;
// One pass for each resource
for (var i = 0; i < BARTER_RESOURCES.length; i++)
{
var resource = BARTER_RESOURCES[i];
// One pass for 'sell' row and another for 'buy'
for (var j = 0; j < 2; j++)
{
var selectedResourceIndex = [g_barterSell, g_barterBuy][j];
var action = BARTER_ACTIONS[j];
var imageNameSuffix = (i == selectedResourceIndex) ? "selected" : "inactive";
var icon = getGUIObjectByName("unitBarter" + action + "Icon["+i+"]");
var button = getGUIObjectByName("unitBarter" + action + "Button["+i+"]");
button.size = (i * 46) + " 0 " + ((i + 1) * 46) + " 46";
var amountToBuy;
// In 'buy' row show black icon in place corresponding to selected resource in 'sell' row
if (j == 1 && i == g_barterSell)
{
button.enabled = false;
button.tooltip = "";
icon.sprite = "";
amountToBuy = "";
}
else
{
button.enabled = true;
button.tooltip = action + " " + resource;
icon.sprite = "stretched:session/resources/" + resource + "_" + imageNameSuffix + ".png";
var sellPrice = unitEntState.barterMarket.prices["sell"][BARTER_RESOURCES[g_barterSell]];
var buyPrice = unitEntState.barterMarket.prices["buy"][resource];
amountToBuy = "+" + Math.round(sellPrice / buyPrice * amountToSell);
}
var amount;
if (j == 0)
{
button.onpress = (function(i){ return function() { selectBarterResourceToSell(i); } })(i);
amount = (i == g_barterSell) ? "-" + amountToSell : "";
}
else
{
button.onpress = (function(i){ return function() { g_barterBuy = i; } })(i);
amount = amountToBuy;
}
getGUIObjectByName("unitBarter" + action + "Amount["+i+"]").caption = amount;
}
}
var performDealButton = getGUIObjectByName("PerformDealButton");
var exchangeResourcesParameters = { "sell": BARTER_RESOURCES[g_barterSell], "buy": BARTER_RESOURCES[g_barterBuy], "amount": amountToSell };
performDealButton.onpress = function() { exchangeResources(exchangeResourcesParameters) };
}
// Updates right Unit Commands Panel - runs in the main session loop via updateSelectionDetails()
function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
{
@ -424,6 +503,13 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
function (item) { performStance(entState.id, item); } );
}
getGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
if (entState.barterMarket)
{
usedPanels["Barter"] = 1;
setupUnitBarterPanel(entState);
}
if (entState.buildEntities && entState.buildEntities.length)
{
setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement);

View file

@ -0,0 +1,139 @@
// True price of 100 units of resource (for case if some resource is more worth).
// With current bartering system only relative values makes sense
// so if for example stone is two times more expensive than wood,
// there will 2:1 exchange rate.
const TRUE_PRICES = { "food": 100, "wood": 100, "stone": 100, "metal": 100 };
// Constant part of price difference between true price and buy/sell price.
// In percents.
// Buy price equal to true price plus constant difference.
// Sell price equal to true price minus constant difference.
const CONSTANT_DIFFERENCE = 10;
// Additional difference of prices, added after each deal to specified resource price.
// In percents.
const DIFFERENCE_PER_DEAL = 5;
// Price difference which restored each restore timer tick
// In percents.
const DIFFERENCE_RESTORE = 2;
// Interval of timer which slowly restore prices after deals
const RESTORE_TIMER_INTERVAL = 5000;
// Array of resource names
const RESOURCES = ["food", "wood", "stone", "metal"];
function Barter() {}
Barter.prototype.Schema =
"<a:component type='system'/><empty/>";
Barter.prototype.Init = function()
{
this.priceDifferences = {};
for each (var resource in RESOURCES)
this.priceDifferences[resource] = 0;
this.restoreTimer = undefined;
};
Barter.prototype.GetPrices = function()
{
var prices = { "buy": {}, "sell": {} };
for each (var resource in RESOURCES)
{
prices["buy"][resource] = TRUE_PRICES[resource] * (100 + CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
prices["sell"][resource] = TRUE_PRICES[resource] * (100 - CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
}
return prices;
};
Barter.prototype.PlayerHasMarket = function(playerEntity)
{
var cmpPlayer = Engine.QueryInterface(playerEntity, IID_Player);
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var entities = cmpRangeManager.GetEntitiesByPlayer(cmpPlayer.GetPlayerID());
for each (var entity in entities)
{
var cmpFoundation = Engine.QueryInterface(entity, IID_Foundation);
var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
if (!cmpFoundation && cmpIdentity.HasClass("BarterMarket"))
return true;
}
return false;
}
Barter.prototype.ExchangeResources = function(playerEntity, resourceToSell, resourceToBuy, amount)
{
// Data verification
if (amount <= 0)
{
warn("ExchangeResources: incorrect amount: " + uneval(amount));
return;
}
if (RESOURCES.indexOf(resourceToSell) == -1)
{
warn("ExchangeResources: incorrect resource to sell: " + uneval(resourceToSell));
return;
}
if (RESOURCES.indexOf(resourceToBuy) == -1)
{
warn("ExchangeResources: incorrect resource to buy: " + uneval(resourceToBuy));
return;
}
if (!this.PlayerHasMarket(playerEntity))
{
warn("ExchangeResources: player has no markets");
return;
}
var cmpPlayer = Engine.QueryInterface(playerEntity, IID_Player);
var prices = this.GetPrices();
var amountsToSubtract = {};
amountsToSubtract[resourceToSell] = amount;
if (cmpPlayer.TrySubtractResources(amountsToSubtract))
{
var amountToAdd = Math.round(prices["sell"][resourceToSell] / prices["buy"][resourceToBuy] * amount);
cmpPlayer.AddResource(resourceToBuy, amountToAdd);
var numberOfDeals = Math.round(amount / 100);
// Increase price difference for both exchange resources.
// Overal price difference (constant + dynamic) can't exceed +-99%
// so both buy/sell prices limited to [1%; 199%] interval.
this.priceDifferences[resourceToSell] -= DIFFERENCE_PER_DEAL * numberOfDeals;
this.priceDifferences[resourceToSell] = Math.min(99-CONSTANT_DIFFERENCE, Math.max(CONSTANT_DIFFERENCE-99, this.priceDifferences[resourceToSell]));
this.priceDifferences[resourceToBuy] += DIFFERENCE_PER_DEAL * numberOfDeals;
this.priceDifferences[resourceToBuy] = Math.min(99-CONSTANT_DIFFERENCE, Math.max(CONSTANT_DIFFERENCE-99, this.priceDifferences[resourceToBuy]));
}
if (this.restoreTimer == undefined)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.restoreTimer = cmpTimer.SetInterval(this.entity, IID_Barter, "ProgressTimeout", RESTORE_TIMER_INTERVAL, RESTORE_TIMER_INTERVAL, {});
}
};
Barter.prototype.ProgressTimeout = function(data)
{
var needRestore = false;
for each (var resource in RESOURCES)
{
// Calculate value to restore, it should be limited to [-DIFFERENCE_RESTORE; DIFFERENCE_RESTORE] interval
var differenceRestore = Math.min(DIFFERENCE_RESTORE, Math.max(-DIFFERENCE_RESTORE, this.priceDifferences[resource]));
differenceRestore = -differenceRestore;
this.priceDifferences[resource] += differenceRestore;
// If price difference still exists then set flag to run timer again
if (this.priceDifferences[resource] != 0)
needRestore = true;
}
if (!needRestore)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.restoreTimer);
this.restoreTimer = undefined;
}
}
Engine.RegisterComponentType(IID_Barter, "Barter", Barter);

View file

@ -254,6 +254,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
};
}
if (!cmpFoundation && cmpIdentity.HasClass("BarterMarket"))
{
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
ret.barterMarket = { "prices": cmpBarter.GetPrices() };
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);

View file

@ -82,6 +82,7 @@ Identity.prototype.Schema =
"<value>CivCentre</value>" +
"<value>Economic</value>" +
"<value>Defensive</value>" +
"<value>BarterMarket</value>" +
"<value>Village</value>" +
"<value>Town</value>" +
"<value>City</value>" +

View file

@ -0,0 +1 @@
Engine.RegisterInterface("Barter");

View file

@ -394,6 +394,11 @@ function ProcessCommand(player, cmd)
}
break;
case "barter":
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
cmpBarter.ExchangeResources(playerEnt, cmd.sell, cmd.buy, cmd.amount);
break;
default:
error("Invalid command: unknown command type: "+uneval(cmd));
}

View file

@ -23,8 +23,8 @@
</Health>
<Identity>
<GenericName>Market</GenericName>
<Tooltip>Create Trade units and Barter resources. (Currently a useless structure)</Tooltip>
<Classes datatype="tokens">Town</Classes>
<Tooltip>Create Trade units and Barter resources.</Tooltip>
<Classes datatype="tokens">Town BarterMarket</Classes>
<Icon>structures/market.png</Icon>
</Identity>
<Obstruction>

View file

@ -120,6 +120,7 @@ public:
componentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
LOAD_SCRIPTED_COMPONENT("AIInterface");
LOAD_SCRIPTED_COMPONENT("Barter");
LOAD_SCRIPTED_COMPONENT("EndGameManager");
LOAD_SCRIPTED_COMPONENT("GuiInterface");
LOAD_SCRIPTED_COMPONENT("PlayerManager");