Jump to content
Sign in to follow this  
Palantius

Idea on a new Way to Recruit Units

Recommended Posts

I believe that it's very important for 0ad itself that experiments with the gameplay are conducted. And that's just how I view this mod, it's more of an experiment for 0ad than a separate mod - that's why I'll do my best to convince you all to implement the changes in this mod to 0ad itself. If that won't happen despite my efforts, then I'll make sure my mod becomes more popular than 0ad :)

I won't upload the mod yet, because it's not really playable (for example, the AI doesn't do anything) but I'll update on my progress so far:

  1. Removed starting units for all civs. The idea is to only start with workers, no more units than that.
  2. Created a worker-unit. Currently it uses a female actor, but it should be a male actor that looks like a worker. Also, only athenians has the worker.
  3. Training restriction added to workers.
  4. Implemented that patch from sanderd17, so now each civ-centre increases worker limit by 10.

What I'll do next:

  1. Make buildings deploy units directly so that you don't have to train units.
  2. Make buildings upgrade-able (basic implementation).
  3. Removing food, aggressive animals and remove mills. Cleaning up some gameplay elements in general.
  4. Set a minimum of 5 units to build civ-centres.
  5. Fixing graphics and gui-related stuff for the worker unit and make one for each civ.
  6. Adjusting some values, mainly building costs.
  7. Create an example map.
  8. Fix the AI (it must be updated to work with the changes).

After this (approximately) I will make this mod available, meanwhile, I'll just report on my progress.

  • Like 1

Share this post


Link to post
Share on other sites

I Agree with 1 and may be the 2.

The 3 no way that's the Empire earth 3 do it, and all gameplay fall into the crap.

5 hard to do.

But is your mod if interesting do as experiment but too mucho design and redesign.

Good luck, but you can concentrate in some points

Resources

Time to construct

Available phases or levels

Aesthetic

And try choose a model existing in RTS even triple AAA games fail to innovate this mechanic/gameplay design.

Share this post


Link to post
Share on other sites

I Agree with 1 and may be the 2.

The 3 no way that's the Empire earth 3 do it, and all gameplay fall into the crap.

5 hard to do.

But is your mod if interesting do as experiment but too mucho design and redesign.

Good luck, but you can concentrate in some points

Resources

Time to construct

Available phases or levels

Aesthetic

And try choose a model existing in RTS even triple AAA games fail to innovate this mechanic/gameplay design.

Well, with point 5 (to fix graphics and gui-stuff for the worker unit) I had in mind to use existing graphics of "workers", you know, when you tell a unit to work its graphics are changed into a worker. Also, I know that there will be much redesigning, but the way 0ad handles modding, maybe it's manageable.

However, you just gave me the idea that I should try to make a somewhat complete mod where I change only the most important and central parts of my idea. The only new thing in this version would be that there is a specific worker unit that is the only unit able to gather resources, and civ centres deploy units in this new, special fashion.

I realize that I haven't really explained how I see how the mod would make the game better, but only said what and how I think some things should be changed. So, I'll soon get back to my idea with a minimal mod and then try to make it as convincing as possible and I'll really try to sell it then. At the moment however, I need to work on getting the basics of the mod working.

I'm currently trying to add the possibility for structures to directly deploy units when built (and without it costing resources even if normal training does). However, it's hard and I would really appreciate any help. I would at least now in the start of my modding career need some help with scripting because I'm both inexperienced and I just want these main "things" (direct deployment and basic structure-upgrading) out of my way as soon as possible.

This is the way I think the xml code to add this possibility to structures should look like:


<UnitSpawner>
<UnitType>units/{civ}_worker</UnitType>
<Count>10</Count>
</UnitSpawner>

So I created a file called UnitSpawner.js in the components folder. Currently I only have this: (is the Schema correct?)


function UnitSpawner() {}
UnitSpawner.prototype.Schema =
"<element name='UnitType'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"</element>" +
"<element name='Count'>" +
"<data type='positiveInteger'/>" +
"</element>";
UnitSpawner.prototype.init = function() {};

So what I mostly need help with is:

  1. How to actually spawn the units. I've found the SpawnUnits function in ProductionQueue.js, but should and could I use it and is it even possible to access it from my other script?
  2. How to know when a building is build (or upgraded). I found a function called isFinished in Foundation.js, but, again, can I use it from the other script?

Also, anyone has any ideas on how to implement possibility to upgrade buildings? I'm thinking of simply replacing the building with a new one that has has the old as parent.

  • Like 1

Share this post


Link to post
Share on other sites

So what I mostly need help with is:

  1. How to actually spawn the units. I've found the SpawnUnits function in ProductionQueue.js, but should and could I use it and is it even possible to access it from my other script?
  2. How to know when a building is build (or upgraded). I found a function called isFinished in Foundation.js, but, again, can I use it from the other script?

Also, anyone has any ideas on how to implement possibility to upgrade buildings? I'm thinking of simply replacing the building with a new one that has has the old as parent.

For both things, take a look at the Promotion component. That replaces a unit with an upgraded unit, and copies many things (like ownership, position, current and planned tasks ...) to the new one. For buildings, you could use the same I think (copying the production queue etc). And you can use some of that code to let it spawn arbitrary units. In the Garrison component, you can also see how to easy pick a good spawn position.

And I believe the Init function is only called when the building is complete. Like the BuildingAI starts firing at enemies directly after the Init function. while in game it only happens when the building is complete. If you ever need to do something on the foundation, you should make that part of the template copyable to the foundation, but I'll have to research on how to do this myself.

But if you're going to implement this all, I'd prefer if you split it in single functionality patches. Some of those patches could make it directly to the main game (if the code quality is good enough). But one big blob of code, with a zillion functionalities added will be too hard to review and never make it to the main game. If you keep all those patches in separate component files, I think this won't be too hard to split up.

  • Like 2

Share this post


Link to post
Share on other sites

Thanks Sander, you've saved me many hours with your help! However, I still need guidance to come somewhere with scripting. At the moment when I'm thinking about scripting my brain just turns into some sort of a pudding, so I've probably missed all the important questions about what I would need help with...

Progress on "direct deployment":

ProductionQueue.js:


"<optional>" +
"<element name='DeployUnit'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='DeployCount'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>";

[...] Inside the init:


if (this.DeployUnit && this.DeployCount)
{
this.SpawnUnits(this.DeployUnit, this.DeployCount, this.metadata);
}

template_structure_civic_civil_centre.xml:


<DeployUnit datatype="tokens">
units/{civ}_support_female_citizen
</DeployUnit>
<DeployCount>10</DeployCount>

  • Nothing happens... no idea why
  • I just guessed with "this.metadata", I have no idea what I'm supposed to pass into SpawnUnits.

Progress on "structure upgrades":

Upgrade.js:


function Upgrade() {}
Upgrade.prototype.Schema =
"<element name='Entity'>" +
"<text/>" +
"</element>";
Upgrade.prototype.Init = function() {}
Upgrade.prototype.GetUpgradedTemplateName = function()
{
return this.template.Entity;
}
Upgrade.prototype.Upgrade = function(upgradedTemplateName)
{
// Create the upgraded entity
var upgradedStructureEntity = Engine.AddEntity(upgradedTemplateName);

// Copy parameters from current entity to the upgraded one
// Position and rotation
var cmpCurrentStructurePosition = Engine.QueryInterface(this.entity, IID_Position);
var cmpUpgradedStructurePosition = Engine.QueryInterface(upgradedStructureEntity, IID_Position);
if (cmpCurrentStructurePosition.IsInWorld())
{
var pos = cmpCurrentStructurePosition.GetPosition2D();
cmpUpgradedStructurePosition.JumpTo(pos.x, pos.y);
}
var rot = cmpCurrentStructurePosition.GetRotation();
cmpUpgradedStructurePosition.SetYRotation(rot.y);
cmpUpgradedStructurePosition.SetXZRotation(rot.x, rot.z);
var heightOffset = cmpCurrentStructurePosition.GetHeightOffset();
cmpUpgradedStructurePosition.SetHeightOffset(heightOffset);
// Ownership
var cmpCurrentStructureOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpUpgradedStructureOwnership = Engine.QueryInterface(promotedUnitEntity, IID_Ownership);
cmpUpgradedStructureOwnership.SetOwner(cmpCurrentStructureOwnership.GetOwner());

// Change upgraded structure's health to the same percent of hitpoints as the structure had before the upgrade
var cmpCurrentStructureHealth = Engine.QueryInterface(this.entity, IID_Health);
var cmpUpgradedStructureHealth = Engine.QueryInterface(upgradedStructureEntity, IID_Health);
var healthFraction = Math.max(0, Math.min(1, cmpCurrentStructureHealth.GetHitpoints() / cmpCurrentStructureHealth.GetMaxHitpoints()));
var upgradedStructureHitpoints = Math.round(cmpUpgradedStructureHealth.GetMaxHitpoints() * healthFraction);
cmpUpgradedStructureHealth.SetHitpoints(upgradedStructureHitpoints);
//...
Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: upgradedStructureEntity });

