source: ps/trunk/binaries/data/mods/public/simulation/components/Builder.js@ 25235

Last change on this file since 25235 was 25235, checked in by Freagarach, 3 years ago

Attack using cmpAttack instead of UnitAI.

Keep responsibilities separated, allow easier modding of attacking behaviour.
Ideally the BuildingAI attacking routine would be merged in here as well, but not done for now.

Part of #5810
Differential revision: D3816
Comments by: @smiley, @Stan

  • Property svn:eol-style set to native
File size: 6.1 KB
Line 
1function Builder() {}
2
3Builder.prototype.Schema =
4 "<a:help>Allows the unit to construct and repair buildings.</a:help>" +
5 "<a:example>" +
6 "<Rate>1.0</Rate>" +
7 "<Entities datatype='tokens'>" +
8 "\n structures/{civ}/barracks\n structures/{native}/civil_centre\n structures/pers/apadana\n " +
9 "</Entities>" +
10 "</a:example>" +
11 "<element name='Rate' a:help='Construction speed multiplier (1.0 is normal speed, higher values are faster).'>" +
12 "<ref name='positiveDecimal'/>" +
13 "</element>" +
14 "<element name='Entities' a:help='Space-separated list of entity template names that this unit can build. The special string \"{civ}\" will be automatically replaced by the civ code of the unit&apos;s owner, while the string \"{native}\" will be automatically replaced by the unit&apos;s civ code. This element can also be empty, in which case no new foundations may be placed by the unit, but they can still repair existing buildings.'>" +
15 "<attribute name='datatype'>" +
16 "<value>tokens</value>" +
17 "</attribute>" +
18 "<text/>" +
19 "</element>";
20
21/*
22 * Build interval and repeat time, in ms.
23 */
24Builder.prototype.BUILD_INTERVAL = 1000;
25
26Builder.prototype.Init = function()
27{
28};
29
30Builder.prototype.GetEntitiesList = function()
31{
32 let string = this.template.Entities._string;
33 if (!string)
34 return [];
35
36 let cmpPlayer = QueryOwnerInterface(this.entity);
37 if (!cmpPlayer)
38 return [];
39
40 string = ApplyValueModificationsToEntity("Builder/Entities/_string", string, this.entity);
41
42 let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
43 if (cmpIdentity)
44 string = string.replace(/\{native\}/g, cmpIdentity.GetCiv());
45
46 let entities = string.replace(/\{civ\}/g, cmpPlayer.GetCiv()).split(/\s+/);
47
48 let disabledTemplates = cmpPlayer.GetDisabledTemplates();
49
50 let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
51
52 return entities.filter(ent => !disabledTemplates[ent] && cmpTemplateManager.TemplateExists(ent));
53};
54
55Builder.prototype.GetRange = function()
56{
57 let max = 2;
58 let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
59 if (cmpObstruction)
60 max += cmpObstruction.GetSize();
61
62 return { "max": max, "min": 0 };
63};
64
65Builder.prototype.GetRate = function()
66{
67 return ApplyValueModificationsToEntity("Builder/Rate", +this.template.Rate, this.entity);
68};
69
70/**
71 * @param {number} target - The target to check.
72 * @return {boolean} - Whether we can build/repair the given target.
73 */
74Builder.prototype.CanRepair = function(target)
75{
76 let cmpFoundation = QueryMiragedInterface(target, IID_Foundation);
77 let cmpRepairable = QueryMiragedInterface(target, IID_Repairable);
78 if (!cmpFoundation && !cmpRepairable)
79 return false;
80
81 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
82 return cmpOwnership && IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target);
83};
84
85/**
86 * @param {number} target - The target to repair.
87 * @param {number} callerIID - The IID to notify on specific events.
88 * @return {boolean} - Whether we started repairing.
89 */
90Builder.prototype.StartRepairing = function(target, callerIID)
91{
92 if (this.target)
93 this.StopRepairing();
94
95 if (!this.CanRepair(target))
96 return false;
97
98 let cmpBuilderList = QueryBuilderListInterface(target);
99 if (cmpBuilderList)
100 cmpBuilderList.AddBuilder(this.entity);
101
102 let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
103 if (cmpVisual)
104 cmpVisual.SelectAnimation("build", false, 1.0);
105
106 this.target = target;
107 this.callerIID = callerIID;
108
109 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
110 this.timer = cmpTimer.SetInterval(this.entity, IID_Builder, "PerformBuilding", this.BUILD_INTERVAL, this.BUILD_INTERVAL, null);
111
112 return true;
113};
114
115/**
116 * @param {string} reason - The reason why we stopped repairing.
117 */
118Builder.prototype.StopRepairing = function(reason)
119{
120 if (!this.target)
121 return;
122
123 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
124 cmpTimer.CancelTimer(this.timer);
125 delete this.timer;
126
127 let cmpBuilderList = QueryBuilderListInterface(this.target);
128 if (cmpBuilderList)
129 cmpBuilderList.RemoveBuilder(this.entity);
130
131 delete this.target;
132
133 let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
134 if (cmpVisual)
135 cmpVisual.SelectAnimation("idle", false, 1.0);
136
137 // The callerIID component may start again,
138 // replacing the callerIID, hence save that.
139 let callerIID = this.callerIID;
140 delete this.callerIID;
141
142 if (reason && callerIID)
143 {
144 let component = Engine.QueryInterface(this.entity, callerIID);
145 if (component)
146 component.ProcessMessage(reason, null);
147 }
148};
149
150/**
151 * Repair our target entity.
152 * @params - data and lateness are unused.
153 */
154Builder.prototype.PerformBuilding = function(data, lateness)
155{
156 if (!this.CanRepair(this.target))
157 {
158 this.StopRepairing("TargetInvalidated");
159 return;
160 }
161
162 if (!this.IsTargetInRange(this.target))
163 {
164 this.StopRepairing("OutOfRange");
165 return;
166 }
167
168 // ToDo: Enable entities to keep facing a target.
169 Engine.QueryInterface(this.entity, IID_UnitAI)?.FaceTowardsTarget(this.target);
170
171 let cmpFoundation = Engine.QueryInterface(this.target, IID_Foundation);
172 if (cmpFoundation)
173 {
174 cmpFoundation.Build(this.entity, this.GetRate());
175 return;
176 }
177
178 let cmpRepairable = Engine.QueryInterface(this.target, IID_Repairable);
179 if (cmpRepairable)
180 {
181 cmpRepairable.Repair(this.entity, this.GetRate());
182 return;
183 }
184};
185
186/**
187 * @param {number} - The entity ID of the target to check.
188 * @return {boolean} - Whether this entity is in range of its target.
189 */
190Builder.prototype.IsTargetInRange = function(target)
191{
192 let range = this.GetRange();
193 let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
194 return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false);
195};
196
197Builder.prototype.OnValueModification = function(msg)
198{
199 if (msg.component != "Builder" || !msg.valueNames.some(name => name.endsWith('_string')))
200 return;
201
202 // Token changes may require selection updates.
203 let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
204 if (cmpPlayer)
205 Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID());
206};
207
208Engine.RegisterComponentType(IID_Builder, "Builder", Builder);
Note: See TracBrowser for help on using the repository browser.