From 8dddd369ef62b80b153c2161903a147fa172e23b Mon Sep 17 00:00:00 2001 From: elexis Date: Thu, 21 Jul 2016 15:12:49 +0000 Subject: [PATCH] Basic regicide gamemode. Based on patch by Sandarac, refs #2160. Spawn a random hero at gamestart and defeat the player if the hero dies. Consider nomad maps where units spawn without buildings or start on a ship. Correct a comment for TriggerHelper.SpawnUnits. Make shuffleArray available to GUI and simulation and use it to randomize heroes. This was SVN commit r18544. --- .../data/mods/public/globalscripts/utility.js | 18 +++ .../mods/public/maps/random/rmgen/library.js | 20 +--- .../data/mods/public/maps/scripts/Regicide.js | 104 ++++++++++++++++++ .../mods/public/maps/scripts/TriggerHelper.js | 4 +- .../settings/victory_conditions/regicide.json | 15 +++ .../ScenarioEditor/Sections/Map/Map.cpp | 3 +- 6 files changed, 142 insertions(+), 22 deletions(-) create mode 100644 binaries/data/mods/public/maps/scripts/Regicide.js create mode 100644 binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json diff --git a/binaries/data/mods/public/globalscripts/utility.js b/binaries/data/mods/public/globalscripts/utility.js index 828b7051d6..a860775108 100644 --- a/binaries/data/mods/public/globalscripts/utility.js +++ b/binaries/data/mods/public/globalscripts/utility.js @@ -16,3 +16,21 @@ function clone(o) r[key] = clone(o[key]); return r; } + +/** + * "Inside-out" implementation of Fisher-Yates shuffle + */ +function shuffleArray(source) +{ + if (!source.length) + return []; + + let result = [source[0]]; + for (let i = 1; i < source.length; ++i) + { + let j = Math.floor(Math.random() * i); + result[i] = result[j]; + result[j] = source[i]; + } + return result; +} diff --git a/binaries/data/mods/public/maps/random/rmgen/library.js b/binaries/data/mods/public/maps/random/rmgen/library.js index cccd8d80ea..bd2ce5aaf1 100644 --- a/binaries/data/mods/public/maps/random/rmgen/library.js +++ b/binaries/data/mods/public/maps/random/rmgen/library.js @@ -11,7 +11,7 @@ const FALLBACK_CIV = "athen"; /** * Constants needed for heightmap_manipulation.js */ -const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE // Engine limit, Roughly 700 meters +const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE; // Engine limit, Roughly 700 meters const MIN_HEIGHT = - SEA_LEVEL; const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL; // Default angle for buildings @@ -91,24 +91,6 @@ function min(a, b) return a < b ? a : b; } -/** - * "Inside-out" implementation of Fisher-Yates shuffle - */ -function shuffleArray(source) -{ - if (!source.length) - return []; - - let result = [source[0]]; - for (let i = 1; i < source.length; ++i) - { - let j = randInt(0, i); - result[i] = result[j]; - result[j] = source[i]; - } - return result; -} - /** * Retries the given function with those arguments as often as specified. */ diff --git a/binaries/data/mods/public/maps/scripts/Regicide.js b/binaries/data/mods/public/maps/scripts/Regicide.js new file mode 100644 index 0000000000..ac43589243 --- /dev/null +++ b/binaries/data/mods/public/maps/scripts/Regicide.js @@ -0,0 +1,104 @@ +Trigger.prototype.CheckRegicideDefeat = function(data) +{ + if (data.entity == this.regicideHeroes[data.from]) + TriggerHelper.DefeatPlayer(data.from); +}; + +Trigger.prototype.InitRegicideGame = function(msg) +{ + let playersCivs = []; + for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) + playersCivs[playerID] = QueryPlayerIDInterface(playerID).GetCiv(); + + // Get all hero templates of these civs + let heroTemplates = {}; + let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + for (let templateName of cmpTemplateManager.FindAllTemplates(false)) + { + if (templateName.substring(0,6) != "units/") + continue; + + let identity = cmpTemplateManager.GetTemplate(templateName).Identity; + let classes = GetIdentityClasses(identity); + + if (classes.indexOf("Hero") == -1 || + playersCivs.every(civ => civ != identity.Civ)) + continue; + + if (!heroTemplates[identity.Civ]) + heroTemplates[identity.Civ] = []; + + if (heroTemplates[identity.Civ].indexOf(templateName) == -1) + heroTemplates[identity.Civ].push({ + "templateName": templateName, + "classes": classes + }); + } + + // Sort available spawn points by preference + let spawnPreference = ["Ship", "Structure", "CivilCentre"]; + let getSpawnPreference = entity => { + let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); + let classes = cmpIdentity.GetClassesList(); + return spawnPreference.findIndex(className => classes.indexOf(className) != -1); + }; + + // Attempt to spawn one hero per player + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID) + { + let spawnPoints = cmpRangeManager.GetEntitiesByPlayer(playerID).sort((entity1, entity2) => + getSpawnPreference(entity2) - getSpawnPreference(entity1)); + + this.regicideHeroes[playerID] = this.SpawnRegicideHero(playerID, heroTemplates[playersCivs[playerID]], spawnPoints); + } +}; + +/** + * Spawn a random hero at one of the given locations (which are checked in order). + * Garrison it if the location is a ship. + * + * @param spawnPoints - entity IDs at which to spawn + */ +Trigger.prototype.SpawnRegicideHero = function(playerID, heroTemplates, spawnPoints) +{ + for (let heroTemplate of shuffleArray(heroTemplates)) + for (let spawnPoint of spawnPoints) + { + let cmpPosition = Engine.QueryInterface(spawnPoint, IID_Position); + if (!cmpPosition || !cmpPosition.IsInWorld()) + continue; + + // Consider nomad maps where units start on a ship + let isShip = TriggerHelper.EntityHasClass(spawnPoint, "Ship"); + if (isShip) + { + let cmpGarrisonHolder = Engine.QueryInterface(spawnPoint, IID_GarrisonHolder); + if (cmpGarrisonHolder.IsFull() || + !MatchesClassList(heroTemplate.classes, cmpGarrisonHolder.GetAllowedClasses())) + continue; + } + + let hero = TriggerHelper.SpawnUnits(spawnPoint, heroTemplate.templateName, 1, playerID); + if (!hero.length) + continue; + + hero = hero[0]; + + if (isShip) + { + let cmpUnitAI = Engine.QueryInterface(hero, IID_UnitAI); + cmpUnitAI.Garrison(spawnPoint); + } + + return hero; + } + + error("Couldn't spawn hero for player " + playerID); + return undefined; +}; + +let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); +cmpTrigger.regicideHeroes = []; +cmpTrigger.DoAfterDelay(0, "InitRegicideGame", {}); +cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CheckRegicideDefeat", { "enabled": true }); diff --git a/binaries/data/mods/public/maps/scripts/TriggerHelper.js b/binaries/data/mods/public/maps/scripts/TriggerHelper.js index 0b7955e55c..f1acb69eed 100644 --- a/binaries/data/mods/public/maps/scripts/TriggerHelper.js +++ b/binaries/data/mods/public/maps/scripts/TriggerHelper.js @@ -22,8 +22,8 @@ TriggerHelper.GetOwner = function(ent) }; /** - * Can be used to "force" a building to spawn a group of entities. - * Only works for buildings that can already train units. + * Can be used to "force" a building/unit to spawn a group of entities. + * * @param source Entity id of the point where they will be spawned from * @param template Name of the template * @param count Number of units to spawn diff --git a/binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json b/binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json new file mode 100644 index 0000000000..9d46feb2bb --- /dev/null +++ b/binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json @@ -0,0 +1,15 @@ +{ + "TranslatedKeys": ["Title", "Description"], + "Data": + { + "Title": "Regicide", + "Description": "Defeat opponents by killing their hero", + "Scripts": + [ + "scripts/TriggerHelper.js", + "scripts/ConquestCommon.js", + "scripts/Conquest.js", + "scripts/Regicide.js" + ] + } +} diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp index 5045b084f3..8ee259e075 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -148,6 +148,7 @@ void MapSettingsControl::CreateWidgets() gameTypes.Add(_T("conquest_units")); gameTypes.Add(_T("wonder")); gameTypes.Add(_T("endless")); + gameTypes.Add(_T("regicide")); wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5); gridSizer->AddGrowableCol(1);