// Destroy current entity
Engine.DestroyEntity(this.entity);
}

interfaces/Upgrade.js:


Engine.RegisterInterface("Upgrade");

  • Not entirely finished, not yet implemented copying the production queue
  • How would you add a button for upgrading a structure, any ideas?
  • How can I learn more about scripting?

  • Why are interface scripts necessary? What are they?
  • How do you call a function in one script from another?
  • Can you turn of the shroud of darkness and keep the fog of war?

I don't really ask for answers on all my questions, I just need to get a little help so that I can get one script done.

Thanks in advance!

Share this post


Link to post
Share on other sites

Thanks Sander, you've saved me many hours with your help! However, I still need guidance to come somewhere with scripting. At the moment when I'm thinking about scripting my brain just turns into some sort of a pudding, so I've probably missed all the important questions about what I would need help with...

Progress on "direct deployment":

ProductionQueue.js:


"<optional>" +
"<element name='DeployUnit'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='DeployCount'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>";

[...] Inside the init:


if (this.DeployUnit && this.DeployCount)
{
this.SpawnUnits(this.DeployUnit, this.DeployCount, this.metadata);
}

template_structure_civic_civil_centre.xml:


<DeployUnit datatype="tokens">
units/{civ}_support_female_citizen
</DeployUnit>
<DeployCount>10</DeployCount>

  • Nothing happens... no idea why
  • I just guessed with "this.metadata", I have no idea what I'm supposed to pass into SpawnUnits.

this.DeplayUnit won't exist. To access the definitions in the template, you need this.template.DeployUnit. The same for deploycount, although it's best to convert it it a number first. You do that by adding a '+' sign. So +this.template.DeployCount. Btw, AutoDeployUnit, or AutoSpawn would probably be a better name for it. And you also need to replace the {civ} string with the right name (see a bit further in the productionqueue).

Progress on "structure upgrades":

Upgrade.js:


function Upgrade() {}
Upgrade.prototype.Schema =
"<element name='Entity'>" +
"<text/>" +
"</element>";
Upgrade.prototype.Init = function() {}
Upgrade.prototype.GetUpgradedTemplateName = function()
{
return this.template.Entity;
}
Upgrade.prototype.Upgrade = function(upgradedTemplateName)
{
// Create the upgraded entity
var upgradedStructureEntity = Engine.AddEntity(upgradedTemplateName);

// Copy parameters from current entity to the upgraded one
// Position and rotation
var cmpCurrentStructurePosition = Engine.QueryInterface(this.entity, IID_Position);
var cmpUpgradedStructurePosition = Engine.QueryInterface(upgradedStructureEntity, IID_Position);
if (cmpCurrentStructurePosition.IsInWorld())
{
var pos = cmpCurrentStructurePosition.GetPosition2D();
cmpUpgradedStructurePosition.JumpTo(pos.x, pos.y);
}
var rot = cmpCurrentStructurePosition.GetRotation();
cmpUpgradedStructurePosition.SetYRotation(rot.y);
cmpUpgradedStructurePosition.SetXZRotation(rot.x, rot.z);
var heightOffset = cmpCurrentStructurePosition.GetHeightOffset();
cmpUpgradedStructurePosition.SetHeightOffset(heightOffset);
// Ownership
var cmpCurrentStructureOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpUpgradedStructureOwnership = Engine.QueryInterface(promotedUnitEntity, IID_Ownership);
cmpUpgradedStructureOwnership.SetOwner(cmpCurrentStructureOwnership.GetOwner());

// Change upgraded structure's health to the same percent of hitpoints as the structure had before the upgrade
var cmpCurrentStructureHealth = Engine.QueryInterface(this.entity, IID_Health);
var cmpUpgradedStructureHealth = Engine.QueryInterface(upgradedStructureEntity, IID_Health);
var healthFraction = Math.max(0, Math.min(1, cmpCurrentStructureHealth.GetHitpoints() / cmpCurrentStructureHealth.GetMaxHitpoints()));
var upgradedStructureHitpoints = Math.round(cmpUpgradedStructureHealth.GetMaxHitpoints() * healthFraction);
cmpUpgradedStructureHealth.SetHitpoints(upgradedStructureHitpoints);
//...
Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: upgradedStructureEntity });

