// See helpers/TraderGain.js for the CalculateTaderGain() function which works out how many
// resources a trader gets
function Trader() {}
Trader.prototype.Schema =
"Lets the unit generate resources while moving between markets (or docks in case of water trading)." +
"" +
"0.75" +
"0.2" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Trader.prototype.Init = function()
{
this.markets = [];
this.index = -1;
this.goods = {
"type": null,
"amount": null
};
};
Trader.prototype.CalculateGain = function(currentMarket, nextMarket)
{
let cmpMarket = QueryMiragedInterface(currentMarket, IID_Market);
let gain = cmpMarket && cmpMarket.CalculateTraderGain(nextMarket, this.template, this.entity);
if (!gain) // One of our markets must have been destroyed
return null;
// For garrisonable unit increase gain for each garrisoned trader
// Calculate this here to save passing unnecessary stuff into the CalculateTraderGain function
let garrisonGainMultiplier = this.GetGarrisonGainMultiplier();
if (garrisonGainMultiplier === undefined)
return gain;
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
return gain;
let garrisonMultiplier = 1;
let garrisonedTradersCount = 0;
for (let entity of cmpGarrisonHolder.GetEntities())
{
let cmpGarrisonedUnitTrader = Engine.QueryInterface(entity, IID_Trader);
if (cmpGarrisonedUnitTrader)
++garrisonedTradersCount;
}
garrisonMultiplier *= 1 + garrisonGainMultiplier * garrisonedTradersCount;
if (gain.traderGain)
gain.traderGain = Math.round(garrisonMultiplier * gain.traderGain);
if (gain.market1Gain)
gain.market1Gain = Math.round(garrisonMultiplier * gain.market1Gain);
if (gain.market2Gain)
gain.market2Gain = Math.round(garrisonMultiplier * gain.market2Gain);
return gain;
};
/**
* Remove market from trade route iff only first market is set.
* @param {number} id of market to be removed.
* @return {boolean} true iff removal was successful.
*/
Trader.prototype.RemoveTargetMarket = function(target)
{
if (this.markets.length != 1 || this.markets[0] != target)
return false;
let cmpTargetMarket = QueryMiragedInterface(target, IID_Market);
if (!cmpTargetMarket)
return false;
cmpTargetMarket.RemoveTrader(this.entity);
this.index = -1;
this.markets = [];
return true;
};
// Set target as target market.
// Return true if at least one of markets was changed.
Trader.prototype.SetTargetMarket = function(target, source)
{
let cmpTargetMarket = QueryMiragedInterface(target, IID_Market);
if (!cmpTargetMarket)
return false;
if (source)
{
// Establish a trade route with both markets in one go.
let cmpSourceMarket = QueryMiragedInterface(source, IID_Market);
if (!cmpSourceMarket)
return false;
this.markets = [source];
}
if (this.markets.length >= 2)
{
// If we already have both markets - drop them
// and use the target as first market
for (let market of this.markets)
{
let cmpMarket = QueryMiragedInterface(market, IID_Market);
if (cmpMarket)
cmpMarket.RemoveTrader(this.entity);
}
this.index = 0;
this.markets = [target];
cmpTargetMarket.AddTrader(this.entity);
}
else if (this.markets.length == 1)
{
// If we have only one market and target is different from it,
// set the target as second one
if (target == this.markets[0])
return false;
this.index = 0;
this.markets.push(target);
cmpTargetMarket.AddTrader(this.entity);
this.goods.amount = this.CalculateGain(this.markets[0], this.markets[1]);
}
else
{
// Else we don't have target markets at all,
// set the target as first market
this.index = 0;
this.markets = [target];
cmpTargetMarket.AddTrader(this.entity);
}
// Drop carried goods if markets were changed
this.goods.amount = null;
return true;
};
Trader.prototype.GetFirstMarket = function()
{
return this.markets[0] || null;
};
Trader.prototype.GetSecondMarket = function()
{
return this.markets[1] || null;
};
Trader.prototype.GetTraderGainMultiplier = function()
{
return ApplyValueModificationsToEntity("Trader/GainMultiplier", +this.template.GainMultiplier, this.entity);
};
Trader.prototype.GetGarrisonGainMultiplier = function()
{
if (this.template.GarrisonGainMultiplier === undefined)
return undefined;
return ApplyValueModificationsToEntity("Trader/GarrisonGainMultiplier", +this.template.GarrisonGainMultiplier, this.entity);
};
Trader.prototype.HasBothMarkets = function()
{
return this.markets.length >= 2;
};
Trader.prototype.CanTrade = function(target)
{
const cmpTargetMarket = QueryMiragedInterface(target, IID_Market);
if (!cmpTargetMarket)
return false;
const cmpTargetFoundation = Engine.QueryInterface(target, IID_Foundation);
if (cmpTargetFoundation)
return false;
const cmpTraderIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (!(cmpTraderIdentity.HasClass("Organic") && cmpTargetMarket.HasType("land")) &&
!(cmpTraderIdentity.HasClass("Ship") && cmpTargetMarket.HasType("naval")))
return false;
const cmpTraderDiplomacy = QueryOwnerInterface(this.entity, IID_Diplomacy);
const cmpTargetPlayer = QueryOwnerInterface(target, IID_Player);
return cmpTraderDiplomacy && cmpTargetPlayer && !cmpTraderDiplomacy.IsEnemy(cmpTargetPlayer.GetPlayerID());
};
Trader.prototype.AddResources = function(ent, gain)
{
let cmpPlayer = QueryOwnerInterface(ent);
if (cmpPlayer)
cmpPlayer.AddResource(this.goods.type, gain);
let cmpStatisticsTracker = QueryOwnerInterface(ent, IID_StatisticsTracker);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseTradeIncomeCounter(gain);
};
Trader.prototype.GenerateResources = function(currentMarket, nextMarket)
{
this.AddResources(this.entity, this.goods.amount.traderGain);
if (this.goods.amount.market1Gain)
this.AddResources(currentMarket, this.goods.amount.market1Gain);
if (this.goods.amount.market2Gain)
this.AddResources(nextMarket, this.goods.amount.market2Gain);
};
Trader.prototype.PerformTrade = function(currentMarket)
{
let previousMarket = this.markets[this.index];
if (previousMarket != currentMarket) // Inconsistent markets
{
this.goods.amount = null;
return INVALID_ENTITY;
}
this.index = ++this.index % this.markets.length;
let nextMarket = this.markets[this.index];
if (this.goods.amount && this.goods.amount.traderGain)
this.GenerateResources(previousMarket, nextMarket);
let cmpPlayer = QueryOwnerInterface(this.entity);
if (!cmpPlayer)
return INVALID_ENTITY;
this.goods.type = cmpPlayer.GetNextTradingGoods();
this.goods.amount = this.CalculateGain(currentMarket, nextMarket);
return nextMarket;
};
Trader.prototype.GetGoods = function()
{
return this.goods;
};
/**
* Returns true if the trader has the given market (can be either a market or a mirage)
*/
Trader.prototype.HasMarket = function(market)
{
return this.markets.indexOf(market) != -1;
};
/**
* Remove a market when this trader can no longer trade with it
*/
Trader.prototype.RemoveMarket = function(market)
{
let index = this.markets.indexOf(market);
if (index == -1)
return;
this.markets.splice(index, 1);
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.MarketRemoved(market);
};
/**
* Switch between a market and its mirage according to visibility
*/
Trader.prototype.SwitchMarket = function(oldMarket, newMarket)
{
let index = this.markets.indexOf(oldMarket);
if (index == -1)
return;
this.markets[index] = newMarket;
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.SwitchMarketOrder(oldMarket, newMarket);
};
Trader.prototype.StopTrading = function()
{
for (let market of this.markets)
{
let cmpMarket = QueryMiragedInterface(market, IID_Market);
if (cmpMarket)
cmpMarket.RemoveTrader(this.entity);
}
this.index = -1;
this.markets = [];
this.goods.amount = null;
this.markets = [];
};
// Get range in which deals with market are available,
// i.e. trader should be in no more than MaxDistance from market
// to be able to trade with it.
Trader.prototype.GetRange = function()
{
let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
let max = 1;
if (cmpObstruction)
max += cmpObstruction.GetSize() * 1.5;
return { "min": 0, "max": max };
};
Trader.prototype.OnGarrisonedUnitsChanged = function()
{
if (this.HasBothMarkets())
this.goods.amount = this.CalculateGain(this.markets[0], this.markets[1]);
};
Engine.RegisterComponentType(IID_Trader, "Trader", Trader);