Ticket #3102: t3102_survival.diff

File t3102_survival.diff, 16.8 KB (added by bb, 3 years ago)

rebased still interesting parts of proposed changes

  • binaries/data/mods/public/maps/random/survivalofthefittest.js

     
    202202    var femaleLocation = getTIPIADBON([ix, iz], [mapSize / 2, mapSize / 2], [-3 , 3.5], 1, 3);
    203203    if (femaleLocation !== undefined)
    204204    {
     205        var spawnPoints =  ["C","D","E","F"];
    205206        placeObject(femaleLocation[0], femaleLocation[1], "skirmish/units/default_support_female_citizen", id, playerAngle[i] + PI);
    206207        addToClass(floor(femaleLocation[0]), floor(femaleLocation[1]), clWomen);
     208        placeObject(femaleLocation[0], femaleLocation[1], "special/trigger_point_" + spawnPoints[id-1], id, playerAngle[i] + PI);
     209        // addToClass(floor(femaleLocation[0]), floor(femaleLocation[1]), clPlayer);
    207210    }
    208211}
    209212
  • binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js

     
    1 var treasures =
    2 [
     1/*
     2 * This is the Script containing all the Triggers for the "Survival of the Fittest" Map.
     3 */
     4
     5var g_treasures = [
    36    "gaia/special_treasure_food_barrel",
    47    "gaia/special_treasure_food_bin",
    58    "gaia/special_treasure_food_crate",
     
    1013    "gaia/special_treasure_wood",
    1114    "gaia/special_treasure_wood"
    1215];
    13 var attackerEntityTemplates =
    14 [
     16
     17var g_resourceLoopCount = 0;
     18
     19var g_resourceLoopTime = 1.5; // Time in minutes when resources are distributed periodically
     20var g_resourceLoopOffset = 15; // Time in minutes after which resources are distributed periodically.
     21
     22// These are the templates of the attacking units, sorted by civ
     23var g_attackerEntityTemplates = [
    1524    [
    1625        "units/athen_champion_infantry",
    1726        "units/athen_champion_marine",
    1827        "units/athen_champion_ranged",
    19         "units/athen_siege_lithobolos_packed",
    20         "units/athen_siege_oxybeles_packed",
     28        "units/athen_mechanical_siege_lithobolos_packed",
     29        "units/athen_mechanical_siege_oxybeles_packed"
    2130    ],
    2231    [
    2332        "units/brit_champion_cavalry",
    2433        "units/brit_champion_infantry",
    25         "units/brit_mechanical_siege_ram",
     34        "units/brit_mechanical_siege_ram"
    2635    ],
    2736    [
    2837        "units/cart_champion_cavalry",
    2938        "units/cart_champion_elephant",
    3039        "units/cart_champion_infantry",
    31         "units/cart_champion_pikeman",
     40        "units/cart_champion_pikeman"
    3241    ],
    3342    [
    3443        "units/gaul_champion_cavalry",
    3544        "units/gaul_champion_fanatic",
    3645        "units/gaul_champion_infantry",
    37         "units/gaul_mechanical_siege_ram",
     46        "units/gaul_mechanical_siege_ram"
    3847    ],
    3948    [
    4049        "units/iber_champion_cavalry",
    4150        "units/iber_champion_infantry",
    42         "units/iber_mechanical_siege_ram",
     51        "units/iber_mechanical_siege_ram"
    4352    ],
    4453    [
    4554        "units/mace_champion_cavalry",
     
    4655        "units/mace_champion_infantry_a",
    4756        "units/mace_champion_infantry_e",
    4857        "units/mace_mechanical_siege_lithobolos_packed",
    49         "units/mace_mechanical_siege_oxybeles_packed",
     58        "units/mace_mechanical_siege_oxybeles_packed"
    5059    ],
    5160    [
    5261        "units/maur_champion_chariot",
     
    5362        "units/maur_champion_elephant",
    5463        "units/maur_champion_infantry",
    5564        "units/maur_champion_maiden",
    56         "units/maur_champion_maiden_archer",
     65        "units/maur_champion_maiden_archer"
    5766    ],
    5867    [
    5968        "units/pers_champion_cavalry",
    60         "units/pers_champion_infantry",
    6169        "units/pers_champion_elephant",
     70        "units/pers_champion_infantry"
     71
    6272    ],
    6373    [
    6474        "units/ptol_champion_cavalry",
    65         "units/ptol_champion_elephant",
    66     ],
     75        "units/ptol_champion_elephant"
     76    ]
    6777    [
    6878        "units/rome_champion_cavalry",
    6979        "units/rome_champion_infantry",
    7080        "units/rome_mechanical_siege_ballista_packed",
    71         "units/rome_mechanical_siege_scorpio_packed",
     81        "units/rome_mechanical_siege_scorpio_packed"
    7282    ],
    7383    [
    7484        "units/sele_champion_cavalry",
     
    7585        "units/sele_champion_chariot",
    7686        "units/sele_champion_elephant",
    7787        "units/sele_champion_infantry_pikeman",
    78         "units/sele_champion_infantry_swordsman",
     88        "units/sele_champion_infantry_swordsman"
    7989    ],
    8090    [
    8191        "units/spart_champion_infantry_pike",
    8292        "units/spart_champion_infantry_spear",
    8393        "units/spart_champion_infantry_sword",
    84         "units/spart_mechanical_siege_ram",
     94        "units/spart_mechanical_siege_ram"
    8595    ],
    8696];
    8797
     
    8898Trigger.prototype.StartAnEnemyWave = function()
    8999{
    90100    let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    91     let attackerEntities = attackerEntityTemplates[Math.floor(Math.random() * attackerEntityTemplates.length)];
     101    let attackerEntities = g_attackerEntityTemplates[Math.floor(Math.random() * g_attackerEntityTemplates.length)];
    92102    // A soldier for each 2-3 minutes of the game. Should be waves of 20 soldiers after an hour
    93103    let nextTime = Math.round(120000 + Math.random() * 60000);
    94104    let attackerCount = Math.round(cmpTimer.GetTime() / nextTime / attackerEntities.length);
    95105
    96     // spawn attackers
     106    // Spawn attackers
    97107    let attackers =  [];
    98108    for (let attackerEntity of attackerEntities)
    99109        attackers.push(TriggerHelper.SpawnUnitsFromTriggerPoints("A", attackerEntity, attackerCount, 0));
     
    103113        for (let origin in entityType)
    104114        {
    105115            let cmpPlayer = QueryOwnerInterface(+origin, IID_Player);
    106             if (cmpPlayer.GetState() != "active")
     116            if (!cmpPlayer || !cmpTrigger.playerCivicCenter[cmpPlayer.GetPlayerID()])
    107117                continue;
    108118
    109             let cmpPosition =  Engine.QueryInterface(this.playerCivicCenter[cmpPlayer.GetPlayerID()], IID_Position);
    110             // this shouldn't happen if the player is still active
     119            let cmpPosition =  Engine.QueryInterface(cmpTrigger.playerCivicCenter[cmpPlayer.GetPlayerID()], IID_Position);
     120            // This shouldn't happen if the player is still active
    111121            if (!cmpPosition || !cmpPosition.IsInWorld)
    112122                continue;
    113123
    114             // store the x and z coordinates in the command
     124            // Store the x and z coordinates in the command
    115125            let cmd = cmpPosition.GetPosition();
    116126            cmd.type = "attack-walk";
    117127            cmd.entities = entityType[origin];
    118128            cmd.queued = true;
    119129            cmd.targetClasses = undefined;
    120             // send the attack-walk command
     130            // Send the attack-walk command
    121131            ProcessCommand(0, cmd);
    122132        }
    123133    }
     
    130140    cmpTrigger.DoAfterDelay(nextTime, "StartAnEnemyWave", {}); // The next wave will come in 3 minutes
    131141};
    132142
     143/*
     144 * Try to mark inactive players by tracking the number of entities they own.
     145 * An unassigned Player will never produce a new entity.
     146 * This will stop running once all players have been marked.
     147 * So, when checking cmpTrigger.playerTrack[playerID].unassigned, you must always check if cmpTrigger.playerTrack[playerID] exists.
     148 *
     149 * For each player marked as Unassigned, less treasures will spawn.
     150 */
     151Trigger.prototype.MarkUnassignedPlayers = function(data)
     152{
     153    if (!data || !data.playerID)
     154    {
     155        warn("no information provided to player tracker");
     156        return;
     157    }
     158    if (!cmpTrigger.playerTrack[data.playerID])
     159        cmpTrigger.playerTrack[data.playerID] = {
     160            "eCount": data.eCount,
     161            "unassigned": false,
     162            "loop": 1
     163        };
     164    else
     165    {
     166        ++cmpTrigger.playerTrack[data.playerID].loop;
     167
     168        let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     169        let playerEntities = cmpRangeManager.GetEntitiesByPlayer(data.playerID);
     170        let eCount = playerEntities.length;
     171
     172        // The player must have produced something, so being for real. Exit the loop.
     173        if (eCount > cmpTrigger.playerTrack[data.playerID].eCount)
     174            return;
     175
     176        cmpTrigger.playerTrack[data.playerID].eCount = eCount;
     177
     178        if (cmpTrigger.playerTrack[data.playerID].loop > 5)
     179        {
     180            cmpTrigger.playerTrack[data.playerID].unassigned = true; // So long created nothing, must be placeholder
     181            return;
     182        }
     183    }
     184    cmpTrigger.DoAfterDelay(0.95 * 60 * 1000, "MarkUnassignedPlayers", { "playerID": data.playerID });
     185};
     186
     187/*
     188 * The main init function, called at the start
     189 */
    133190Trigger.prototype.InitGame = function()
    134191{
    135192    let numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
    136     // Find all of the civic centers, disable some structures
     193    // Find all of the civic centers and females, disable some structures, setup activity watch
    137194    for (let i = 1; i < numberOfPlayers; ++i)
    138195    {
     196        /*// At first, determine how many players are actually playing (humans)
     197        let cmpPlayer = TriggerHelper.GetPlayerComponent(i);
     198        warn(cmpPlayer.GetState());*/
     199
     200        // Now handle the other stuff
    139201        let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    140202        let playerEntities = cmpRangeManager.GetEntitiesByPlayer(i); // Get all of each player's entities
    141203
    142204        for (let entity of playerEntities)
     205        {
    143206            if (TriggerHelper.EntityHasClass(entity, "CivilCentre"))
    144207                cmpTrigger.playerCivicCenter[i] = entity;
     208            if (TriggerHelper.EntityHasClass(entity, "Female"))
     209                cmpTrigger.females[i] =  entity;
     210        }
     211        this.MarkUnassignedPlayers({ "eCount": playerEntities.length, "playerID": i });
    145212    }
    146213
    147214    // Fix alliances
     
    183250    let triggerPoints = cmpTrigger.GetTriggerPoints(point);
    184251    for (let point of triggerPoints)
    185252    {
    186         let template = treasures[Math.floor(Math.random() * treasures.length)]
    187         TriggerHelper.SpawnUnits(point, template, 1, 0);
     253        let template = g_treasures[Math.floor(Math.random() * g_treasures.length)];
     254        let cmpPosition = Engine.QueryInterface(point, IID_Position);
     255        let ent = Engine.AddEntity(template);
     256
     257        let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
     258        let cmpEntPosition = Engine.QueryInterface(ent, IID_Position);
     259
     260        if (cmpEntOwnership)
     261            cmpEntOwnership.SetOwner(0);
     262
     263        let xOffset = RandomInt(0,23);
     264        let zOffset = RandomInt(0,23);
     265
     266        if (Math.random() >= 0.8 )
     267        {
     268            xOffset += RandomInt(5,8);
     269            zOffset += RandomInt(5,8);
     270        }
     271
     272        if (Math.round(Math.random()) == 1 )
     273            xOffset = xOffset * -1;
     274
     275        if (Math.round(Math.random()) == 1 )
     276            zOffset = zOffset * -1;
     277
     278        cmpEntPosition.JumpTo(cmpPosition.GetPosition().x + xOffset, cmpPosition.GetPosition().z - zOffset);
    188279    }
    189     cmpTrigger.DoAfterDelay(4*60*1000, "PlaceTreasures", {}); //Place more treasures after 4 minutes
     280    let time = (3 + Math.random() * 10) * 60 * 1000; // Place more treasures after 2-4 minutes
     281    cmpTrigger.DoAfterDelay(time, "PlaceTreasures", {});
    190282};
    191283
     284/*
     285 * This function starts the timer for the waves
     286 */
    192287Trigger.prototype.InitializeEnemyWaves = function()
    193288{
    194289    let time = (5 + Math.round(Math.random() * 10)) * 60 * 1000;
     
    200295    cmpTrigger.DoAfterDelay(time, "StartAnEnemyWave", {});
    201296};
    202297
    203 Trigger.prototype.DefeatPlayerOnceCCIsDestroyed = function(data)
     298var TimeString = function(data)
    204299{
     300    if (!data.time)
     301    {
     302        warn("No time provided in TimeString");
     303        return "";
     304    }
     305    let displayTime = data.time/60000;
     306    let grammar = "";
     307    if (displayTime > 1)
     308        grammar = "minutes";
     309    else if (data.time/1000 < 60)
     310    {
     311        grammar = "seconds";
     312        displayTime = data.time/1000;
     313    }
     314    else if (displayTime == 1)
     315        grammar = "minute";
     316    else if (data.time/1000 == 1)
     317    {
     318        grammar = "second";
     319        displayTime = data.time/1000;
     320    }
     321    return displayTime + " " + grammar;
     322};
     323
     324/*
     325 * NotifyPlayers: Keep the active players up to date about whats happening. This is called from inside the other functions.
     326 * "data" mut be an object which contains string "situation", and any "argument" properties. "argument" is optional.
     327*/
     328Trigger.prototype.NotifyPlayers = function(data)
     329{
     330    if (!data || !data.situation)
     331    {
     332        error("Nothing to talk :-(");
     333        return;
     334    }
     335
     336    let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     337    let players = [];
     338    let numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
     339
     340    // Only deliver messages to active players (not e.g. dead)
     341    for (let i = 1; i < numberOfPlayers; ++i)
     342        if (QueryPlayerIDInterface(i).GetState() == "active")
     343            players.push(i);
     344
     345    switch (data.situation)
     346    {
     347    case "beginGame":
     348        cmpGUIInterface.PushNotification({
     349            "players": players,
     350            "message": markForTranslation("Welcome to Survival of the Fittest"),
     351            "translateMessage": true
     352        });
     353        cmpGUIInterface.PushNotification({
     354            "players": players,
     355            "message": markForTranslation("Collect treasures with your woman to prepare for the enemies."),
     356            "translateMessage": true
     357        });
     358        cmpGUIInterface.PushNotification({
     359            "players": players,
     360            "message": markForTranslation("The first wave will start in " + TimeString({ "time": data.argument }) + "!"),
     361            "translateMessage": true
     362        });
     363        return;
     364
     365    case "beforeStart":
     366        let timeRemaining = data.argument;
     367        if (!timeRemaining)
     368            return;
     369        cmpGUIInterface.PushNotification({
     370            "players": players,
     371            "message": markForTranslation("The first wave will start in " + TimeString({ "time": timeRemaining }) +", be prepared!"),
     372            "translateMessage": true
     373        });
     374        return;
     375
     376    case "treasuresPlaced":
     377        cmpGUIInterface.PushNotification({
     378            "players": players,
     379            "message": markForTranslation("New treasures have been placed!"),
     380            "translateMessage": true
     381        });
     382        return;
     383
     384    case "waveIncoming":
     385        cmpGUIInterface.PushNotification({
     386            "players": players,
     387            "message": markForTranslation("Rioting soldiers are attacking you! Defend your City!"),
     388            "translateMessage": true
     389        });
     390        return;
     391
     392    case "notifyRespawn":
     393        if (!data.argument.time || !data.argument.playerIDs)
     394        {
     395            warn("NotifyPlayers doesnt know whom to address");
     396            return;
     397        }
     398        cmpGUIInterface.PushNotification({
     399            "players": data.argument.playerIDs,
     400            "message": markForTranslation("Your treasure seeker has died! You will get a new one in " + TimeString({ "time": data.argument.time }) + "."),
     401            "translateMessage": true
     402        });
     403        return;
     404
     405    case "resourceLoopStarted":
     406        cmpGUIInterface.PushNotification({
     407            "players": players,
     408            "message": markForTranslation("From now on, you will receive some resources from time to time"),
     409            "translateMessage": true
     410        });
     411        return;
     412
     413    default:
     414        warn("NotifyPlayers is panicking, some unknown gossip came in");
     415        return;
     416    }
     417};
     418
     419Trigger.prototype.SpawnNewWoman = function(data)
     420{
     421    if (!data.playerId || data.playerId < 1)
     422    {
     423        warn("No women for gaia");
     424        return;
     425    }
     426
     427    if (!cmpTrigger.females[data.playerId])
     428    {
     429        warn("This is not a treasure seeker: Player " + data.playerId);
     430        return;
     431    }
     432
     433    let spawnPoints =  ["C","D","E","F"];
     434    if (TriggerHelper.GetOwner(cmpTrigger.females[data.playerId]) != -1)
     435    {
     436        warn("Female still alive, exiting");
     437        return;
     438    }
     439
     440    let triggerPoints = cmpTrigger.GetTriggerPoints(spawnPoints[data.playerId-1]);
     441    let newWoman = TriggerHelper.SpawnUnits(triggerPoints[0], data.template, 1, data.playerId);
     442    cmpTrigger.females[data.playerId] = newWoman[0];
     443};
     444
     445/*
     446 * HandleSpecialEntities occurs when a unit dies, and checks for anything to be done in case its a treasure woman/civic center
     447 */
     448Trigger.prototype.HandleSpecialEntities = function(data)
     449{
    205450    // Defeat a player that has lost his civic center
    206     if (data.entity == cmpTrigger.playerCivicCenter[data.from])
     451    if (data.entity == cmpTrigger.playerCivicCenter[data.from] && data.to == -1)
    207452    {
    208453        TriggerHelper.DefeatPlayer(data.from);
    209454
     
    222467        if (numPlayersStanding == 1)
    223468            TriggerHelper.SetPlayerWon(lastPlayerStanding);
    224469    }
     470
     471    if (cmpTrigger.females[data.from] && data.from >= 1 && data.entity == cmpTrigger.females[data.from] && data.to == -1)
     472    {
     473        let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     474        let template = cmpTemplateManager.GetCurrentTemplateName(data.entity);
     475        let time = 15 * 1000; // Time after which women respawn
     476
     477        cmpTrigger.DoAfterDelay(time, "SpawnNewWoman", {
     478            "playerId": data.from,
     479            "template": template
     480        });
     481
     482        this.NotifyPlayers({
     483            "situation": "notifyRespawn",
     484            "argument": {
     485                "time": time,
     486                "playerIDs": [data.from]
     487            }
     488        });
     489    }
    225490};
    226491
     492/*
     493 * This function is constantly Looping and provides resources to all players.
     494 * It starts after resourceLoopOffset
     495 */
     496Trigger.prototype.ResourceLoop = function(data)
     497{
     498    ++g_resourceLoopCount;
     499
     500    if (g_resourceLoopCount == 1)
     501        this.NotifyPlayers({ "situation": "resourceLoopStarted" });
     502
     503    let numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
     504    for (let i = 1; i < numberOfPlayers; ++i)
     505    {
     506        let cmpPlayer = QueryPlayerIDInterface(i);
     507        cmpPlayer.AddResources({
     508            "wood": 100,
     509            "stone": 100,
     510            "metal": 100,
     511            "food": 100
     512        });
     513    }
     514    cmpTrigger.DoAfterDelay(g_resourceLoopTime * 60 * 1000, "ResourceLoop" , {});
     515};
     516
    227517var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
    228518cmpTrigger.playerCivicCenter = {};
     519cmpTrigger.females = {};
     520cmpTrigger.playerTrack = {};
     521
    229522cmpTrigger.DoAfterDelay(0, "InitGame", {});
    230523cmpTrigger.DoAfterDelay(1000, "InitializeEnemyWaves", {});
     524cmpTrigger.DoAfterDelay(g_resourceLoopOffset * 60 * 1000, "ResourceLoop" , {});
    231525
    232 cmpTrigger.RegisterTrigger("OnOwnershipChanged", "DefeatPlayerOnceCCIsDestroyed", { "enabled": true });
     526cmpTrigger.RegisterTrigger("OnOwnershipChanged", "HandleSpecialEntities", { "enabled": true });