// Destroy current entity
Engine.DestroyEntity(this.entity);
}

interfaces/Upgrade.js:


Engine.RegisterInterface("Upgrade");

  • Not entirely finished, not yet implemented copying the production queue
  • How would you add a button for upgrading a structure, any ideas?
  • How can I learn more about scripting?

  • Why are interface scripts necessary? What are they?
  • How do you call a function in one script from another?
  • Can you turn of the shroud of darkness and keep the fog of war?

I don't really ask for answers on all my questions, I just need to get a little help so that I can get one script done.

Thanks in advance!

UI is a mess, sorry for that. Take a look to the gui/session directory. In session.xml, the panels are defined. If you search for "Gate", you should see how the panel to convert a piece of wall to a gate is defined. The panels themselves are filled in unit_commands.js. But you'll probably run into the same problems as mentioned in http://trac.wildfire...com/ticket/2084 (we should really look into getting that part of the UI fixed).

To learn more about scripting, take a look at https://developer.mozilla.org. The tutorial maybe isn't the best, but it's a great reference work. And very accurate as we work with spidermonkey.

Adding the interfaces makes your component callable by other parts. So components can interact with each other. But this is something specific to thist way of coding, so isn't documented outside 0AD. To call functions of a different component, you should use the interface name. var cmp = Engine.QueryInterface(entityID, IID_InterfaceName) gives you access to the component of a specific entity, so you can use all functions and attributes defined in the component for that entity.

For changing the SOD and FOW, you should look to the rangeManager (in source/simulation2). I don't know what the method is, but there should be something to 'visit' a certain tile.

If you have more questions, please visit #0ad-dev on Quakenet to get better explanation.

  • Like 1

Share this post


Link to post
Share on other sites

I've been working on the DirectDeploy (what do you think about the name? :)) But I'm facing quite complicated issues...

First of all, I must say that I changed the approach. Instead of just spawning the units when the structure is complete, a "special" batch of the units is added to the structure's productionQueue. Both approaches gives somewhat different errors, but I believe that they have something in common.

This is the new code in the init of ProductionQueue.js:


//Deploy units once the structure is built
if (this.template.DirectDeployUnit && this.template.DirectDeployCount)
{
var directDeployCount = +this.template.DirectDeployCount;
var directDeployUnit = this.template.DirectDeployUnit._string;
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
directDeployUnit = directDeployUnit.replace(/\{civ\}/g, cmpIdentity.GetCiv())
//Deploy the units
this.AddBatch(directDeployUnit, "unit", directDeployCount, "");
//this.SpawnUnits(directDeployUnit, directDeployCount, "");
}

The error is:


ERROR: Script message handler onCreate failed
ERROR: Error in timer on entity 5398, IID 73, function TimerHandler: TypeError: cmpPlayer is null
(10)@simulation/components/ProductionQueue.js: 434
("units/maur_support_female_citizen", "unit", 10, "")@simulation/components/ProductionQueue.js:220 [var timeMult = this.GetBatchTime(count);]
()@simulation/component/ProductionQueue.js:92 [this.AddBatch(directDeployUnit, "unit", directDeployCount, "");]
(5398,1)@simulation/components/Foundation.js:264 [var building = Engine.AddEntity(this.finalTemplateName);]
(5422)@simulation/components/Builder.js:61 [cmpFoundation.Build(this.entity, rate);]
([object Object])@simulation/components/UnitAI.js:2145 [cmpBuilder.PerformBuilding(target);]
([object Objects],[object Object])@simulation/helpers/FSM.js:274
([object Objects],0])@simulation/components/UnitAI.js:2946
([object Object])@simulation/components/Timer.js:93

WARNING: JavaScript warning: simulation/ai/common-api-v3/shared.js line 291
reference to undefined property this._entities[id]
ERROR: JavaScript error: simulation/ai/common-api-v3/shared.js line 291
TypeError: this._entities[id] is undefined
([object Object])@simulation/ai/common-api-v3/shared.js:291
([object Object])@simulation/ai/common-api-v3/shared.js:196

As I understand it, there might be a problem using the init in the ProductionQueue.js because it loads before other important scripts. That's why it can't create a cmpPlayer, and that's why it can't define the playerID for the units/the batch. But that's just a guess, I don't know so much about 0ad scripting.

Thanks!

Share this post


Link to post
Share on other sites

Ah, yeah, that needs a valid player.

You've seen how an entity gets created. First, you have the Engine.CreateEntity. This calls the init functions of all components in that entity template. After that, the position is set to something valid, the player is set to something valid etc. So when executing the init function, you have no access to the player yet.

But, when the player is set, the method ...OnOwnershipChanged(msg) is called on every component (see the BuildingAI for an example). The first time this happens, msg.from == -1 (-1 is the invalid player Id). When it's destroyed, msg.to == -1.

So you could do the following instead:


ProductionQueue.prototype.OnOwnershipChanged = function(msg)
{
if (msg.from != -1)
return;

// code to deploy units here
}
// Disclaimer: this code has been typed without testing, so it could contain typos or other bugs

The name sounds good

Share this post


Link to post
Share on other sites

I don't want to be annoying and ask for help every time I stumble upon trouble, but I really do need help...

Notice anything wrong?


ProductionQueue.prototype.OnOwnershipChanged = function(msg)
{
asdasdasd

if (msg.from != -1)
return;

//Deploy units once the structure is built
//if (this.template.DirectDeployUnit && this.template.DirectDeployCount)
//{
var directDeployCount = +this.template.DirectDeployCount;
var directDeployUnit = this.template.DirectDeployUnit._string;
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
directDeployUnit = directDeployUnit.replace(/\{civ\}/g, cmpIdentity.GetCiv())
//directDeployUnit = directDeployUnit.split(/\s+/);
//Deploy the units
this.AddBatch(directDeployUnit, "unit", directDeployCount, "");
this.SpawnUnits(directDeployUnit, directDeployCount, "");
//}
};

