Line data Source code
1 : // See helpers/TraderGain.js for the CalculateTaderGain() function which works out how many
2 : // resources a trader gets
3 :
4 : function Trader() {}
5 :
6 0 : Trader.prototype.Schema =
7 : "<a:help>Lets the unit generate resouces while moving between markets (or docks in case of water trading).</a:help>" +
8 : "<a:example>" +
9 : "<GainMultiplier>0.75</GainMultiplier>" +
10 : "<GarrisonGainMultiplier>0.2</GarrisonGainMultiplier>" +
11 : "</a:example>" +
12 : "<element name='GainMultiplier' a:help='Trader gain for a 100m distance and mapSize = 1024'>" +
13 : "<ref name='positiveDecimal'/>" +
14 : "</element>" +
15 : "<optional>" +
16 : "<element name='GarrisonGainMultiplier' a:help='Additional gain for garrisonable unit for each garrisoned trader (1.0 means 100%)'>" +
17 : "<ref name='positiveDecimal'/>" +
18 : "</element>" +
19 : "</optional>";
20 :
21 0 : Trader.prototype.Init = function()
22 : {
23 0 : this.markets = [];
24 0 : this.index = -1;
25 0 : this.goods = {
26 : "type": null,
27 : "amount": null
28 : };
29 : };
30 :
31 0 : Trader.prototype.CalculateGain = function(currentMarket, nextMarket)
32 : {
33 0 : let cmpMarket = QueryMiragedInterface(currentMarket, IID_Market);
34 0 : let gain = cmpMarket && cmpMarket.CalculateTraderGain(nextMarket, this.template, this.entity);
35 0 : if (!gain) // One of our markets must have been destroyed
36 0 : return null;
37 :
38 : // For garrisonable unit increase gain for each garrisoned trader
39 : // Calculate this here to save passing unnecessary stuff into the CalculateTraderGain function
40 0 : let garrisonGainMultiplier = this.GetGarrisonGainMultiplier();
41 0 : if (garrisonGainMultiplier === undefined)
42 0 : return gain;
43 :
44 0 : let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
45 0 : if (!cmpGarrisonHolder)
46 0 : return gain;
47 :
48 0 : let garrisonMultiplier = 1;
49 0 : let garrisonedTradersCount = 0;
50 0 : for (let entity of cmpGarrisonHolder.GetEntities())
51 : {
52 0 : let cmpGarrisonedUnitTrader = Engine.QueryInterface(entity, IID_Trader);
53 0 : if (cmpGarrisonedUnitTrader)
54 0 : ++garrisonedTradersCount;
55 : }
56 0 : garrisonMultiplier *= 1 + garrisonGainMultiplier * garrisonedTradersCount;
57 :
58 0 : if (gain.traderGain)
59 0 : gain.traderGain = Math.round(garrisonMultiplier * gain.traderGain);
60 0 : if (gain.market1Gain)
61 0 : gain.market1Gain = Math.round(garrisonMultiplier * gain.market1Gain);
62 0 : if (gain.market2Gain)
63 0 : gain.market2Gain = Math.round(garrisonMultiplier * gain.market2Gain);
64 :
65 0 : return gain;
66 : };
67 :
68 : /**
69 : * Remove market from trade route iff only first market is set.
70 : * @param {number} id of market to be removed.
71 : * @return {boolean} true iff removal was successful.
72 : */
73 0 : Trader.prototype.RemoveTargetMarket = function(target)
74 : {
75 0 : if (this.markets.length != 1 || this.markets[0] != target)
76 0 : return false;
77 0 : let cmpTargetMarket = QueryMiragedInterface(target, IID_Market);
78 0 : if (!cmpTargetMarket)
79 0 : return false;
80 0 : cmpTargetMarket.RemoveTrader(this.entity);
81 0 : this.index = -1;
82 0 : this.markets = [];
83 0 : return true;
84 : };
85 :
86 : // Set target as target market.
87 : // Return true if at least one of markets was changed.
88 0 : Trader.prototype.SetTargetMarket = function(target, source)
89 : {
90 0 : let cmpTargetMarket = QueryMiragedInterface(target, IID_Market);
91 0 : if (!cmpTargetMarket)
92 0 : return false;
93 :
94 0 : if (source)
95 : {
96 : // Establish a trade route with both markets in one go.
97 0 : let cmpSourceMarket = QueryMiragedInterface(source, IID_Market);
98 0 : if (!cmpSourceMarket)
99 0 : return false;
100 0 : this.markets = [source];
101 : }
102 0 : if (this.markets.length >= 2)
103 : {
104 : // If we already have both markets - drop them
105 : // and use the target as first market
106 0 : for (let market of this.markets)
107 : {
108 0 : let cmpMarket = QueryMiragedInterface(market, IID_Market);
109 0 : if (cmpMarket)
110 0 : cmpMarket.RemoveTrader(this.entity);
111 : }
112 0 : this.index = 0;
113 0 : this.markets = [target];
114 0 : cmpTargetMarket.AddTrader(this.entity);
115 : }
116 0 : else if (this.markets.length == 1)
117 : {
118 : // If we have only one market and target is different from it,
119 : // set the target as second one
120 0 : if (target == this.markets[0])
121 0 : return false;
122 :
123 0 : this.index = 0;
124 0 : this.markets.push(target);
125 0 : cmpTargetMarket.AddTrader(this.entity);
126 0 : this.goods.amount = this.CalculateGain(this.markets[0], this.markets[1]);
127 : }
128 : else
129 : {
130 : // Else we don't have target markets at all,
131 : // set the target as first market
132 0 : this.index = 0;
133 0 : this.markets = [target];
134 0 : cmpTargetMarket.AddTrader(this.entity);
135 : }
136 : // Drop carried goods if markets were changed
137 0 : this.goods.amount = null;
138 0 : return true;
139 : };
140 :
141 0 : Trader.prototype.GetFirstMarket = function()
142 : {
143 0 : return this.markets[0] || null;
144 : };
145 :
146 0 : Trader.prototype.GetSecondMarket = function()
147 : {
148 0 : return this.markets[1] || null;
149 : };
150 :
151 0 : Trader.prototype.GetTraderGainMultiplier = function()
152 : {
153 0 : return ApplyValueModificationsToEntity("Trader/GainMultiplier", +this.template.GainMultiplier, this.entity);
154 : };
155 :
156 0 : Trader.prototype.GetGarrisonGainMultiplier = function()
157 : {
158 0 : if (this.template.GarrisonGainMultiplier === undefined)
159 0 : return undefined;
160 0 : return ApplyValueModificationsToEntity("Trader/GarrisonGainMultiplier", +this.template.GarrisonGainMultiplier, this.entity);
161 : };
162 :
163 0 : Trader.prototype.HasBothMarkets = function()
164 : {
165 0 : return this.markets.length >= 2;
166 : };
167 :
168 0 : Trader.prototype.CanTrade = function(target)
169 : {
170 0 : let cmpTraderIdentity = Engine.QueryInterface(this.entity, IID_Identity);
171 :
172 0 : let cmpTargetMarket = QueryMiragedInterface(target, IID_Market);
173 0 : if (!cmpTargetMarket)
174 0 : return false;
175 :
176 0 : let cmpTargetFoundation = Engine.QueryInterface(target, IID_Foundation);
177 0 : if (cmpTargetFoundation)
178 0 : return false;
179 :
180 0 : if (!(cmpTraderIdentity.HasClass("Organic") && cmpTargetMarket.HasType("land")) &&
181 : !(cmpTraderIdentity.HasClass("Ship") && cmpTargetMarket.HasType("naval")))
182 0 : return false;
183 :
184 0 : let cmpTraderPlayer = QueryOwnerInterface(this.entity, IID_Player);
185 0 : let cmpTargetPlayer = QueryOwnerInterface(target, IID_Player);
186 :
187 0 : return cmpTraderPlayer && cmpTargetPlayer && !cmpTraderPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID());
188 : };
189 :
190 0 : Trader.prototype.AddResources = function(ent, gain)
191 : {
192 0 : let cmpPlayer = QueryOwnerInterface(ent);
193 0 : if (cmpPlayer)
194 0 : cmpPlayer.AddResource(this.goods.type, gain);
195 :
196 0 : let cmpStatisticsTracker = QueryOwnerInterface(ent, IID_StatisticsTracker);
197 0 : if (cmpStatisticsTracker)
198 0 : cmpStatisticsTracker.IncreaseTradeIncomeCounter(gain);
199 : };
200 :
201 0 : Trader.prototype.GenerateResources = function(currentMarket, nextMarket)
202 : {
203 0 : this.AddResources(this.entity, this.goods.amount.traderGain);
204 :
205 0 : if (this.goods.amount.market1Gain)
206 0 : this.AddResources(currentMarket, this.goods.amount.market1Gain);
207 :
208 0 : if (this.goods.amount.market2Gain)
209 0 : this.AddResources(nextMarket, this.goods.amount.market2Gain);
210 : };
211 :
212 0 : Trader.prototype.PerformTrade = function(currentMarket)
213 : {
214 0 : let previousMarket = this.markets[this.index];
215 0 : if (previousMarket != currentMarket) // Inconsistent markets
216 : {
217 0 : this.goods.amount = null;
218 0 : return INVALID_ENTITY;
219 : }
220 :
221 0 : this.index = ++this.index % this.markets.length;
222 0 : let nextMarket = this.markets[this.index];
223 :
224 0 : if (this.goods.amount && this.goods.amount.traderGain)
225 0 : this.GenerateResources(previousMarket, nextMarket);
226 :
227 0 : let cmpPlayer = QueryOwnerInterface(this.entity);
228 0 : if (!cmpPlayer)
229 0 : return INVALID_ENTITY;
230 :
231 0 : this.goods.type = cmpPlayer.GetNextTradingGoods();
232 0 : this.goods.amount = this.CalculateGain(currentMarket, nextMarket);
233 :
234 0 : return nextMarket;
235 : };
236 :
237 0 : Trader.prototype.GetGoods = function()
238 : {
239 0 : return this.goods;
240 : };
241 :
242 : /**
243 : * Returns true if the trader has the given market (can be either a market or a mirage)
244 : */
245 0 : Trader.prototype.HasMarket = function(market)
246 : {
247 0 : return this.markets.indexOf(market) != -1;
248 : };
249 :
250 : /**
251 : * Remove a market when this trader can no longer trade with it
252 : */
253 0 : Trader.prototype.RemoveMarket = function(market)
254 : {
255 0 : let index = this.markets.indexOf(market);
256 0 : if (index == -1)
257 0 : return;
258 0 : this.markets.splice(index, 1);
259 0 : let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
260 0 : if (cmpUnitAI)
261 0 : cmpUnitAI.MarketRemoved(market);
262 : };
263 :
264 : /**
265 : * Switch between a market and its mirage according to visibility
266 : */
267 0 : Trader.prototype.SwitchMarket = function(oldMarket, newMarket)
268 : {
269 0 : let index = this.markets.indexOf(oldMarket);
270 0 : if (index == -1)
271 0 : return;
272 0 : this.markets[index] = newMarket;
273 0 : let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
274 0 : if (cmpUnitAI)
275 0 : cmpUnitAI.SwitchMarketOrder(oldMarket, newMarket);
276 : };
277 :
278 0 : Trader.prototype.StopTrading = function()
279 : {
280 0 : for (let market of this.markets)
281 : {
282 0 : let cmpMarket = QueryMiragedInterface(market, IID_Market);
283 0 : if (cmpMarket)
284 0 : cmpMarket.RemoveTrader(this.entity);
285 : }
286 0 : this.index = -1;
287 0 : this.markets = [];
288 0 : this.goods.amount = null;
289 0 : this.markets = [];
290 : };
291 :
292 : // Get range in which deals with market are available,
293 : // i.e. trader should be in no more than MaxDistance from market
294 : // to be able to trade with it.
295 0 : Trader.prototype.GetRange = function()
296 : {
297 0 : let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
298 0 : let max = 1;
299 0 : if (cmpObstruction)
300 0 : max += cmpObstruction.GetSize() * 1.5;
301 0 : return { "min": 0, "max": max };
302 : };
303 :
304 0 : Trader.prototype.OnGarrisonedUnitsChanged = function()
305 : {
306 0 : if (this.HasBothMarkets())
307 0 : this.goods.amount = this.CalculateGain(this.markets[0], this.markets[1]);
308 : };
309 :
310 0 : Engine.RegisterComponentType(IID_Trader, "Trader", Trader);
|