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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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");