Well, the engine doesn't notice it, which should mean that the function OnOwnershipChanged never got called, which I don't understand why.

Also is there a "trace"/"print"-function that could be used to print text to the console instead of my current "asdasdasd"?

Thanks again!

Share this post


Link to post
Share on other sites

You should normally see an error that the productionQueue (and everything following it) can't be read. Something like asdasdasd isn't valid JS code.

To print warnings and error messages, you have two functions available:


warn("message") // will print "message" in yellow
error("message") // will print "message" in red

To see how an object is structured (f.e. what the msg you get looks like), you can do


warn(uneval(msg))

which will show you a string representation of the msg object (as long as it isn't too large).

Also see http://trac.wildfiregames.com/wiki/Logging

But please, for more direct feedback, join the #0ad-dev IRC channel on quakenet.

Share this post


Link to post
Share on other sites

So, I've been scripting and I've finished the "directDeploy" functionality, and with the entityLimitsChanger-script already done (http://trac.wildfire...com/ticket/2076), the only functionality left is to make so that structures can be upgraded.

I've posted my progress on structure-upgrading in a previous post, and there are some things left to do:

  • Make sure to copy all attributes to the upgraded instance, one that I know is missing is the production-queue.
  • GUI: add a button that allows to upgrade the selected structure.
  • Restrain building of structures based on the size of the highest leveled version of the structure.Note that this goes both ways: you can't build if there is to little room

I got quite frustrated with how slow I script (approximately 2 lines/hour...), so I'm asking for help with the upgrade-script, and this is a functionality that could be useful in the "real" 0ad as well.

This time I could use to work on some models and some easier scripting. Attached you'll see some models for the Athenian civ-centre with three upgrades. Modelling those only took me a few hours! (The models are however not finished.)

I don't really know what this project will be and how much time I can spend on it in the future, but since I've got some things already done, maybe it's time to think of a name for it and stuff like that. I'm currently thinking "0 a.d. GEM" (=gameplay experiment mod), but I don't know... any ideas? Also, is it possible to get a sub-forum in the Game Modification-forum? I'm planning to start many different threads and doing a lot of writing.

Here is my "directDeploy"-script: (I tried to make a patch of it, but I got stuck on the first step)

ProductionQueue.js:


var g_ProgressInterval = 1000;
const MAX_QUEUE_SIZE = 16;
function ProductionQueue() {}
ProductionQueue.prototype.Schema =
"<a:help>Allows the building to train new units and research technologies</a:help>" +
"<a:example>" +
"<BatchTimeModifier>0.7</BatchTimeModifier>" +
"<Entities datatype='tokens'>" +
"\n units/{civ}_support_female_citizen\n units/{civ}_support_trader\n units/celt_infantry_spearman_b\n " +
"</Entities>" +
"</a:example>" +
"<element name='BatchTimeModifier' a:help='Modifier that influences the time benefit for batch training'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<optional>" +
"<element name='Entities' a:help='Space-separated list of entity template names that this building can train. The special string \"{civ}\" will be automatically replaced by the building's four-character civ code'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='Technologies' a:help='Space-separated list of technology names that this building can research.'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='DirectDeployUnit'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='DirectDeployCount'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>";
ProductionQueue.prototype.Init = function()
{
this.nextID = 1;
this.queue = [];
// Queue items are:
// {
// "id": 1,
// "player": 1, // who paid for this batch; we need this to cope with refunds cleanly
// "unitTemplate": "units/example",
// "count": 10,
// "neededSlots": 3, // number of population slots missing for production to begin
// "resources": { "wood": 100, ... }, // resources per unit, multiply by count to get total
// "population": 1, // population per unit, multiply by count to get total
// "productionStarted": false, // true iff we have reserved population
// "timeTotal": 15000, // msecs
// "timeRemaining": 10000, // msecs
// }
//
// {
// "id": 1,
// "player": 1, // who paid for this research; we need this to cope with refunds cleanly
// "technologyTemplate": "example_tech",
// "resources": { "wood": 100, ... }, // resources needed for research
// "productionStarted": false, // true iff production has started
// "timeTotal": 15000, // msecs
// "timeRemaining": 10000, // msecs
// }

this.timer = undefined; // g_ProgressInterval msec timer, active while the queue is non-empty

this.entityCache = [];
this.spawnNotified = false;
};
ProductionQueue.prototype.OnOwnershipChanged = function(msg)
{
if (msg.from != -1)
{
// Unset flag that previous owner's training may be blocked
var cmpPlayer = QueryPlayerIDInterface(msg.from, IID_Player);
if (cmpPlayer && this.queue.length > 0)
cmpPlayer.UnBlockTraining();

// Reset the production queue whenever the owner changes.
// (This should prevent players getting surprised when they capture
// an enemy building, and then loads of the enemy's civ's soldiers get
// created from it. Also it means we don't have to worry about
// updating the reserved pop slots.)
this.ResetQueue();
}
else
{
//Deploy units once the structure is built
if (this.template.DirectDeployUnit && this.template.DirectDeployCount)
{
var directDeployCount = +this.template.DirectDeployCount;
var directDeployUnit = this.template.DirectDeployUnit._string;
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
directDeployUnit = directDeployUnit.replace(/\{civ\}/g, cmpIdentity.GetCiv())
//directDeployUnit = directDeployUnit.split(/\s+/);
//Deploy the units
this.AddBatch(directDeployUnit, "directDeploy", directDeployCount, "");
//this.SpawnUnits(directDeployUnit, directDeployCount, "");
}
}
};
ProductionQueue.prototype.OnDestroy = function()
{
// Reset the queue to refund any resources
this.ResetQueue();
if (this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
}
};
/*
* Returns list of entities that can be trained by this building.
*/
ProductionQueue.prototype.GetEntitiesList = function()
{
if (!this.template.Entities)
return [];

var string = this.template.Entities._string;

// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv());

return string.split(/\s+/);
};
/*
* Returns list of technologies that can be researched by this building.
*/
ProductionQueue.prototype.GetTechnologiesList = function()
{
if (!this.template.Technologies)
return [];

var string = this.template.Technologies._string;

var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
if (!cmpTechnologyManager)
return [];

var techs = string.split(/\s+/);
var techList = [];
var superseded = {}; // Stores the tech which supersedes the key

// Add any top level technologies to an array which corresponds to the displayed icons
// Also store what a technology is superceded by in the superceded object {"tech1":"techWhichSupercedesTech1", ...}
for (var i in techs)
{
var tech = techs[i];
var template = cmpTechnologyManager.GetTechnologyTemplate(tech);
if (!template.supersedes || techs.indexOf(template.supersedes) === -1)
techList.push(tech);
else
superseded[template.supersedes] = tech;
}

// Now make researched/in progress techs invisible
for (var i in techList)
{
var tech = techList[i];
while (this.IsTechnologyResearchedOrInProgress(tech))
{
tech = superseded[tech];
}

techList[i] = tech;
}

var ret = []

// This inserts the techs into the correct positions to line up the technology pairs
for (var i = 0; i < techList.length; i++)
{
var tech = techList[i];
if (!tech)
{
ret[i] = undefined;
continue;
}

var template = cmpTechnologyManager.GetTechnologyTemplate(tech);
if (template.top)
ret[i] = {"pair": true, "top": template.top, "bottom": template.bottom};
else
ret[i] = tech;
}

return ret;
};
ProductionQueue.prototype.IsTechnologyResearchedOrInProgress = function(tech)
{
if (!tech)
return false;

var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);

var template = cmpTechnologyManager.GetTechnologyTemplate(tech);
if (template.top)
{
return (cmpTechnologyManager.IsTechnologyResearched(template.top) || cmpTechnologyManager.IsInProgress(template.top)
|| cmpTechnologyManager.IsTechnologyResearched(template.bottom) || cmpTechnologyManager.IsInProgress(template.bottom))
}
else
{
return (cmpTechnologyManager.IsTechnologyResearched(tech) || cmpTechnologyManager.IsInProgress(tech))
}
};
/*
* Adds a new batch of identical units to train or a technology to research to the production queue.
*/
ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadata)
{
// TODO: there should probably be a limit on the number of queued batches
// TODO: there should be a way for the GUI to determine whether it's going
// to be possible to add a batch (based on resource costs and length limits)
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (this.queue.length < MAX_QUEUE_SIZE)
{

if (type == "unit")
{
// Find the template data so we can determine the build costs
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.GetTemplate(templateName);
if (!template)
return;

// Apply a time discount to larger batches.
var timeMult = this.GetBatchTime(count);

// We need the costs after tech modifications
// Obviously we don't have the entities yet, so we must use template data
var costs = {};
var totalCosts = {};
var buildTime = ApplyTechModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, this.entity, template);
var time = timeMult * buildTime;
for (var r in template.Cost.Resources)
{
costs[r] = ApplyTechModificationsToTemplate("Cost/Resources/"+r, +template.Cost.Resources[r], this.entity, template);
totalCosts[r] = Math.floor(count * costs[r]);
}
var population = +template.Cost.Population;
// TrySubtractResources should report error to player (they ran out of resources)
if (!cmpPlayer.TrySubtractResources(totalCosts))
return;
// Update entity count in the EntityLimits component
if (template.TrainingRestrictions)
{
var unitCategory = template.TrainingRestrictions.Category;
var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
cmpPlayerEntityLimits.IncreaseCount(unitCategory, count);
}
this.queue.push({
"id": this.nextID++,
"player": cmpPlayer.GetPlayerID(),
"unitTemplate": templateName,
"count": count,
"metadata": metadata,
"resources": costs,
"population": population,
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
}
else if (type == "directDeploy")
{
// Find the template data so we can determine the build costs
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.GetTemplate(templateName);
if (!template)
return;

// Apply a time discount to larger batches.
var timeMult = this.GetBatchTime(count);

// We need the costs after tech modifications
// Obviously we don't have the entities yet, so we must use template data
var costs = {};
var totalCosts = {};
var buildTime = ApplyTechModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, this.entity, template);
var time = 1;
for (var r in template.Cost.Resources)
{
costs[r] = 0;
totalCosts[r] = Math.floor(count * costs[r]);
}
var population = +template.Cost.Population;
// TrySubtractResources should report error to player (they ran out of resources)
if (!cmpPlayer.TrySubtractResources(totalCosts))
return;
// Update entity count in the EntityLimits component
if (template.TrainingRestrictions)
{
var unitCategory = template.TrainingRestrictions.Category;
var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
cmpPlayerEntityLimits.IncreaseCount(unitCategory, count);
}

this.queue.push({
"id": this.nextID++,
"player": cmpPlayer.GetPlayerID(),
"unitTemplate": templateName,
"count": count,
"metadata": metadata,
"resources": costs,
"population": population,
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
}
else if (type == "technology")
{
// Load the technology template
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
var template = cmpTechTempMan.GetTemplate(templateName);
if (!template)
return;
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var time = template.researchTime * cmpPlayer.cheatTimeMultiplier;
var cost = {};
for each (var r in ["food", "wood", "stone", "metal"])
cost[r] = Math.floor(template.cost[r]);

// TrySubtractResources should report error to player (they ran out of resources)
if (!cmpPlayer.TrySubtractResources(cost))
return;

// Tell the technology manager that we have started researching this so that people can't research the same
// thing twice.
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
cmpTechnologyManager.QueuedResearch(templateName, this.entity);
if (this.queue.length == 0)
cmpTechnologyManager.StartedResearch(templateName);
this.queue.push({
"id": this.nextID++,
"player": cmpPlayer.GetPlayerID(),
"count": 1,
"technologyTemplate": templateName,
"resources": deepcopy(template.cost), // need to copy to avoid serialization problems
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
}
else
{
warn("Tried to add invalid item of type \"" + type + "\" and template \"" + templateName + "\" to a production queue");
return;
}

Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
// If this is the first item in the queue, start the timer
if (!this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, {});
}
}
else
{
var notification = {"player": cmpPlayer.GetPlayerID(), "message": "The production queue is full."};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
}
};
/*
* Removes an existing batch of units from the production queue.
* Refunds resource costs and population reservations.
*/
ProductionQueue.prototype.RemoveBatch = function(id)
{
// Destroy any cached entities (those which didn't spawn for some reason)
for (var i = 0; i < this.entityCache.length; ++i)
{
Engine.DestroyEntity(this.entityCache[i]);
}
this.entityCache = [];

for (var i = 0; i < this.queue.length; ++i)
{
var item = this.queue[i];
if (item.id != id)
continue;
// Now we've found the item to remove

var cmpPlayer = QueryPlayerIDInterface(item.player, IID_Player);
// Update entity count in the EntityLimits component
if (item.unitTemplate)
{
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.GetTemplate(item.unitTemplate);
if (template.TrainingRestrictions)
{
var unitCategory = template.TrainingRestrictions.Category;
var cmpPlayerEntityLimits = QueryPlayerIDInterface(item.player, IID_EntityLimits);
cmpPlayerEntityLimits.DecreaseCount(unitCategory, item.count);
}
}
// Refund the resource cost for this batch
var totalCosts = {};
var cmpStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker);
for each (var r in ["food", "wood", "stone", "metal"])
{
totalCosts[r] = Math.floor(item.count * item.resources[r]);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseResourceUsedCounter(r, -totalCosts[r]);
}

cmpPlayer.AddResources(totalCosts);

// Remove reserved population slots if necessary
if (item.productionStarted && item.unitTemplate)
cmpPlayer.UnReservePopulationSlots(item.population * item.count);

// Mark the research as stopped if we cancel it
if (item.technologyTemplate)
{
// item.player is used as this.entity's owner may be invalid (deletion, etc.)
var cmpTechnologyManager = QueryPlayerIDInterface(item.player, IID_TechnologyManager);
cmpTechnologyManager.StoppedResearch(item.technologyTemplate);
}

// Remove from the queue
// (We don't need to remove the timer - it'll expire if it discovers the queue is empty)
this.queue.splice(i, 1);
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
return;
}
};
/*
* Returns basic data from all batches in the production queue.
*/
ProductionQueue.prototype.GetQueue = function()
{
var out = [];
for each (var item in this.queue)
{
out.push({
"id": item.id,
"unitTemplate": item.unitTemplate,
"technologyTemplate": item.technologyTemplate,
"count": item.count,
"neededSlots": item.neededSlots,
"progress": 1-(item.timeRemaining/item.timeTotal),
"metadata": item.metadata,
});
}
return out;
};
/*
* Removes all existing batches from the queue.
*/
ProductionQueue.prototype.ResetQueue = function()
{
// Empty the production queue and refund all the resource costs
// to the player. (This is to avoid players having to micromanage their
// buildings' queues when they're about to be destroyed or captured.)
while (this.queue.length)
this.RemoveBatch(this.queue[0].id);
};
/*
* Returns batch build time.
*/
ProductionQueue.prototype.GetBatchTime = function(batchSize)
{
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var batchTimeModifier = ApplyTechModificationsToEntity("ProductionQueue/BatchTimeModifier", +this.template.BatchTimeModifier, this.entity);
// TODO: work out what equation we should use here.
return Math.pow(batchSize, batchTimeModifier) * cmpPlayer.cheatTimeMultiplier;
};
/*
* This function creates the entities and places them in world if possible.
* returns the number of successfully spawned entities.
*/
ProductionQueue.prototype.SpawnUnits = function(templateName, count, metadata)
{
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);

var spawnedEnts = [];

if (this.entityCache.length == 0)
{
// We need entities to test spawning, but we don't want to waste resources,
// so only create them once and use as needed
for (var i = 0; i < count; ++i)
{
var ent = Engine.AddEntity(templateName);
this.entityCache.push(ent);
// Decrement entity count in the EntityLimits component
// since it will be increased by EntityLimits.OnGlobalOwnershipChanged function,
// i.e. we replace a 'trained' entity to an 'alive' one
var cmpTrainingRestrictions = Engine.QueryInterface(ent, IID_TrainingRestrictions);
if (cmpTrainingRestrictions)
{
var unitCategory = cmpTrainingRestrictions.GetCategory();
var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
cmpPlayerEntityLimits.DecrementCount(unitCategory);
}
}
}
for (var i = 0; i < count; ++i)
{
var ent = this.entityCache[0];
var pos = cmpFootprint.PickSpawnPoint(ent);
if (pos.y < 0)
{
// Fail: there wasn't any space to spawn the unit
break;
}
else
{
// Successfully spawned
var cmpNewPosition = Engine.QueryInterface(ent, IID_Position);
cmpNewPosition.JumpTo(pos.x, pos.z);
// TODO: what direction should they face in?
var cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());

var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
cmpPlayerStatisticsTracker.IncreaseTrainedUnitsCounter();
// Play a sound, but only for the first in the batch (to avoid nasty phasing effects)
if (spawnedEnts.length == 0)
PlaySound("trained", ent);

this.entityCache.shift();
spawnedEnts.push(ent);
}
}
if (spawnedEnts.length > 0)
{
// If a rally point is set, walk towards it (in formation) using a suitable command based on where the
// rally point is placed.
if (cmpRallyPoint)
{
var rallyPos = cmpRallyPoint.GetPositions()[0];
if (rallyPos)
{
var commands = GetRallyPointCommands(cmpRallyPoint, spawnedEnts);
for each(var com in commands)
{
ProcessCommand(cmpOwnership.GetOwner(), com);
}
}
}
Engine.PostMessage(this.entity, MT_TrainingFinished, {
"entities": spawnedEnts,
"owner": cmpOwnership.GetOwner(),
"metadata": metadata,
});
}

return spawnedEnts.length;
};
/*
* Increments progress on the first batch in the production queue, and blocks the
* queue if population limit is reached or some units failed to spawn.
*/
ProductionQueue.prototype.ProgressTimeout = function(data)
{
// Allocate the 1000msecs to as many queue items as it takes
// until we've used up all the time (so that we work accurately
// with items that take fractions of a second)
var time = g_ProgressInterval;
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
while (time > 0 && this.queue.length)
{
var item = this.queue[0];
if (!item.productionStarted)
{
// If the item is a unit then do population checks
if (item.unitTemplate)
{
// Batch's training hasn't started yet.
// Try to reserve the necessary population slots
item.neededSlots = cmpPlayer.TryReservePopulationSlots(item.population * item.count);
if (item.neededSlots)
{
// Not enough slots available - don't train this batch now
// (we'll try again on the next timeout)
// Set flag that training is blocked
cmpPlayer.BlockTraining();
break;
}

// Unset flag that training is blocked
cmpPlayer.UnBlockTraining();
}
if (item.technologyTemplate)
{
// Mark the research as started.
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
cmpTechnologyManager.StartedResearch(item.technologyTemplate);
}
item.productionStarted = true;
}
// If we won't finish the batch now, just update its timer
if (item.timeRemaining > time)
{
item.timeRemaining -= time;
// send a message for the AIs.
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
break;
}
if (item.unitTemplate)
{
var numSpawned = this.SpawnUnits(item.unitTemplate, item.count, item.metadata);
if (numSpawned == item.count)
{
// All entities spawned, this batch finished
cmpPlayer.UnReservePopulationSlots(item.population * numSpawned);
time -= item.timeRemaining;
this.queue.shift();
// Unset flag that training is blocked
cmpPlayer.UnBlockTraining();
this.spawnNotified = false;
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
}
else
{
if (numSpawned > 0)
{
// Only partially finished
cmpPlayer.UnReservePopulationSlots(item.population * numSpawned);
item.count -= numSpawned;
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
}
// Some entities failed to spawn
// Set flag that training is blocked
cmpPlayer.BlockTraining();

if (!this.spawnNotified)
{
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Can't find free space to spawn trained units" };
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
this.spawnNotified = true;
}
break;
}
}
else if (item.technologyTemplate)
{
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
cmpTechnologyManager.ResearchTechnology(item.technologyTemplate);

time -= item.timeRemaining;

this.queue.shift();
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
}
}
// If the queue's empty, delete the timer, else repeat it
if (this.queue.length == 0)
{
this.timer = undefined;
// Unset flag that training is blocked
// (This might happen when the player unqueues all batches)
cmpPlayer.UnBlockTraining();
}
else
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, data);
}
}
Engine.RegisterComponentType(IID_ProductionQueue, "ProductionQueue", ProductionQueue);

