Ticket #3697: trade-v2.3.patch

File trade-v2.3.patch, 55.6 KB (added by wraitii, 8 years ago)
  • binaries/data/mods/public/globalscripts/Templates.js

     
    245245        if (template.UnitMotion.Run)
    246246            ret.speed.run = func("UnitMotion/Run/Speed", +template.UnitMotion.Run.Speed, player, template);
    247247    }
    248 
    249     if (template.Trader)
    250     {
    251         ret.trader = {
    252             "GainMultiplier": func("Trader/GainMultiplier", +template.Trader.GainMultiplier, player, template)
    253         };
    254     }
    255248       
    256249    if (template.WallSet)
    257250    {
  • binaries/data/mods/public/gui/session/unit_actions.js

     
    320320            case "is first":
    321321                tooltip = translate("Origin trade market.");
    322322                if (tradingDetails.hasBothMarkets)
    323                     tooltip += "\n" + sprintf(translate("Gain: %(gain)s"), {
    324                         gain: getTradingTooltip(tradingDetails.gain)
    325                     });
     323                    tooltip += "\n" + getTradeRouteTooltip(tradingDetails.gain);
    326324                else
    327325                    tooltip += "\n" + translate("Right-click on another market to set it as a destination trade market.");
    328326                break;
    329327            case "is second":
    330                 tooltip = translate("Destination trade market.") + "\n" + sprintf(translate("Gain: %(gain)s"), {
    331                     gain: getTradingTooltip(tradingDetails.gain)
    332                 });
     328                tooltip = translate("Destination trade market.") + "\n" + getTradeRouteTooltip(tradingDetails.gain);
    333329                break;
    334330            case "set first":
    335331                tooltip = translate("Right-click to set as origin trade market");
    336332                break;
    337333            case "set second":
    338                 if (tradingDetails.gain.traderGain == 0)   // markets too close
    339                     return false;
    340                 tooltip = translate("Right-click to set as destination trade market.") + "\n" + sprintf(translate("Gain: %(gain)s"), {
    341                     gain: getTradingTooltip(tradingDetails.gain)
    342                 });
     334                tooltip = translate("Right-click to set as destination trade market.") + "\n" + getTradeRouteTooltip(tradingDetails.gain);
    343335                break;
    344336            }
    345             return {"possible": true, "tooltip": tooltip};
    346 
     337            if (tradingDetails.gain !== undefined && tradingDetails.gain == 0)
     338                return { "possible": false, "tooltip": translate("This market is too close to trade with.") };
     339            else
     340                return { "possible": true, "tooltip": tooltip };
    347341        },
    348342        "actionCheck": function(target)
    349343        {
    350344            var actionInfo = getActionInfo("setup-trade-route", target);
    351             if (!actionInfo.possible)
     345
     346            if (!actionInfo.possible && actionInfo.tooltip != undefined)
     347                return { "type": "none", "cursor": "cursor-no", "tooltip": actionInfo.tooltip, "target": null };
     348            else if (!actionInfo.possible)
    352349                return false;
    353             return {"type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target};
     350            return { "type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target };
    354351        },
    355352        "specificness": 0,
    356353    },
     
    500497                cursor = "action-attack-move";
    501498            }
    502499
    503             if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "Ally"]))
     500            if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id &&
     501                    (!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket")) && !playerCheck(entState, targetState, ["Enemy"]))
    504502            {
     503                // Find a trader (if any) that this building can produce.
     504                let trader;
     505                if (entState.production && entState.production.entities.length)
     506                    for (let i = 0; i < entState.production.entities.length; ++i)
     507                        if ((trader = GetTemplateData(entState.production.entities[i]).trader))
     508                            break;
     509                let traderData = { "firstMarket": entState.id, "secondMarket": targetState.id, "tradeOwner": entState.player };
     510                let gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData);
     511                if (gain)
     512                {
     513                    data.command = "trade";
     514                    data.target = traderData.secondMarket;
     515                    data.source = traderData.firstMarket;
     516                    cursor = "action-setup-trade-route";
     517                    tooltip = translate("Right-click to establish a default route for new traders.");
     518                }
     519            }
     520            else if (targetState.garrisonHolder && playerCheck(entState, targetState, ["Player", "Ally"]))
     521            {
    505522                data.command = "garrison";
    506523                data.target = targetState.id;
    507524                cursor = "action-garrison";
     
    523540                data.resourceType = resourceType;
    524541                data.resourceTemplate = targetState.template;
    525542            }
    526             else if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id &&
    527                     (!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket")) && !playerCheck(entState, targetState, ["Enemy"]))
    528             {
    529                 // Find a trader (if any) that this building can produce.
    530                 var trader;
    531                 if (entState.production && entState.production.entities.length)
    532                     for (var i = 0; i < entState.production.entities.length; ++i)
    533                         if ((trader = GetTemplateData(entState.production.entities[i]).trader))
    534                             break;
    535 
    536                 var traderData = { "firstMarket": entState.id, "secondMarket": targetState.id, "template": trader };
    537                 var gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData);
    538                 if (gain && gain.traderGain)
    539                 {
    540                     data.command = "trade";
    541                     data.target = traderData.secondMarket;
    542                     data.source = traderData.firstMarket;
    543                     cursor = "action-setup-trade-route";
    544                     tooltip = translate("Right-click to establish a default route for new traders.");
    545                     if (trader)
    546                         tooltip += "\n" + sprintf(translate("Gain: %(gain)s"), { gain: getTradingTooltip(gain) });
    547                     else // Foundation or cannot produce traders
    548                         tooltip += "\n" + sprintf(translate("Expected gain: %(gain)s"), { gain: getTradingTooltip(gain) });
    549                 }
    550             }
    551543            else if (targetState.foundation && playerCheck(entState, targetState, ["Ally"]))
    552544            {
    553545                data.command = "build";
  • binaries/data/mods/public/gui/session/utility_functions.js

     
    8282/**
    8383 * Returns a message with the details of the trade gain.
    8484 */
    85 function getTradingTooltip(gain)
     85function getTradeRouteTooltip(gain)
    8686{
     87    return translate("This trader is currently trading between two markets, creating wealth for his empire.") + "\n" +
     88           sprintf(translate("The basic value of this trade route is %(value).1f."), { value:gain });
     89}
    8790
    88     var playerID = Engine.GetPlayerID();
    89     var simState = GetSimState();
    90    
    91     var gainString = gain.traderGain;
    92     if (gain.market1Gain && gain.market1Owner == gain.traderOwner)
    93         gainString += translate("+") + gain.market1Gain;
    94     if (gain.market2Gain && gain.market2Owner == gain.traderOwner)
    95         gainString += translate("+") + gain.market2Gain;
    96 
    97     var tooltip = sprintf(translate("%(gain)s (%(player)s)"), {
    98         gain: gainString,
    99         player: (!g_IsNetworked && gain.traderOwner == playerID) ? translate("You") : simState.players[gain.traderOwner].name
    100     });
    101    
    102     if (gain.market1Gain && gain.market1Owner != gain.traderOwner)
    103         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    104             gain: gain.market1Gain,
    105             player: (!g_IsNetworked && gain.market1Owner == playerID) ? translate("You") : simState.players[gain.market1Owner].name
    106         });
    107     if (gain.market2Gain && gain.market2Owner != gain.traderOwner)
    108         tooltip += translateWithContext("Separation mark in an enumeration", ", ") + sprintf(translate("%(gain)s (%(player)s)"), {
    109             gain: gain.market2Gain,
    110             player: (!g_IsNetworked && gain.market2Owner == playerID) ? translate("You") : simState.players[gain.market2Owner].name
    111         });
    112 
     91/**
     92 * Returns a message with the details of the trade income.
     93 */
     94function getTradeIncomeTooltip(details)
     95{
     96    if (details.connections.length === 0)
     97        return translate("This market has no trade routes to other markets. Use traders to connect markets together and create wealth.");
     98    let avgStrength = 0.0;
     99    for (let connection in details.connections)
     100        avgStrength += Math.min(100, details.connections[connection].strength);
     101    avgStrength /= details.connections.length;
     102    let tooltip = sprintf("[font=\"sans-bold-13\"]" + translate("Trade income") + ":[/font] " + translate("%(tradeRate).1f") + " [font=\"sans-10\"]" + translate("resources/minute") + "[/font]" +
     103                        "\n[font=\"sans-bold-13\"]" + translate("Efficiency")   + ":[/font] " + translate("%(tradeEfficiency)u%%") +
     104                        "\n[font=\"sans-bold-13\"]" + translate("Number of trade routes") + ":[/font] " + translate("%(MarketConnection)s") +
     105                        "\n[font=\"sans-bold-13\"]" + translate("Average trade route effiency") + ":[/font] " + translate("%(ConnectionStrenght)s%%"),
     106                  { "tradeRate" : details.rate*60.0, "tradeEfficiency": details.rawRate*100,  "MarketConnection": details.connections.length, "ConnectionStrenght": avgStrength });
     107    tooltip += "[font=\"sans-10\"]\n\n" + translate("To increase your income, garrison workers until the efficiency is 100% and trade with more markets, or reinforce your current trade lines' strengths.") + "[/font]";
    113108    return tooltip;
    114109}
    115110
  • binaries/data/mods/public/gui/session/selection_panels.js

     
    947947                else
    948948                    data.carried[carrying.type] = carrying.amount;
    949949            }
    950 
    951             if (state.trader && state.trader.goods && state.trader.goods.amount)
    952             {
    953                 if (!data.carried)
    954                     data.carried = {};
    955                 var amount = state.trader.goods.amount;
    956                 var type = state.trader.goods.type;
    957                 var totalGain = amount.traderGain;
    958                 if (amount.market1Gain)
    959                     totalGain += amount.market1Gain;
    960                 if (amount.market2Gain)
    961                     totalGain += amount.market2Gain;
    962                 if (data.carried[type])
    963                     data.carried[type] += totalGain;
    964                 else
    965                     data.carried[type] = totalGain;
    966             }
    967950        }
    968951        return true;
    969952    },
  • binaries/data/mods/public/gui/session/selection_details.js

     
    175175        Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { "amount": carried.amount, "max": carried.max });
    176176        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "";
    177177    }
    178     // Use the same indicators for traders
    179     else if (entState.trader && entState.trader.goods.amount)
    180     {
    181         Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
    182         Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
    183         Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+entState.trader.goods.type+".png";
    184         let totalGain = entState.trader.goods.amount.traderGain;
    185         if (entState.trader.goods.amount.market1Gain)
    186             totalGain += entState.trader.goods.amount.market1Gain;
    187         if (entState.trader.goods.amount.market2Gain)
    188             totalGain += entState.trader.goods.amount.market2Gain;
    189         Engine.GetGUIObjectByName("resourceCarryingText").caption = totalGain;
    190         Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(gain)s"), { "gain": getTradingTooltip(entState.trader.goods.amount) });
    191     }
     178    // Use the same indicators for traders
     179    else if (entState.trader && entState.trader.trading)
     180    {
     181        var nextMarket = GetEntityState(entState.trader.nextMarket);
     182        // Find the connection if it exists.
     183        let connection = null;
     184        if (nextMarket && nextMarket.tradeIncome)
     185            connection = nextMarket.tradeIncome.connections.find(conn => conn[0] === entState.trader.originMarket);
     186
     187        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
     188        Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
     189        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/trade.png";
     190        Engine.GetGUIObjectByName("resourceCarryingText").caption = "";
     191        if (connection)
     192            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("This trader is part of a trade route between two markets.\nThis route's efficiency is %(strength)u%%."), { "strength": Math.min(connection[1], 100) });
     193        else
     194            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("This trader is trying to establish a trade route, but the number of traders isn't high enough for this trade route to be viable yet.");
     195    }
    192196    // And for number of workers
    193197    else if (entState.foundation && entState.visibility == "visible")
    194198    {
     
    206210        else
    207211            Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders.");
    208212    }
     213    // And for the trade income icon
     214    else if (entState.tradeIncome)
     215    {
     216        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
     217        Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
     218        Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/trade.png";
     219        Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(MarketEfficiency)u%%  x%(NumberOfConnections)u"),
     220                                                                            { "MarketEfficiency": +entState.tradeIncome.rawRate*100,
     221                                                                              "NumberOfConnections": entState.tradeIncome.connections.length });
     222        Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = getTradeIncomeTooltip(entState.tradeIncome);
     223    }
    209224    else if (entState.repairable && entState.repairable.numBuilders > 0 && entState.visibility == "visible")
    210225    {
    211226        Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
  • binaries/data/mods/public/shaders/glsl/water_high.fs

     
    127127}
    128128
    129129void main()
    130 {
    131     //gl_FragColor = texture2D(waterEffectsTex, gl_FragCoord.xy/screenSize);
    132     //return;
    133    
     130{   
    134131    float fresnel;
    135     float t;                // Temporary variable
    136132    vec2 reflCoords, refrCoords;
    137133    vec3 reflColor, refrColor, specular;
    138     float losMod;
     134    float losMod, reflMod;
    139135   
    140     vec3 l = -sunDir;
    141136    vec3 v = normalize(cameraPos - worldPos);
    142     vec3 h = normalize(l + v);
    143137   
    144138    // Calculate water normals.
    145139
     
    147141    float baseScale = waveParams1.g;
    148142    float flattenism = waveParams1.b;
    149143    float baseBump = waveParams1.a;
    150 
    151     float smallIntensity = waveParams2.r;
    152     float smallBase = waveParams2.g;
    153144    float BigMovement = waveParams2.b;
    154     float SmallMovement = waveParams2.a;
    155145   
    156146    float moddedTime = mod(time * 60.0, 8.0) / 8.0;
    157147   
    158148    // This method uses 60 animated water frames. We're blending between each two frames
    159     // TODO: could probably have fewer frames thanks to this blending.
    160149    // Scale the normal textures by waviness so that big waviness means bigger waves.
    161150    vec3 ww1 = texture2D(normalMap, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).xzy;
    162151    vec3 ww2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * BigMovement*waviness/10.0) * (baseScale - waviness/wavyEffect)).xzy;
     
    165154    ww1.x = wwInterp.x * WindCosSin.x - wwInterp.z * WindCosSin.y;
    166155    ww1.z = wwInterp.x * WindCosSin.y + wwInterp.z * WindCosSin.x;
    167156    ww1.y = wwInterp.y;
    168    
    169     vec3 smallWW = texture2D(normalMap, (normalCoords.st + normalCoords.zw * SmallMovement*waviness/10.0) * baseScale*3.0).xzy;
    170     vec3 smallWW2 = texture2D(normalMap2, (normalCoords.st + normalCoords.zw * SmallMovement*waviness/10.0) * baseScale*3.0).xzy;
    171     vec3 smallWWInterp = mix(smallWW, smallWW2, moddedTime) - vec3(0.5,0.0,0.5);
    172 
    173     smallWW.x = smallWWInterp.x * WindCosSin.x - smallWWInterp.z * WindCosSin.y;
    174     smallWW.z = smallWWInterp.x * WindCosSin.y + smallWWInterp.z * WindCosSin.x;
    175     smallWW.y = smallWWInterp.y;
    176 
    177     ww1 += vec3(smallWW)*(fwaviness/10.0*smallIntensity + smallBase);
    178    
    179     ww1 = mix(smallWW, ww1, waterInfo.r);
    180157       
    181158    // Flatten them based on waviness.
    182159    vec3 n = normalize(mix(vec3(0.0,1.0,0.0),ww1, clamp(baseBump + fwaviness/flattenism,0.0,1.0)));
     
    189166        n = mix(vec3(0.0,1.0,0.0), n,0.5 + waterInfo.r/2.0);
    190167    #endif
    191168
    192     n = vec3(-n.x,n.y,-n.z);
     169    n = vec3(-n.x,n.y,-n.z); // the final wave normal vector
    193170   
    194     // simulates how parallel the "point->sun", "view->point" vectors are.
    195     float ndoth = dot(n , h);
    196    
    197171    // how perpendicular to the normal our view is. Used for fresnel.
    198172    float ndotv = clamp(dot(n, v),0.0,1.0);
    199173   
    200     // diffuse lighting-like. used for shadows?
    201     float ndotl = (dot(n, l) + 1.0)/2.0;
     174    // Fresnel for "how much reflection vs how much refraction".
     175    fresnel = clamp(((pow(1.1 - ndotv, 3.0)) * 1.5), 0.2, 1.0); // approximation. I'm using 1.1 and not 1.0 because it causes artifacts, see #1714
    202176   
     177    // Specular lighting vectors
     178    vec3 specVector = normalize(reflect(sunDir, n));
     179    float specIntensity = pow(abs(dot(specVector, v)), 256.0);
     180
     181    specular = specIntensity * sunColor * fresnel;
     182   
    203183    float depth;
    204184#if USE_REAL_DEPTH
    205185    // Don't change these two. They should match the values in the config (TODO: dec uniforms).
     
    238218    depth = max(depth,fancyeffects.a);
    239219#endif
    240220   
    241     // Fresnel for "how much reflection vs how much refraction".
    242     // Since we're not trying to simulate a realistic ocean 100%, aim for something that gives a little too much reflection
    243     // because we're not used to seeing the see from above.
    244     fresnel = clamp(pow(1.05 - ndotv, 1.1),0.0,0.8); // approximation. I'm using 1.05 and not 1.0 because it causes artifacts, see #1714
    245     // multiply by v.y so that in the distance refraction wins.
    246     // TODO: this is a hack because reflections don't work in the distance.
    247     fresnel = clamp(fresnel*1.5,0.0,0.9);
    248     fresnel *= min(1.0,log(1.0 + v.y*5.0));
    249    
    250     //gl_FragColor = vec4(fresnel,fresnel,fresnel,1.0);
    251     //return;
    252    
    253221#if USE_SHADOWS_ON_WATER && USE_SHADOW
    254222    float shadow = get_shadow(vec4(v_shadow.xy, v_shadow.zw));
    255223#endif
     
    256224   
    257225    // for refraction, we want to adjust the value by v.y slightly otherwise it gets too different between "from above" and "from the sides".
    258226    // And it looks weird (again, we are not used to seeing water from above).
    259     float fixedVy = max(v.y,0.1);
     227    float fixedVy = max(v.y,0.01);
    260228   
    261     float distoFactor = clamp(depth/2.0,0.0,7.0);
     229    // This somewhat complicated function controls the amount of distortion relative to depth.
     230    // The overall function is depth/log(depth) to keep distortions from becoming too extreme,
     231    // and the extra terms modify the amount of refraction and prevent numerical singularities from
     232    // causing artifacts.
     233    float distoFactor = (1.5 * depth) / max(1.0, 2.0 * log(1.0 + depth));
    262234   
    263235    float murky = mix(200.0,0.1,pow(murkiness,0.25));
    264236   
    265237#if USE_REFRACTION
    266     refrCoords = clamp( (0.5*refractionCoords.xy - n.xz * distoFactor*7.0) / refractionCoords.z + 0.5,0.0,1.0); // Unbias texture coords
     238    // distort the texture coords under where the water is to simulate refraction.
     239    refrCoords = (0.5 * refractionCoords.xy - n.xz * distoFactor) / refractionCoords.z + 0.5;
    267240    vec3 refColor = texture2D(refractionMap, refrCoords).rgb;
    268     if (refColor.r > refColor.g + refColor.b + 0.25)
     241   
     242    // note, the refraction map is cleared using (255, 0, 0), so pixels outside of the water plane are pure red.
     243    // if we get a pure red fragment, use an undistorted/less distorted coord instead.
     244    if (refColor.r == 1.0)
    269245    {
    270         refrCoords = clamp( (0.5*refractionCoords.xy + n.xz) / refractionCoords.z + 0.5,0.0,1.0);   // Unbias texture coords
     246        refrCoords = (0.5*refractionCoords.xy + n.xz) / refractionCoords.z + 0.5;
    271247        refColor = texture2D(refractionMap, refrCoords).rgb;
    272248    }
    273249   
    274     // TODO: make murkiness (both types rematter on that.
    275     // linearly extinct the water. This is how quickly we see nothing but the pure water color
     250    // Apply water tint and murk color.
    276251    float extFact = max(0.0,1.0 - (depth*fixedVy/murky));
    277     // This is how tinted the water is, ie how quickly the refracted floor takes the tint of the water
    278252    float ColextFact = max(0.0,1.0 - (depth*fixedVy/murky));
    279253    vec3 colll = mix(refColor*tint,refColor,ColextFact);
    280254   
    281 #if USE_SHADOWS_ON_WATER && USE_SHADOW
    282     // TODO:
    283255    refrColor = mix(color, colll, extFact);
    284256#else
     257    // Apply water tint and murk color only.
     258    float extFact = max(0.0,1.0 - (depth*fixedVy/murky));
     259    float ColextFact = max(0.0,1.0 - (depth*fixedVy/murky));
     260    vec3 colll = mix(refColor*tint,refColor,ColextFact);
     261   
    285262    refrColor = mix(color, colll, extFact);
    286263#endif
    287 #else
    288     // linearly extinct the water. This is how quickly we see nothing but the pure water color
    289     float extFact = max(0.0,1.0 - (depth*fixedVy/20.0));
    290     // using both those factors, get our transparency.
    291     // This will be our base transparency on top.
    292     float base = 0.4 + depth*fixedVy/15.0; // TODO: murkiness.
    293     float alphaCoeff = mix(1.0, base, extFact);
    294     refrColor = color;
    295 #endif
    296264   
    297265#if USE_REFLECTION
    298266    // Reflections
    299267    // we use real reflections against th skybox, and distort a texture of objects closer.
    300268    vec3 eye = reflect(v,n);
    301     //eye.y = min(-0.2,eye.y);
     269   
    302270    // let's calculate where we intersect with the skycube.
    303271    Ray myRay = Ray(vec3(worldPos.x/4.0,worldPos.y,worldPos.z/4.0),eye);
    304272    vec3 start = vec3(-1500.0 + mapSize/2.0,-100.0,-1500.0 + mapSize/2.0);
     
    309277    newpos *= skyBoxRot;
    310278    newpos.y *= 4.0;
    311279    reflColor = textureCube(skyCube, newpos.rgb).rgb;
    312    
    313     // Reflections appear more distorted when viewed from a lower angle. Simulate this.
    314     float angleEffect = clamp(1.3 - dot(vec3(0.0,1.0,0.0), v),0.0,1.0);
    315280
    316     reflCoords = clamp( (0.5*reflectionCoords.xy - 40.0 * n.zx * angleEffect) / reflectionCoords.z + 0.5,0.0,1.0);  // Unbias texture coords
     281    // Distort the reflection coords based on waves.
     282    reflCoords = (0.5*reflectionCoords.xy - 15.0 * n.zx) / reflectionCoords.z + 0.5;
    317283    vec4 refTex = texture2D(reflectionMap, reflCoords);
    318     fresnel = clamp(fresnel+refTex.a/3.0,0.0,1.0);
    319     reflColor = refTex.rgb * refTex.a + reflColor*(1.0-refTex.a);
    320284   
     285    // interpolate between the sky color and nearby objects.
     286    reflColor = mix(reflColor.rgb, refTex.rgb, refTex.a);
     287    // reflMod is used to reduce the intensity of sky reflections, which otherwise are too extreme.
     288    reflMod = max(refTex.a, 0.55);
     289   
    321290#else
    322     // Temp fix for some ATI cards (see irc logs on th 1st of august betwee, fexor and wraitii)
    323     //reflCoords = clamp( (0.5*reflectionCoords.xy - waviness * mix(1.0, 20.0,waviness/10.0) * n.zx) / reflectionCoords.z + 0.5,0.0,1.0);   // Unbias texture coords
    324     //vec3 refTex = texture2D(reflectionMap, reflCoords).rgb;
    325     //reflColor = refTex.rgb;
    326291    reflColor = vec3(0.15, 0.7, 0.82);
    327292#endif
    328293   
    329     // TODO: At very low angles the reflection stuff doesn't really work any more:
    330     // IRL you would get a blur of the sky, but we don't have that precision (would require mad oversampling)
    331     // So tend towards a predefined color (per-map) which looks like what the skybox would look like if you really blurred it.
    332     // The TODO here would be to precompute a band (1x32?) that represents the average color around the map.
    333     // TODO: another issue is that at high distances (half map) the texture blurs into flatness. Using better mipmaps won't really solve it
    334     // So we'll need to stop showing reflections and default to sky color there too.
    335     // Unless maybe waviness is so low that you would see like in a mirror anyways.
    336     //float disttt = distance(worldPos,cameraPos);
    337     //reflColor = mix(vec3(0.5,0.5,0.55), reflColor, clamp(1.0-disttt/600.0*disttt/600.0,0.0,1.0));//clamp(-0.05 + v.y*20.0,0.0,1.0));
    338    
    339     // Specular.
    340     specular = pow(ndoth, mix(5.0,2000.0, clamp(v.y*v.y*2.0,0.0,1.0)))*sunColor * 1.5;// * sunColor * 1.5 * ww.r;
    341    
    342294    losMod = texture2D(losMap, losCoords.st).a;
    343295    losMod = losMod < 0.03 ? 0.0 : losMod;
    344296   
    345     float wavesFresnel = 1.0;
    346    
    347 #if USE_FANCY_EFFECTS
    348     wavesFresnel = mix(1.0-fancyeffects.a,1.0,clamp(depth,0.0,1.0));
    349 #endif
    350    
    351297    vec3 color;
    352298#if USE_SHADOWS_ON_WATER && USE_SHADOW
    353299    float fresShadow = mix(fresnel, fresnel*shadow, 0.05 + murkiness*0.2);
    354     color = mix(refrColor, reflColor, fresShadow * wavesFresnel);
     300    color = mix(refrColor, reflColor, fresShadow);
    355301#else
    356     color = mix(refrColor, reflColor, fresnel * wavesFresnel);
     302    color = mix(refrColor, reflColor, fresnel * reflMod);
    357303#endif
    358304   
    359305#if USE_SHADOWS_ON_WATER && USE_SHADOW
     
    373319    foaminterp *= mix(foam3, foam4, moddedTime);
    374320   
    375321    foam1.x = foaminterp.x * WindCosSin.x - foaminterp.z * WindCosSin.y;
    376     //foam1.z = foaminterp.x * WindCosSin.y + foaminterp.z * WindCosSin.x;
    377     //foam1.y = foaminterp.y;
     322   
    378323    float foam = FoamEffects.r * FoamEffects.a*0.4 + pow(foam1.x*(5.0+waviness),(2.6 - waviness/5.5));
    379     foam *= ndotl;
    380324   
    381     gl_FragColor.rgb = get_fog(color) * losMod + foam * losMod;// + fancyeffects.a * losMod;
     325    gl_FragColor.rgb = get_fog(color) * losMod + foam * losMod;
    382326#else
    383327    gl_FragColor.rgb = get_fog(color) * losMod;
    384328#endif
     
    393337    if (fancyeffects.a < 0.05 && waterDepth < -1.0 )
    394338        gl_FragColor.a = 0.0;
    395339#endif
    396    
    397     //gl_FragColor = vec4(sunColor,1.0);
    398340}
  • binaries/data/mods/public/simulation/components/TradeIncome.js

     
     1// Array of resource names
     2const RESOURCES = ["food", "wood", "stone", "metal"];
     3
     4function TradeIncome() {}
     5
     6TradeIncome.prototype.Schema =
     7    "<a:help>Lets the unit generate resources that traders could pick up and trade with.</a:help>" +
     8    "<a:example>" +
     9        "<Efficiency>2.0</Efficiency>" +
     10    "</a:example>" +
     11    "<element name='Efficiency' a:help='Gives the basic trade income of the building at full efficiency.'>" +
     12        "<ref name='positiveDecimal'/>" +
     13    "</element>" +
     14    "<optional>" +
     15        "<element name='GarrisonEfficiency' a:help='Improve the efficiency of trading by garrisoning units inside'>" +
     16            "<optional>" +
     17                "<element name='Classes' a:help='only units having one of the following classes will be considered. Defaults to all.'>" +
     18                    "<text/>" +
     19                "</element>" +
     20            "</optional>" +
     21            "<element name='EmptyEfficiency' a:help='Basic efficiency when empty. Expressed as a % of total efficiency.'>" +
     22                "<ref name='nonNegativeDecimal'/>" +
     23            "</element>" +
     24            "<optional>" +
     25                "<element name='MaxGarrison' a:help='Number of unit required for efficiency to be 100%. Defaults to \"garrison capacity\"'>" +
     26                    "<data type='nonNegativeInteger'/>" +
     27                "</element>" +
     28            "</optional>" +
     29        "</element>" +
     30    "</optional>";
     31
     32TradeIncome.prototype.Init = function()
     33{
     34    this.connections = new Map();
     35
     36    // Call the timer
     37    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     38    cmpTimer.SetInterval(this.entity, IID_TradeIncome, "GenerateResources", 5000, 5000, undefined);
     39
     40    // Quick sanity check
     41    var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     42    if (this.template.GarrisonEfficiency && !cmpGarrisonHolder)
     43        warn("Entity " + this.entity + " has a TradeIncome component that makes use of garrisoned entity but does not support garrisoning.");
     44};
     45
     46// When a trader arrives, it either establishes or reinforces a connection.
     47TradeIncome.prototype.AddConnectionWithMarket = function(market, amount)
     48{
     49    if (!this.connections.get(market))
     50        this.connections.set(market, { "strength" : 0, "distance" : 0, "international" : false });
     51
     52    this.connections.get(market).strength += +amount;
     53    // Set up a maximal amount so that you can't just get enough connection strength then delete your traders.
     54    if (this.connections.get(market).strength > 1000)
     55        this.connections.get(market).strength = 1000;
     56
     57    // Reset distance. Ought not change.
     58    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     59    var cmpConnectionPosition = Engine.QueryInterface(market, IID_Position);
     60    if (!cmpPosition || !cmpPosition.IsInWorld() || !cmpConnectionPosition || !cmpConnectionPosition.IsInWorld())
     61        return;
     62
     63    var Position = cmpPosition.GetPosition2D();
     64    var ConnectionPosition = cmpConnectionPosition.GetPosition2D();
     65    var distance = Math.sqrt(Math.pow(Position.x - ConnectionPosition.x, 2)
     66                           + Math.pow(Position.y - ConnectionPosition.y, 2));
     67    this.connections.get(market).distance = distance;
     68
     69    // Reset internationality, in case the owner changes: from our POV until a trader brings news, we are selling items from a faraway land.
     70    var owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     71    var ownerConnection = Engine.QueryInterface(market, IID_Ownership).GetOwner();
     72
     73    this.connections.get(market).international = owner != ownerConnection;
     74}
     75
     76TradeIncome.prototype.GetConnections = function()
     77{
     78    var ret = [];
     79    for (let conn of this.connections.keys())
     80        ret.push(
     81            {
     82                "marketID": conn,
     83                "strength": this.connections.get(conn).strength,
     84                "distance": this.connections.get(conn).distance,
     85                "international": this.connections.get(conn).international
     86            });
     87    return ret;
     88}
     89
     90TradeIncome.prototype.GetTradeIncomeRate = function()
     91{
     92    if (this.connections.size === 0)
     93        return 0;
     94
     95    // Calculate our connection bonuses.
     96    var connectionEffect = 0;
     97
     98    var owner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
     99
     100    for (let i of this.connections.keys())
     101    {
     102        let bonus = CalculateMarketConnectionBonusDirect(this.connections.get(i).distance, owner, this.connections.get(i).international);
     103        connectionEffect += bonus * Math.min(1.0, this.connections.get(i).strength/100.0);
     104    }
     105    return Math.sqrt(connectionEffect) * this.GetRawEfficiency();
     106}
     107
     108TradeIncome.prototype.GetRawEfficiency = function()
     109{
     110    if (!this.template.GarrisonEfficiency)
     111        return 1.0;
     112
     113    var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
     114    if (!cmpGarrisonHolder)
     115        return 1.0; // Probably should not happen
     116
     117
     118    // Depending on how many units are garrisoned, modulate efficiency
     119    var capacity = this.template.GarrisonEfficiency.MaxGarrison === undefined ? cmpGarrisonHolder.GetCapacity() : +this.template.GarrisonEfficiency.MaxGarrison;
     120    var currentCount = this.template.GarrisonEfficiency.Classes === undefined ?
     121                          cmpGarrisonHolder.GetEntities().length : cmpGarrisonHolder.GetGarrisonedArcherCount(this.template.GarrisonEfficiency.Classes);
     122    return 0.2 + (currentCount / capacity) * 0.8;
     123}
     124
     125TradeIncome.prototype.GenerateResources = function()
     126{
     127    if (this.connections.size === 0)
     128        return;
     129
     130    var cmpPlayer = QueryOwnerInterface(this.entity);
     131    if (!cmpPlayer)
     132        return;
     133
     134    var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
     135
     136    var rate = this.GetTradeIncomeRate() * 5;
     137    var goods = cmpPlayer.GetTradingGoods();
     138
     139    for (let good in goods)
     140    {
     141        if (RESOURCES.indexOf(good) === -1)
     142            continue;
     143
     144        cmpPlayer.AddResource(good, rate * goods[good]/100.0);  // Should add up to 100%
     145
     146        if (cmpStatisticsTracker)
     147            cmpStatisticsTracker.IncreaseTradeIncomeCounter(rate * goods[good]);
     148    }
     149
     150    for (let i of this.connections.keys())
     151    {
     152        this.connections.get(i).strength -= 10;
     153        if (this.connections.get(i).strength <= 0)
     154            this.connections.delete(i);
     155    }
     156};
     157
     158Engine.RegisterComponentType(IID_TradeIncome, "TradeIncome", TradeIncome);
  • binaries/data/mods/public/simulation/components/UnitAI.js

    Property changes on: binaries/data/mods/public/simulation/components/TradeIncome.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    52215221
    52225222    if (this.CheckTargetRange(currentMarket, IID_Trader))
    52235223    {
    5224         var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
     5224        let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
    52255225        cmpTrader.PerformTrade(currentMarket);
    5226         if (!cmpTrader.GetGain().traderGain)
    5227         {
    5228             this.StopTrading();
    5229             return;
    5230         }
    52315226
    52325227        if (this.order.data.route && this.order.data.route.length)
    52335228        {
  • binaries/data/mods/public/simulation/components/Trader.js

     
    1 // See helpers/TraderGain.js for the CalculateTaderGain() function which works out how many
    2 // resources a trader gets
    3 
    4 // Additional gain for ships for each garrisoned trader, in percents
    5 const GARRISONED_TRADER_ADDITION = 20;
    6 
    71// Array of resource names
    82const RESOURCES = ["food", "wood", "stone", "metal"];
    93
     
    126Trader.prototype.Schema =
    137    "<a:help>Lets the unit generate resouces while moving between markets (or docks in case of water trading).</a:help>" +
    148    "<a:example>" +
    15         "<MaxDistance>2.0</MaxDistance>" +
    16         "<GainMultiplier>1.0</GainMultiplier>" +
     9        "<ConnectionStrength>5.0</ConnectionStrength>" +
    1710    "</a:example>" +
    18     "<element name='GainMultiplier' a:help='Additional gain multiplier'>" +
     11    "<element name='ConnectionStrength' a:help='How much more connection strength a trader gives.'>" +
    1912        "<ref name='positiveDecimal'/>" +
    2013    "</element>";
    2114
     
    2316{
    2417    this.firstMarket = INVALID_ENTITY;
    2518    this.secondMarket = INVALID_ENTITY;
    26     // Gain from one pass between markets
    27     this.gain = null;
    28     // Selected resource for trading
    29     this.requiredGoods = undefined;
     19
    3020    // Currently carried goods
    31     this.goods = { "type": null, "amount": null, "origin": null };
     21    this.goodsOrigin = null;
    3222};
    3323
    34 Trader.prototype.CalculateGain = function(firstMarket, secondMarket)
    35 {
    36     var gain = CalculateTraderGain(firstMarket, secondMarket, this.template, this.entity);
    37     if (!gain)  // One of our markets must have been destroyed
    38         return null;
    39 
    40     // For ship increase gain for each garrisoned trader
    41     // Calculate this here to save passing unnecessary stuff into the CalculateTraderGain function
    42     var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
    43     if (cmpIdentity && cmpIdentity.HasClass("Ship"))
    44     {
    45         var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
    46         if (cmpGarrisonHolder)
    47         {
    48             var garrisonMultiplier = 1;
    49             var garrisonedTradersCount = 0;
    50             for each (var entity in cmpGarrisonHolder.GetEntities())
    51             {
    52                 var cmpGarrisonedUnitTrader = Engine.QueryInterface(entity, IID_Trader);
    53                 if (cmpGarrisonedUnitTrader)
    54                     garrisonedTradersCount++;
    55             }
    56             garrisonMultiplier *= 1 + GARRISONED_TRADER_ADDITION * garrisonedTradersCount / 100;
    57 
    58             if (gain.traderGain)
    59                 gain.traderGain = Math.round(garrisonMultiplier * gain.traderGain);
    60             if (gain.market1Gain)
    61                 gain.market1Gain = Math.round(garrisonMultiplier * gain.market1Gain);
    62             if (gain.market2Gain)
    63                 gain.market2Gain = Math.round(garrisonMultiplier * gain.market2Gain);
    64         }
    65     }
    66    
    67     return gain;
    68 };
    69 
    70 Trader.prototype.GetGain = function()
    71 {
    72     return this.gain;
    73 };
    74 
    7524// Set target as target market.
    7625// Return true if at least one of markets was changed.
    7726Trader.prototype.SetTargetMarket = function(target, source)
     
    11059        if (target == this.firstMarket)
    11160            marketsChanged = false;
    11261        else
    113         {
    11462            this.secondMarket = target;
    115             this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
    116         }
    11763    }
    11864    else
    11965    {
     
    12470    if (marketsChanged)
    12571    {
    12672        // Drop carried goods
    127         this.goods.amount = null;
     73        this.goodsOrigin = null;
    12874    }
    12975    return marketsChanged;
    13076};
     
    14490    return this.firstMarket && this.secondMarket;
    14591};
    14692
    147 Trader.prototype.GetRequiredGoods = function()
    148 {
    149     return this.requiredGoods;
    150 };
    151 
    152 Trader.prototype.SetRequiredGoods = function(requiredGoods)
    153 {
    154     // Check that argument is a correct resource name
    155     if (!requiredGoods || RESOURCES.indexOf(requiredGoods) == -1)
    156         this.requiredGoods = undefined;
    157     else
    158         this.requiredGoods = requiredGoods;
    159 };
    160 
    16193Trader.prototype.CanTrade = function(target)
    16294{
    16395    var cmpTraderIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     
    186118
    187119Trader.prototype.PerformTrade = function(currentMarket)
    188120{
    189     if (this.goods.amount && this.goods.amount.traderGain)
     121    if (this.goodsOrigin && this.goodsOrigin !== currentMarket)
    190122    {
    191         var cmpPlayer = QueryOwnerInterface(this.entity);
    192         if (cmpPlayer)
    193             cmpPlayer.AddResource(this.goods.type, this.goods.amount.traderGain);
    194 
    195         var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
    196         if (cmpStatisticsTracker)
    197             cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount.traderGain);
    198 
    199         if (this.goods.amount.market1Gain)
    200         {
    201             var cmpPlayer = QueryOwnerInterface(this.firstMarket);
    202             if (cmpPlayer)
    203                 cmpPlayer.AddResource(this.goods.type, this.goods.amount.market1Gain);
    204 
    205             var cmpStatisticsTracker = QueryOwnerInterface(this.firstMarket, IID_StatisticsTracker);
    206             if (cmpStatisticsTracker)
    207                 cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount.market1Gain);
    208         }
    209 
    210         if (this.goods.amount.market2Gain)
    211         {
    212             var cmpPlayer = QueryOwnerInterface(this.secondMarket);
    213             if (cmpPlayer)
    214                 cmpPlayer.AddResource(this.goods.type, this.goods.amount.market2Gain);
    215 
    216             var cmpStatisticsTracker = QueryOwnerInterface(this.secondMarket, IID_StatisticsTracker);
    217             if (cmpStatisticsTracker)
    218                 cmpStatisticsTracker.IncreaseTradeIncomeCounter(this.goods.amount.market2Gain);
    219         }
     123        let cmpTradeIncome = Engine.QueryInterface(currentMarket, IID_TradeIncome);
     124        if (cmpTradeIncome)
     125            cmpTradeIncome.AddConnectionWithMarket(this.goodsOrigin, this.template.ConnectionStrength);
    220126    }
    221 
    222     // First take the preferred goods of the trader if any,
    223     // otherwise choose one according to the player's trading priorities
    224     // if still nothing (but should never happen), choose metal
    225     // and recomputes the gain in case it has changed (for example by technology)
    226     var nextGoods = this.GetRequiredGoods();
    227     if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
    228     {
    229         var cmpPlayer = QueryOwnerInterface(this.entity);
    230         if (cmpPlayer)
    231             nextGoods = cmpPlayer.GetNextTradingGoods();
    232 
    233         if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
    234             nextGoods = "metal";
    235     }
    236     this.goods.type = nextGoods;
    237     this.goods.amount = this.CalculateGain(this.firstMarket, this.secondMarket);
    238     this.goods.origin = currentMarket;
     127    this.goodsOrigin = +currentMarket;
    239128};
    240129
    241 Trader.prototype.GetGoods = function()
    242 {
    243     return this.goods;
    244 };
    245 
    246130Trader.prototype.GetNextMarket = function()
    247131{
    248     if (this.goods.amount && this.goods.origin == this.firstMarket)
     132    if (this.goodsOrigin == this.firstMarket)
    249133        return this.secondMarket;
    250134
    251     if (this.goods.amount && this.goods.origin != this.secondMarket)
    252         this.goods.amount = null;   // leftover from previous trading
     135    if (this.goodsOrigin != this.secondMarket)
     136        this.goodsOrigin = null;   // Leftover from previous trading
    253137    return this.firstMarket;
    254138};
    255139
     140Trader.prototype.GetOriginMarket = function()
     141{
     142    return this.GetNextMarket() == this.firstMarket ? this.secondMarket : this.firstMarket;
     143};
     144
    256145Trader.prototype.StopTrading = function()
    257146{
    258147    // Drop carried goods
    259     this.goods.amount = null;
     148    this.goodsOrigin = null;
    260149    // Reset markets
    261150    this.firstMarket = INVALID_ENTITY;
    262151    this.secondMarket = INVALID_ENTITY;
     
    274163    return { "min": 0, "max": max};
    275164};
    276165
    277 Trader.prototype.OnGarrisonedUnitsChanged = function()
    278 {
    279     if (this.HasBothMarkets())
    280         this.gain = this.CalculateGain(this.firstMarket, this.secondMarket);
    281 };
    282 
    283166Engine.RegisterComponentType(IID_Trader, "Trader", Trader);
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    240240        "resourceCarrying": null,
    241241        "rotation": null,
    242242        "trader": null,
     243        "tradeIncome": null,
    243244        "unitAI": null,
    244245        "visibility": null,
    245246    };
     
    303304    let cmpTrader = Engine.QueryInterface(ent, IID_Trader);
    304305    if (cmpTrader)
    305306        ret.trader = {
    306             "goods": cmpTrader.GetGoods(),
    307             "requiredGoods": cmpTrader.GetRequiredGoods()
     307            "trading": cmpTrader.HasBothMarkets(),
     308            "nextMarket": cmpTrader.GetNextMarket(),
     309            "originMarket": cmpTrader.GetOriginMarket()
    308310        };
    309311
     312    var cmpTradeIncome = Engine.QueryInterface(ent, IID_TradeIncome);
     313    if (cmpTradeIncome)
     314        ret.tradeIncome = {
     315            "connections": cmpTradeIncome.GetConnections(),
     316            "rate": cmpTradeIncome.GetTradeIncomeRate(),
     317            "rawRate": cmpTradeIncome.GetRawEfficiency()
     318        };
     319   
    310320    let cmpFogging = Engine.QueryInterface(ent, IID_Fogging);
    311321    if (cmpFogging)
    312322    {
     
    16461656    if (!data.firstMarket || !data.secondMarket)
    16471657        return null;
    16481658
    1649     return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template);
     1659    return CalculateMarketConnectionBonus(data.firstMarket, data.secondMarket, data.tradeOwner);
    16501660};
    16511661
    16521662GuiInterface.prototype.GetTradingDetails = function(player, data)
     
    16651675            "hasBothMarkets": cmpEntityTrader.HasBothMarkets()
    16661676        };
    16671677        if (cmpEntityTrader.HasBothMarkets())
    1668             result.gain = cmpEntityTrader.GetGain();
     1678            result.gain = CalculateMarketConnectionBonus(firstMarket, secondMarket, player)
    16691679    }
    16701680    else if (data.target === secondMarket)
    16711681    {
    16721682        result = {
    16731683            "type": "is second",
    1674             "gain": cmpEntityTrader.GetGain(),
     1684            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    16751685        };
    16761686    }
    16771687    else if (!firstMarket)
     
    16821692    {
    16831693        result = {
    16841694            "type": "set second",
    1685             "gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
     1695            "gain": CalculateMarketConnectionBonus(firstMarket, data.target, player)
    16861696        };
    16871697    }
    16881698    else
  • binaries/data/mods/public/simulation/components/Looter.js

     
    3636        for (let resource of cmpResourceGatherer.GetCarryingStatus())
    3737            resources[resource.type] += resource.amount;
    3838
    39     // Loot resources traders carry
    40     var cmpTrader = Engine.QueryInterface(targetEntity, IID_Trader);
    41     if (cmpTrader)
    42     {
    43         let carriedGoods = cmpTrader.GetGoods();
    44         if (carriedGoods.amount)
    45         {
    46             resources[carriedGoods.type] +=
    47                 + (carriedGoods.amount.traderGain || 0)
    48                 + (carriedGoods.amount.market1Gain || 0)
    49                 + (carriedGoods.amount.market2Gain || 0);
    50         }
    51     }
    52 
    5339    // Transfer resources
    5440    var cmpPlayer = QueryOwnerInterface(this.entity);
    5541    cmpPlayer.AddResources(resources);
  • binaries/data/mods/public/simulation/components/interfaces/TradeIncome.js

     
     1Engine.RegisterInterface("TradeIncome");
     2 No newline at end of file
  • binaries/data/mods/public/simulation/helpers/TraderGain.js

    Property changes on: binaries/data/mods/public/simulation/components/interfaces/TradeIncome.js
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    11// This constant used to adjust gain value depending on distance
    2 const DISTANCE_FACTOR = 1 / 115;
     2const DISTANCE_FACTOR = 1 / 100.0;
    33
    4 // Additional gain (applying to each market) for trading performed between markets of different players, in percents
    5 const INTERNATIONAL_TRADING_ADDITION = 25;
     4// When performing trade between markets owned by two different players, multiply trader gain by this value.
     5// When trading between your markets, you gain the resources at both markets.
     6// So this must be at least above 2 to make it interesting to trade with your ally.
     7const INTERNATIONAL_TRADING_MULTIPLICATION = 1.4;
    68
    7 // If trader undefined, the trader owner is supposed to be the same as the first market
    8 function CalculateTraderGain(firstMarket, secondMarket, template, trader)
     9function CalculateMarketConnectionBonus(firstMarket, secondMarket, tradeOwner)
    910{
    10     var gain = {};
     11    var bonus = 1;
    1112
    1213    var cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
    1314    var cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
    1415    if (!cmpFirstMarketPosition || !cmpFirstMarketPosition.IsInWorld() ||
    15         !cmpSecondMarketPosition || !cmpSecondMarketPosition.IsInWorld())
     16        !cmpSecondMarketPosition || !cmpSecondMarketPosition.IsInWorld())
    1617        return null;
    1718    var firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
    1819    var secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
    1920
     21   
    2022    // Calculate ordinary Euclidean distance between markets.
    2123    // We don't use pathfinder, because ordinary distance looks more fair.
    22     var distance = firstMarketPosition.distanceTo(secondMarketPosition);
    23     // We calculate gain as square of distance to encourage trading between remote markets
    24     gain.traderGain = Math.pow(distance * DISTANCE_FACTOR, 2);
    25     if (template && template.GainMultiplier)
    26     {
    27         if (trader)
    28             gain.traderGain *= ApplyValueModificationsToEntity("Trader/GainMultiplier", +template.GainMultiplier, trader);
    29         else    // called from the gui with modifications already applied
    30             gain.traderGain *= template.GainMultiplier;
    31     }
    32     // If trader undefined, the trader owner is supposed to be the same as the first market
    33     var cmpOwnership = trader ? Engine.QueryInterface(trader, IID_Ownership) : Engine.QueryInterface(firstMarket, IID_Ownership);
    34     if (!cmpOwnership)
    35         return null;
    36     gain.traderOwner = cmpOwnership.GetOwner();
     24    var distance = Math.sqrt(Math.pow(firstMarketPosition.x - secondMarketPosition.x, 2) + Math.pow(firstMarketPosition.y - secondMarketPosition.y, 2));
    3725
    3826    // If markets belong to different players, add gain from international trading
    3927    var ownerFirstMarket = Engine.QueryInterface(firstMarket, IID_Ownership).GetOwner();
    4028    var ownerSecondMarket = Engine.QueryInterface(secondMarket, IID_Ownership).GetOwner();
    41     if (ownerFirstMarket != ownerSecondMarket)
    42     {
    43         gain.market1Gain = gain.traderGain * ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_ADDITION, firstMarket) / 100;
    44         gain.market1Owner = ownerFirstMarket;
    45         gain.market2Gain = gain.traderGain * ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_ADDITION, secondMarket) / 100;
    46         gain.market2Owner = ownerSecondMarket;
    47     }
     29   
     30    return CalculateMarketConnectionBonusDirect(distance, tradeOwner, ownerFirstMarket != ownerSecondMarket);
     31}
    4832
    49     // Add potential trade multipliers and roundings
    50     var cmpPlayer = trader ? QueryOwnerInterface(trader) : QueryOwnerInterface(firstMarket);
     33function CalculateMarketConnectionBonusDirect(distance, tradeOwner, international)
     34{
     35    var bonus = Math.log(distance*DISTANCE_FACTOR);
     36    var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     37    var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(tradeOwner), IID_Player);
    5138    if (cmpPlayer)
    52         gain.traderGain *= cmpPlayer.GetTradeRateMultiplier();
    53     gain.traderGain = Math.round(gain.traderGain);
     39        bonus *= cmpPlayer.GetTradeRateMultiplier();
    5440
    55     if (ownerFirstMarket != ownerSecondMarket)
    56     {
    57         if ((cmpPlayer = QueryOwnerInterface(firstMarket)))
    58             gain.market1Gain *= cmpPlayer.GetTradeRateMultiplier();
    59         gain.market1Gain = Math.round(gain.market1Gain);
    60 
    61         if ((cmpPlayer = QueryOwnerInterface(secondMarket)))
    62             gain.market2Gain *= cmpPlayer.GetTradeRateMultiplier();
    63         gain.market2Gain = Math.round(gain.market2Gain);
    64     }
    65 
    66     return gain;
     41    if (international)
     42        bonus *= +ApplyValueModificationsToEntity("Trade/International", INTERNATIONAL_TRADING_MULTIPLICATION, tradeOwner);
     43    return Math.max(0, bonus);
    6744}
    6845
    69 Engine.RegisterGlobal("CalculateTraderGain", CalculateTraderGain);
     46Engine.RegisterGlobal("CalculateMarketConnectionBonusDirect", CalculateMarketConnectionBonusDirect);
     47Engine.RegisterGlobal("CalculateMarketConnectionBonus", CalculateMarketConnectionBonus);
  • binaries/data/mods/public/simulation/templates/template_formation.xml

     
    4040    <WalkSpeed>1.0</WalkSpeed>
    4141    <PassabilityClass>large</PassabilityClass>
    4242  </UnitMotion>
    43   <Trader>
    44     <GainMultiplier>1.0</GainMultiplier>
    45   </Trader>
    4643</Entity>
  • binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

     
    7171      armor_ship_hullsheathing
    7272    </Technologies>
    7373  </ProductionQueue>
     74  <TradeIncome>
     75    <Efficiency>0.5</Efficiency>
     76  </TradeIncome>
    7477  <Vision>
    7578    <Range>40</Range>
    7679  </Vision>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml

     
    4545    <HeightOffset>6.0</HeightOffset>
    4646  </StatusBars>
    4747  <Trader>
    48     <GainMultiplier>1.0</GainMultiplier>
     48    <ConnectionStrength>100</ConnectionStrength>
    4949  </Trader>
    5050  <UnitAI>
    5151    <DefaultStance>passive</DefaultStance>
  • binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml

     
    22<Entity parent="template_structure_economic">
    33  <BuildRestrictions>
    44    <Category>Market</Category>
     5    <Distance>
     6      <FromClass>CivilCentre</FromClass>
     7      <MaxDistance>140</MaxDistance>
     8    </Distance>
    59  </BuildRestrictions>
    610  <Cost>
    711    <BuildTime>150</BuildTime>
     
    1317    <Square width="33.0" depth="29.0"/>
    1418    <Height>8.0</Height>
    1519  </Footprint>
     20  <GarrisonHolder>
     21    <Max>10</Max>
     22    <EjectHealth>0.2</EjectHealth>
     23    <EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
     24    <List datatype="tokens">Worker</List>
     25    <BuffHeal>0</BuffHeal>
     26    <LoadingRange>2</LoadingRange>
     27  </GarrisonHolder>
    1628  <Health>
    1729    <Max>1500</Max>
    1830    <SpawnEntityOnDeath>rubble/rubble_stone_5x5</SpawnEntityOnDeath>
     
    4759    <Radius>40</Radius>
    4860    <Weight>30000</Weight>
    4961  </TerritoryInfluence>
     62  <TradeIncome>
     63    <Efficiency>1.0</Efficiency>
     64    <GarrisonEfficiency>
     65      <EmptyEfficiency>0.2</EmptyEfficiency>
     66    </GarrisonEfficiency>
     67  </TradeIncome>
    5068  <ProductionQueue>
    5169    <BatchTimeModifier>0.7</BatchTimeModifier>
    5270    <Technologies datatype="tokens">
  • binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml

     
    1818    <Tooltip>Trade resources between your own markets and those of your allies.</Tooltip>
    1919    <Formations disable=""/>
    2020  </Identity>
     21  <Loot>
     22    <xp>10</xp>
     23    <food>25</food>
     24    <wood>25</wood>
     25    <stone>15</stone>
     26    <metal>15</metal>
     27  </Loot>
    2128  <Sound>
    2229    <SoundGroups>
    2330      <select>voice/{lang}/civ/civ_{gender}_select.xml</select>
     
    3946    </SoundGroups>
    4047  </Sound>
    4148  <Trader>
    42     <GainMultiplier>1.0</GainMultiplier>
     49    <ConnectionStrength>50</ConnectionStrength>
    4350  </Trader>
    4451  <UnitAI>
    4552    <CanGuard>false</CanGuard>
  • binaries/data/mods/public/simulation/templates/special/player.xml

     
    3030      <Library>1</Library>
    3131      <Lighthouse>1</Lighthouse>
    3232      <Juggernaut>1</Juggernaut>
     33      <Market>0</Market>
    3334    </Limits>
    3435    <LimitChangers>
     36      <Market>
     37        <CivilCentre>1</CivilCentre>
     38      </Market>
    3539      <WarDog>
    3640        <Kennel>10</Kennel>
    3741      </WarDog>
  • binaries/data/mods/public/simulation/templates/structures/cart_dock.xml

     
    2727      -training_naval_architects
    2828    </Technologies>
    2929  </ProductionQueue>
     30  <TradeIncome>
     31    <Efficiency>0.8</Efficiency>
     32  </TradeIncome>
    3033  <VisualActor>
    3134    <Actor>structures/carthaginians/dock.xml</Actor>
    3235    <FoundationActor>structures/fndn_6x4_dock.xml</FoundationActor>
  • binaries/data/mods/public/simulation/templates/structures/pers_market.xml

     
    1515  <Obstruction>
    1616    <Static width="24.0" depth="24.0"/>
    1717  </Obstruction>
     18  <TradeIncome>
     19    <Efficiency>1.25</Efficiency>
     20  </TradeIncome>
    1821  <VisualActor>
    1922    <Actor>structures/persians/market.xml</Actor>
    2023    <FoundationActor>structures/fndn_5x5.xml</FoundationActor>
  • binaries/data/mods/public/simulation/templates/units/cart_ship_merchant.xml

     
    1212    <Icon>units/cart_ship_merchant.png</Icon>
    1313    <RequiredTechnology>phase_village</RequiredTechnology>
    1414  </Identity>
    15   <Trader>
    16     <GainMultiplier>1.25</GainMultiplier>
    17   </Trader>
    1815  <VisualActor>
    1916    <Actor>structures/carthaginians/merchant_ship.xml</Actor>
    2017  </VisualActor>
  • binaries/data/mods/public/simulation/templates/units/pers_support_trader.xml

     
    2020      <death>actor/fauna/death/death_camel.xml</death>
    2121    </SoundGroups>
    2222  </Sound>
    23   <Trader>
    24     <GainMultiplier>1.25</GainMultiplier>
    25   </Trader>
    2623  <VisualActor>
    2724    <Actor>units/persians/trader.xml</Actor>
    2825  </VisualActor>