-
From 379ae72627fd347e448545d05e2d7b051972aebb Mon Sep 17 00:00:00 2001
From: Ben Brian <ben@wildfiregames.com>
Date: Fri, 8 Jun 2012 16:57:35 -0400
Subject: [PATCH 1/3] Extends engine to load all global scripts with new
function (separate from replacing RNG). Calls that
function for GUI, AI, map generator and simulation
script contexts. Adds dummy folder to fix simulation
tests.
---
.../data/mods/public/globalscripts/Technologies.js | 24 +++++++++
source/graphics/MapGenerator.cpp | 3 +-
source/gui/GUIManager.cpp | 2 +
source/scriptinterface/ScriptInterface.cpp | 54 +++++++++++---------
source/scriptinterface/ScriptInterface.h | 11 +++-
.../scriptinterface/tests/test_ScriptInterface.h | 2 +-
source/simulation2/components/CCmpAIManager.cpp | 6 ++-
source/simulation2/system/ComponentManager.cpp | 3 +-
8 files changed, 75 insertions(+), 30 deletions(-)
create mode 100644 binaries/data/mods/_test.sim/globalscripts/.gitignore
create mode 100644 binaries/data/mods/public/globalscripts/Technologies.js
diff --git a/binaries/data/mods/_test.sim/globalscripts/.gitignore b/binaries/data/mods/_test.sim/globalscripts/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/binaries/data/mods/public/globalscripts/Technologies.js b/binaries/data/mods/public/globalscripts/Technologies.js
new file mode 100644
index 0000000..f1ee71a
-
|
+
|
|
| 1 | /** |
| 2 | * This file contains shared logic for applying tech modifications |
| 3 | * in GUI, AI, and simulation scripts. |
| 4 | * As such it must be completely deterministic and can't have any global state, |
| 5 | * but each context should do its own caching as needed. |
| 6 | * |
| 7 | * fullEntityTemplateData: object containing all raw entity template data |
| 8 | * fullTechTemplateData: object containing all raw tech template data |
| 9 | * currentResearchedTechs: array of objects, one for each player containing their |
| 10 | * currently researched techs (acquired from the simulation/TechManager) |
| 11 | * playerID: the player which is testing this tech |
| 12 | * entityTemplateName: name of the entity template to retrieve the property |
| 13 | * propertyName: name of the tech property we're applying |
| 14 | * |
| 15 | */ |
| 16 | function GetTechModifiedProperty( |
| 17 | fullEntityTemplateData, fullTechTemplateData, currentResearchedTechs, |
| 18 | playerID, entityTemplateName, propertyName) |
| 19 | { |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | } |
-
diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp
index 518e98e..b804165 100644
a
|
b
|
bool CMapGeneratorWorker::Run()
|
88 | 88 | m_ScriptInterface->SetCallbackData(static_cast<void*> (this)); |
89 | 89 | |
90 | 90 | // Replace RNG with a seeded deterministic function |
91 | | m_ScriptInterface->ReplaceNondeterministicFunctions(m_MapGenRNG); |
| 91 | m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG); |
| 92 | m_ScriptInterface->LoadGlobalScripts(); |
92 | 93 | |
93 | 94 | // Functions for RMS |
94 | 95 | m_ScriptInterface->RegisterFunction<bool, std::wstring, CMapGeneratorWorker::LoadLibrary>("LoadLibrary"); |
-
diff --git a/source/gui/GUIManager.cpp b/source/gui/GUIManager.cpp
index 2c55661..bccfc0f 100644
a
|
b
|
CGUIManager::CGUIManager(ScriptInterface& scriptInterface) :
|
53 | 53 | { |
54 | 54 | ENSURE(ScriptInterface::GetCallbackData(scriptInterface.GetContext()) == NULL); |
55 | 55 | scriptInterface.SetCallbackData(this); |
| 56 | |
| 57 | scriptInterface.LoadGlobalScripts(); |
56 | 58 | } |
57 | 59 | |
58 | 60 | CGUIManager::~CGUIManager() |
-
diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp
index 9a16a0d..b5b346f 100644
a
|
b
|
void* ScriptInterface::GetCallbackData(JSContext* cx)
|
609 | 609 | return JS_GetContextPrivate(cx); |
610 | 610 | } |
611 | 611 | |
612 | | void ScriptInterface::ReplaceNondeterministicFunctions(boost::rand48& rng) |
| 612 | bool ScriptInterface::LoadGlobalScripts() |
613 | 613 | { |
614 | | jsval math; |
615 | | if (!JS_GetProperty(m->m_cx, m->m_glob, "Math", &math) || !JSVAL_IS_OBJECT(math)) |
616 | | { |
617 | | LOGERROR(L"ReplaceNondeterministicFunctions: failed to get Math"); |
618 | | return; |
619 | | } |
| 614 | // Ignore this failure in tests |
| 615 | if (!g_VFS) |
| 616 | return false; |
620 | 617 | |
621 | | JSFunction* random = JS_DefineFunction(m->m_cx, JSVAL_TO_OBJECT(math), "random", Math_random, 0, |
622 | | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); |
623 | | if (!random) |
| 618 | // Load and execute *.js in the global scripts directory |
| 619 | VfsPaths pathnames; |
| 620 | vfs::GetPathnames(g_VFS, L"globalscripts/", L"*.js", pathnames); |
| 621 | for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it) |
624 | 622 | { |
625 | | LOGERROR(L"ReplaceNondeterministicFunctions: failed to replace Math.random"); |
626 | | return; |
| 623 | if (!LoadGlobalScriptFile(*it)) |
| 624 | { |
| 625 | LOGERROR(L"LoadGlobalScripts: Failed to load script %ls", it->string().c_str()); |
| 626 | return false; |
| 627 | } |
627 | 628 | } |
628 | | // Store the RNG in a slot which is sort-of-guaranteed to be unused by the JS engine |
629 | | JS_SetReservedSlot(m->m_cx, JS_GetFunctionObject(random), 0, PRIVATE_TO_JSVAL(&rng)); |
630 | | |
631 | | // Load a script which replaces some of the system dependent trig functions |
632 | | VfsPath path("globalscripts/Math.js"); |
633 | | |
634 | | // This function is called from tests without the VFS set up, |
635 | | // so silently fail in that case because every other option seems worse |
636 | | if (g_VFS && VfsFileExists(path)) |
| 629 | |
| 630 | return true; |
| 631 | } |
| 632 | |
| 633 | bool ScriptInterface::ReplaceNondeterministicRNG(boost::rand48& rng) |
| 634 | { |
| 635 | jsval math; |
| 636 | if (JS_GetProperty(m->m_cx, m->m_glob, "Math", &math) && JSVAL_IS_OBJECT(math)) |
637 | 637 | { |
638 | | if (!this->LoadGlobalScriptFile(path)) |
| 638 | JSFunction* random = JS_DefineFunction(m->m_cx, JSVAL_TO_OBJECT(math), "random", Math_random, 0, |
| 639 | JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); |
| 640 | if (random) |
639 | 641 | { |
640 | | LOGERROR(L"ReplaceNondeterministicFunctions: failed to load %ls", path.string().c_str()); |
641 | | return; |
| 642 | // Store the RNG in a slot which is sort-of-guaranteed to be unused by the JS engine |
| 643 | if (JS_SetReservedSlot(m->m_cx, JS_GetFunctionObject(random), 0, PRIVATE_TO_JSVAL(&rng))) |
| 644 | return true; |
642 | 645 | } |
643 | 646 | } |
| 647 | |
| 648 | LOGERROR(L"ReplaceNondeterministicRNG: failed to replace Math.random"); |
| 649 | return false; |
644 | 650 | } |
645 | 651 | |
646 | 652 | void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) |
-
diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h
index c0118d0..f80f671 100644
a
|
b
|
public:
|
98 | 98 | JSContext* GetContext() const; |
99 | 99 | JSRuntime* GetRuntime() const; |
100 | 100 | |
101 | | void ReplaceNondeterministicFunctions(boost::rand48& rng); |
| 101 | /** |
| 102 | * Load global scripts that most script contexts need, |
| 103 | * located in the /globalscripts directory. VFS must be initialized. |
| 104 | */ |
| 105 | bool LoadGlobalScripts(); |
| 106 | |
| 107 | /** |
| 108 | * Replace the default JS random number geenrator with a seeded, network-sync'd one. |
| 109 | */ |
| 110 | bool ReplaceNondeterministicRNG(boost::rand48& rng); |
102 | 111 | |
103 | 112 | /** |
104 | 113 | * Call a constructor function, equivalent to JS "new ctor(arg)". |
-
diff --git a/source/scriptinterface/tests/test_ScriptInterface.h b/source/scriptinterface/tests/test_ScriptInterface.h
index ff4ab3e..8168306 100644
a
|
b
|
public:
|
116 | 116 | TS_ASSERT_DIFFERS(d1, d2); |
117 | 117 | |
118 | 118 | boost::rand48 rng; |
119 | | script.ReplaceNondeterministicFunctions(rng); |
| 119 | script.ReplaceNondeterministicRNG(rng); |
120 | 120 | rng.seed((u64)0); |
121 | 121 | TS_ASSERT(script.Eval("Math.random()", d1)); |
122 | 122 | TS_ASSERT(script.Eval("Math.random()", d2)); |
-
diff --git a/source/simulation2/components/CCmpAIManager.cpp b/source/simulation2/components/CCmpAIManager.cpp
index 72f9463..c2470c1 100644
a
|
b
|
private:
|
82 | 82 | { |
83 | 83 | m_ScriptInterface.SetCallbackData(static_cast<void*> (this)); |
84 | 84 | |
85 | | m_ScriptInterface.ReplaceNondeterministicFunctions(rng); |
| 85 | m_ScriptInterface.ReplaceNondeterministicRNG(rng); |
| 86 | m_ScriptInterface.LoadGlobalScripts(); |
86 | 87 | |
87 | 88 | m_ScriptInterface.RegisterFunction<void, std::wstring, CAIPlayer::IncludeModule>("IncludeModule"); |
88 | 89 | m_ScriptInterface.RegisterFunction<void, CScriptValRooted, CAIPlayer::PostCommand>("PostCommand"); |
… |
… |
public:
|
269 | 270 | m_ScriptInterface.SetCallbackData(static_cast<void*> (this)); |
270 | 271 | |
271 | 272 | // TODO: ought to seed the RNG (in a network-synchronised way) before we use it |
272 | | m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG); |
| 273 | m_ScriptInterface.ReplaceNondeterministicRNG(m_RNG); |
| 274 | m_ScriptInterface.LoadGlobalScripts(); |
273 | 275 | } |
274 | 276 | |
275 | 277 | ~CAIWorker() |
-
diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp
index c98db5c..09dce48 100644
a
|
b
|
CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFuncti
|
62 | 62 | m_ScriptInterface.SetCallbackData(static_cast<void*> (this)); |
63 | 63 | |
64 | 64 | // TODO: ought to seed the RNG (in a network-synchronised way) before we use it |
65 | | m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG); |
| 65 | m_ScriptInterface.ReplaceNondeterministicRNG(m_RNG); |
| 66 | m_ScriptInterface.LoadGlobalScripts(); |
66 | 67 | |
67 | 68 | // For component script tests, the test system sets up its own scripted implementation of |
68 | 69 | // these functions, so we skip registering them here in those cases |
-
--
1.7.10.msysgit.1
From a0e0b16c5cd89e3ba85b9777deac29bb3fd8171b Mon Sep 17 00:00:00 2001
From: historicbruno <bb51@evansville.edu>
Date: Fri, 15 Jun 2012 21:08:07 -0400
Subject: [PATCH 2/3] Implements global GetTechModifiedProperty function by
reusing modifications data from TechnologyManager (as
described at
http://trac.wildfiregames.com/ticket/1358#comment:7).
GuiInterface.GetTemplateData applies tech modifications
directly to its returned data which is already cached
by the UI which avoids needing a new separate caching
mechanism. Fixes GUIInterface tests.
---
.../data/mods/public/globalscripts/Technologies.js | 55 +++++++++++++----
.../public/simulation/components/GuiInterface.js | 58 +++++++++---------
.../simulation/components/TechnologyManager.js | 63 ++++----------------
.../components/tests/test_GuiInterface.js | 6 ++
4 files changed, 92 insertions(+), 90 deletions(-)
diff --git a/binaries/data/mods/public/globalscripts/Technologies.js b/binaries/data/mods/public/globalscripts/Technologies.js
index f1ee71a..46104d3 100644
a
|
b
|
|
4 | 4 | * As such it must be completely deterministic and can't have any global state, |
5 | 5 | * but each context should do its own caching as needed. |
6 | 6 | * |
7 | | * fullEntityTemplateData: object containing all raw entity template data |
8 | | * fullTechTemplateData: object containing all raw tech template data |
9 | | * currentResearchedTechs: array of objects, one for each player containing their |
10 | | * currently researched techs (acquired from the simulation/TechManager) |
11 | | * playerID: the player which is testing this tech |
12 | | * entityTemplateName: name of the entity template to retrieve the property |
13 | | * propertyName: name of the tech property we're applying |
14 | | * |
15 | 7 | */ |
16 | | function GetTechModifiedProperty( |
17 | | fullEntityTemplateData, fullTechTemplateData, currentResearchedTechs, |
18 | | playerID, entityTemplateName, propertyName) |
| 8 | |
| 9 | function GetTechModifiedProperty(currentTechModifications, entityTemplateData, propertyName, propertyValue) |
19 | 10 | { |
| 11 | // Get all modifications to this value |
| 12 | var modifications = currentTechModifications[propertyName]; |
| 13 | if (!modifications) // no modifications so return the original value |
| 14 | return propertyValue; |
| 15 | |
| 16 | // Get the classes which this entity template belongs to |
| 17 | var rawClasses = entityTemplateData.Identity.Classes; |
| 18 | var classes = (rawClasses && "_string" in rawClasses ? rawClasses._string.split(/\s+/) : []); |
| 19 | |
| 20 | var retValue = propertyValue; |
20 | 21 | |
| 22 | for (var i in modifications) |
| 23 | { |
| 24 | var modification = modifications[i]; |
| 25 | var applies = false; |
| 26 | // See if any of the lists of classes matches this entity |
| 27 | for (var j in modification.affects) |
| 28 | { |
| 29 | var hasAllClasses = true; |
| 30 | // Check each class in affects is present for the entity |
| 31 | for (var k in modification.affects[j]) |
| 32 | hasAllClasses = hasAllClasses && (classes.indexOf(modification.affects[j][k]) !== -1); |
21 | 33 | |
| 34 | if (hasAllClasses) |
| 35 | { |
| 36 | applies = true; |
| 37 | break; |
| 38 | } |
| 39 | } |
22 | 40 | |
| 41 | // We found a match, apply the modification |
| 42 | if (applies) |
| 43 | { |
| 44 | // Nothing is cumulative so that ordering doesn't matter as much as possible |
| 45 | if (modification.multiplier) |
| 46 | retValue += (modification.multiplier - 1) * propertyValue; |
| 47 | else if (modification.add) |
| 48 | retValue += modification.add; |
| 49 | else if (modification.replace !== undefined) // This will depend on ordering because there is no choice |
| 50 | retValue = modification.replace; |
| 51 | else |
| 52 | warn("GetTechModifiedProperty: modification format not recognised (modifying " + propertyName + "): " + uneval(modification)); |
| 53 | } |
| 54 | } |
23 | 55 | |
| 56 | return retValue; |
24 | 57 | } |
-
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 4ce8c71..15a9514 100644
a
|
b
|
GuiInterface.prototype.GetSimulationState = function(player)
|
53 | 53 | var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player); |
54 | 54 | |
55 | 55 | // Work out what phase we are in |
56 | | var cmpTechMan = Engine.QueryInterface(playerEnt, IID_TechnologyManager); |
| 56 | var cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager); |
57 | 57 | var phase = ""; |
58 | | if (cmpTechMan.IsTechnologyResearched("phase_city")) |
| 58 | if (cmpTechnologyManager.IsTechnologyResearched("phase_city")) |
59 | 59 | phase = "city"; |
60 | | else if (cmpTechMan.IsTechnologyResearched("phase_town")) |
| 60 | else if (cmpTechnologyManager.IsTechnologyResearched("phase_town")) |
61 | 61 | phase = "town"; |
62 | | else if (cmpTechMan.IsTechnologyResearched("phase_village")) |
| 62 | else if (cmpTechnologyManager.IsTechnologyResearched("phase_village")) |
63 | 63 | phase = "village"; |
64 | 64 | |
65 | 65 | // store player ally/enemy data as arrays |
… |
… |
GuiInterface.prototype.GetSimulationState = function(player)
|
85 | 85 | "isAlly": allies, |
86 | 86 | "isEnemy": enemies, |
87 | 87 | "buildLimits": cmpPlayerBuildLimits.GetLimits(), |
88 | | "buildCounts": cmpPlayerBuildLimits.GetCounts() |
| 88 | "buildCounts": cmpPlayerBuildLimits.GetCounts(), |
| 89 | "techModifications": cmpTechnologyManager.GetTechModifications() |
89 | 90 | }; |
90 | 91 | ret.players.push(playerData); |
91 | 92 | } |
… |
… |
GuiInterface.prototype.ClearRenamedEntities = function(player)
|
133 | 134 | |
134 | 135 | GuiInterface.prototype.GetEntityState = function(player, ent) |
135 | 136 | { |
136 | | var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); |
| 137 | var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); |
137 | 138 | |
138 | 139 | // All units must have a template; if not then it's a nonexistent entity id |
139 | | var template = cmpTempMan.GetCurrentTemplateName(ent); |
| 140 | var template = cmpTemplateManager.GetCurrentTemplateName(ent); |
140 | 141 | if (!template) |
141 | 142 | return null; |
142 | 143 | |
… |
… |
GuiInterface.prototype.GetEntityState = function(player, ent)
|
312 | 313 | |
313 | 314 | GuiInterface.prototype.GetTemplateData = function(player, name) |
314 | 315 | { |
315 | | var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); |
316 | | var template = cmpTempMan.GetTemplate(name); |
| 316 | var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); |
| 317 | var template = cmpTemplateManager.GetTemplate(name); |
317 | 318 | |
318 | 319 | if (!template) |
319 | 320 | return null; |
320 | 321 | |
321 | 322 | var ret = {}; |
322 | 323 | |
| 324 | var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager); |
| 325 | var techMods = cmpTechnologyManager.GetTechModifications(); |
| 326 | |
323 | 327 | if (template.Armour) |
324 | 328 | { |
325 | 329 | ret.armour = { |
326 | | "hack": +template.Armour.Hack, |
327 | | "pierce": +template.Armour.Pierce, |
328 | | "crush": +template.Armour.Crush, |
| 330 | "hack": GetTechModifiedProperty(techMods, template, "Armour/Hack", +template.Armour.Hack), |
| 331 | "pierce": GetTechModifiedProperty(techMods, template, "Armour/Pierce", +template.Armour.Pierce), |
| 332 | "crush": GetTechModifiedProperty(techMods, template, "Armour/Crush", +template.Armour.Crush), |
329 | 333 | }; |
330 | 334 | } |
331 | 335 | |
… |
… |
GuiInterface.prototype.GetTemplateData = function(player, name)
|
335 | 339 | for (var type in template.Attack) |
336 | 340 | { |
337 | 341 | ret.attack[type] = { |
338 | | "hack": (+template.Attack[type].Hack || 0), |
339 | | "pierce": (+template.Attack[type].Pierce || 0), |
340 | | "crush": (+template.Attack[type].Crush || 0), |
| 342 | "hack": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Hack", +template.Attack[type].Hack || 0), |
| 343 | "pierce": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Pierce", +template.Attack[type].Pierce || 0), |
| 344 | "crush": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Crush", +template.Attack[type].Crush || 0), |
341 | 345 | }; |
342 | 346 | } |
343 | 347 | } |
… |
… |
GuiInterface.prototype.GetTemplateData = function(player, name)
|
365 | 369 | if (template.Cost) |
366 | 370 | { |
367 | 371 | ret.cost = {}; |
368 | | if (template.Cost.Resources.food) ret.cost.food = +template.Cost.Resources.food; |
369 | | if (template.Cost.Resources.wood) ret.cost.wood = +template.Cost.Resources.wood; |
370 | | if (template.Cost.Resources.stone) ret.cost.stone = +template.Cost.Resources.stone; |
371 | | if (template.Cost.Resources.metal) ret.cost.metal = +template.Cost.Resources.metal; |
372 | | if (template.Cost.Population) ret.cost.population = +template.Cost.Population; |
373 | | if (template.Cost.PopulationBonus) ret.cost.populationBonus = +template.Cost.PopulationBonus; |
| 372 | if (template.Cost.Resources.food) ret.cost.food = GetTechModifiedProperty(techMods, template, "Cost/Resources/food", +template.Cost.Resources.food); |
| 373 | if (template.Cost.Resources.wood) ret.cost.wood = GetTechModifiedProperty(techMods, template, "Cost/Resources/wood", +template.Cost.Resources.wood); |
| 374 | if (template.Cost.Resources.stone) ret.cost.stone = GetTechModifiedProperty(techMods, template, "Cost/Resources/stone", +template.Cost.Resources.stone); |
| 375 | if (template.Cost.Resources.metal) ret.cost.metal = GetTechModifiedProperty(techMods, template, "Cost/Resources/metal", +template.Cost.Resources.metal); |
| 376 | if (template.Cost.Population) ret.cost.population = GetTechModifiedProperty(techMods, template, "Cost/Population", +template.Cost.Population); |
| 377 | if (template.Cost.PopulationBonus) ret.cost.populationBonus = GetTechModifiedProperty(techMods, template, "Cost/PopulationBonus", +template.Cost.PopulationBonus); |
374 | 378 | } |
375 | 379 | |
376 | 380 | if (template.Footprint) |
… |
… |
GuiInterface.prototype.GetTechnologyData = function(player, name)
|
508 | 512 | |
509 | 513 | GuiInterface.prototype.IsTechnologyResearched = function(player, tech) |
510 | 514 | { |
511 | | var cmpTechMan = QueryPlayerIDInterface(player, IID_TechnologyManager); |
| 515 | var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager); |
512 | 516 | |
513 | | if (!cmpTechMan) |
| 517 | if (!cmpTechnologyManager) |
514 | 518 | return false; |
515 | 519 | |
516 | | return cmpTechMan.IsTechnologyResearched(tech); |
| 520 | return cmpTechnologyManager.IsTechnologyResearched(tech); |
517 | 521 | }; |
518 | 522 | |
519 | 523 | // Checks whether the requirements for this technology have been met |
520 | 524 | GuiInterface.prototype.CheckTechnologyRequirements = function(player, tech) |
521 | 525 | { |
522 | | var cmpTechMan = QueryPlayerIDInterface(player, IID_TechnologyManager); |
| 526 | var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager); |
523 | 527 | |
524 | | if (!cmpTechMan) |
| 528 | if (!cmpTechnologyManager) |
525 | 529 | return false; |
526 | 530 | |
527 | | return cmpTechMan.CanResearch(tech); |
| 531 | return cmpTechnologyManager.CanResearch(tech); |
528 | 532 | }; |
529 | 533 | |
530 | 534 | GuiInterface.prototype.PushNotification = function(notification) |
-
diff --git a/binaries/data/mods/public/simulation/components/TechnologyManager.js b/binaries/data/mods/public/simulation/components/TechnologyManager.js
index c52705c..5312ce2 100644
a
|
b
|
TechnologyManager.prototype.ApplyModifications = function(valueName, curValue, e
|
316 | 316 | this.modificationCache[valueName] = {}; |
317 | 317 | |
318 | 318 | if (!this.modificationCache[valueName][ent]) |
319 | | this.modificationCache[valueName][ent] = this.ApplyModificationsWorker(valueName, curValue, ent); |
320 | | |
321 | | return this.modificationCache[valueName][ent]; |
322 | | } |
323 | | |
324 | | // The code to actually apply the modification |
325 | | TechnologyManager.prototype.ApplyModificationsWorker = function(valueName, curValue, ent) |
326 | | { |
327 | | // Get all modifications to this value |
328 | | var modifications = this.modifications[valueName]; |
329 | | if (!modifications) // no modifications so return the original value |
330 | | return curValue; |
331 | | |
332 | | // Get the classes which this entity belongs to |
333 | | var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); |
334 | | var classes = cmpIdentity.GetClassesList(); |
335 | | |
336 | | var retValue = curValue; |
337 | | |
338 | | for (var i in modifications) |
339 | 319 | { |
340 | | var modification = modifications[i]; |
341 | | var applies = false; |
342 | | // See if any of the lists of classes matches this entity |
343 | | for (var j in modification.affects) |
344 | | { |
345 | | var hasAllClasses = true; |
346 | | // Check each class in affects is present for the entity |
347 | | for (var k in modification.affects[j]) |
348 | | hasAllClasses = hasAllClasses && (classes.indexOf(modification.affects[j][k]) !== -1); |
349 | | |
350 | | if (hasAllClasses) |
351 | | { |
352 | | applies = true; |
353 | | break; |
354 | | } |
355 | | } |
356 | | |
357 | | // We found a match, apply the modification |
358 | | if (applies) |
359 | | { |
360 | | // Nothing is cumulative so that ordering doesn't matter as much as possible |
361 | | if (modification.multiplier) |
362 | | retValue += (modification.multiplier - 1) * curValue; |
363 | | else if (modification.add) |
364 | | retValue += modification.add; |
365 | | else if (modification.replace !== undefined) // This will depend on ordering because there is no choice |
366 | | retValue = modification.replace; |
367 | | else |
368 | | warn("modification format not recognised (modifying " + valueName + "): " + uneval(modification)); |
369 | | } |
| 320 | var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); |
| 321 | var templateName = cmpTemplateManager.GetCurrentTemplateName(ent); |
| 322 | this.modificationCache[valueName][ent] = GetTechModifiedProperty(this.modifications, cmpTemplateManager.GetTemplate(templateName), valueName, curValue); |
370 | 323 | } |
371 | 324 | |
372 | | return retValue; |
373 | | }; |
| 325 | return this.modificationCache[valueName][ent]; |
| 326 | } |
374 | 327 | |
375 | 328 | // Marks a technology as being currently researched |
376 | 329 | TechnologyManager.prototype.StartedResearch = function (tech) |
… |
… |
TechnologyManager.prototype.IsInProgress = function(tech)
|
393 | 346 | return false; |
394 | 347 | }; |
395 | 348 | |
| 349 | // Get helper data for tech modifications |
| 350 | TechnologyManager.prototype.GetTechModifications = function() |
| 351 | { |
| 352 | return this.modifications; |
| 353 | }; |
| 354 | |
396 | 355 | Engine.RegisterComponentType(IID_TechnologyManager, "TechnologyManager", TechnologyManager); |
-
diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
index f72db6c..558a95b 100644
a
|
b
|
AddMock(100, IID_BuildLimits, {
|
73 | 73 | |
74 | 74 | AddMock(100, IID_TechnologyManager, { |
75 | 75 | IsTechnologyResearched: function(tech) { return false; }, |
| 76 | GetTechModifications: function() { return {}; }, |
76 | 77 | }); |
77 | 78 | |
78 | 79 | AddMock(100, IID_StatisticsTracker, { |
… |
… |
AddMock(101, IID_BuildLimits, {
|
123 | 124 | |
124 | 125 | AddMock(101, IID_TechnologyManager, { |
125 | 126 | IsTechnologyResearched: function(tech) { if (tech == "phase_village") return true; else return false; }, |
| 127 | GetTechModifications: function() { return {}; }, |
126 | 128 | }); |
127 | 129 | |
128 | 130 | AddMock(101, IID_StatisticsTracker, { |
… |
… |
TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
|
170 | 172 | isEnemy: [true, true, true], |
171 | 173 | buildLimits: {"Foo": 10}, |
172 | 174 | buildCounts: {"Foo": 5}, |
| 175 | techModifications: {}, |
173 | 176 | }, |
174 | 177 | { |
175 | 178 | name: "Player 2", |
… |
… |
TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
|
187 | 190 | isEnemy: [false, false, false], |
188 | 191 | buildLimits: {"Bar": 20}, |
189 | 192 | buildCounts: {"Bar": 0}, |
| 193 | techModifications: {}, |
190 | 194 | } |
191 | 195 | ], |
192 | 196 | circularMap: false, |
… |
… |
TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
|
211 | 215 | isEnemy: [true, true, true], |
212 | 216 | buildLimits: {"Foo": 10}, |
213 | 217 | buildCounts: {"Foo": 5}, |
| 218 | techModifications: {}, |
214 | 219 | statistics: { |
215 | 220 | unitsTrained: 10, |
216 | 221 | unitsLost: 9, |
… |
… |
TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
|
244 | 249 | isEnemy: [false, false, false], |
245 | 250 | buildLimits: {"Bar": 20}, |
246 | 251 | buildCounts: {"Bar": 0}, |
| 252 | techModifications: {}, |
247 | 253 | statistics: { |
248 | 254 | unitsTrained: 10, |
249 | 255 | unitsLost: 9, |
-
--
1.7.10.msysgit.1
From 83a0ae5b04bd3f91ad5f2949410e3be447bfc019 Mon Sep 17 00:00:00 2001
From: historicbruno <bb51@evansville.edu>
Date: Fri, 15 Jun 2012 21:26:17 -0400
Subject: [PATCH 3/3] Adds tech modifications support to AIs (common-api-v2)
using the same tech modifications data as the UI via
GuiInterface. Entity and EntityTemplates are
constructed with this data to return accurate stats
(existing objects will need to be updated somehow, in
response to new research). Caching may need to be
considered as well. Fixes AI serialization with
slightly hacky solution.
---
.../public/simulation/ai/common-api-v2/base.js | 10 ++++++-
.../public/simulation/ai/common-api-v2/entity.js | 30 +++++++++++---------
.../mods/public/simulation/ai/jubot/gamestate.js | 3 +-
.../mods/public/simulation/ai/qbot/gamestate.js | 3 +-
.../mods/public/simulation/components/AIProxy.js | 2 ++
5 files changed, 31 insertions(+), 17 deletions(-)
diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/base.js b/binaries/data/mods/public/simulation/ai/common-api-v2/base.js
index 93ad9da..2a71484 100644
a
|
b
|
function BaseAI(settings)
|
9 | 9 | Object.defineProperty(this, "_derivedTemplates", {value: {}, enumerable: false}); |
10 | 10 | |
11 | 11 | this._entityMetadata = {}; |
| 12 | this._techModifications = {}; |
12 | 13 | |
13 | 14 | this._entityCollections = []; |
14 | 15 | this._entityCollectionsByDynProp = {}; |
… |
… |
BaseAI.prototype.Serialize = function()
|
29 | 30 | return { |
30 | 31 | _rawEntities: rawEntities, |
31 | 32 | _entityMetadata: this._entityMetadata, |
| 33 | _techModifications: this._techModifications, |
32 | 34 | }; |
33 | 35 | |
34 | 36 | // TODO: ought to get the AI script subclass to serialize its own state |
… |
… |
BaseAI.prototype.Deserialize = function(data)
|
40 | 42 | { |
41 | 43 | var rawEntities = data._rawEntities; |
42 | 44 | this._entityMetadata = data._entityMetadata; |
43 | | this._entities = {} |
| 45 | this._entities = {}; |
| 46 | this._techModifications = data._techModifications; |
44 | 47 | |
45 | 48 | for (var id in rawEntities) |
46 | 49 | { |
… |
… |
BaseAI.prototype.GetTemplate = function(name)
|
89 | 92 | |
90 | 93 | BaseAI.prototype.HandleMessage = function(state) |
91 | 94 | { |
| 95 | // Update tech modifications data (used for constructing entities and templates) |
| 96 | this._techModifications = state.players[this._player].techModifications; |
| 97 | |
92 | 98 | if (!this._entities) |
93 | 99 | { |
94 | 100 | // Do a (shallow) clone of all the initial entity properties (in order |
… |
… |
BaseAI.prototype.HandleMessage = function(state)
|
116 | 122 | this.templates = this._templates; |
117 | 123 | this.territoryMap = state.territoryMap; |
118 | 124 | this.timeElapsed = state.timeElapsed; |
| 125 | this.techModifications = this._techModifications; |
119 | 126 | |
120 | 127 | Engine.ProfileStop(); |
121 | 128 | |
… |
… |
BaseAI.prototype.HandleMessage = function(state)
|
131 | 138 | delete this.templates; |
132 | 139 | delete this.territoryMap; |
133 | 140 | delete this.timeElapsed; |
| 141 | delete this.techModifications; |
134 | 142 | }; |
135 | 143 | |
136 | 144 | BaseAI.prototype.ApplyEntitiesDelta = function(state) |
-
diff --git a/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js b/binaries/data/mods/public/simulation/ai/common-api-v2/entity.js
index 7f8cb44..9e182ad 100644
a
|
b
|
|
1 | 1 | var EntityTemplate = Class({ |
2 | 2 | |
3 | | _init: function(template) |
| 3 | _init: function(template, techModifications) |
4 | 4 | { |
| 5 | this._techModifications = techModifications; |
5 | 6 | this._template = template; |
6 | 7 | }, |
7 | 8 | |
… |
… |
var EntityTemplate = Class({
|
34 | 35 | |
35 | 36 | var ret = {}; |
36 | 37 | for (var type in this._template.Cost.Resources) |
37 | | ret[type] = +this._template.Cost.Resources[type]; |
| 38 | ret[type] = GetTechModifiedProperty(this._techModifications, this._template, "Cost/Resources/"+type, +this._template.Cost.Resources[type]); |
38 | 39 | return ret; |
39 | 40 | }, |
40 | 41 | |
… |
… |
var EntityTemplate = Class({
|
69 | 70 | return undefined; |
70 | 71 | |
71 | 72 | return { |
72 | | hack: +this._template.Armour.Hack, |
73 | | pierce: +this._template.Armour.Pierce, |
74 | | crush: +this._template.Armour.Crush |
| 73 | hack: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Hack", +this._template.Armour.Hack), |
| 74 | pierce: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Pierce", +this._template.Armour.Pierce), |
| 75 | crush: GetTechModifiedProperty(this._techModifications, this._template, "Armour/Crush", +this._template.Armour.Crush) |
75 | 76 | }; |
76 | 77 | }, |
77 | 78 | |
… |
… |
var EntityTemplate = Class({
|
91 | 92 | return undefined; |
92 | 93 | |
93 | 94 | return { |
94 | | max: +this._template.Attack[type].MaxRange, |
95 | | min: +(this._template.Attack[type].MinRange || 0) |
| 95 | max: GetTechModifiedProperty(this._techModifications, this._template, "Attack/MaxRange", +this._template.Attack[type].MaxRange), |
| 96 | min: GetTechModifiedProperty(this._techModifications, this._template, "Attack/MinRange", +(this._template.Attack[type].MinRange || 0)) |
96 | 97 | }; |
97 | 98 | }, |
98 | 99 | |
… |
… |
var EntityTemplate = Class({
|
101 | 102 | return undefined; |
102 | 103 | |
103 | 104 | return { |
104 | | hack: +(this._template.Attack[type].Hack || 0), |
105 | | pierce: +(this._template.Attack[type].Pierce || 0), |
106 | | crush: +(this._template.Attack[type].Crush || 0) |
| 105 | hack: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Hack", +(this._template.Attack[type].Hack || 0)), |
| 106 | pierce: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Pierce", +(this._template.Attack[type].Pierce || 0)), |
| 107 | crush: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/Crush", +(this._template.Attack[type].Crush || 0)) |
107 | 108 | }; |
108 | 109 | }, |
109 | 110 | |
… |
… |
var EntityTemplate = Class({
|
112 | 113 | return undefined; |
113 | 114 | |
114 | 115 | return { |
115 | | prepare: +(this._template.Attack[type].PrepareTime || 0), |
116 | | repeat: +(this._template.Attack[type].RepeatTime || 1000) |
| 116 | prepare: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/PrepareTime", +(this._template.Attack[type].PrepareTime || 0)), |
| 117 | repeat: GetTechModifiedProperty(this._techModifications, this._template, "Attack/"+type+"/RepeatTime", +(this._template.Attack[type].RepeatTime || 1000)) |
117 | 118 | }; |
118 | 119 | }, |
119 | 120 | |
… |
… |
var EntityTemplate = Class({
|
152 | 153 | if (!this._template.ResourceGatherer) |
153 | 154 | return undefined; |
154 | 155 | var ret = {}; |
| 156 | var baseSpeed = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/BaseSpeed", +this._template.ResourceGatherer.BaseSpeed); |
155 | 157 | for (var r in this._template.ResourceGatherer.Rates) |
156 | | ret[r] = this._template.ResourceGatherer.Rates[r] * this._template.ResourceGatherer.BaseSpeed; |
| 158 | ret[r] = GetTechModifiedProperty(this._techModifications, this._template, "ResourceGatherer/Rates/"+r, +this._template.ResourceGatherer.Rates[r]) * baseSpeed; |
157 | 159 | return ret; |
158 | 160 | }, |
159 | 161 | |
… |
… |
var Entity = Class({
|
229 | 231 | |
230 | 232 | _init: function(baseAI, entity) |
231 | 233 | { |
232 | | this._super.call(this, baseAI.GetTemplate(entity.template)); |
| 234 | this._super.call(this, baseAI.GetTemplate(entity.template), baseAI._techModifications); |
233 | 235 | |
234 | 236 | this._ai = baseAI; |
235 | 237 | this._templateName = entity.template; |
-
diff --git a/binaries/data/mods/public/simulation/ai/jubot/gamestate.js b/binaries/data/mods/public/simulation/ai/jubot/gamestate.js
index d7ae01c..5fc910a 100644
a
|
b
|
var GameState = Class({
|
14 | 14 | this.entities = ai.entities; |
15 | 15 | this.player = ai.player; |
16 | 16 | this.playerData = ai.playerData; |
| 17 | this.techModifications = ai.techModifications; |
17 | 18 | |
18 | 19 | if (!this.ai._gameStateStore){ |
19 | 20 | this.ai._gameStateStore = {}; |
… |
… |
var GameState = Class({
|
30 | 31 | { |
31 | 32 | if (!this.templates[type]) |
32 | 33 | return null; |
33 | | return new EntityTemplate(this.templates[type]); |
| 34 | return new EntityTemplate(this.templates[type], this.techModifications); |
34 | 35 | }, |
35 | 36 | |
36 | 37 | applyCiv: function(str) |
-
diff --git a/binaries/data/mods/public/simulation/ai/qbot/gamestate.js b/binaries/data/mods/public/simulation/ai/qbot/gamestate.js
index cd49dee..e9e14d7 100644
a
|
b
|
var GameState = function(ai) {
|
12 | 12 | this.player = ai.player; |
13 | 13 | this.playerData = ai.playerData; |
14 | 14 | this.buildingsBuilt = 0; |
| 15 | this.techModifications = ai.techModifications; |
15 | 16 | |
16 | 17 | if (!this.ai._gameStateStore){ |
17 | 18 | this.ai._gameStateStore = {}; |
… |
… |
GameState.prototype.getTemplate = function(type) {
|
41 | 42 | return null; |
42 | 43 | } |
43 | 44 | |
44 | | return new EntityTemplate(this.templates[type]); |
| 45 | return new EntityTemplate(this.templates[type], this.techModifications); |
45 | 46 | }; |
46 | 47 | |
47 | 48 | GameState.prototype.applyCiv = function(str) { |
-
diff --git a/binaries/data/mods/public/simulation/components/AIProxy.js b/binaries/data/mods/public/simulation/components/AIProxy.js
index aa73f4f..7e0bc4a 100644
a
|
b
|
AIProxy.prototype.OnFoundationProgressChanged = function(msg)
|
155 | 155 | |
156 | 156 | // TODO: event handlers for all the other things |
157 | 157 | |
| 158 | // TODO: research - could updates tech modifications data in the Entity/EntityTemplates |
| 159 | |
158 | 160 | AIProxy.prototype.GetFullRepresentation = function() |
159 | 161 | { |
160 | 162 | var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); |