post-15504-0-86320900-1377976130_thumb.p

Edited by Palantius

Share this post


Link to post
Share on other sites

For the buildings, we will probably look into that, as upgrading the visual state of some buildings when phasing up would be nice, and upgrading individual buildings for a cost would also be nice (which are sadly enough two different things). I don't think we should care about building size, successive templates should just have the same obstruction size already, otherwise it becomes messy. But it will only be implemented when someone likes to do that, so it might not be for immediately.

WRT you models, I don't think the size should be any bigger than the current CC model. If we ever implement building upgrades, I think the current CC is suited for town phase, something smaller (but with the same footprint) could be made for village phase, and something bigger (but also with the same footprint) for city phase.

If you have a definitive name for your mod, I think we can move it, but as Linus says:

20_main.jpg

To get a patch (you are using svn, right?), you should just execute "svn diff" from the svn root directory, or there should be something available in tortouse svn too (if you use that).

For your code, some comments:

  • Don't leave old, commented-out code in your files, like lines 104 and 107
  • Don't copy entire blocks of code like you did around line 324, working with logical operators can save you a lot of copy work.
  • Our code conventions require you to use tabs, no spaces: http://trac.wildfiregames.com/wiki/Coding_Conventions, you changed the entire file to spaces. This messes up the patch (every line has changed), and is a pain to fix, as you even used a different number of spaces for indentation.
  • It's better to have positive conditions (e.g. if (msg.from == -1) instead of if (msg.from != -1)) when possible. This makes code reading easier. The only reason to have negative conditions would be when there is no "else" block following.

