Index: binaries/data/mods/public/globalscripts/Technologies.js
===================================================================
--- binaries/data/mods/public/globalscripts/Technologies.js
+++ binaries/data/mods/public/globalscripts/Technologies.js
@@ -20,27 +20,24 @@
{
let modifications = currentTechModifications[propertyName] || [];
- let multiply = 1;
- let add = 0;
-
+ let value = propertyValue;
for (let modification of modifications)
{
if (!DoesModificationApply(modification, classes))
continue;
if (modification.replace !== undefined)
- return modification.replace;
- if (modification.multiply)
- multiply *= modification.multiply;
+ value = modification.replace;
+ else if (typeof value != "number" && (modification.multiply || modification.add))
+ warn("GetTechModifiedProperty: modifying a non-numeric (modifying " + propertyName + ") by adding or multiplying it is forbidden." + uneval(modification));
+ else if (modification.multiply)
+ value *= modification.multiply;
else if (modification.add)
- add += modification.add;
+ value += modification.add;
else
warn("GetTechModifiedProperty: modification format not recognised (modifying " + propertyName + "): " + uneval(modification));
}
- // Note, some components pass non-numeric values (for which only the "replace" modification makes sense)
- if (typeof propertyValue == "number")
- return propertyValue * multiply + add;
- return propertyValue;
+ return value;
}
/**
Index: binaries/data/mods/public/maps/scenarios/Roll The Dice Abilities.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/maps/scenarios/Roll The Dice Abilities.js
@@ -0,0 +1,40 @@
+// Welcome to the wonderful world of Javascript.
+// we are going to add a custom ability for this script.
+// The conditions will be RTD-specific.
+// The effect wil be calling a trigger function.
+
+function TriggerAction()
+{
+ this.name = "TriggerAction";
+
+ this.schema =
+ "" +
+ "" +
+ "";
+}
+
+TriggerAction.prototype.SetupData = function(entity, templateData)
+{
+ return templateData;
+}
+
+TriggerAction.prototype.OnFire = function(entity, templateData, commandData)
+{
+ let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+ if (!cmpTrigger[templateData.TriggerAction.Trigger])
+ return;
+ cmpTrigger[templateData.TriggerAction.Trigger](TriggerHelper.GetOwner(entity), entity);
+}
+
+TriggerAction.prototype.IsFinished = function(entity, templateData, commandData)
+{
+ return true;
+}
+
+TriggerAction.prototype.Validate = function(entity, templateData, sendMessages)
+{
+ return true;
+}
+
+var TriggerAction = new TriggerAction();
+AbilityEffects.RegisterEffect(TriggerAction);
Index: binaries/data/mods/public/maps/scenarios/Roll The Dice.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/maps/scenarios/Roll The Dice.js
@@ -0,0 +1,621 @@
+// Roll The Dice trigger script.
+// This is basically a port from Age of Empires'2 Roll The Dice map.
+
+////////////////////
+//// Generic State
+// some constants about the map
+const MAP_SIZE = 64*4 + 2;
+const INNERLAKE = 7*4;
+
+// multiply resources given by this amount, for rounding convenience.
+const RESS_MULTIPLIER = 10;
+// map RTD resources to actual resources in an agnostic manner.
+// All we need is a sufficient number of resources, but it doesn't really matter which.
+var RESOURCES = {};
+{
+ let ress = ["kills", "attack"];
+ [0, 1].forEach(i => RESOURCES[ress[i]] = Resources.GetCodes()[i]);
+}
+// helper function
+var RTD_AddResource = function(player, ress, amount)
+{
+ let cmpPlayer = QueryPlayerIDInterface(player);
+ cmpPlayer.AddResource(RESOURCES[ress], amount);
+}
+
+// helper for messages
+var BroadCastMessage = function(message, players)
+{
+ if (!players)
+ {
+ let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ players = cmpPlayerManager.playerEntities.map(x => Engine.QueryInterface(x, IID_Player).GetPlayerID());
+ }
+ let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGuiInterface.PushNotification({ "type": "text", "players": players, "message": message });
+}
+
+////////////////////
+//// Hack simulation
+// disable capturing because that's annoying.
+Capturable.prototype.CanCapture = function() { return false };
+// disable producing
+ProductionQueue.prototype.GetEntitiesList = function() { return []; }
+ProductionQueue.prototype.GetTechnologiesList = function() { return []; }
+Builder.prototype.GetEntitiesList = function() { return []; }
+// disable auras
+Auras.prototype.CanApply = function() { return false; }
+// disable loot
+Loot.prototype.GetResources = function() { return {}; }
+// can't disable repairing entirely but this'll do.
+Repairable.prototype.GetRepairRate = function() { return 0; }
+// warn player
+Repairable.prototype.Repair = function(builderEnt, rate)
+{
+ BroadCastMessage("Repairing is disabled in Roll The Dice", [TriggerHelper.GetOwner(builderEnt)]);
+}
+
+// Instead of idle units return a currently idle hero
+GuiInterface.prototype.FindIdleUnits = function(player)
+{
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let entities = cmpRangeManager.GetEntitiesByPlayer(player);
+ for (let ent of entities)
+ {
+ if (ent == g_MonumentByPlayer[player])
+ continue;
+ let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ if (cmpUnitAI && cmpUnitAI.IsIdle())
+ return [ent];
+ }
+ return [];
+}
+GuiInterface.prototype.HasIdleUnits = function(player)
+{
+ return this.FindIdleUnits(player).length;
+}
+////////////////////
+//// Gameplay state
+
+// store a tuple for each entity starting position to avoid recomputing each time.
+var g_EntityStartPositions = [];
+
+// player ID: monument entity ID
+var g_MonumentByPlayer = {};
+var g_MonumentsID = [];
+
+// Player ID : damage count.
+var RTD_DamageCount = {'0': 0};
+// player ID : how much extra damage they bought.
+var RTD_AttackBought = {'0': 0};
+// player ID : how much extra HP they bought.
+var RTD_HPBought = {'0': 0};
+
+var RTD_HasPanic = {'0': false};
+// RULES OF ROLL THE DICE
+// As adapted from Age of Empire's 2 custom scenario.
+
+// 1. At any time, you have a Monument and one (or potentially more) unit on the map.
+// 2. If your Monument is destroyed, you lose.
+// 3. A side-goal is to kill enemy units, thus accruing kills. Actually count damage dealt as a % of enemy HP, to be fair and avoid "stealing" kills.
+// As you kill units, you will be able to upgrade your Monument or your units to make the game easier.
+
+
+// Mechanics to add:
+// A death streak without kill should give bonuses
+// A kill streak should also give bonuses (but warn players so than can gang up on you).
+// Random "drops" of stuff.
+// Terrain changing
+// I have this idea for water rising and sharks attacking units randomly.
+
+Trigger.prototype.InitRTD = function()
+{
+ // Fetch player information.
+ let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager)
+
+ let players = [];
+
+ for (let x = 1; x < cmpPlayerManager.GetNumPlayers(); ++x)
+ {
+ let id = cmpPlayerManager.GetPlayerByID(x);
+ let cmpPlayer = Engine.QueryInterface(id, IID_Player);
+
+ g_EntityStartPositions.push([MAP_SIZE/2 + 3*INNERLAKE * Math.cos(x/cmpPlayerManager.GetNumPlayers()*6.283), MAP_SIZE/2 + 3*INNERLAKE * Math.sin(x/cmpPlayerManager.GetNumPlayers()*6.283)]);
+
+ players.push(x);
+ }
+
+ // create a monument for each player.
+ let count = players.length;
+ for (let id of players)
+ {
+ let monument = Engine.AddEntity("structures/athen_outpost");
+ g_MonumentByPlayer[id] = monument;
+ g_MonumentsID.push(monument);
+
+
+ let cmpOwner = Engine.QueryInterface(monument, IID_Ownership);
+ cmpOwner.SetOwner(+id);
+
+ let cmpModManager = QueryOwnerInterface(monument, IID_ModifiersManager);
+ cmpModManager.AddGlobalModifiers("triggers_RTD_Struct", 100, {
+ "Capturable/RegenRate": {"affects": ["Structure"], "replace": 100},
+ "GarrisonHolder/Max": {"affects": ["Structure"], "replace": 0},
+ "Vision/Range": {"affects": ["Structure"], "replace": 50},
+ "Health/Max": {"affects": ["Structure"], "replace": 10000},
+ });
+ cmpModManager.AddGlobalModifiers("triggers_RTD_Unit", 100, {
+ "Looter/Resource/food": {"affects": ["Unit"], "replace": 0},
+ "Looter/Resource/wood": {"affects": ["Unit"], "replace": 0},
+ "Looter/Resource/stone": {"affects": ["Unit"], "replace": 0},
+ "Looter/Resource/metal": {"affects": ["Unit"], "replace": 0},
+ "Vision/Range": {"affects": ["Unit"], "replace": 20},
+ "Promotion/RequiredXp": {"affects": ["Unit"], "replace": 9999999},
+ // bump attack levels across the board to not make this atrociously slow.
+ "Attack/Ranged/Hack": {"affects": ["Unit"], "add": 20},
+ "Attack/Ranged/Pierce": {"affects": ["Unit"], "add": 20},
+ "Attack/Melee/Hack": {"affects": ["Unit"], "add": 30},
+ "Attack/Melee/Pierce": {"affects": ["Unit"], "add": 30},
+ "Attack/Ranged/Spread": {"affects": ["Unit"], "add": -2}
+ });
+
+ // Share all vision.
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ cmpRangeManager.SetSharedLos(+id, players)
+
+ let cmpPosition = Engine.QueryInterface(monument, IID_Position);
+ cmpPosition.JumpTo(MAP_SIZE/2 + INNERLAKE * Math.cos(players.indexOf(id)/count*6.283), MAP_SIZE/2 + INNERLAKE * Math.sin(players.indexOf(id)/count*6.283))
+ cmpPosition.SetYRotation(1.5707 + players.indexOf(id)/count*6.283)
+
+ // Init rules
+ RTD_DamageCount[id] = 0;
+
+ RTD_AttackBought[id] = 0;
+ RTD_HPBought[id] = 0;
+ RTD_HasPanic[id] = true;
+
+ let cmpPlayer = QueryOwnerInterface(monument, IID_Player);
+ let resources = {};
+ // set to 0.01 otherwise we have rounding issues in the GUI.
+ Resources.GetCodes().forEach(ress => resources[ress] = 0.01);
+ cmpPlayer.SetResourceCounts(resources);
+
+ // Create the starting entities for each player.
+ this.CreateNewEntity(id, true);
+ }
+}
+
+Trigger.prototype.PickRandomEntity = function()
+{
+ // list of possible units.
+ // TODO: curate this a bit.
+ let listByTier = [
+ // Tier 0 : special, weird, and useless units.
+ [
+ "units/brit_war_dog_e",
+ "units/spart_support_female_citizen",
+ "units/mace_mechanical_siege_ram",
+ "units/pers_mechanical_siege_ram",
+ "units/rome_mechanical_siege_ballista_packed",
+ "units/sele_mechanical_siege_lithobolos_packed",
+ "units/sele_mechanical_siege_tower",
+ "units/spart_mechanical_siege_oxybeles_packed",
+ "units/spart_mechanical_siege_ram",
+ ],
+ // Tier 1 : regular citizen soldiers. Includes cavalry and infantry
+ [
+ "units/athen_infantry_javelinist_e",
+ "units/athen_infantry_marine_archer_e",
+ "units/athen_infantry_slinger_e",
+ "units/athen_infantry_spearman_e",
+ "units/brit_infantry_javelinist_e",
+ "units/brit_infantry_slinger_e",
+ "units/brit_infantry_spearman_e",
+ "units/cart_infantry_archer_e",
+ "units/cart_infantry_javelinist_iber_e",
+ "units/cart_infantry_slinger_iber_e",
+ "units/cart_infantry_spearman_e",
+ "units/cart_infantry_swordsman_gaul_e",
+ "units/cart_infantry_swordsman_ital_e",
+ "units/gaul_infantry_javelinist_e",
+ "units/gaul_infantry_slinger_e",
+ "units/gaul_infantry_spearman_e",
+ "units/iber_infantry_javelinist_e",
+ "units/iber_infantry_slinger_e",
+ "units/iber_infantry_spearman_e",
+ "units/iber_infantry_swordsman_e",
+ "units/mace_infantry_archer_e",
+ "units/mace_infantry_javelinist_e",
+ "units/mace_infantry_pikeman_e",
+ "units/mace_infantry_slinger_e",
+ "units/maur_infantry_archer_e",
+ "units/maur_infantry_spearman_e",
+ "units/maur_infantry_swordsman_e",
+ "units/pers_infantry_archer_e",
+ "units/pers_infantry_javelinist_e",
+ "units/pers_infantry_spearman_e",
+ "units/ptol_infantry_archer_e",
+ "units/ptol_infantry_archer_nubian",
+ "units/ptol_infantry_javelinist_e",
+ "units/ptol_infantry_pikeman_e",
+ "units/ptol_infantry_slinger_e",
+ "units/ptol_infantry_spearman_merc_e",
+ "units/ptol_infantry_swordsman_merc_e",
+ "units/rome_infantry_javelinist_e",
+ "units/rome_infantry_spearman_e",
+ "units/rome_infantry_swordsman_e",
+ "units/sele_infantry_archer_merc_e",
+ "units/sele_infantry_javelinist_e",
+ "units/sele_infantry_pikeman_e",
+ "units/sele_infantry_spearman_e",
+ "units/sele_infantry_swordsman_merc_e",
+ "units/spart_champion_infantry_sword",
+ "units/spart_infantry_javelinist_e",
+ "units/spart_infantry_spearman_e",
+ "units/athen_cavalry_javelinist_e",
+ "units/athen_cavalry_swordsman_e",
+ "units/brit_cavalry_javelinist_e",
+ "units/brit_cavalry_swordsman_e",
+ "units/cart_cavalry_javelinist_e",
+ "units/cart_cavalry_spearman_ital_e",
+ "units/cart_cavalry_swordsman_gaul_e",
+ "units/cart_cavalry_swordsman_iber_e",
+ "units/gaul_cavalry_javelinist_e",
+ "units/gaul_cavalry_swordsman_e",
+ "units/iber_cavalry_javelinist_e",
+ "units/iber_cavalry_spearman_e",
+ "units/mace_cavalry_javelinist_e",
+ "units/mace_cavalry_spearman_e",
+ "units/maur_cavalry_javelinist_e",
+ "units/maur_cavalry_swordsman_e",
+ "units/maur_elephant_archer_e",
+ "units/pers_cavalry_archer_e",
+ "units/pers_cavalry_javelinist_e",
+ "units/pers_cavalry_spearman_e",
+ "units/pers_cavalry_swordsman_e",
+ "units/ptol_cavalry_archer_e",
+ "units/ptol_cavalry_javelinist_merc_e",
+ "units/ptol_cavalry_spearman_merc_e",
+ "units/rome_cavalry_javelinist_e",
+ "units/rome_cavalry_spearman_e",
+ "units/sele_cavalry_archer_e",
+ "units/sele_cavalry_javelinist_e",
+ "units/sele_cavalry_spearman_merc_e",
+ "units/spart_cavalry_javelinist_e",
+ "units/spart_cavalry_spearman_e",
+ ],
+ // Tier 2 : champions and such
+ [
+ "units/athen_champion_infantry",
+ "units/athen_champion_marine",
+ "units/athen_champion_ranged",
+ "units/brit_champion_cavalry",
+ "units/brit_champion_infantry",
+ "units/cart_champion_cavalry",
+ "units/cart_champion_elephant",
+ "units/cart_champion_infantry",
+ "units/cart_champion_pikeman",
+ "units/gaul_champion_cavalry",
+ "units/gaul_champion_fanatic",
+ "units/gaul_champion_infantry",
+ "units/iber_champion_cavalry",
+ "units/iber_champion_infantry",
+ "units/mace_champion_cavalry",
+ "units/mace_champion_infantry_a",
+ "units/maur_champion_chariot",
+ "units/maur_champion_elephant",
+ "units/maur_champion_infantry",
+ "units/maur_champion_maiden_archer",
+ "units/maur_champion_maiden",
+ "units/merc_black_cloak",
+ "units/merc_thorakites",
+ "units/merc_thureophoros",
+ "units/pers_champion_cavalry_archer",
+ "units/pers_champion_cavalry",
+ "units/pers_champion_elephant",
+ "units/pers_champion_infantry",
+ "units/pers_kardakes_hoplite",
+ "units/pers_kardakes_skirmisher",
+ "units/ptol_champion_cavalry",
+ "units/ptol_champion_elephant",
+ "units/ptol_champion_infantry_pikeman",
+ "units/rome_champion_cavalry",
+ "units/rome_champion_infantry",
+ "units/rome_legionnaire_marian",
+ "units/samnite_skirmisher",
+ "units/samnite_spearman",
+ "units/samnite_swordsman",
+ "units/sele_champion_cavalry",
+ "units/sele_champion_chariot",
+ "units/sele_champion_elephant",
+ "units/sele_champion_infantry_pikeman",
+ "units/sele_champion_infantry_swordsman",
+ "units/spart_champion_infantry_pike",
+ "units/spart_champion_infantry_spear",
+ "units/thebes_sacred_band_hoplitai",
+ "units/thespian_melanochitones",
+ ],
+ // Tier 3 : Heroes
+ [
+ "units/athen_hero_iphicrates",
+ "units/athen_hero_pericles",
+ "units/athen_hero_themistocles",
+ "units/brit_hero_boudicca_sword",
+ "units/brit_hero_boudicca",
+ "units/brit_hero_caratacos",
+ "units/brit_hero_cunobelin_infantry",
+ "units/brit_hero_cunobelin",
+ "units/cart_hero_hamilcar",
+ "units/cart_hero_hannibal",
+ "units/cart_hero_maharbal",
+ "units/gaul_hero_brennus",
+ "units/gaul_hero_britomartus",
+ "units/gaul_hero_vercingetorix",
+ "units/iber_hero_caros",
+ "units/iber_hero_indibil",
+ "units/iber_hero_viriato",
+ "units/mace_hero_alexander",
+ "units/mace_hero_craterus",
+ "units/mace_hero_demetrius",
+ "units/mace_hero_philip_pike",
+ "units/mace_hero_philip",
+ "units/mace_hero_pyrrhus",
+ "units/maur_hero_ashoka",
+ "units/maur_hero_maurya",
+ "units/pers_hero_cyrus",
+ "units/pers_hero_darius",
+ "units/pers_hero_xerxes_chariot",
+ "units/pers_hero_xerxes",
+ "units/ptol_hero_cleopatra",
+ "units/ptol_hero_ptolemy_I",
+ "units/ptol_hero_ptolemy_IV",
+ "units/rome_hero_marcellus",
+ "units/rome_hero_maximus",
+ "units/rome_hero_scipio",
+ "units/sele_hero_antiochus_great",
+ "units/sele_hero_antiochus_righteous",
+ "units/sele_hero_seleucus_victor",
+ "units/spart_hero_agis",
+ "units/spart_hero_brasidas",
+ "units/spart_hero_leonidas",
+ ]
+ ];
+
+ let probabilities = [10,60,90,100];
+
+ let random = randIntExclusive(0,100);
+ let tier = probabilities.filter(x => random < x).length - 1;
+
+ let item = pickRandom(listByTier[tier]);
+
+ return item;
+}
+
+Trigger.prototype.CreateNewEntity = function(player, startPos)
+{
+ // TODO: validate current entity is killed?
+
+ let entity = Engine.AddEntity(this.PickRandomEntity());
+
+ BroadCastMessage("You have rolled " + Engine.QueryInterface(entity, IID_Identity).GetGenericName() + ". Good Luck!", [+player])
+
+ let cmpOwner = Engine.QueryInterface(entity, IID_Ownership);
+ cmpOwner.SetOwner(+player);
+
+ let cmpPosition = Engine.QueryInterface(entity, IID_Position);
+ let pos = startPos ? g_EntityStartPositions[player-1] : pickRandom(g_EntityStartPositions);
+ cmpPosition.JumpTo(pos[0], pos[1]);
+
+ let cmpHealth = Engine.QueryInterface(entity, IID_Health);
+ cmpHealth.SetUndeletable(true);
+
+ // reset rules
+ RTD_AttackBought[player] = 0;
+ let cmpPlayer = QueryPlayerIDInterface(player);
+ cmpPlayer.resourceCount[RESOURCES["attack"]] = 0;
+
+ let cmpAbilities = Engine.QueryInterface(entity, IID_Abilities);
+
+ {
+ let ability = {
+ "Name": "Re-roll your unit",
+ "Tooltip": "This allows you to kill your current unit and re-roll it.",
+ "Cost": {},
+ "Icon": "icons/kill.png",
+ "TriggerAction": {"Trigger": "RTD_ReRoll"}
+ };
+ ability.Cost[RESOURCES["kills"]] = 2*RESS_MULTIPLIER;
+ cmpAbilities.AddAbility("Reroll", ability);
+ }
+ // for now just do it like this it'll work OK.
+ if (RTD_HasPanic[player])
+ {
+ let ability = {
+ "Name": "Panic",
+ "Tooltip": "This kills every unit on the map. Can only be used once.",
+ "Icon": "portraits/technologies/skull_swords.png",
+ "TriggerAction": {"Trigger": "RTD_Panic"}
+ };
+ cmpAbilities.AddAbility("Panic", ability);
+ }
+ {
+ let ability = {
+ "Name": "Increase Attack",
+ "Tooltip": "This will increase this unit's attack by +" + RTD_ATTACK_BUFF + " Hack/Pierce, stacking. You will lose this when the unit dies.",
+ "Cost": {},
+ "Icon": "portraits/technologies/sword.png",
+ "TriggerAction": {"Trigger": "RTD_PurchaseAttack"}
+ };
+ ability.Cost[RESOURCES["kills"]] = 2*RESS_MULTIPLIER;
+ cmpAbilities.AddAbility("PurchaseAttack", ability);
+ }
+ {
+ let ability = {
+ "Name": "Boost Outpost HP",
+ "Tooltip": "This will give +1000 HP to your outpost HP, including maximum HP.",
+ "Cost": {},
+ "Icon": "portraits/structures/outpost.png",
+ "TriggerAction": {"Trigger": "RTD_PurchaseHP"}
+ };
+ ability.Cost[RESOURCES["kills"]] = 10*RESS_MULTIPLIER;
+ cmpAbilities.AddAbility("PurchaseHP", ability);
+ }
+ // This ability lets you trade 5K HP for a new unit.
+ // If you're close to 1 HP, it might be worth activating anyways.
+ {
+ let ability = {
+ "Name": "Living on the Edge",
+ "Tooltip": "Sacrifice 5000 HP on your Monument in exchange for an additional unit. If you have less than 5000HP left, this will leave you with 1 HP.",
+ "Cost": {},
+ "Icon": "icons/die.png",
+ "TriggerAction": {"Trigger": "RTD_AdditionalUnit"}
+ };
+ ability.Cost[RESOURCES["kills"]] = 5*RESS_MULTIPLIER;
+ cmpAbilities.AddAbility("AdditionalUnit", ability);
+ }
+}
+
+Trigger.prototype.ChangeKillCount = function(player, amount)
+{
+ RTD_DamageCount[player] += +amount;
+ RTD_AddResource(player, "kills", amount*RESS_MULTIPLIER);
+}
+
+// Abilities triggers
+
+Trigger.prototype.RTD_ReRoll = function(player, entity)
+{
+ let cmpHealth = Engine.QueryInterface(entity, IID_Health);
+ cmpHealth.Kill();
+ this.CreateNewEntity(player);
+}
+
+Trigger.prototype.RTD_Panic = function(player)
+{
+ RTD_HasPanic[player] = false;
+
+ BroadCastMessage("Player " + player + " has used the PANIC button!");
+ // TODO: sound
+
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let entities = cmpRangeManager.GetNonGaiaEntities();
+ for (let ent of entities)
+ {
+ let player = TriggerHelper.GetOwner(ent);
+ if (ent == g_MonumentByPlayer[player])
+ continue;
+ let cmpHealth = Engine.QueryInterface(ent, IID_Health);
+ cmpHealth.Kill();
+ this.CreateNewEntity(player);
+ }
+}
+
+Trigger.prototype.RTD_AdditionalUnit = function(player)
+{
+ BroadCastMessage("Player " + player + " has traded HP for another unit!");
+ // TODO: sound
+
+ let cmpHealth = Engine.QueryInterface(g_MonumentByPlayer[player], IID_Health);
+ cmpHealth.Reduce(cmpHealth.GetHitpoints() > 5000 ? 5000 : cmpHealth.GetHitpoints() - 1);
+ this.CreateNewEntity(player);
+}
+
+const RTD_ATTACK_BUFF = 10;
+Trigger.prototype.RTD_PurchaseAttack = function(player, entity)
+{
+ RTD_AttackBought[player] += RTD_ATTACK_BUFF;
+ RTD_AddResource(player, "attack", RTD_ATTACK_BUFF);
+
+ let cmpModManager = QueryPlayerIDInterface(player, IID_ModifiersManager);
+ cmpModManager.RemoveLocalModifiers(entity, "RTD_PurchaseAttack");
+ cmpModManager.AddLocalModifiers(entity, "RTD_PurchaseAttack", 1000, {
+ "Attack/Ranged/Hack": {"affects": ["Unit"], "add": RTD_AttackBought[player]},
+ "Attack/Ranged/Pierce": {"affects": ["Unit"], "add": RTD_AttackBought[player]},
+ "Attack/Melee/Hack": {"affects": ["Unit"], "add": RTD_AttackBought[player]},
+ "Attack/Melee/Pierce": {"affects": ["Unit"], "add": RTD_AttackBought[player]}
+ });
+}
+
+Trigger.prototype.RTD_PurchaseHP = function(player)
+{
+ let cmpHealth = Engine.QueryInterface(g_MonumentByPlayer[player], IID_Health);
+ let health = cmpHealth.GetHitpoints();
+
+ RTD_HPBought[player] += 1000;
+ let cmpModManager = QueryPlayerIDInterface(player, IID_ModifiersManager);
+ cmpModManager.RemoveGlobalModifiers("RTD_PurchaseHP");
+ cmpModManager.AddGlobalModifiers("RTD_PurchaseHP", 1000, {
+ "Health/Max": {"affects": ["Structure"], "add": RTD_HPBought[player]},
+ });
+ let newhealth = cmpHealth.GetHitpoints();
+ cmpHealth.increase(Math.max(0, 1000 - (newhealth - health)));
+}
+
+/// Pseudo-AI, pick a random entity to attack if we're idle.
+Trigger.prototype.PseudoAITick = function()
+{
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let entities = cmpRangeManager.GetNonGaiaEntities();
+ for (let ent of entities)
+ {
+ let enemy = pickRandom(entities);
+ if (TriggerHelper.GetOwner(enemy) == TriggerHelper.GetOwner(ent))
+ continue; // too bad
+ if (!QueryOwnerInterface(ent, IID_Player).IsAI())
+ continue;
+ let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
+ if (!cmpUnitAI || !cmpUnitAI.IsIdle())
+ continue;
+ cmpUnitAI.Attack(enemy, false);
+ }
+}
+
+/// EVENTS
+
+Trigger.prototype.OnOwnershipChanged = function(msg)
+{
+ if (msg.to != -1)
+ return;
+ if (msg.from > 0 && g_MonumentByPlayer[msg.from] == msg.entity)
+ {
+ TriggerHelper.DefeatPlayer(msg.from);
+ // check if any player has won.
+ let standing = g_MonumentsID.map(id => Engine.QueryInterface(id, IID_Health) != undefined).filter(x => x);
+ if (standing.length == 1)
+ TriggerHelper.SetPlayerWon(TriggerHelper.GetOwner(standing[0]));
+ }
+}
+
+Trigger.prototype.OnAttacked = function(msg)
+{
+ if (g_MonumentsID.some(id => msg.target == id))
+ return;
+
+ let cmpHealth = Engine.QueryInterface(msg.target, IID_Health);
+ // on principle this shouldn't happen
+ if (!cmpHealth)
+ return;
+ let damage = msg.damage / cmpHealth.GetMaxHitpoints();
+ // damage caps at 1.
+ this.ChangeKillCount(msg.attackerOwner, damage);
+
+ if (cmpHealth.GetHitpoints() == 0 && TriggerHelper.GetOwner(msg.target) != 0)
+ {
+ // can't use ownership changed because packing and unpacking would give free units.
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "CreateNewEntity", 1000, TriggerHelper.GetOwner(msg.target));
+ }
+}
+
+{
+ let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+
+ cmpTrigger.RegisterTrigger("OnInitGame", "InitRTD", { "enabled": true });
+ cmpTrigger.RegisterTrigger("OnOwnershipChanged", "OnOwnershipChanged", { "enabled": true });
+ cmpTrigger.RegisterTrigger("OnAttacked", "OnAttacked", { "enabled": true });
+
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.SetInterval(SYSTEM_ENTITY, IID_Trigger, "PseudoAITick", 1000, 2000, {});
+
+ BroadCastMessage("Welcome to Roll The Dice. Read the map description for a brief overview of the rules.", [1,2,3,4,5,6,7,8])
+}
\ No newline at end of file
Index: binaries/data/mods/public/maps/scenarios/Roll The Dice.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/maps/scenarios/Roll The Dice.xml
@@ -0,0 +1,1384 @@
+
+
+
+
+ sunny 1
+
+
+
+
+
+
+ 0
+ 0.5
+
+
+
+
+ lake
+
+
+ 79
+ 1.23047
+ 0.493164
+ 0
+
+
+
+ 0
+ 1
+ 0.99
+ 0.1999
+ default
+
+
+
+
+
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|flora/trees/palm_cretan_date.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/shrub_fanpalm.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/decal_palms_patch.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/grass_temp_field_dry.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_a.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_b.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_b.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_b.xml
+
+
+
+
+
+ actor|props/flora/reeds_pond_lush_b.xml
+
+
+
+
+
+ actor|props/flora/shrub_tropic_plant_flower.xml
+
+
+
+
+
+ actor|props/flora/shrub_tropic_plant_flower.xml
+
+
+
+
+
+ actor|props/flora/shrub_tropic_plant_flower.xml
+
+
+
+
+
+ actor|props/flora/shrub_tropic_plant_flower.xml
+
+
+
+
+
+ actor|props/flora/shrub_tropic_plant_flower.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/flower_bright.xml
+
+
+
+
+
+ actor|props/flora/ferns.xml
+
+
+
+
+
+ actor|props/flora/ferns.xml
+
+
+
+
+
+ actor|props/flora/ferns.xml
+
+
+
+
+
+ actor|props/flora/ferns.xml
+
+
+
+
+
+ actor|props/flora/ferns.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|props/flora/water_lillies.xml
+
+
+
+
+
+ actor|geology/shoreline_small.xml
+
+
+
+
+
+ actor|geology/shoreline_small.xml
+
+
+
+
+
+ actor|geology/shoreline_small.xml
+
+
+
+
+
+ actor|geology/shoreline_small.xml
+
+
+
+
+
+ actor|geology/shoreline_small.xml
+
+
+
+
+
+ actor|geology/shoreline_small.xml
+
+
+
+
+
+ actor|geology/shoreline_small.xml
+
+
+
+
+
+ actor|geology/shoreline_large.xml
+
+
+
+
+
+ actor|geology/shoreline_large.xml
+
+
+
+
+
+ actor|geology/shoreline_large.xml
+
+
+
+
+
+ actor|geology/shoreline_large.xml
+
+
+
+
+
+ actor|geology/shoreline_large.xml
+
+
+
+
+
+ actor|geology/shoreline_large.xml
+
+
+
+
+
+ actor|geology/shoreline_large.xml
+
+
+
+
+
+ gaia/fauna_shark
+ 0
+
+
+
+
+
+ gaia/fauna_shark
+ 0
+
+
+
+
+
+ gaia/fauna_shark
+ 0
+
+
+
+
+
+ gaia/fauna_shark
+ 0
+
+
+
+
+
+ gaia/fauna_shark
+ 0
+
+
+
+
+
+
+
\ No newline at end of file
Index: binaries/data/mods/public/simulation/components/Trigger.js
===================================================================
--- binaries/data/mods/public/simulation/components/Trigger.js
+++ binaries/data/mods/public/simulation/components/Trigger.js
@@ -24,7 +24,8 @@
"StructureBuilt",
"TrainingFinished",
"TrainingQueued",
- "TreasureCollected"
+ "TreasureCollected",
+ "Attacked"
];
Trigger.prototype.Init = function()
@@ -277,6 +278,11 @@
this.CallEvent("DiplomacyChanged", msg);
};
+Trigger.prototype.OnGlobalAttacked = function(msg)
+{
+ this.CallEvent("Attacked", msg);
+};
+
/**
* Execute a function after a certain delay.
*
Index: binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit.xml
+++ binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -1,5 +1,6 @@
+
1
1