On the functionality, did you test it? I don't think it does what you want. It also requires build time and costs resources. I think at least the buildtime and resource costs should be set to zero. Btw, if you don't have enough resources after creating a building, it won't deploy any units.

Share this post


Link to post
Share on other sites

I really want to thank you for the feedback on my code! The directDeploy does what I want it to. A problem is however that the AI does not know how to handle it, it just breaks down and cries. The tabs were converted to spaces when I posted it on the forum, so my code is with tabs. And I have problems with creating a patch, since I'm using Ubuntu and I tried something called RabbitVCS, however when I want to set up a checkout according to the instructions, it just crashes :( But I really don't want to think about that at the moment... I want to make progress on the mod.

It's been a while since my last post, but that's because I've been quite busy lately, not because but I have given up! I've been trying to implement building upgrades, but it's really hard... I've also been spending some time to get better at 0ad and get a better understanding of it.

Well, yeah, it's true that talk is cheap, but 0ad is cheaper :) Actually it's not just talk, the entire point of this mod/project is to try to find the most optimal gameplay from a strategic point of view and really go far with experiments on the gameplay. Therefore, the most important thing to do at this early stage is to make analyzes and doing some brainstorming.

I'm thinking of calling the mod "0ad SRTS mod" for "strategic real time strategy". Provocative enough?

Share this post


Link to post
Share on other sites

Well, in that case, open a ticket, and place your code there. I want to see what you've done. For that, I need a diff file (so I can exactly see what lines you modified). But if you modified every line (by using spaces as in the code here), I can't review it.

Share this post


Link to post
Share on other sites

I don't even know what I did with my ticket, I just wanted to upload the code...

I would really need help with adding a button to the GUI that would allow for upgrading the selected building. The function that should be called is UpgradeStructure, and it works. Here's my current progress on Upgrade.js:


function Upgrade() {}
Upgrade.prototype.Schema =
"<element name='Entity'>" +
"<attribute name='datatype'>" +
"<value>tokens</value>" +
"</attribute>" +
"<text/>" +
"</element>";
Upgrade.prototype.Init = function() {}
Upgrade.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to != -1)
{
//this.UpgradeStructure();
}
}
Upgrade.prototype.GetUpgradedTemplateName = function()
{
return this.template.Entity;
}
Upgrade.prototype.UpgradeStructure = function()
{
//TODO:cost
//TODO:cancelling upgrade
//TODO:graphics and footprint
//TODO:GUI

var upgradedTemplateName = this.template.Entity._string;
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
upgradedTemplateName = upgradedTemplateName.replace(/\{civ\}/g, cmpIdentity.GetCiv());

// Create the upgraded entity
var upgradedStructureEntity = Engine.AddEntity(upgradedTemplateName);

// Copy parameters from current entity to the upgraded one
// Position and rotation
var cmpCurrentStructurePosition = Engine.QueryInterface(this.entity, IID_Position);
var cmpUpgradedStructurePosition = Engine.QueryInterface(upgradedStructureEntity, IID_Position);
if (cmpCurrentStructurePosition.IsInWorld())
{
var pos = cmpCurrentStructurePosition.GetPosition2D();
cmpUpgradedStructurePosition.JumpTo(pos.x, pos.y);
}
var rot = cmpCurrentStructurePosition.GetRotation();
cmpUpgradedStructurePosition.SetYRotation(rot.y);
cmpUpgradedStructurePosition.SetXZRotation(rot.x, rot.z);
var heightOffset = cmpCurrentStructurePosition.GetHeightOffset();
cmpUpgradedStructurePosition.SetHeightOffset(heightOffset);
// Ownership
var cmpCurrentStructureOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpUpgradedStructureOwnership = Engine.QueryInterface(upgradedStructureEntity, IID_Ownership);
cmpUpgradedStructureOwnership.SetOwner(cmpCurrentStructureOwnership.GetOwner());

// Change upgraded structure's health to the same percent of hitpoints as the structure had before the upgrade
var cmpCurrentStructureHealth = Engine.QueryInterface(this.entity, IID_Health);
var cmpUpgradedStructureHealth = Engine.QueryInterface(upgradedStructureEntity, IID_Health);
var healthFraction = Math.max(0, Math.min(1, cmpCurrentStructureHealth.GetHitpoints() / cmpCurrentStructureHealth.GetMaxHitpoints()));
var upgradedStructureHitpoints = Math.round(cmpUpgradedStructureHealth.GetMaxHitpoints() * healthFraction);
cmpUpgradedStructureHealth.SetHitpoints(upgradedStructureHitpoints);
//...
Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: upgradedStructureEntity });

// Destroy current entity
Engine.DestroyEntity(this.entity);
}
Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade);

Then there's also the interface script:


Engine.RegisterInterface("Upgrade");

And this is added to the structure's xml:


<Upgrade>
<Entity datatype="tokens">
structures/{civ}_fortress
</Entity>
</Upgrade>

I've been looking at how the gate works, but there is so many files that I can't find everything and understand how it all fits together. And the documentation is "mostly outdated". I really need help with the GUI!

  • Like 1

Share this post


Link to post
Share on other sites

For me, there are two ways of upgrading a building. One is via a technology (like upgrading a CC when you phase up). The other is via a button for individual upgrades (like army camp to fortress or CC).

The individual upgrade would indeed need some work. It would probably need to cooperate with the productionqueue to show the elapsed time etc. It needs a way to define the cost, the time, the icon ...

To differentiate it in XML, I'd use something like


<Upgrade>
<Entity>templateName</Entity>
<TechPhase>2</TechPhase>
</Upgrade>

Or


<Upgrade>
<Entity>templateName</Entity>
<Button>
<Time>25</Time>
<Icon>blabla.png</Icon>
...
</Button>
</Upgrade>

Some of the button child nodes should be optional, and fetched from the entity template when undefined. But I'd think you want to edit some of those, so they should be definable too.

Now, as the technology upgrade is easier, I'd start with that. What you need for that is listening to the TechnologyModification message. When a technology enlarges the TechPhase of a certain building (like the town phase technology can add one to the TechPhase of civil centres), you upgrade the buildings. The code should look something like


Upgrade.prototype.OnTechnologyModification = function(msg)
{
if (msg.component != Upgrade)
return;
var techPhase = ApplyTechModificationsToEntity("Upgrade/TechPhase", 1, this.entity);
if (this.techPhase < techPhase)
this.UpgradeStructure();
};

  • Like 1

Share this post


Link to post
Share on other sites

I've been quite busy lately, but now I'm back!

Upgrading a structure based on town phase is quite useful (especially for centers) and would be relatively easy to implement. But it would only make sense to implement it if there were models for each upgrade, so maybe it's too early for that.

I've been thinking about my idea, and I've come to the conclusion, that implementing it would require very much work on other areas to make everything fit together. And if I would like to put that much effort into experimenting with the gameplay, then I could even try bolder things. After all, scripting in 0ad allows for very much. So I guess that I'll put the upgrade-functionality on hold.

However, something I would like to say, is that I believe gameplay design deserves much more attention. At least the game design document is, in my opinion, insufficient. I know these complaints are very unspecified, but I'll get back to that topic soon - I'm currently reading about game design theory (it's very interesting!).

Share this post


Link to post
Share on other sites

I've been quite busy lately, but now I'm back!

Upgrading a structure based on town phase is quite useful (especially for centers) and would be relatively easy to implement. But it would only make sense to implement it if there were models for each upgrade, so maybe it's too early for that.

Meanwhile, also auras are in, which generalises some of those concepts. And now I can also see how individual entity upgrades could work (like pay 100 stone to make one fortress even stronger). This can easily use the same technology interface (so there's no difference on the receiving end), which would mean that individual model changes don't need extra code apart from knowing about technology changes. That button (including stuff like conversion time, cost, ...) could be handled by the individual upgrade component.

I've been thinking about my idea, and I've come to the conclusion, that implementing it would require very much work on other areas to make everything fit together. And if I would like to put that much effort into experimenting with the gameplay, then I could even try bolder things. After all, scripting in 0ad allows for very much. So I guess that I'll put the upgrade-functionality on hold.

However, something I would like to say, is that I believe gameplay design deserves much more attention. At least the game design document is, in my opinion, insufficient. I know these complaints are very unspecified, but I'll get back to that topic soon - I'm currently reading about game design theory (it's very interesting!).

Share this post


Link to post
Share on other sites

For me, there are two ways of upgrading a building. One is via a technology (like upgrading a CC when you phase up). The other is via a button for individual upgrades (like army camp to fortress or CC).

The individual upgrade would indeed need some work. It would probably need to cooperate with the productionqueue to show the elapsed time etc. It needs a way to define the cost, the time, the icon ...

To differentiate it in XML, I'd use something like

<Upgrade>  <Entity>templateName</Entity>  <TechPhase>2</TechPhase></Upgrade>

"TechPhase" here would probably be "RequiredTechnology" instead. Make sure to keep the same or similar terminology and methodology.

  • Like 2

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...